@@ -177,6 +177,10 @@ pub const Scanner = struct {
177177
178178 // Extract metadata using the enhanced extractor (includes OpenLibrary enrichment)
179179 var metadata = try self .metadata_extractor .extractMetadata (path );
180+
181+ if (metadata .isbn == null ) {
182+ metadata .isbn = try self .deriveIsbnFromPath (path );
183+ }
180184 defer metadata .deinit (self .allocator );
181185
182186 std .log .debug ("Metadata extracted - title: {s}, author: {s}, isbn: {s}" , .{ metadata .title orelse "null" , metadata .author orelse "null" , metadata .isbn orelse "null" });
@@ -328,4 +332,43 @@ pub const Scanner = struct {
328332
329333 return result ;
330334 }
335+
336+ fn deriveIsbnFromPath (self : * Scanner , path : []const u8 ) ! ? []const u8 {
337+ const basename = std .fs .path .basename (path );
338+ const name_without_ext = if (std .mem .lastIndexOfScalar (u8 , basename , '.' )) | dot_index |
339+ basename [0.. dot_index ]
340+ else
341+ basename ;
342+
343+ var digit_buffer : [20 ]u8 = undefined ;
344+ var digit_count : usize = 0 ;
345+
346+ for (name_without_ext ) | c | {
347+ if (std .ascii .isDigit (c )) {
348+ if (digit_count < digit_buffer .len ) {
349+ digit_buffer [digit_count ] = c ;
350+ digit_count += 1 ;
351+ }
352+ continue ;
353+ }
354+
355+ if ((c == 'x' or c == 'X' ) and digit_count == 9 ) {
356+ digit_buffer [digit_count ] = 'X' ;
357+ digit_count += 1 ;
358+ continue ;
359+ }
360+
361+ // When we hit any other character, finalize current run
362+ if (digit_count == 10 or digit_count == 13 ) {
363+ return try self .allocator .dupe (u8 , digit_buffer [0.. digit_count ]);
364+ }
365+ digit_count = 0 ;
366+ }
367+
368+ if (digit_count == 10 or digit_count == 13 ) {
369+ return try self .allocator .dupe (u8 , digit_buffer [0.. digit_count ]);
370+ }
371+
372+ return null ;
373+ }
331374};
0 commit comments