]> granicus.if.org Git - handbrake/commitdiff
Add metadata support to libhb, add importing of MP4 metadata, add export of MP4 metad...
authoreddyg <eddyg.hb@myreflection.org>
Tue, 2 Dec 2008 01:07:02 +0000 (01:07 +0000)
committereddyg <eddyg.hb@myreflection.org>
Tue, 2 Dec 2008 01:07:02 +0000 (01:07 +0000)
git-svn-id: svn://svn.handbrake.fr/HandBrake/trunk@1987 b64f7644-9d1e-0410-96f1-a4d463321fa5

14 files changed:
contrib/Jamfile
contrib/version_libmp4v2.txt
libhb/Makefile
libhb/common.c
libhb/common.h
libhb/decavcodec.c
libhb/decmetadata.c [new file with mode: 0644]
libhb/hb.c
libhb/internal.h
libhb/muxmp4.c
libhb/reader.c
libhb/stream.c
libhb/sync.c
macosx/HandBrake.xcodeproj/project.pbxproj

index c6dd0e4fe75f14d49ccdc72a136b7fb38530a393..c24c7602df7cd5fdf71b5c33e89b7c3611cde01b 100644 (file)
@@ -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 ;
index 1b75cd9c96f9a5e15fbff5c8edef688e3891406b..f061943091744a6ff31ad45c07ad891ced8f4d8c 100644 (file)
@@ -1 +1 @@
-http://download.m0k.org/handbrake/contrib/libmp4v2-r45.tar.gz
+http://download.m0k.org/handbrake/contrib/libmp4v2-2.0-20081201.tar.gz
index 858d5b1aa67497fc5064c8a1f44cbeefc65fb015..9992d7cd58b285f8375b2b31caa292a94b6ad112 100644 (file)
@@ -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)
 
index 7d26905a61a33168e5248fe9ed083c2e5819304d..0ddcccef576384690b08f411d42ac71ef8961591 100644 (file)
@@ -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;
 }
index 2a12e8f4597632ab74ab6bdcbbe1f61fb11aec30..2545e2d50db34a26ff846b2be3678a63a2296408 100644 (file)
@@ -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;
index 81ea4996b456b123d099a158347229c7bfdfaba8..8298468807aefe6b4b11632202a6a2135bcc3c62 100644 (file)
@@ -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 (file)
index 0000000..7ac9bcf
--- /dev/null
@@ -0,0 +1,191 @@
+/* decmetadata.c - Extract and decode metadata from the source
+
+   This file is part of the HandBrake source code.
+   Homepage: <http://handbrake.fr/>.
+   It may be used under the terms of the GNU General Public License. */
+
+#include <mp4v2/mp4v2.h>
+
+#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;
+    }
+}
index 267c5b716d3c06540dbd570973733ac5a4aa744b..0589248481e3c128dac3a105d9a36864b7b7bd24 100644 (file)
@@ -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();
 
index d21a281e0b9cac89b69c59832c278b62cb5de494..29a88dca6d379c0b9c32b6e58db47c51675a6674 100644 (file)
@@ -160,6 +160,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
  **********************************************************************/
index afc1f333b960c281b5bbbe92af8f48b5d633bbb8..c0d1b0799d6e0545faab76f5e6ca4e5287ed2011 100644 (file)
@@ -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 )
index 911a33bd65190168cb6f2b0e89e58252ae00d556..be7a9aecc2fb891405d9532fee3615b3ba2f4231 100644 (file)
@@ -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 )
         {
index d5ab019d54d7c0009d04ecb600bbc519f74e92e7..4837556596c37585d6635aeeea8f8d2c992dd4c4 100755 (executable)
@@ -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
@@ -1081,6 +1085,70 @@ int hb_stream_read( hb_stream_t * src_stream, hb_buffer_t * b )
     return hb_ts_stream_decode( src_stream, 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;
 }
index 03de54c458cf76708d8425cbabecdfe37d1b8650..e8109590781cc2edd86b31a908b79952cab00d75 100644 (file)
@@ -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
index 6bf95721014e1f04a1cab6fd9ad957b397c728fb..ad3f661242feb9c82054fe0f6a2f830c297b37a9 100644 (file)
                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 */; };
                A2D7AD660C998AD30082CA33 /* Source.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = Source.tiff; sourceTree = "<group>"; };
                A9AC41DD0C918DB500DDF9B8 /* HBAdvancedController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = HBAdvancedController.m; sourceTree = "<group>"; };
                A9AC41DE0C918DB500DDF9B8 /* HBAdvancedController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = HBAdvancedController.h; sourceTree = "<group>"; };
+               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 = "<absolute>"; };
                E3003C7E0C88505D0072F2A8 /* DeleteHighlightPressed.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = DeleteHighlightPressed.png; sourceTree = "<group>"; };
                29B97314FDCFA39411CA2CEA /* HandBrake */ = {
                        isa = PBXGroup;
                        children = (
+                               B45342080EE3619C005D6F26 /* decmetadata.c */,
                                526FBC930B4CAA260064E04C /* HandBrake Sources */,
                                526FBC920B4CAA120064E04C /* HandBrakeCLI Sources */,
                                526FBC8D0B4CA9F90064E04C /* libhb Sources */,
                                FC8519560C59A02C0073812C /* deblock.c in Sources */,
                                FC8519570C59A02C0073812C /* detelecine.c in Sources */,
                                749701100DC281BB009200D8 /* decomb.c in Sources */,
+                               B453420B0EE3619C005D6F26 /* decmetadata.c in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                                B48359A80C82960500E04440 /* lang.c in Sources */,
                                A25289E60D87A27D00461D5B /* enctheora.c in Sources */,
                                7497010F0DC281BB009200D8 /* decomb.c in Sources */,
+                               B453420A0EE3619C005D6F26 /* decmetadata.c in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };