OSDN Git Service

x264 bump to r1339-82b80ef
[handbrake-jp/handbrake-jp-git.git] / libhb / muxmp4.c
index bdeea8c..1264f9b 100644 (file)
@@ -31,7 +31,11 @@ struct hb_mux_object_s
 
 struct hb_mux_data_s
 {
-    MP4TrackId track;
+    MP4TrackId  track;
+    uint8_t     subtitle;
+    int         sub_format;
+
+    uint64_t    sum_dur; // sum of the frame durations so far
 };
 
 /* Tune video track chunk duration.
@@ -56,7 +60,7 @@ static int MP4TuneTrackDurationPerChunk( hb_mux_object_t* m, MP4TrackId trackId
         return 0;
     }
 
-    hb_deep_log( 2, "muxmp4: track %u, chunk duration %llu", MP4FindTrackIndex( m->file, trackId ), dur );
+    hb_deep_log( 2, "muxmp4: track %u, chunk duration %"PRIu64, MP4FindTrackIndex( m->file, trackId ), dur );
     return 1;
 }
 
@@ -73,6 +77,7 @@ static int MP4Init( hb_mux_object_t * m )
     hb_audio_t    * audio;
     hb_mux_data_t * mux_data;
     int i;
+    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;
@@ -98,7 +103,7 @@ 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;
 
     if (!(MP4SetTimeScale( m->file, 90000 )))
@@ -220,7 +225,7 @@ static int MP4Init( hb_mux_object_t * m )
     for( i = 0; i < hb_list_count( title->list_audio ); i++ )
     {
         audio = hb_list_item( title->list_audio, i );
-        mux_data = malloc( sizeof( hb_mux_data_t ) );
+        mux_data = calloc(1, sizeof( hb_mux_data_t ) );
         audio->priv.mux_data = mux_data;
 
         if( audio->config.out.codec == HB_ACODEC_AC3 )
@@ -399,6 +404,100 @@ static int MP4Init( hb_mux_object_t * m )
 
     }
 
+    // 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++ )
+    {
+        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
@@ -431,13 +530,250 @@ static int MP4Init( hb_mux_object_t * m )
     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;
     int64_t duration;
     int64_t offset = 0;
-    int i;
 
     if( mux_data == job->mux_data )
     {
@@ -450,8 +786,8 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
             offset = buf->start + m->init_delay - m->sum_dur;
             if ( offset < 0 )
             {
-                hb_log("MP4Mux: illegal render offset %lld, start %lld,"
-                       "stop %lld, sum_dur %lld",
+                hb_log("MP4Mux: illegal render offset %"PRId64", start %"PRId64","
+                       "stop %"PRId64", sum_dur %"PRId64,
                        offset, buf->start, buf->stop, m->sum_dur );
                 offset = 0;
             }
@@ -498,8 +834,8 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
                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 %lld, start %lld,"
-                   "stop %lld, sum_dur %lld",
+            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"
@@ -569,8 +905,75 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
             *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);
+
+            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
     {
+        /*
+         * Audio
+         */
         if( !MP4WriteSample( m->file,
                              mux_data->track,
                              buf->data,
@@ -584,33 +987,6 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
         }
     }
 
-    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->dest == PASSTHRUSUB )
-        {
-            /*
-             * Should be adding this one if the timestamp is right.
-             */
-            hb_buffer_t *sub;
-
-            while( ( sub = hb_fifo_see( subtitle->fifo_out )) != NULL )
-            {
-                if (sub->start < buf->start ) {
-                    sub = hb_fifo_get( subtitle->fifo_out );
-                    hb_log("MuxMP4: Text Sub:%lld: %s", sub->start, sub->data);
-                    hb_buffer_close( &sub );
-                } else {
-                    /*
-                     * Not time yet
-                     */
-                    break;
-                }
-            }
-        }
-    }
 
     return 0;
 }
@@ -672,13 +1048,20 @@ static int MP4End( hb_mux_object_t * m )
         MP4TagsFetch( tags, m->file );
 
         /* populate */
-        MP4TagsSetName( tags, md->name );
-        MP4TagsSetArtist( tags, md->artist );
-        MP4TagsSetComposer( tags, md->composer );
-        MP4TagsSetComments( tags, md->comment );
-        MP4TagsSetReleaseDate( tags, md->release_date );
-        MP4TagsSetAlbum( tags, md->album );
-        MP4TagsSetGenre( tags, md->genre );
+        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 )
         {