@@ -80,10 +80,11 @@ static void buffer_reset(void) {
8080#define MP4_stsz 0x7374737A /* 'stsz' - Sample size atom */
8181#define MP4_mdhd 0x6D646864 /* 'mdhd' - Media header atom */
8282
83+ #define MAX_LEN 256
8384/* Structure to hold chapter information */
8485struct chapter_info {
8586 uint64_t timestamp ; /* Chapter start time in milliseconds */
86- char title [256 ]; /* Chapter title */
87+ char title [MAX_LEN ]; /* Chapter title */
8788};
8889
8990/* MP4 utility functions */
@@ -152,23 +153,24 @@ static int search_for_atom(int fd, off_t start_pos, off_t end_pos, uint32_t targ
152153}
153154
154155/* Parse Apple chapter track - look for text track with chapter data */
155- static int parse_apple_chapter_track (int fd , off_t track_start , off_t track_size ,
156- struct chapter_info * chapters ) {
156+ static struct chapter_info * parse_apple_chapter_track (int fd , off_t track_start , off_t track_size , int * num_chapters )
157+ {
158+ * num_chapters = 0 ;
157159 off_t track_end = track_start + track_size ;
158160 int chapter_count = 0 ;
159161 uint32_t track_timescale = 1000 ; /* Default timescale */
160162
161163 /* Look for track header to check if this is a text track */
162164 off_t tkhd_pos , tkhd_size ;
163165 if (!search_for_atom (fd , track_start , track_end , MP4_tkhd , & tkhd_pos , & tkhd_size )) {
164- DEBUGF ("No tkhd found in track" );
166+ DEBUGF ("No tkhd found in track\n " );
165167 return 0 ;
166168 }
167169
168170 /* Look for media atom */
169171 off_t mdia_pos , mdia_size ;
170172 if (!search_for_atom (fd , track_start , track_end , MP4_mdia , & mdia_pos , & mdia_size )) {
171- DEBUGF ("No mdia found in track" );
173+ DEBUGF ("No mdia found in track\n " );
172174 return 0 ;
173175 }
174176
@@ -189,37 +191,37 @@ static int parse_apple_chapter_track(int fd, off_t track_start, off_t track_size
189191 track_timescale = read_uint32be (fd );
190192 }
191193
192- DEBUGF ("Found track timescale: %u" , track_timescale );
194+ DEBUGF ("Found track timescale: %u\n " , track_timescale );
193195 } else {
194- DEBUGF ("No mdhd found, using default timescale" );
196+ DEBUGF ("No mdhd found, using default timescale\n " );
195197 }
196198
197199 /* Look for media handler to check track type */
198200 off_t hdlr_pos , hdlr_size ;
199201 if (!search_for_atom (fd , mdia_pos , mdia_pos + mdia_size , MP4_hdlr , & hdlr_pos , & hdlr_size )) {
200- DEBUGF ("No hdlr found in track" );
202+ DEBUGF ("No hdlr found in track\n " );
201203 return 0 ;
202204 }
203205
204206 /* Check if this is a text track */
205207 rb -> lseek (fd , hdlr_pos + 8 + 8 , SEEK_SET ); /* Skip atom header + version/flags */
206208 uint32_t handler_type = read_uint32be (fd );
207209
208- DEBUGF ("Track handler type: %c%c%c%c" ,
210+ DEBUGF ("Track handler type: %c%c%c%c\n " ,
209211 (char )(handler_type >> 24 ), (char )(handler_type >> 16 ),
210212 (char )(handler_type >> 8 ), (char )handler_type );
211213
212214 if (handler_type != MP4_TEXT && handler_type != MP4_tx3g ) {
213- DEBUGF ("Not a text track, skipping" );
215+ DEBUGF ("Not a text track, skipping\n " );
214216 return 0 ; /* Not a text track */
215217 }
216218
217- DEBUGF ("Found text track!" );
219+ DEBUGF ("Found text track!\n " );
218220
219221 /* Look for media information atom first */
220222 off_t minf_pos , minf_size ;
221223 if (!search_for_atom (fd , mdia_pos + 8 , mdia_pos + mdia_size , MP4_minf , & minf_pos , & minf_size )) {
222- DEBUGF ("No minf found in text track" );
224+ DEBUGF ("No minf found in text track\n " );
223225 return 0 ;
224226 }
225227
@@ -228,7 +230,7 @@ static int parse_apple_chapter_track(int fd, off_t track_start, off_t track_size
228230 /* Look for sample table */
229231 off_t stbl_pos , stbl_size ;
230232 if (!search_for_atom (fd , minf_pos + 8 , minf_pos + minf_size , MP4_stbl , & stbl_pos , & stbl_size )) {
231- DEBUGF ("No stbl found in text track" );
233+ DEBUGF ("No stbl found in text track\n " );
232234 return 0 ;
233235 }
234236
@@ -237,14 +239,14 @@ static int parse_apple_chapter_track(int fd, off_t track_start, off_t track_size
237239 /* Get time-to-sample information */
238240 off_t stts_pos , stts_size ;
239241 if (!search_for_atom (fd , stbl_pos + 8 , stbl_pos + stbl_size , MP4_stts , & stts_pos , & stts_size )) {
240- DEBUGF ("No stts found" );
242+ DEBUGF ("No stts found\n " );
241243 return 0 ;
242244 }
243245
244246 /* Get sample size information to know how many samples we have */
245247 off_t stsz_pos , stsz_size ;
246248 if (!search_for_atom (fd , stbl_pos + 8 , stbl_pos + stbl_size , MP4_stsz , & stsz_pos , & stsz_size )) {
247- DEBUGF ("No stsz found" );
249+ DEBUGF ("No stsz found\n " );
248250 return 0 ;
249251 }
250252
@@ -271,12 +273,14 @@ static int parse_apple_chapter_track(int fd, off_t track_start, off_t track_size
271273
272274 DEBUGF ("Sample count: %u, default size: %u, timescale: %u\n" , sample_count , default_sample_size , track_timescale );
273275
274- if (sample_count == 0 || sample_count > 1000 ) {
276+ if (sample_count == 0 ) {
275277 return 0 ; /* Sanity check */
276278 }
277279
278280 // Allocate memory for chapters
279- buffer_alloc (sizeof (struct chapter_info ) * sample_count );
281+ struct chapter_info * chapters = buffer_alloc (sizeof (struct chapter_info ) * sample_count );
282+ * num_chapters = sample_count ;
283+
280284
281285 /* Calculate memory requirements */
282286 size_t sample_sizes_bytes = (default_sample_size == 0 ) ? sample_count * sizeof (uint32_t ) : 0 ;
@@ -341,11 +345,16 @@ static int parse_apple_chapter_track(int fd, off_t track_start, off_t track_size
341345 /* Build timestamp array from time-to-sample data */
342346 uint64_t current_time = 0 ;
343347 uint32_t sample_index = 0 ;
344- uint32_t current_chunk = 0 ;
348+ uint64_t current_chunk_pos = 0 ; /* Position within current chunk */
345349
346350 /* Helper buffer for reading sample data */
347351 char sample_buffer [512 ];
348352
353+ /* Start at the beginning of the first chunk */
354+ if (chunk_count > 0 ) {
355+ current_chunk_pos = chunk_offsets [0 ];
356+ }
357+
349358 for (uint32_t i = 0 ; i < stts_entry_count ; i ++ ) {
350359 rb -> lseek (fd , stts_pos + 8 + 8 + (i * 8 ), SEEK_SET );
351360 uint32_t samples_in_entry = read_uint32be (fd );
@@ -357,12 +366,12 @@ static int parse_apple_chapter_track(int fd, off_t track_start, off_t track_size
357366 for (uint32_t j = 0 ; j < samples_in_entry && sample_index < sample_count ; j ++ ) {
358367 /* Convert from track timescale to milliseconds */
359368 chapters [chapter_count ].timestamp = (current_time * 1000 ) / track_timescale ;
360- chapters [ chapter_count ]. title [ 0 ] = 0 ;
369+
361370 DEBUGF ("Chapter %d: time=%lu units, timestamp=%lu ms (timescale=%u)\n" ,
362371 chapter_count , (unsigned long )current_time , (unsigned long )chapters [chapter_count ].timestamp , track_timescale );
363372
364373 /* Try to read the actual chapter title from sample data */
365- if (chunk_offsets != NULL && current_chunk < chunk_count ) {
374+ if (chunk_count > 0 ) {
366375 /* Get sample size */
367376 uint32_t sample_size ;
368377 if (default_sample_size > 0 ) {
@@ -374,8 +383,8 @@ static int parse_apple_chapter_track(int fd, off_t track_start, off_t track_size
374383 }
375384
376385 if (sample_size > 0 && sample_size < sizeof (sample_buffer )) {
377- /* Read sample data */
378- rb -> lseek (fd , chunk_offsets [ current_chunk ] , SEEK_SET );
386+ /* Read sample data from current position in chunk */
387+ rb -> lseek (fd , current_chunk_pos , SEEK_SET );
379388 int bytes_read = rb -> read (fd , sample_buffer , sample_size );
380389
381390 if (bytes_read > 0 ) {
@@ -388,12 +397,7 @@ static int parse_apple_chapter_track(int fd, off_t track_start, off_t track_size
388397 uint16_t text_len = (sample_buffer [0 ] << 8 ) | sample_buffer [1 ];
389398 if (text_len > 0 && text_len < sample_size - 2 && text_len < 200 ) {
390399 title_text = & sample_buffer [2 ];
391- /* Ensure null termination */
392- size_t copy_len = text_len ;
393- if (copy_len >= sizeof (chapters [chapter_count ].title )) {
394- copy_len = sizeof (chapters [chapter_count ].title ) - 1 ;
395- }
396- rb -> strlcpy (chapters [chapter_count ].title , title_text , copy_len + 1 );
400+ rb -> strlcpy (chapters [chapter_count ].title , title_text , MIN (text_len + 1 , MAX_LEN ));
397401 }
398402 }
399403
@@ -411,11 +415,8 @@ static int parse_apple_chapter_track(int fd, off_t track_start, off_t track_size
411415 }
412416
413417 if (text_end - k > 3 ) { /* At least 4 characters */
414- size_t copy_len = text_end - k ;
415- if (copy_len >= sizeof (chapters [chapter_count ].title )) {
416- copy_len = sizeof (chapters [chapter_count ].title ) - 1 ;
417- }
418- rb -> strlcpy (chapters [chapter_count ].title , & sample_buffer [k ], copy_len + 1 );
418+ size_t copy_len = text_end - k + 1 ;
419+ rb -> strlcpy (chapters [chapter_count ].title , & sample_buffer [k ], MIN (copy_len , MAX_LEN ));
419420 title_text = chapters [chapter_count ].title ;
420421 break ;
421422 }
@@ -426,14 +427,14 @@ static int parse_apple_chapter_track(int fd, off_t track_start, off_t track_size
426427 DEBUGF ("Sample %u: size=%u, title='%s'\n" , sample_index , sample_size ,
427428 title_text ? chapters [chapter_count ].title : "failed" );
428429 }
430+
431+ /* Move to next sample position within the chunk */
432+ current_chunk_pos += sample_size ;
429433 }
430-
431- /* Move to next chunk for next sample (simplified - assumes one sample per chunk) */
432- current_chunk ++ ;
433434 }
434435
435436 /* Fallback to generic title if we couldn't read the text */
436- if (chapters [chapter_count ].title [ 0 ] == 0 ) {
437+ if (rb -> strlen ( chapters [chapter_count ].title ) == 0 ) {
437438 rb -> snprintf (chapters [chapter_count ].title , sizeof (chapters [chapter_count ].title ),
438439 "Chapter %d" , chapter_count + 1 );
439440 }
@@ -444,22 +445,21 @@ static int parse_apple_chapter_track(int fd, off_t track_start, off_t track_size
444445 }
445446 }
446447
447- DEBUGF ("Extracted %d chapters from text track" , chapter_count );
448+ DEBUGF ("Extracted %d chapters from text track\n " , chapter_count );
448449
449- return chapter_count ;
450+ return chapters ;
450451}
451452
452453/* Find and parse MP4 chapters (both Nero and Apple formats) */
453- static int find_mp4_chapters (const char * mp4_path , struct chapter_info * chapters , int max_chapters ) {
454+ static struct chapter_info * find_mp4_chapters (const char * mp4_path , int * chapter_count ) {
454455 int fd = rb -> open (mp4_path , O_RDONLY );
455- if (fd < 0 ) return -1 ;
456+ if (fd < 0 ) return NULL ;
456457
457- int chapter_count = 0 ;
458458 uint32_t atom_size , atom_type ;
459459 off_t file_pos = 0 ;
460460
461461 /* First pass: Look for Nero chpl atoms (simpler format) */
462- while (chapter_count == 0 ) {
462+ while (1 ) {
463463 if (rb -> lseek (fd , file_pos , SEEK_SET ) < 0 ) break ;
464464 if (rb -> read (fd , & atom_size , 4 ) != 4 ) break ;
465465 if (rb -> read (fd , & atom_type , 4 ) != 4 ) break ;
@@ -476,31 +476,32 @@ static int find_mp4_chapters(const char *mp4_path, struct chapter_info *chapters
476476 /* Found Nero chapter list atom */
477477 rb -> lseek (fd , 8 , SEEK_CUR ); /* Skip version and flags */
478478 uint8_t num_chapters = read_uint8 (fd );
479-
480- for (int i = 0 ; i < num_chapters && chapter_count < max_chapters ; i ++ ) {
479+ struct chapter_info * chapters = buffer_alloc ( sizeof ( * chapters ) * num_chapters );
480+ for (int i = 0 ; i < num_chapters ; i ++ ) {
481481 uint64_t timestamp_100ns = read_uint64be (fd );
482- chapters [chapter_count ].timestamp = timestamp_100ns / 10000 ; /* Convert from 100ns to milliseconds */
483-
484- /* Read chapter title (Pascal string) */
485- uint8_t title_len = read_uint8 (fd );
486- if (title_len > 0 && title_len < 255 ) {
487- rb -> read (fd , chapters [chapter_count ].title , title_len );
488- chapters [chapter_count ].title [title_len ] = '\0' ;
489- } else {
490- rb -> snprintf (chapters [chapter_count ].title , sizeof (chapters [chapter_count ].title ),
491- "Chapter %d" , chapter_count + 1 );
492- }
482+ chapters [i ].timestamp = timestamp_100ns / 10000 ; /* Convert from 100ns to milliseconds */
493483
494- chapter_count ++ ;
484+ /* Read chapter title (Pascal string) */
485+ ssize_t title_len = read_uint8 (fd );
486+ title_len = rb -> read (fd , chapters [i ].title , MIN (title_len , MAX_LEN - 1 ));
487+ if (title_len > 0 )
488+ chapters [i ].title [title_len ] = '\0' ;
489+ else
490+ rb -> snprintf (chapters [i ].title , sizeof (chapters [i ].title ),
491+ "Chapter %d" , i + 1 );
495492 }
496- break ;
493+
494+ * chapter_count = num_chapters ;
495+ rb -> close (fd );
496+ return chapters ; /* Found Nero chapters */
497497 }
498498
499499 file_pos += atom_size ;
500500 }
501501
502502 /* If no Nero chapters found, look for Apple chapters in moov atom */
503- if (chapter_count == 0 ) {
503+ struct chapter_info * chapters = NULL ;
504+ {
504505 file_pos = 0 ;
505506
506507 while (1 ) {
@@ -518,13 +519,13 @@ static int find_mp4_chapters(const char *mp4_path, struct chapter_info *chapters
518519
519520 if (atom_type == MP4_moov ) {
520521 /* Found movie atom - look for chapter tracks */
521- DEBUGF ("Found moov atom, searching for tracks..." );
522+ DEBUGF ("Found moov atom, searching for tracks...\n " );
522523 off_t moov_end = file_pos + atom_size ;
523524 off_t moov_pos = file_pos + 8 ;
524525 int track_num = 0 ;
525526
526527 /* Look for tracks that might contain chapters */
527- while (moov_pos < moov_end && chapter_count == 0 ) {
528+ while (moov_pos < moov_end && * chapter_count == 0 ) {
528529 rb -> lseek (fd , moov_pos , SEEK_SET );
529530 uint32_t sub_size = read_uint32be (fd );
530531 uint32_t sub_type = read_uint32be (fd );
@@ -533,15 +534,15 @@ static int find_mp4_chapters(const char *mp4_path, struct chapter_info *chapters
533534
534535 if (sub_type == MP4_trak ) {
535536 track_num ++ ;
536- DEBUGF ("Checking track %d for chapters..." , track_num );
537+ DEBUGF ("Checking track %d for chapters...\n " , track_num );
537538 /* Check if this track contains chapters */
538- chapter_count = parse_apple_chapter_track (fd , moov_pos + 8 , sub_size - 8 ,
539- chapters );
540- if (chapter_count > 0 ) {
541- DEBUGF ("Found %d chapters in track %d!" , chapter_count , track_num );
539+ chapters = parse_apple_chapter_track (fd , moov_pos + 8 , sub_size - 8 , chapter_count );
540+
541+ if (* chapter_count > 0 ) {
542+ DEBUGF ("Found %d chapters in track %d!\n " , * chapter_count , track_num );
542543 break ;
543544 } else {
544- DEBUGF ("Track %d: no chapters found" , track_num );
545+ DEBUGF ("Track %d: no chapters found\n " , track_num );
545546 }
546547 }
547548
@@ -555,7 +556,7 @@ static int find_mp4_chapters(const char *mp4_path, struct chapter_info *chapters
555556 }
556557
557558 rb -> close (fd );
558- return chapter_count ;
559+ return chapters ; /* No chapters found */
559560}
560561
561562/* Generate CUE file content */
@@ -638,29 +639,22 @@ enum plugin_status plugin_start(const void* parameter) {
638639 /* Reset buffer before use */
639640 buffer_reset ();
640641
641- /* HACK. GET RAW BUFFER. DO ALLOCATION WHEN SIZE IS KNONW. Make sure no other allocations are done BEFORE*/
642- struct chapter_info * chapters = (struct chapter_info * )buffer_alloc (0 );
643- if (!chapters ) {
644- rb -> splash (HZ * 2 , "Out of memory for chapters" );
645- return PLUGIN_ERROR ;
646- }
647-
642+
648643 /* Find MP4 chapters */
649- int chapter_count = find_mp4_chapters (mp4_path , chapters , 1000 );
644+ int chapter_count ;
645+ struct chapter_info * chapters = find_mp4_chapters (mp4_path , & chapter_count );
650646
651647 if (chapter_count <= 0 ) {
652648 rb -> splash (HZ * 2 , "No chapters found" );
653649 return PLUGIN_OK ;
654650 }
655-
656- rb -> splashf (HZ , "Found %d chapters" , chapter_count );
657-
651+
658652 /* Generate CUE file */
659653 if (generate_cue_file (mp4_path , chapters , chapter_count )) {
660654 rb -> splashf (HZ * 2 , "CUE file created with %d tracks" , chapter_count );
661655 return PLUGIN_OK ;
662656 } else {
663- rb -> splash (HZ * 2 , "Failed to create CUE file" );
657+ rb -> splashf (HZ * 2 , "Failed to create CUE file with %d tracks" , chapter_count );
664658 return PLUGIN_ERROR ;
665659 }
666660}
0 commit comments