OSDN Git Service

x264 bump to r1339-82b80ef
[handbrake-jp/handbrake-jp-git.git] / libhb / muxmp4.c
index 2f375e8..1264f9b 100644 (file)
@@ -1,20 +1,14 @@
 /* $Id: muxmp4.c,v 1.24 2005/11/04 13:09:41 titer Exp $
 
    This file is part of the HandBrake source code.
-   Homepage: <http://handbrake.m0k.org/>.
+   Homepage: <http://handbrake.fr/>.
    It may be used under the terms of the GNU General Public License. */
 
-/* libmp4v2 header */
-#include "mp4.h"
+#include "mp4v2/mp4v2.h"
+#include "a52dec/a52.h"
 
 #include "hb.h"
 
-void AddIPodUUID(MP4FileHandle, MP4TrackId);
-
-/* B-frame muxing variables */
-MP4SampleId thisSample = 0;
-uint64_t initDelay;
-
 struct hb_mux_object_s
 {
     HB_MUX_COMMON;
@@ -24,9 +18,11 @@ struct hb_mux_object_s
     /* libmp4v2 handle */
     MP4FileHandle file;
 
-    /* Cumulated durations so far, in timescale units (see MP4Mux) */
-    uint64_t sum_dur;
-       
+    int64_t sum_dur;    // sum of video frame durations so far
+
+    // bias to keep render offsets in ctts atom positive (set up by encx264)
+    int64_t init_delay;
+
     /* Chapter state information for muxing */
     MP4TrackId chapter_track;
     int current_chapter;
@@ -35,94 +31,39 @@ struct hb_mux_object_s
 
 struct hb_mux_data_s
 {
-    MP4TrackId track;
-};
+    MP4TrackId  track;
+    uint8_t     subtitle;
+    int         sub_format;
 
-struct hb_text_sample_s
-{
-    uint8_t     sample[1280];
-    uint32_t    length;
-    MP4Duration duration;
+    uint64_t    sum_dur; // sum of the frame durations so far
 };
 
-/**********************************************************************
- * MP4CreateTextSample
- **********************************************************************
- * Creates a buffer for a text track sample
- *********************************************************************/
-static struct hb_text_sample_s *MP4CreateTextSample( char *textString, uint64_t duration )
+/* Tune video track chunk duration.
+ * libmp4v2 default duration == dusamplerate == 1 second.
+ * Per van's suggestion we desire duration == 4 frames.
+ * Should be invoked immediately after track creation.
+ *
+ * return true on fail, false on success.
+ */
+static int MP4TuneTrackDurationPerChunk( hb_mux_object_t* m, MP4TrackId trackId )
 {
-    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 = m->current_chapter;
-    hb_chapter_t *chapter_data = hb_list_item( m->job->title->list_chapter, chapter - 1 );
-    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 )
+    uint32_t tscale;
+    MP4Duration dur;
+
+    tscale = MP4GetTrackTimeScale( m->file, trackId );
+    dur = (MP4Duration)ceil( (double)tscale * (double)m->job->vrate_base / (double)m->job->vrate * 4.0 );
+
+    if( !MP4SetTrackDurationPerChunk( m->file, trackId, dur ))
     {
-        snprintf( tmp_buffer, 1023, "Chapter %03i", chapter );
-        string = tmp_buffer;
+        hb_error( "muxmp4.c: MP4SetTrackDurationPerChunk failed!" );
+        *m->job->die = 1;
+        return 0;
     }
-    
-    return MP4CreateTextSample( string, duration );
+
+    hb_deep_log( 2, "muxmp4: track %u, chunk duration %"PRIu64, MP4FindTrackIndex( m->file, trackId ), dur );
+    return 1;
 }
 
 /**********************************************************************
  * MP4Init
  **********************************************************************
@@ -132,29 +73,28 @@ static int MP4Init( hb_mux_object_t * m )
 {
     hb_job_t   * job   = m->job;
     hb_title_t * title = job->title;
-    
+
     hb_audio_t    * audio;
     hb_mux_data_t * mux_data;
     int i;
-    u_int16_t language_code;
-    
+    int subtitle_default;
+
     /* Flags for enabling/disabling tracks in an MP4. */
     typedef enum { TRACK_DISABLED = 0x0, TRACK_ENABLED = 0x1, TRACK_IN_MOVIE = 0x2, TRACK_IN_PREVIEW = 0x4, TRACK_IN_POSTER = 0x8}  track_header_flags;
-    
 
     /* Create an empty mp4 file */
     if (job->largeFileSize)
     /* Use 64-bit MP4 file */
     {
-        m->file = MP4Create( job->file, MP4_DETAILS_ERROR, MP4_CREATE_64BIT_DATA ); 
-        hb_log("Using 64-bit MP4 formatting.");
+        m->file = MP4Create( job->file, MP4_DETAILS_ERROR, MP4_CREATE_64BIT_DATA );
+        hb_deep_log( 2, "muxmp4: using 64-bit MP4 formatting.");
     }
     else
     /* Limit MP4s to less than 4 GB */
     {
         m->file = MP4Create( job->file, MP4_DETAILS_ERROR, 0 );
     }
-    
+
     if (m->file == MP4_INVALID_FILE_HANDLE)
     {
         hb_error("muxmp4.c: MP4Create failed!");
@@ -163,14 +103,10 @@ static int MP4Init( hb_mux_object_t * m )
     }
 
     /* Video track */
-    mux_data      = malloc( sizeof( hb_mux_data_t ) );
+    mux_data      = calloc(1, sizeof( hb_mux_data_t ) );
     job->mux_data = mux_data;
 
-    /* When using the standard 90000 timescale, QuickTime tends to have
-       synchronization issues (audio not playing at the correct speed).
-       To workaround this, we use the audio samplerate as the
-       timescale */
-    if (!(MP4SetTimeScale( m->file, job->arate )))
+    if (!(MP4SetTimeScale( m->file, 90000 )))
     {
         hb_error("muxmp4.c: MP4SetTimeScale failed!");
         *job->die = 1;
@@ -180,42 +116,43 @@ static int MP4Init( hb_mux_object_t * m )
     if( job->vcodec == HB_VCODEC_X264 )
     {
         /* Stolen from mp4creator */
-        if(!(MP4SetVideoProfileLevel( m->file, 0x7F )))
-        {
-            hb_error("muxmp4.c: MP4SetVideoProfileLevel failed!");
-            *job->die = 1;
-            return 0;
-        }
-
-               mux_data->track = MP4AddH264VideoTrack( m->file, job->arate,
+        MP4SetVideoProfileLevel( m->file, 0x7F );
+               mux_data->track = MP4AddH264VideoTrack( m->file, 90000,
                        MP4_INVALID_DURATION, job->width, job->height,
                        job->config.h264.sps[1], /* AVCProfileIndication */
                        job->config.h264.sps[2], /* profile_compat */
                        job->config.h264.sps[3], /* AVCLevelIndication */
                        3 );      /* 4 bytes length before each NAL unit */
-               
+        if ( mux_data->track == MP4_INVALID_TRACK_ID )
+        {
+            hb_error( "muxmp4.c: MP4AddH264VideoTrack failed!" );
+            *job->die = 1;
+            return 0;
+        }
+
+        /* Tune track chunk duration */
+        if( !MP4TuneTrackDurationPerChunk( m, mux_data->track ))
+        {
+            return 0;
+        }
 
         MP4AddH264SequenceParameterSet( m->file, mux_data->track,
                 job->config.h264.sps, job->config.h264.sps_length );
         MP4AddH264PictureParameterSet( m->file, mux_data->track,
                 job->config.h264.pps, job->config.h264.pps_length );
 
-               if( job->h264_level == 30)
+               if( job->h264_level == 30 || job->ipod_atom)
                {
-                       hb_log("About to add iPod atom");
-                       AddIPodUUID(m->file, mux_data->track);
+                       hb_deep_log( 2, "muxmp4: adding iPod atom");
+                       MP4AddIPodUUID(m->file, mux_data->track);
                }
 
+        m->init_delay = job->config.h264.init_delay;
     }
     else /* FFmpeg or XviD */
     {
-        if(!(MP4SetVideoProfileLevel( m->file, MPEG4_SP_L3 )))
-        {
-            hb_error("muxmp4.c: MP4SetVideoProfileLevel failed!");
-            *job->die = 1;
-            return 0;
-        }
-        mux_data->track = MP4AddVideoTrack( m->file, job->arate,
+        MP4SetVideoProfileLevel( m->file, MPEG4_SP_L3 );
+        mux_data->track = MP4AddVideoTrack( m->file, 90000,
                 MP4_INVALID_DURATION, job->width, job->height,
                 MP4_MPEG4_VIDEO_TYPE );
         if (mux_data->track == MP4_INVALID_TRACK_ID)
@@ -224,7 +161,12 @@ static int MP4Init( hb_mux_object_t * m )
             *job->die = 1;
             return 0;
         }
-        
+
+        /* Tune track chunk duration */
+        if( !MP4TuneTrackDurationPerChunk( m, mux_data->track ))
+        {
+            return 0;
+        }
 
         /* VOL from FFmpeg or XviD */
         if (!(MP4SetTrackESConfiguration( m->file, mux_data->track,
@@ -236,164 +178,671 @@ static int MP4Init( hb_mux_object_t * m )
         }
     }
 
-       /* apply the anamorphic transformation matrix if needed */
+    // COLR atom for color and gamma correction.
+    // Per the notes at:
+    //   http://developer.apple.com/quicktime/icefloe/dispatch019.html#colr
+    //   http://forum.doom9.org/showthread.php?t=133982#post1090068
+    // the user can set it from job->color_matrix, otherwise by default
+    // we say anything that's likely to be HD content is ITU BT.709 and
+    // DVD, SD TV & other content is ITU BT.601.  We look at the title height
+    // rather than the job height here to get uncropped input dimensions.
+    if( job->color_matrix == 1 )
+    {
+        // ITU BT.601 DVD or SD TV content
+        MP4AddColr(m->file, mux_data->track, 6, 1, 6);
+    }
+    else if( job->color_matrix == 2 )
+    {
+        // ITU BT.709 HD content
+        MP4AddColr(m->file, mux_data->track, 1, 1, 1);        
+    }
+    else if ( job->title->width >= 1280 || job->title->height >= 720 )
+    {
+        // we guess that 720p or above is ITU BT.709 HD content
+        MP4AddColr(m->file, mux_data->track, 1, 1, 1);
+    }
+    else
+    {
+        // ITU BT.601 DVD or SD TV content
+        MP4AddColr(m->file, mux_data->track, 6, 1, 6);
+    }
 
-       if( job->pixel_ratio ) {
+    if( job->anamorphic.mode )
+    {
+        /* PASP atom for anamorphic video */
+        float width, height;
 
-               uint8_t* val;
-               uint8_t nval[38];
-               uint32_t *ptr32 = (uint32_t*) (nval + 2);
-               uint32_t size;
+        width  = job->anamorphic.par_width;
 
-               MP4GetBytesProperty(m->file, "moov.trak.tkhd.reserved3", &val, &size);
+        height = job->anamorphic.par_height;
 
-               if (size == 38) {
+        MP4AddPixelAspectRatio(m->file, mux_data->track, (uint32_t)width, (uint32_t)height);
 
-                       memcpy(nval, val, size);
+        MP4SetTrackFloatProperty(m->file, mux_data->track, "tkhd.width", job->width * (width / height));
+    }
 
-                       float width, height;
-                       width = job->pixel_aspect_width;
-                       height = job->pixel_aspect_height;
+       /* add the audio tracks */
+    for( i = 0; i < hb_list_count( title->list_audio ); i++ )
+    {
+        audio = hb_list_item( title->list_audio, i );
+        mux_data = calloc(1, sizeof( hb_mux_data_t ) );
+        audio->priv.mux_data = mux_data;
 
-           uint32_t pixelRatioInt;
-           if (width >= height)
-           {
-               pixelRatioInt = (uint32_t)((width / height) * 0x10000);
+        if( audio->config.out.codec == HB_ACODEC_AC3 )
+        {
+            uint8_t fscod = 0;
+            uint8_t bsid = audio->config.in.version;
+            uint8_t bsmod = audio->config.in.mode;
+            uint8_t acmod = audio->config.flags.ac3 & 0x7;
+            uint8_t lfeon = (audio->config.flags.ac3 & A52_LFE) ? 1 : 0;
+            uint8_t bit_rate_code = 0;
 
-#ifdef WORDS_BIGENDIAN
-               ptr32[0] = pixelRatioInt;
-#else
-               /* we need to switch the endianness, as the file format expects big endian */
-               ptr32[0] = ((pixelRatioInt & 0x000000FF) << 24) + ((pixelRatioInt & 0x0000FF00) << 8) + ((pixelRatioInt & 0x00FF0000) >> 8) + ((pixelRatioInt & 0xFF000000) >> 24);
-#endif
+            /*
+             * Rewrite AC3 information into correct format for dac3 atom
+             */
+            switch( audio->config.in.samplerate )
+            {
+            case 48000:
+                fscod = 0;
+                break;
+            case 44100:
+                fscod = 1;
+                break;
+            case 32000:
+                fscod = 2;
+                break;
+            default:
+                /*
+                 * Error value, tells decoder to not decode this audio.
+                 */
+                fscod = 3;
+                break;
+            }
 
-           }
-           else
-           {
-               pixelRatioInt = (uint32_t)((height / width) * 0x10000);
-#ifdef WORDS_BIGENDIAN
-               ptr32[4] = pixelRatioInt;
-#else
-                /* we need to switch the endianness, as the file format expects big endian */
-                ptr32[4] = ((pixelRatioInt & 0x000000FF) << 24) + ((pixelRatioInt & 0x0000FF00) << 8) + ((pixelRatioInt & 0x00FF0000) >> 8) + ((pixelRatioInt & 0xFF000000) >> 24);
-#endif
+            switch( audio->config.in.bitrate )
+            {
+            case 32000:
+                bit_rate_code = 0;
+                break;
+            case 40000:
+                bit_rate_code = 1;
+                break;
+            case 48000:
+                bit_rate_code = 2;
+                break;
+            case 56000:
+                bit_rate_code = 3;
+                break;
+            case 64000:
+                bit_rate_code = 4;
+                break;
+            case 80000:
+                bit_rate_code = 5;
+                break;
+            case 96000:
+                bit_rate_code = 6;
+                break;
+            case 112000:
+                bit_rate_code = 7;
+                break;
+            case 128000:
+                bit_rate_code = 8;
+                break;
+            case 160000:
+                bit_rate_code = 9;
+                break;
+            case 192000:
+                bit_rate_code = 10;
+                break;
+            case 224000:
+                bit_rate_code = 11;
+                break;
+            case 256000:
+                bit_rate_code = 12;
+                break;
+            case 320000:
+                bit_rate_code = 13;
+                break;
+            case 384000:
+                bit_rate_code = 14;
+                break;
+            case 448000:
+                bit_rate_code = 15;
+                break;
+            case 512000:
+                bit_rate_code = 16;
+                break;
+            case 576000:
+                bit_rate_code = 17;
+                break;
+            case 640000:
+                bit_rate_code = 18;
+                break;
+            default:
+                hb_error("Unknown AC3 bitrate");
+                bit_rate_code = 0;
+                break;
             }
 
+            mux_data->track = MP4AddAC3AudioTrack(
+                m->file,
+                audio->config.out.samplerate, 
+                fscod,
+                bsid,
+                bsmod,
+                acmod,
+                lfeon,
+                bit_rate_code);
 
-                       if(!MP4SetBytesProperty(m->file, "moov.trak.tkhd.reserved3", nval, size)) {
-                               hb_log("Problem setting transform matrix");
-                       }
-                       
-               }
+            /* Tune track chunk duration */
+            MP4TuneTrackDurationPerChunk( m, mux_data->track );
 
-       }
+            if (audio->config.out.name == NULL) {
+                MP4SetTrackBytesProperty(
+                    m->file, mux_data->track,
+                    "udta.name.value",
+                    (const uint8_t*)"Surround", strlen("Surround"));
+            }
+            else {
+                MP4SetTrackBytesProperty(
+                    m->file, mux_data->track,
+                    "udta.name.value",
+                    (const uint8_t*)(audio->config.out.name),
+                    strlen(audio->config.out.name));
+            }
+        } else {
+            mux_data->track = MP4AddAudioTrack(
+                m->file,
+                audio->config.out.samplerate, 1024, MP4_MPEG4_AUDIO_TYPE );
 
-       /* end of transformation matrix */
+            /* Tune track chunk duration */
+            MP4TuneTrackDurationPerChunk( m, mux_data->track );
 
-       /* firstAudioTrack will be used to reference the first audio track when we add a chapter track */
-       MP4TrackId firstAudioTrack = 0;
+            if (audio->config.out.name == NULL) {
+                MP4SetTrackBytesProperty(
+                    m->file, mux_data->track,
+                    "udta.name.value",
+                    (const uint8_t*)"Stereo", strlen("Stereo"));
+            }
+            else {
+                MP4SetTrackBytesProperty(
+                    m->file, mux_data->track,
+                    "udta.name.value",
+                    (const uint8_t*)(audio->config.out.name),
+                    strlen(audio->config.out.name));
+            }
+
+            MP4SetAudioProfileLevel( m->file, 0x0F );
+            MP4SetTrackESConfiguration(
+                m->file, mux_data->track,
+                audio->priv.config.aac.bytes, audio->priv.config.aac.length );
+
+            /* Set the correct number of channels for this track */
+             MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.mp4a.channels", (uint16_t)HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->config.out.mixdown));
+        }
 
-       /* add the audio tracks */
-    for( i = 0; i < hb_list_count( title->list_audio ); i++ )
-    {
-       static u_int8_t reserved2[16] = {
-               0x00, 0x00, 0x00, 0x00, 
-               0x00, 0x00, 0x00, 0x00, 
-               0x00, 0x02, 0x00, 0x10,
-               0x00, 0x00, 0x00, 0x00, 
-           };
-           
-        audio = hb_list_item( title->list_audio, i );
-        mux_data = malloc( sizeof( hb_mux_data_t ) );
-        audio->mux_data = mux_data;
-
-        mux_data->track = MP4AddAudioTrack( m->file,
-                job->arate, 1024, MP4_MPEG4_AUDIO_TYPE );
-        MP4SetAudioProfileLevel( m->file, 0x0F );
-        MP4SetTrackESConfiguration( m->file, mux_data->track,
-                audio->config.aac.bytes, audio->config.aac.length );
-                
         /* Set the language for this track */
-        /* The language is stored as 5-bit text - 0x60 */
-        language_code = audio->iso639_2[0] - 0x60;   language_code <<= 5;
-        language_code |= audio->iso639_2[1] - 0x60;  language_code <<= 5;
-        language_code |= audio->iso639_2[2] - 0x60;
-        MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.mdhd.language", language_code);
-        
-        /* Set the correct number of channels for this track */
-        reserved2[9] = (u_int8_t)HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->amixdown);
-        MP4SetTrackBytesProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.mp4a.reserved2", reserved2, sizeof(reserved2));
-        
-        /* store a reference to the first audio track,
-        so we can use it to feed the chapter text track's sample rate */
+        MP4SetTrackLanguage(m->file, mux_data->track, audio->config.lang.iso639_2);
+
+        if( hb_list_count( title->list_audio ) > 1 )
+        {
+            /* Set the audio track alternate group */
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.alternate_group", 1);
+        }
+
         if (i == 0) {
-            firstAudioTrack = mux_data->track;
-            
             /* Enable the first audio track */
             MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.flags", (TRACK_ENABLED | TRACK_IN_MOVIE));
         }
-
         else
             /* Disable the other audio tracks so QuickTime doesn't play
                them all at once. */
         {
             MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.flags", (TRACK_DISABLED | TRACK_IN_MOVIE));
-            hb_log("Disabled extra audio track %i", mux_data->track-1);
+            hb_deep_log( 2, "muxmp4: disabled extra audio track %u", MP4FindTrackIndex( m->file, mux_data->track ));
         }
-               
+
     }
 
-       if (job->chapter_markers) 
+    // Quicktime requires that at least one subtitle is enabled,
+    // else it doesn't show any of the subtitles.
+    // So check to see if any of the subtitles are flagged to be
+    // the defualt.  The default will the the enabled track, else
+    // enable the first track.
+    subtitle_default = 0;
+    for( i = 0; i < hb_list_count( job->list_subtitle ); i++ )
+    {
+        hb_subtitle_t *subtitle = hb_list_item( job->list_subtitle, i );
+
+        if( subtitle && subtitle->format == TEXTSUB && 
+            subtitle->config.dest == PASSTHRUSUB )
+        {
+            if ( subtitle->config.default_track )
+                subtitle_default = 1;
+        }
+    }
+    for( i = 0; i < hb_list_count( job->list_subtitle ); i++ )
     {
-               /* add a text track for the chapters */
-               MP4TrackId textTrack;
-               textTrack = MP4AddChapterTextTrack(m->file, firstAudioTrack);
-        
+        hb_subtitle_t *subtitle = hb_list_item( job->list_subtitle, i );
+
+        if( subtitle && subtitle->format == TEXTSUB && 
+            subtitle->config.dest == PASSTHRUSUB )
+        {
+            uint64_t width, height = 60;
+            if( job->anamorphic.mode )
+                width = job->width * ( (float) job->anamorphic.par_width / job->anamorphic.par_height );
+            else
+                width = job->width;
+
+            mux_data = calloc(1, sizeof( hb_mux_data_t ) );
+            subtitle->mux_data = mux_data;
+            mux_data->subtitle = 1;
+            mux_data->sub_format = subtitle->format;
+            mux_data->track = MP4AddSubtitleTrack( m->file, 90000, width, height );
+
+            MP4SetTrackLanguage(m->file, mux_data->track, subtitle->iso639_2);
+
+            /* Tune track chunk duration */
+            MP4TuneTrackDurationPerChunk( m, mux_data->track );
+
+            const uint8_t textColor[4] = { 255,255,255,255 };
+
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.alternate_group", 2);
+
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.dataReferenceIndex", 1);
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.horizontalJustification", 1);
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.verticalJustification", 255);
+
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.bgColorAlpha", 255);
+
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.defTextBoxBottom", height);
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.defTextBoxRight", width);
+
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.fontID", 1);
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.fontSize", 24);
+
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.fontColorRed", textColor[0]);
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.fontColorGreen", textColor[1]);
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.fontColorBlue", textColor[2]);
+            MP4SetTrackIntegerProperty(m->file, mux_data->track, "mdia.minf.stbl.stsd.tx3g.fontColorAlpha", textColor[3]);
+            
+            /* translate the track */
+            uint8_t* val;
+            uint8_t nval[36];
+            uint32_t *ptr32 = (uint32_t*) nval;
+            uint32_t size;
+
+            MP4GetTrackBytesProperty(m->file, mux_data->track, "tkhd.matrix", &val, &size);
+            memcpy(nval, val, size);
+
+            const uint32_t ytranslation = (job->height - height) * 0x10000;
+                
+#ifdef WORDS_BIGENDIAN
+            ptr32[7] = ytranslation;
+#else
+            /* we need to switch the endianness, as the file format expects big endian */
+            ptr32[7] = ((ytranslation & 0x000000FF) << 24) + ((ytranslation & 0x0000FF00) << 8) + 
+                            ((ytranslation & 0x00FF0000) >> 8) + ((ytranslation & 0xFF000000) >> 24);
+#endif
+
+            MP4SetTrackBytesProperty(m->file, mux_data->track, "tkhd.matrix", nval, size);  
+            if ( !subtitle_default || subtitle->config.default_track ) {
+                /* Enable the default subtitle track */
+                MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.flags", (TRACK_ENABLED | TRACK_IN_MOVIE));
+                subtitle_default = 1;
+            }
+            else
+            {
+                MP4SetTrackIntegerProperty(m->file, mux_data->track, "tkhd.flags", (TRACK_DISABLED | TRACK_IN_MOVIE));
+            }
+        }
+    }
+
+    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, 0);
+
         m->chapter_track = textTrack;
         m->chapter_duration = 0;
         m->current_chapter = job->chapter_start;
-       }
-       
+    }
+
+    /* Add encoded-by metadata listing version and build date */
+    char *tool_string;
+    tool_string = (char *)malloc(80);
+    snprintf( tool_string, 80, "HandBrake %s %i", HB_PROJECT_VERSION, HB_PROJECT_BUILD);
+
+    /* allocate,fetch,populate,store,free tags structure */
+    const MP4Tags* tags;
+    tags = MP4TagsAlloc();
+    MP4TagsFetch( tags, m->file );
+    MP4TagsSetEncodingTool( tags, tool_string );
+    MP4TagsStore( tags, m->file );
+    MP4TagsFree( tags );
+
+    free(tool_string);
+
     return 0;
 }
 
+typedef struct stylerecord_s {
+    enum style_s {ITALIC, BOLD, UNDERLINE} style;
+    uint16_t start;
+    uint16_t stop;
+    struct stylerecord_s *next;
+} stylerecord;
+
+static void hb_makestylerecord( stylerecord **stack, 
+                                enum style_s style, int start )
+{
+    stylerecord *record = calloc( sizeof( stylerecord ), 1 );
+
+    if( record ) 
+    {
+        record->style = style;
+        record->start = start;
+        record->next = *stack;
+        *stack = record;
+    }
+}
+
+static void hb_makestyleatom( stylerecord *record, uint8_t *style)
+{
+    uint8_t face = 1;
+    hb_deep_log(3, "Made style '%s' from %d to %d", 
+           record->style == ITALIC ? "Italic" : record->style == BOLD ? "Bold" : "Underline", record->start, record->stop);
+    
+    switch( record->style )
+    {
+    case ITALIC:
+        face = 2;
+        break;
+    case BOLD:
+        face = 1;
+        break;
+    case UNDERLINE:
+        face = 4;
+        break;
+    default:
+        face = 2;
+        break;
+    }
+
+    style[0] = (record->start >> 8) & 0xff; // startChar
+    style[1] = record->start & 0xff;
+    style[2] = (record->stop >> 8) & 0xff;   // endChar
+    style[3] = record->stop & 0xff;
+    style[4] = (1 >> 8) & 0xff;    // font-ID
+    style[5] = 1 & 0xff;
+    style[6] = face;   // face-style-flags: 1 bold; 2 italic; 4 underline
+    style[7] = 24;      // font-size
+    style[8] = 255;     // r
+    style[9] = 255;     // g
+    style[10] = 255;    // b
+    style[11] = 255;    // a
+}
+
+/*
+ * Copy the input to output removing markup and adding markup to the style
+ * atom where appropriate.
+ */
+static void hb_muxmp4_process_subtitle_style( uint8_t *input,
+                                              uint8_t *output,
+                                              uint8_t *style, uint16_t *stylesize )
+{
+    uint8_t *reader = input;
+    uint8_t *writer = output;
+    uint8_t stylecount = 0;
+    uint16_t utf8_count = 0;         // utf8 count from start of subtitle
+    stylerecord *stylestack = NULL;
+    stylerecord *oldrecord = NULL;
+    
+    while(*reader != '\0') {
+        if( ( *reader & 0xc0 ) == 0x80 ) 
+        {
+            /*
+             * Track the utf8_count when doing markup so that we get the tx3g stops
+             * based on UTF8 chr counts rather than bytes.
+             */
+            utf8_count++;
+            hb_deep_log( 3, "MuxMP4: Counted %d UTF-8 chrs within subtitle so far", 
+                             utf8_count);
+        }
+        if (*reader == '<') {
+            /*
+             * possible markup, peek at the next chr
+             */
+            switch(*(reader+1)) {
+            case 'i':
+                if (*(reader+2) == '>') {
+                    reader += 3;
+                    hb_makestylerecord(&stylestack, ITALIC, (writer - output - utf8_count));
+                } else {
+                    *writer++ = *reader++;
+                }
+                break;
+            case 'b':
+                if (*(reader+2) == '>') {
+                    reader += 3; 
+                    hb_makestylerecord(&stylestack, BOLD, (writer - output - utf8_count));
+                } else {
+                    *writer++ = *reader++;  
+                }
+                break;
+            case 'u': 
+                if (*(reader+2) == '>') {
+                    reader += 3;
+                    hb_makestylerecord(&stylestack, UNDERLINE, (writer - output - utf8_count));
+                } else {
+                    *writer++ = *reader++;
+                }
+                break;
+            case '/':
+                switch(*(reader+2)) {
+                case 'i':
+                    if (*(reader+3) == '>') {
+                        /*
+                         * Check whether we then immediately start more markup of the same type, if so then
+                         * lets not close it now and instead continue this markup.
+                         */
+                        if ((*(reader+4) && *(reader+4) == '<') &&
+                            (*(reader+5) && *(reader+5) == 'i') &&
+                            (*(reader+6) && *(reader+6) == '>')) {
+                            /*
+                             * Opening italics right after, so don't close off these italics.
+                             */
+                            hb_deep_log(3, "Joining two sets of italics");
+                            reader += (4 + 3);
+                            continue;
+                        }
+
+
+                        if ((*(reader+4) && *(reader+4) == ' ') && 
+                            (*(reader+5) && *(reader+5) == '<') &&
+                            (*(reader+6) && *(reader+6) == 'i') &&
+                            (*(reader+7) && *(reader+7) == '>')) {
+                            /*
+                             * Opening italics right after, so don't close off these italics.
+                             */
+                            hb_deep_log(3, "Joining two sets of italics (plus space)");
+                            reader += (4 + 4);
+                            *writer++ = ' ';
+                            continue;
+                        }
+                        if (stylestack && stylestack->style == ITALIC) {
+                            uint8_t style_record[12];
+                            stylestack->stop = writer - output - utf8_count;
+                            hb_makestyleatom(stylestack, style_record);
+
+                            memcpy(style + 10 + (12 * stylecount), style_record, 12);
+                            stylecount++;
+
+                            oldrecord = stylestack;
+                            stylestack = stylestack->next;
+                            free(oldrecord);
+                        } else {
+                            hb_error("Mismatched Subtitle markup '%s'", input);
+                        }
+                        reader += 4;
+                    } else {
+                        *writer++ = *reader++;
+                    }
+                    break;
+                case 'b':
+                    if (*(reader+3) == '>') {
+                        if (stylestack && stylestack->style == BOLD) {
+                            uint8_t style_record[12];
+                            stylestack->stop = writer - output - utf8_count;
+                            hb_makestyleatom(stylestack, style_record);
+
+                            memcpy(style + 10 + (12 * stylecount), style_record, 12);
+                            stylecount++;
+                            oldrecord = stylestack;
+                            stylestack = stylestack->next;
+                            free(oldrecord);
+                        } else {
+                            hb_error("Mismatched Subtitle markup '%s'", input);
+                        }
+
+                        reader += 4;
+                    } else {
+                        *writer++ = *reader++;
+                    }
+                    break;
+                case 'u': 
+                    if (*(reader+3) == '>') {
+                        if (stylestack && stylestack->style == UNDERLINE) {
+                            uint8_t style_record[12];
+                            stylestack->stop = writer - output - utf8_count;
+                            hb_makestyleatom(stylestack, style_record);
+
+                            memcpy(style + 10 + (12 * stylecount), style_record, 12);
+                            stylecount++;
+
+                            oldrecord = stylestack;
+                            stylestack = stylestack->next;
+                            free(oldrecord);
+                        } else {
+                            hb_error("Mismatched Subtitle markup '%s'", input);
+                        }
+                        reader += 4;
+                    } else {
+                        *writer++ = *reader++;
+                    }
+                    break;
+                default:
+                    *writer++ = *reader++;
+                    break;
+                }
+                break;
+            default:
+                *writer++ = *reader++;
+                break;
+            }
+        } else {
+            *writer++ = *reader++;
+        }
+    }
+    *writer = '\0';
+
+    if( stylecount )
+    {
+        *stylesize = 10 + ( stylecount * 12 );
+
+        memcpy( style + 4, "styl", 4);
+
+        style[0] = 0;
+        style[1] = 0;
+        style[2] = (*stylesize >> 8) & 0xff;
+        style[3] = *stylesize & 0xff;
+        style[8] = (stylecount >> 8) & 0xff;
+        style[9] = stylecount & 0xff;
+
+    }
+
+}
+
 static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
                    hb_buffer_t * buf )
 {
     hb_job_t * job = m->job;
-
-    uint64_t duration;
+    int64_t duration;
+    int64_t offset = 0;
 
     if( mux_data == job->mux_data )
-    {    
+    {
+        /* Video */
+
+        // if there are b-frames compute the render offset
+        // (we'll need it for both the video frame & the chapter track)
+        if ( m->init_delay )
+        {
+            offset = buf->start + m->init_delay - m->sum_dur;
+            if ( offset < 0 )
+            {
+                hb_log("MP4Mux: illegal render offset %"PRId64", start %"PRId64","
+                       "stop %"PRId64", sum_dur %"PRId64,
+                       offset, buf->start, buf->stop, m->sum_dur );
+                offset = 0;
+            }
+        }
+
         /* 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 = MP4GenerateChapterSample( m, (m->sum_dur - m->chapter_duration) );
-            
-            if( !MP4WriteSample(m->file, 
-                                m->chapter_track, 
-                                sample->sample, 
-                                sample->length, 
-                                sample->duration, 
-                                0, true) )
+        {    
+            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
+            // not including the current frame minus the duration of all
+            // chapters up to the previous.
+            // The initial and final chapters can be very short (a second or
+            // less) since they're not really chapters but just a placeholder to
+            // insert a cell command. We don't write chapters shorter than 1.5 sec.
+            duration = m->sum_dur - m->chapter_duration + offset;
+            if ( duration >= (90000*3)/2 )
             {
-                hb_error("Failed to write to output file, disk full?");
-                *job->die = 1;
+                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);
+
+                m->current_chapter = buf->new_chap;
+                m->chapter_duration += duration;
             }
-            free(sample);
-            m->current_chapter++;
-            m->chapter_duration = m->sum_dur;
         }
-    
-        /* Video */
-        /* Because we use the audio samplerate as the timescale,
-           we have to use potentially variable durations so the video
-           doesn't go out of sync */
-        duration    = ( buf->stop * job->arate / 90000 ) - m->sum_dur;
+
+        // We're getting the frames in decode order but the timestamps are
+        // for presentation so we have to use durations and effectively
+        // compute a DTS.
+        duration = buf->stop - buf->start;
+        if ( duration <= 0 )
+        {
+            /* We got an illegal mp4/h264 duration. This shouldn't
+               be possible and usually indicates a bug in the upstream code.
+               Complain in the hope that someone will go find the bug but
+               try to fix the error so that the file will still be playable. */
+            hb_log("MP4Mux: illegal duration %"PRId64", start %"PRId64","
+                   "stop %"PRId64", sum_dur %"PRId64,
+                   duration, buf->start, buf->stop, m->sum_dur );
+            /* we don't know when the next frame starts so we can't pick a
+               valid duration for this one. we pick something "short"
+               (roughly 1/3 of an NTSC frame time) to take time from
+               the next frame. */
+            duration = 1000;
+        }
         m->sum_dur += duration;
     }
     else
@@ -402,103 +851,244 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
         duration = MP4_INVALID_DURATION;
     }
 
-    /* When we do get the first keyframe, use its duration as the
-       initial delay added to the frame order offset for b-frames.
-       Because of b-pyramid, double this duration when there are
-       b-pyramids, as denoted by job->areBframes equalling 2. */
-    if ((mux_data->track == 1) && (thisSample == 0) && (buf->frametype & HB_FRAME_KEY) && (job->vcodec == HB_VCODEC_X264))
+    /* Here's where the sample actually gets muxed. */
+    if( job->vcodec == HB_VCODEC_X264 && mux_data == job->mux_data )
     {
-        initDelay = buf->renderOffset;
-        thisSample++;
+        /* Compute dependency flags.
+         *
+         * This mechanism is (optionally) used by media players such as QuickTime
+         * to offer better scrubbing performance. The most influential bits are
+         * MP4_SDT_HAS_NO_DEPENDENTS and MP4_SDT_EARLIER_DISPLAY_TIMES_ALLOWED.
+         *
+         * Other bits are possible but no example media using such bits have been
+         * found.
+         *
+         * It is acceptable to supply 0-bits for any samples which characteristics
+         * cannot be positively guaranteed.
+         */
+        int sync = 0;
+        uint32_t dflags = 0;
+
+        /* encoding layer signals if frame is referenced by other frames */
+        if( buf->flags & HB_FRAME_REF )
+            dflags |= MP4_SDT_HAS_DEPENDENTS;
+        else
+            dflags |= MP4_SDT_HAS_NO_DEPENDENTS; /* disposable */
+
+        switch( buf->frametype )
+        {
+            case HB_FRAME_IDR:
+                sync = 1;
+                break;
+            case HB_FRAME_I:
+                dflags |= MP4_SDT_EARLIER_DISPLAY_TIMES_ALLOWED;
+                break;
+            case HB_FRAME_P:
+                dflags |= MP4_SDT_EARLIER_DISPLAY_TIMES_ALLOWED;
+                break;
+            case HB_FRAME_BREF:
+            case HB_FRAME_B:
+            default:
+                break; /* nothing to mark */
+        }
+
+        if( !MP4WriteSampleDependency( m->file,
+                                       mux_data->track,
+                                       buf->data,
+                                       buf->size,
+                                       duration,
+                                       offset,
+                                       sync,
+                                       dflags ))
+        {
+            hb_error("Failed to write to output file, disk full?");
+            *job->die = 1;
+        }
     }
+    else if (mux_data->subtitle)
+    {
+        if( mux_data->sub_format == TEXTSUB )
+        {
+            /* Write an empty sample */
+            if ( mux_data->sum_dur < buf->start )
+            {
+                uint8_t empty[2] = {0,0};
+                if( !MP4WriteSample( m->file,
+                                    mux_data->track,
+                                    empty,
+                                    2,
+                                    buf->start - mux_data->sum_dur,
+                                    0,
+                                    1 ))
+                {
+                    hb_error("Failed to write to output file, disk full?");
+                    *job->die = 1;
+                } 
+                mux_data->sum_dur += buf->start - mux_data->sum_dur;
+            }
+            uint8_t styleatom[2048];;
+            uint16_t stylesize = 0;
+            uint8_t buffer[2048];
+            uint16_t buffersize = 0;
+            uint8_t output[2048];
+
+            *buffer = '\0';
+
+            /*
+             * Copy the subtitle into buffer stripping markup and creating
+             * style atoms for them.
+             */
+            hb_muxmp4_process_subtitle_style( buf->data,
+                                              buffer,
+                                              styleatom, &stylesize );
+
+            buffersize = strlen((char*)buffer);
 
-    /* Here's where the sample actually gets muxed. 
-       If it's an audio sample, don't offset the sample's playback.
-       If it's a video sample and there are no b-frames, ditto.
-       If there are b-frames, offset by the initDelay plus the
-       difference between the presentation time stamp x264 gives
-       and the decoding time stamp from the buffer data. */
-    if( !MP4WriteSample( m->file, 
-                         mux_data->track, 
-                         buf->data, 
-                         buf->size,
-                         duration, 
-                         ((mux_data->track != 1) || 
-                          (job->areBframes==0) || 
-                          (job->vcodec != HB_VCODEC_X264)) ? 0 : (  buf->renderOffset * job->arate / 90000),
-                         ((buf->frametype & HB_FRAME_KEY) != 0) ) )
+            hb_deep_log(3, "MuxMP4:Sub:%fs:%"PRId64":%"PRId64":%"PRId64": %s",
+                        (float)buf->start / 90000, buf->start, buf->stop, 
+                        (buf->stop - buf->start), buffer);
+
+            /* Write the subtitle sample */
+            memcpy( output + 2, buffer, buffersize );
+            memcpy( output + 2 + buffersize, styleatom, stylesize);
+            output[0] = ( buffersize >> 8 ) & 0xff;
+            output[1] = buffersize & 0xff;
+
+            if( !MP4WriteSample( m->file,
+                                 mux_data->track,
+                                 output,
+                                 buffersize + stylesize + 2,
+                                 buf->stop - buf->start,
+                                 0,
+                                 1 ))
+            {
+                hb_error("Failed to write to output file, disk full?");
+                *job->die = 1;
+            }
+
+            mux_data->sum_dur += (buf->stop - buf->start);
+        }
+    }
+    else
     {
-        hb_error("Failed to write to output file, disk full?");   
-        *job->die = 1;
+        /*
+         * Audio
+         */
+        if( !MP4WriteSample( m->file,
+                             mux_data->track,
+                             buf->data,
+                             buf->size,
+                             duration,
+                             offset,
+                             ( buf->frametype & HB_FRAME_KEY ) != 0 ))
+        {
+            hb_error("Failed to write to output file, disk full?");
+            *job->die = 1;
+        }
     }
-                                
+
+
     return 0;
 }
 
 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 )
     {
-        struct hb_text_sample_s *sample = MP4GenerateChapterSample( m, (m->sum_dur - m->chapter_duration) );
-    
-        if( !MP4WriteSample(m->file, 
-                            m->chapter_track, 
-                            sample->sample, 
-                            sample->length, 
-                            sample->duration, 
-                            0, true) )
+        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 1.5 seconds just skip it. */
+        if ( duration >= (90000*3)/2 )
         {
-            hb_error("Failed to write to output file, disk full?");      
-            *job->die = 1;
+
+            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);
         }
-        free(sample);
     }
-    
-#if 0
-    hb_job_t * job = m->job;
-    char filename[1024]; memset( filename, 0, 1024 );
-#endif
 
     if (job->areBframes)
-    /* Walk the entire video sample table and find the minumum ctts value. */
     {
-           MP4SampleId count = MP4GetTrackNumberOfSamples( m->file, 1);
-           MP4SampleId i;
-           MP4Duration renderingOffset = 2000000000, tmp;
-           
-           // Find the smallest rendering offset
-           for(i = 1; i <= count; i++)
-           {
-               tmp = MP4GetSampleRenderingOffset(m->file, 1, i);
-               if(tmp < renderingOffset)
-                   renderingOffset = tmp;
-           }
-           
-           // Adjust all ctts values down by renderingOffset
-           for(i = 1; i <= count; i++)
-           {
-               MP4SetSampleRenderingOffset(m->file,1,i,
-                   MP4GetSampleRenderingOffset(m->file,1,i) - renderingOffset);
-           }
-           
            // Insert track edit to get A/V back in sync.  The edit amount is
-           // the rendering offset of the first sample.
-           MP4AddTrackEdit(m->file, 1, MP4_INVALID_EDIT_ID, MP4GetSampleRenderingOffset(m->file,1,1),
-               MP4GetTrackDuration(m->file, 1), 0);
+           // the init_delay.
+           int64_t edit_amt = m->init_delay;
+           MP4AddTrackEdit(m->file, 1, MP4_INVALID_EDIT_ID, edit_amt,
+                           MP4GetTrackDuration(m->file, 1), 0);
+            if ( m->job->chapter_markers )
+            {
+                // apply same edit to chapter track to keep it in sync with video
+                MP4AddTrackEdit(m->file, m->chapter_track, MP4_INVALID_EDIT_ID,
+                                edit_amt,
+                                MP4GetTrackDuration(m->file, m->chapter_track), 0);
+            }
      }
 
+    /*
+     * Write the MP4 iTunes metadata if we have any metadata
+     */
+    if( title->metadata )
+    {
+        hb_metadata_t *md = title->metadata;
+        const MP4Tags* tags;
+
+        hb_deep_log( 2, "Writing Metadata to output file...");
+
+        /* allocate tags structure */
+        tags = MP4TagsAlloc();
+        /* fetch data from MP4 file (in case it already has some data) */
+        MP4TagsFetch( tags, m->file );
+
+        /* populate */
+        if( strlen( md->name ))
+            MP4TagsSetName( tags, md->name );
+        if( strlen( md->artist ))
+            MP4TagsSetArtist( tags, md->artist );
+        if( strlen( md->composer ))
+            MP4TagsSetComposer( tags, md->composer );
+        if( strlen( md->comment ))
+            MP4TagsSetComments( tags, md->comment );
+        if( strlen( md->release_date ))
+            MP4TagsSetReleaseDate( tags, md->release_date );
+        if( strlen( md->album ))
+            MP4TagsSetAlbum( tags, md->album );
+        if( strlen( md->genre ))
+            MP4TagsSetGenre( tags, md->genre );
+
+        if( md->coverart )
+        {
+            MP4TagArtwork art;
+            art.data = md->coverart;
+            art.size = md->coverart_size;
+            art.type = MP4_ART_UNDEFINED; // delegate typing to libmp4v2
+            MP4TagsAddArtwork( tags, &art );
+        }
+
+        /* push data to MP4 file */
+        MP4TagsStore( tags, m->file );
+        /* free memory associated with structure */
+        MP4TagsFree( tags );
+    }
+
     MP4Close( m->file );
 
-#if 0
-    hb_log( "muxmp4: optimizing file" );
-    snprintf( filename, 1024, "%s.tmp", job->file );
-    MP4Optimize( job->file, filename, MP4_DETAILS_ERROR );
-    remove( job->file );
-    rename( filename, job->file );
-#endif
+    if ( job->mp4_optimize )
+    {
+        hb_log( "muxmp4: optimizing file" );
+        char filename[1024]; memset( filename, 0, 1024 );
+        snprintf( filename, 1024, "%s.tmp", job->file );
+        MP4Optimize( job->file, filename, MP4_DETAILS_ERROR );
+        remove( job->file );
+        rename( filename, job->file );
+    }
 
     return 0;
 }