From 0f25cfb3cfac4feff657bb79bcc0b689baa99860 Mon Sep 17 00:00:00 2001 From: eddyg Date: Tue, 2 Dec 2008 01:07:02 +0000 Subject: [PATCH] Add metadata support to libhb, add importing of MP4 metadata, add export of MP4 metadata, add importing of MP4 chapters, add seek to chapter for input files, add new libmp4v2, remove old MP4 chapter muxing - now in libmp4v2. git-svn-id: svn://localhost/HandBrake/trunk@1987 b64f7644-9d1e-0410-96f1-a4d463321fa5 --- contrib/Jamfile | 4 +- contrib/version_libmp4v2.txt | 2 +- libhb/Makefile | 3 +- libhb/common.c | 9 ++ libhb/common.h | 17 +++ libhb/decavcodec.c | 1 + libhb/decmetadata.c | 191 +++++++++++++++++++++++++++++ libhb/hb.c | 28 +++++ libhb/internal.h | 5 + libhb/muxmp4.c | 158 +++++++----------------- libhb/reader.c | 26 +++- libhb/stream.c | 146 ++++++++++++++++++++-- libhb/sync.c | 4 + macosx/HandBrake.xcodeproj/project.pbxproj | 6 + 14 files changed, 469 insertions(+), 131 deletions(-) create mode 100644 libhb/decmetadata.c diff --git a/contrib/Jamfile b/contrib/Jamfile index c6dd0e4f..c24c7602 100644 --- a/contrib/Jamfile +++ b/contrib/Jamfile @@ -268,8 +268,8 @@ actions LibMp4v2 { cd `dirname $(>)` && CONTRIB=`pwd` && rm -rf libmp4v2 && (gzip -dc libmp4v2.tar.gz | tar xf - ) && - cd libmp4v2 && $(LIBMP4V2_PATCH) ./configure --prefix=$CONTRIB && - $(MAKE) && make install && + cd libmp4v2 && $(LIBMP4V2_PATCH) rm -rf build && mkdir build && cd build && + ../configure --disable-shared --disable-debug --prefix=$CONTRIB && $(MAKE) && make install && $(STRIP) $CONTRIB/lib/libmp4v2.a } Wget $(SUBDIR)/libmp4v2.tar.gz : $(SUBDIR)/version_libmp4v2.txt ; diff --git a/contrib/version_libmp4v2.txt b/contrib/version_libmp4v2.txt index 1b75cd9c..f0619430 100644 --- a/contrib/version_libmp4v2.txt +++ b/contrib/version_libmp4v2.txt @@ -1 +1 @@ -http://download.m0k.org/handbrake/contrib/libmp4v2-r45.tar.gz +http://download.m0k.org/handbrake/contrib/libmp4v2-2.0-20081201.tar.gz diff --git a/libhb/Makefile b/libhb/Makefile index 858d5b1a..9992d7cd 100644 --- a/libhb/Makefile +++ b/libhb/Makefile @@ -25,7 +25,8 @@ SRCS = common.c hb.c ports.c scan.c work.c decmpeg2.c encavcodec.c enctheora.c \ update.c demuxmpeg.c fifo.c render.c reader.c muxcommon.c stream.c \ muxmp4.c sync.c decsub.c deca52.c decdca.c encfaac.c declpcm.c encx264.c \ decavcodec.c encxvid.c muxmkv.c muxavi.c enclame.c muxogm.c encvorbis.c \ - dvd.c deblock.c deinterlace.c denoise.c detelecine.c decomb.c lang.c + dvd.c deblock.c deinterlace.c denoise.c detelecine.c decomb.c lang.c \ + decmetadata.c OTMP = $(SRCS:%.c=%.o) OBJS = $(OTMP:%.cpp=%.o) diff --git a/libhb/common.c b/libhb/common.c index 7d26905a..0ddcccef 100644 --- a/libhb/common.c +++ b/libhb/common.c @@ -674,6 +674,15 @@ void hb_title_close( hb_title_t ** _t ) } hb_list_close( &t->list_subtitle ); + if( t->metadata ) + { + if( t->metadata->coverart ) + { + free( t->metadata->coverart ); + } + free( t->metadata ); + } + free( t ); *_t = NULL; } diff --git a/libhb/common.h b/libhb/common.h index 2a12e8f4..2545e2d5 100644 --- a/libhb/common.h +++ b/libhb/common.h @@ -41,6 +41,7 @@ typedef struct hb_chapter_s hb_chapter_t; typedef struct hb_audio_s hb_audio_t; typedef struct hb_audio_config_s hb_audio_config_t; typedef struct hb_subtitle_s hb_subtitle_t; +typedef struct hb_metadata_s hb_metadata_t; typedef struct hb_state_s hb_state_t; typedef union hb_esconfig_u hb_esconfig_t; typedef struct hb_work_private_s hb_work_private_t; @@ -432,6 +433,20 @@ struct hb_subtitle_s #endif }; +struct hb_metadata_s +{ + char name[255]; + char artist[255]; + char composer[255]; + char release_date[255]; + char comment[1024]; + char album[255]; + char genre[255]; + enum arttype {UNKNOWN, BMP, GIF87A, GIF89A, JPG, PNG, TIFFL, TIFFB} coverart_type; + uint32_t coverart_size; + uint8_t *coverart; +}; + struct hb_title_s { char dvd[1024]; @@ -476,6 +491,8 @@ struct hb_title_s uint32_t palette[16]; + hb_metadata_t *metadata; + hb_list_t * list_chapter; hb_list_t * list_audio; hb_list_t * list_subtitle; diff --git a/libhb/decavcodec.c b/libhb/decavcodec.c index 81ea4996..82984688 100644 --- a/libhb/decavcodec.c +++ b/libhb/decavcodec.c @@ -190,6 +190,7 @@ static int decavcodecInit( hb_work_object_t * w, hb_job_t * job ) /*XXX*/ if ( codec_id == 0 ) codec_id = CODEC_ID_MP2; + codec = avcodec_find_decoder( codec_id ); pv->parser = av_parser_init( codec_id ); diff --git a/libhb/decmetadata.c b/libhb/decmetadata.c new file mode 100644 index 00000000..7ac9bcff --- /dev/null +++ b/libhb/decmetadata.c @@ -0,0 +1,191 @@ +/* decmetadata.c - Extract and decode metadata from the source + + This file is part of the HandBrake source code. + Homepage: . + It may be used under the terms of the GNU General Public License. */ + +#include + +#include "common.h" + +void identify_art_type( hb_metadata_t *metadata ) +{ + typedef struct header_s { + enum arttype type; + char* name; // short string describing name of type + char* data; // header-bytes to match + } header; + + // types which may be detected by first-bytes only + static header headers[] = { + { BMP, "bmp", "\x4d\x42" }, + { GIF87A, "GIF (87a)", "GIF87a" }, + { GIF89A, "GIF (89a)", "GIF89a" }, + { JPG, "JPEG", "\xff\xd8\xff\xe0" }, + { PNG, "PNG", "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a" }, + { TIFFL, "TIFF (little-endian)", "II42" }, + { TIFFB, "TIFF (big-endian)", "MM42" }, + { UNKNOWN } // must be last + }; + header* p; + header* found = NULL; + for( p = headers; p->type != UNKNOWN; p++ ) { + header *h = p; + + if( metadata->coverart_size < strlen(h->data) ) + continue; + + if( memcmp(h->data, metadata->coverart, strlen(h->data)) == 0 ) { + metadata->coverart_type = h->type; + break; + } + } +} + +static void decmp4metadata( hb_title_t *title ) +{ + MP4FileHandle input_file; + + hb_deep_log( 2, "Got an MP4 input, read the metadata"); + + input_file = MP4Read( title->dvd, 0 ); + + if( input_file != MP4_INVALID_FILE_HANDLE ) + { + char *value = NULL; + uint8_t *cover_art = NULL; + uint32_t size; + uint32_t count; + + /* + * Store iTunes MetaData + */ + if( MP4GetMetadataName( input_file, &value) && value ) + { + hb_deep_log( 2, "Metadata Name in input file is '%s'", value); + strncpy( title->metadata->name, value, 255); + MP4Free(value); + value = NULL; + } + + if( MP4GetMetadataArtist( input_file, &value) && value ) + { + strncpy( title->metadata->artist, value, 255); + MP4Free(value); + value = NULL; + } + + if( MP4GetMetadataComposer( input_file, &value) && value ) + { + strncpy( title->metadata->composer, value, 255); + MP4Free(value); + value = NULL; + } + + if( MP4GetMetadataComment( input_file, &value) && value ) + { + strncpy( title->metadata->comment, value, 1024); + value = NULL; + } + + if( MP4GetMetadataReleaseDate( input_file, &value) && value ) + { + strncpy( title->metadata->release_date, value, 255); + MP4Free(value); + value = NULL; + } + + if( MP4GetMetadataAlbum( input_file, &value) && value ) + { + strncpy( title->metadata->album, value, 255); + MP4Free(value); + value = NULL; + } + + if( MP4GetMetadataGenre( input_file, &value) && value ) + { + strncpy( title->metadata->genre, value, 255); + MP4Free(value); + value = NULL; + } + + if( MP4GetMetadataCoverArt( input_file, &cover_art, &size, 0) && + cover_art ) + { + title->metadata->coverart = cover_art; + title->metadata->coverart_size = size; + identify_art_type( title->metadata ); + hb_deep_log( 2, "Got some cover art of type %d, size %d", + title->metadata->coverart_type, + title->metadata->coverart_size); + } + + /* + * Handle the chapters. + */ + MP4Chapter_t *chapter_list = NULL; + uint32_t chapter_count; + + MP4GetChapters( input_file, &chapter_list, &chapter_count, + MP4ChapterTypeQt ); + + if( chapter_list ) { + uint i = 1; + while( i <= chapter_count ) + { + hb_chapter_t * chapter; + chapter = calloc( sizeof( hb_chapter_t ), 1 ); + chapter->index = i; + chapter->duration = chapter_list[i-1].duration * 90; + chapter->hours = chapter->duration / 90000 / 3600; + chapter->minutes = ( ( chapter->duration / 90000 ) % 3600 ) / 60; + chapter->seconds = ( chapter->duration / 90000 ) % 60; + strcpy( chapter->title, chapter_list[i-1].title ); + hb_deep_log( 2, "Added chapter %i, name='%s', dur=%lld, (%02i:%02i:%02i)", chapter->index, chapter->title, + chapter->duration, chapter->hours, + chapter->minutes, chapter->seconds); + hb_list_add( title->list_chapter, chapter ); + i++; + } + } + + MP4Close( input_file ); + } +} + +/* + * decmetadata() + * + * Look at the title and extract whatever metadata we can from that title. + */ +void decmetadata( hb_title_t *title ) +{ + if( !title ) + { + return; + } + + if( title->metadata ) + { + free( title->metadata ); + title->metadata = NULL; + } + + title->metadata = calloc( sizeof(hb_metadata_t), 1); + + if( !title->metadata ) + { + return; + } + + /* + * Hacky way of figuring out if this is an MP4, in which case read the data using libmp4v2 + */ + if( title->container_name && strcmp(title->container_name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 ) + { + decmp4metadata( title ); + } else { + free( title->metadata ); + title->metadata = NULL; + } +} diff --git a/libhb/hb.c b/libhb/hb.c index 267c5b71..05892484 100644 --- a/libhb/hb.c +++ b/libhb/hb.c @@ -820,6 +820,34 @@ void hb_add( hb_handle_t * h, hb_job_t * job ) hb_list_add( title_copy->list_chapter, chapter_copy ); } + /* + * Copy the metadata + */ + if( title->metadata ) + { + title_copy->metadata = malloc( sizeof( hb_metadata_t ) ); + + if( title_copy->metadata ) + { + memcpy( title_copy->metadata, title->metadata, sizeof( hb_metadata_t ) ); + + /* + * Need to copy the artwork seperatly (TODO). + */ + if( title->metadata->coverart ) + { + title_copy->metadata->coverart = malloc( title->metadata->coverart_size ); + if( title_copy->metadata->coverart ) + { + memcpy( title_copy->metadata->coverart, title->metadata->coverart, + title->metadata->coverart_size ); + } else { + title_copy->metadata->coverart_size = 0; + } + } + } + } + /* Copy the audio track(s) we want */ title_copy->list_audio = hb_list_init(); diff --git a/libhb/internal.h b/libhb/internal.h index d21a281e..29a88dca 100644 --- a/libhb/internal.h +++ b/libhb/internal.h @@ -161,6 +161,11 @@ int hb_demux_null( hb_buffer_t * ps_buf, hb_list_t * es_list, hb_psdemux_t * ); extern const hb_muxer_t hb_demux[]; /*********************************************************************** + * decmetadata.c + **********************************************************************/ +extern void decmetadata( hb_title_t *title ); + +/*********************************************************************** * dvd.c **********************************************************************/ typedef struct hb_dvd_s hb_dvd_t; diff --git a/libhb/muxmp4.c b/libhb/muxmp4.c index afc1f333..c0d1b079 100644 --- a/libhb/muxmp4.c +++ b/libhb/muxmp4.c @@ -43,96 +43,6 @@ struct hb_mux_data_s MP4TrackId track; }; -struct hb_text_sample_s -{ - uint8_t sample[1280]; - uint32_t length; - MP4Duration duration; -}; - -/********************************************************************** - * MP4CreateTextSample - ********************************************************************** - * Creates a buffer for a text track sample - *********************************************************************/ -static struct hb_text_sample_s *MP4CreateTextSample( char *textString, uint64_t duration ) -{ - struct hb_text_sample_s *sample = NULL; - int stringLength = strlen(textString); - int x; - - if( stringLength < 1024 ) - { - sample = malloc( sizeof( struct hb_text_sample_s ) ); - - //textLength = (stringLength; // Account for BOM - sample->length = stringLength + 2 + 12; // Account for text length code and other marker - sample->duration = (MP4Duration)duration; - - // 2-byte length marker - sample->sample[0] = (stringLength >> 8) & 0xff; - sample->sample[1] = stringLength & 0xff; - - strncpy( (char *)&(sample->sample[2]), textString, stringLength ); - - x = 2 + stringLength; - - // Modifier Length Marker - sample->sample[x] = 0x00; - sample->sample[x+1] = 0x00; - sample->sample[x+2] = 0x00; - sample->sample[x+3] = 0x0C; - - // Modifier Type Code - sample->sample[x+4] = 'e'; - sample->sample[x+5] = 'n'; - sample->sample[x+6] = 'c'; - sample->sample[x+7] = 'd'; - - // Modifier Value - sample->sample[x+8] = 0x00; - sample->sample[x+9] = 0x00; - sample->sample[x+10] = (256 >> 8) & 0xff; - sample->sample[x+11] = 256 & 0xff; - } - - return sample; -} - -/********************************************************************** - * MP4GenerateChapterSample - ********************************************************************** - * Creates a buffer for a text track sample - *********************************************************************/ -static struct hb_text_sample_s *MP4GenerateChapterSample( hb_mux_object_t * m, - uint64_t duration, - int chapter ) -{ - // We substract 1 from the chapter number because the chapters start at - // 1 but our name array starts at 0. We substract another 1 because we're - // writing the text of the previous chapter mark (when we get the start - // of chapter 2 we know the duration of chapter 1 & can write its mark). - hb_chapter_t *chapter_data = hb_list_item( m->job->title->list_chapter, - chapter - 2 ); - char tmp_buffer[1024]; - char *string = tmp_buffer; - - tmp_buffer[0] = '\0'; - - if( chapter_data != NULL ) - { - string = chapter_data->title; - } - - if( strlen(string) == 0 || strlen(string) >= 1024 ) - { - snprintf( tmp_buffer, 1023, "Chapter %03i", chapter - 2 ); - string = tmp_buffer; - } - - return MP4CreateTextSample( string, duration ); -} - /********************************************************************** * MP4Init @@ -477,14 +387,14 @@ static int MP4Init( hb_mux_object_t * m ) } - if (job->chapter_markers) + if (job->chapter_markers) { /* add a text track for the chapters. We add the 'chap' atom to track one which is usually the video track & should never be disabled. The Quicktime spec says it doesn't matter which media track the chap atom is on but it has to be an enabled track. */ MP4TrackId textTrack; - textTrack = MP4AddChapterTextTrack(m->file, 1); + textTrack = MP4AddChapterTextTrack(m->file, 1, 0); m->chapter_track = textTrack; m->chapter_duration = 0; @@ -519,13 +429,14 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data, offset = ( buf->start + m->init_delay ) * m->samplerate / 90000 - m->sum_dur; } + /* Add the sample before the new frame. It is important that this be calculated prior to the duration of the new video sample, as we want to sync to right after it. (This is because of how durations for text tracks work in QT) */ if( job->chapter_markers && buf->new_chap ) - { - struct hb_text_sample_s *sample; + { + hb_chapter_t *chapter = NULL; // this chapter is postioned by writing out the previous chapter. // the duration of the previous chapter is the duration up to but @@ -542,19 +453,14 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data, duration = 1000 * m->samplerate / 90000; } - sample = MP4GenerateChapterSample( m, duration, buf->new_chap ); + chapter = hb_list_item( m->job->title->list_chapter, + buf->new_chap - 2 ); + + MP4AddChapter( m->file, + m->chapter_track, + duration, + (chapter != NULL) ? chapter->title : NULL); - if( !MP4WriteSample(m->file, - m->chapter_track, - sample->sample, - sample->length, - sample->duration, - 0, true) ) - { - hb_error("Failed to write to output file, disk full?"); - *job->die = 1; - } - free(sample); m->current_chapter = buf->new_chap; m->chapter_duration += duration; } @@ -612,25 +518,25 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data, static int MP4End( hb_mux_object_t * m ) { hb_job_t * job = m->job; + hb_title_t * title = job->title; /* Write our final chapter marker */ if( m->job->chapter_markers ) { + hb_chapter_t *chapter = NULL; int64_t duration = m->sum_dur - m->chapter_duration; /* The final chapter can have a very short duration - if it's less * than a second just skip it. */ if ( duration >= m->samplerate ) { - struct hb_text_sample_s *sample = MP4GenerateChapterSample( m, duration, - m->current_chapter + 1 ); - if( ! MP4WriteSample(m->file, m->chapter_track, sample->sample, - sample->length, sample->duration, 0, true) ) - { - hb_error("Failed to write to output file, disk full?"); - *job->die = 1; - } - free(sample); + chapter = hb_list_item( m->job->title->list_chapter, + m->current_chapter - 1 ); + + MP4AddChapter( m->file, + m->chapter_track, + duration, + (chapter != NULL) ? chapter->title : NULL); } } @@ -650,6 +556,28 @@ static int MP4End( hb_mux_object_t * m ) } } + /* + * Write the MP4 iTunes metadata if we have any metadata + */ + if( title->metadata ) + { + hb_metadata_t *md = title->metadata; + + hb_deep_log( 2, "Writing Metadata to output file..."); + + MP4SetMetadataName( m->file, md->name ); + MP4SetMetadataArtist( m->file, md->artist ); + MP4SetMetadataComposer( m->file, md->composer ); + MP4SetMetadataComment( m->file, md->comment ); + MP4SetMetadataReleaseDate( m->file, md->release_date ); + MP4SetMetadataAlbum( m->file, md->album ); + MP4SetMetadataGenre( m->file, md->genre ); + if( md->coverart ) + { + MP4SetMetadataCoverArt( m->file, md->coverart, md->coverart_size); + } + } + MP4Close( m->file ); if ( job->mp4_optimize ) diff --git a/libhb/reader.c b/libhb/reader.c index 911a33bd..be7a9aec 100644 --- a/libhb/reader.c +++ b/libhb/reader.c @@ -215,10 +215,32 @@ static void ReaderFunc( void * _r ) } else if ( r->stream && r->job->start_at_preview ) { + // XXX code from DecodePreviews - should go into its own routine hb_stream_seek( r->stream, (float)( r->job->start_at_preview - 1 ) / ( r->job->seek_points ? ( r->job->seek_points + 1.0 ) : 11.0 ) ); + } + else if( r->stream ) + { + /* + * Standard stream, seek to the starting chapter, if set, and track the + * end chapter so that we end at the right time. + */ + int start = r->job->chapter_start; + hb_chapter_t *chap = hb_list_item( r->title->list_chapter, chapter_end - 1 ); + + chapter_end = chap->index; + if (start > 1) + { + chap = hb_list_item( r->title->list_chapter, start - 1 ); + start = chap->index; + } + + /* + * Seek to the start chapter. + */ + hb_stream_seek_chapter( r->stream, start ); } list = hb_list_init(); @@ -228,9 +250,9 @@ static void ReaderFunc( void * _r ) while( !*r->die && !r->job->done ) { if (r->dvd) - chapter = hb_dvd_chapter( r->dvd ); + chapter = hb_dvd_chapter( r->dvd ); else if (r->stream) - chapter = 1; + chapter = hb_stream_chapter( r->stream ); if( chapter < 0 ) { diff --git a/libhb/stream.c b/libhb/stream.c index d5ab019d..48375565 100755 --- a/libhb/stream.c +++ b/libhb/stream.c @@ -13,6 +13,7 @@ #include "a52dec/a52.h" #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" +#include "mp4v2/mp4v2.h" #define min(a, b) a < b ? a : b @@ -119,6 +120,9 @@ struct hb_stream_s hb_buffer_t *fwrite_buf; /* PS buffer (set by hb_ts_stream_decode) */ + int chapter; /* Chapter that we are currently in */ + uint64_t chapter_end; /* HB time that the current chapter ends */ + /* * Stuff before this point is dynamic state updated as we read the * stream. Stuff after this point is stream description state that @@ -1082,6 +1086,70 @@ int hb_stream_read( hb_stream_t * src_stream, hb_buffer_t * b ) } /*********************************************************************** + * hb_stream_seek_chapter + *********************************************************************** + * + **********************************************************************/ +int hb_stream_seek_chapter( hb_stream_t * stream, int chapter_num ) +{ + AVFormatContext *ic = stream->ffmpeg_ic; + uint64_t end_offset = 0; + uint64_t start_offset = 0; + uint64_t pos = 0; + hb_chapter_t *chapter = NULL; + int i; + + if( !stream || !stream->title ) + { + return 0; + } + + for( i = 0; i < chapter_num; i++) + { + chapter = hb_list_item( stream->title->list_chapter, + i ); + + if( chapter ) + { + /* + * Seeking to a chapter means that we are in that chapter, + * so track which chapter we are in so that we can output + * the correct chapter numbers in buf->new_chap + */ + start_offset = end_offset; + end_offset += chapter->duration; + stream->chapter = i; + stream->chapter_end = end_offset; + } else { + return 0; + } + } + + /* + * Is the the correct way to convert timebases? It seems to get it pretty + * much right - plus a few seconds, which is odd. + */ + pos = ((start_offset * AV_TIME_BASE) / 90000); + + hb_deep_log( 2, "Seeking to chapter %d time (starts: %lld ends %lld) AV pos %lld", chapter_num-1, start_offset, end_offset, pos); + + av_seek_frame( ic, -1, pos, 0); + + return 1; +} + +/*********************************************************************** + * hb_stream_chapter + *********************************************************************** + * Return the number of the chapter that we are currently in. We store + * the chapter number starting from 0, so + 1 for the real chpater num. + **********************************************************************/ +int hb_stream_chapter( hb_stream_t * src_stream ) +{ + return( src_stream->chapter + 1 ); +} + +/*********************************************************************** * hb_stream_seek *********************************************************************** * @@ -2585,16 +2653,6 @@ static hb_title_t *ffmpeg_title_scan( hb_stream_t *stream ) title->minutes = ( dur % 3600 ) / 60; title->seconds = dur % 60; - // One Chapter - hb_chapter_t * chapter; - chapter = calloc( sizeof( hb_chapter_t ), 1 ); - chapter->index = 1; - chapter->duration = title->duration; - chapter->hours = title->hours; - chapter->minutes = title->minutes; - chapter->seconds = title->seconds; - hb_list_add( title->list_chapter, chapter ); - // set the title to decode the first video stream in the file title->demuxer = HB_NULL_DEMUXER; title->video_codec = 0; @@ -2625,6 +2683,26 @@ static hb_title_t *ffmpeg_title_scan( hb_stream_t *stream ) title->container_name = strdup( ic->iformat->name ); title->data_rate = ic->bit_rate; + hb_deep_log( 2, "Found ffmpeg %d chapters, container=%s", ic->nb_chapters, ic->iformat->name); + + /* + * Fill the metadata. + */ + decmetadata( title ); + + if( hb_list_count( title->list_chapter ) == 0 ) + { + // Need at least one chapter + hb_chapter_t * chapter; + chapter = calloc( sizeof( hb_chapter_t ), 1 ); + chapter->index = 1; + chapter->duration = title->duration; + chapter->hours = title->hours; + chapter->minutes = title->minutes; + chapter->seconds = title->seconds; + hb_list_add( title->list_chapter, chapter ); + } + return title; } @@ -2714,6 +2792,54 @@ static int ffmpeg_read( hb_stream_t *stream, hb_buffer_t *buf ) { buf->start = buf->renderOffset; } + + /* + * Check to see whether this video buffer is on a chapter + * boundary, if so mark it as such in the buffer. The chapters for + * a stream have a simple duration for each chapter. So we keep + * track of what chapter we are in currently, and when it is due + * to end. + */ + hb_deep_log( 3, "title=0x%x, job=0x%x, chapter_markers=%d, time=%lld, chapter=%d, end_chapter=%lld", + stream->title, + stream->title ? (stream->title->job ? stream->title->job : 0x0) : 0x0, + stream->title ? (stream->title->job ? stream->title->job->chapter_markers : 2) : 0x0, + buf->start, stream->chapter, stream->chapter_end); + + if( stream->title && + stream->title->job && + stream->title->job->chapter_markers && + buf->id == stream->ffmpeg_video_id && + buf->start >= stream->chapter_end ) + { + hb_chapter_t *chapter = NULL; + + /* + * Store when this chapter ends using HB time. + */ + chapter = hb_list_item( stream->title->list_chapter, + stream->chapter ); + + if( chapter ) + { + if( stream->chapter != 0 ) + { + buf->new_chap = stream->chapter + 2; + } + + hb_deep_log( 2, "Starting chapter %i at %lld", buf->new_chap, buf->start); + stream->chapter_end += chapter->duration; + stream->chapter++; + hb_deep_log( 2, "Looking for chapter %i at %lld", stream->chapter+1, stream->chapter_end ); + } else { + /* + * Must have run out of chapters, stop looking. + */ + stream->chapter_end = -1; + } + } else { + buf->new_chap = 0; + } av_free_packet( stream->ffmpeg_pkt ); return 1; } diff --git a/libhb/sync.c b/libhb/sync.c index 03de54c4..e8109590 100644 --- a/libhb/sync.c +++ b/libhb/sync.c @@ -332,6 +332,10 @@ static void SyncVideo( hb_work_object_t * w ) } } + if( cur->new_chap ) { + hb_log("sync got new chapter %d", cur->new_chap ); + } + /* * since the first frame is always 0 and the upstream reader code * is taking care of adjusting for pts discontinuities, we just have diff --git a/macosx/HandBrake.xcodeproj/project.pbxproj b/macosx/HandBrake.xcodeproj/project.pbxproj index 6bf95721..ad3f6612 100644 --- a/macosx/HandBrake.xcodeproj/project.pbxproj +++ b/macosx/HandBrake.xcodeproj/project.pbxproj @@ -115,6 +115,8 @@ A2D7AD6F0C998AD30082CA33 /* Source.tiff in Resources */ = {isa = PBXBuildFile; fileRef = A2D7AD660C998AD30082CA33 /* Source.tiff */; }; A9AC41DF0C918DB500DDF9B8 /* HBAdvancedController.m in Sources */ = {isa = PBXBuildFile; fileRef = A9AC41DD0C918DB500DDF9B8 /* HBAdvancedController.m */; }; A9AC41E00C918DB500DDF9B8 /* HBAdvancedController.h in Headers */ = {isa = PBXBuildFile; fileRef = A9AC41DE0C918DB500DDF9B8 /* HBAdvancedController.h */; }; + B453420A0EE3619C005D6F26 /* decmetadata.c in Sources */ = {isa = PBXBuildFile; fileRef = B45342080EE3619C005D6F26 /* decmetadata.c */; }; + B453420B0EE3619C005D6F26 /* decmetadata.c in Sources */ = {isa = PBXBuildFile; fileRef = B45342080EE3619C005D6F26 /* decmetadata.c */; }; B48359A80C82960500E04440 /* lang.c in Sources */ = {isa = PBXBuildFile; fileRef = B48359A70C82960500E04440 /* lang.c */; }; D289A9F30DBBE7AC00CE614B /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D289A9F20DBBE7AC00CE614B /* CoreServices.framework */; }; D289AAC40DBBF3F100CE614B /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4DEB2024052B055F00C39CA9 /* IOKit.framework */; }; @@ -275,6 +277,7 @@ A2D7AD660C998AD30082CA33 /* Source.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = Source.tiff; sourceTree = ""; }; A9AC41DD0C918DB500DDF9B8 /* HBAdvancedController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = HBAdvancedController.m; sourceTree = ""; }; A9AC41DE0C918DB500DDF9B8 /* HBAdvancedController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = HBAdvancedController.h; sourceTree = ""; }; + B45342080EE3619C005D6F26 /* decmetadata.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = decmetadata.c; path = ../libhb/decmetadata.c; sourceTree = SOURCE_ROOT; }; B48359A70C82960500E04440 /* lang.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = lang.c; path = ../libhb/lang.c; sourceTree = SOURCE_ROOT; }; D289A9F20DBBE7AC00CE614B /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = /System/Library/Frameworks/CoreServices.framework; sourceTree = ""; }; E3003C7E0C88505D0072F2A8 /* DeleteHighlightPressed.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = DeleteHighlightPressed.png; sourceTree = ""; }; @@ -373,6 +376,7 @@ 29B97314FDCFA39411CA2CEA /* HandBrake */ = { isa = PBXGroup; children = ( + B45342080EE3619C005D6F26 /* decmetadata.c */, 526FBC930B4CAA260064E04C /* HandBrake Sources */, 526FBC920B4CAA120064E04C /* HandBrakeCLI Sources */, 526FBC8D0B4CA9F90064E04C /* libhb Sources */, @@ -770,6 +774,7 @@ FC8519560C59A02C0073812C /* deblock.c in Sources */, FC8519570C59A02C0073812C /* detelecine.c in Sources */, 749701100DC281BB009200D8 /* decomb.c in Sources */, + B453420B0EE3619C005D6F26 /* decmetadata.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -815,6 +820,7 @@ B48359A80C82960500E04440 /* lang.c in Sources */, A25289E60D87A27D00461D5B /* enctheora.c in Sources */, 7497010F0DC281BB009200D8 /* decomb.c in Sources */, + B453420A0EE3619C005D6F26 /* decmetadata.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; -- 2.11.0