Index: test/test.c =================================================================== --- test/test.c (revision 1399) +++ test/test.c (working copy) @@ -78,6 +78,7 @@ static int vfr = 0; static int mp4_optimize = 0; static int ipod_atom = 0; +static char * srt = NULL; /* Exit cleanly on Ctrl-C */ static volatile int die = 0; @@ -258,6 +259,7 @@ if( x264opts ) free (x264opts ); if( x264opts2 ) free (x264opts2 ); if (preset_name) free (preset_name); + if (srt) free (srt); fprintf( stderr, "HandBrake has exited.\n" ); @@ -1126,6 +1128,12 @@ job->native_language = strdup( native_language ); } + if( srt != NULL && *srt != '\0' ) + { + job->srt = 1; + job->srt_file = srt; + } + if( job->mux ) { job->mux = mux; @@ -1408,6 +1416,7 @@ " -N, --native-language Select subtitles with this language if it does not\n" " match the Audio language. Provide the language's\n" " iso639-2 code (fre, eng, spa, dut, et cetera)\n" + " -3 --srt Indicate a filepath for loading text subtitles\n" " -m, --markers Add chapter markers (mp4 output format only)\n" "\n" @@ -1569,6 +1578,7 @@ { "subtitle-scan", no_argument, NULL, 'U' }, { "subtitle-forced", no_argument, NULL, 'F' }, { "native-language", required_argument, NULL,'N' }, + { "srt", required_argument, NULL, '3'}, { "encoder", required_argument, NULL, 'e' }, { "aencoder", required_argument, NULL, 'E' }, @@ -1606,7 +1616,7 @@ int c; c = getopt_long( argc, argv, - "hvuC:f:4i:Io:t:Lc:m::a:6:s:UFN:e:E:2dD:789gpOP::w:l:n:b:q:S:B:r:R:Qx:TY:X:VZ:z", + "hvuC:f:3:4i:Io:t:Lc:m::a:6:s:UFN:e:E:2dD:789gpOP::w:l:n:b:q:S:B:r:R:Qx:TY:X:VZ:z", long_options, &option_index ); if( c < 0 ) { @@ -1764,6 +1774,9 @@ case 'N': native_language = strdup( optarg ); break; + case '3': + srt = strdup( optarg ); + break; case '2': twoPass = 1; break; Index: libhb/Jamfile =================================================================== --- libhb/Jamfile (revision 1399) +++ libhb/Jamfile (working copy) @@ -11,7 +11,7 @@ demuxmpeg.c fifo.c render.c reader.c muxcommon.c muxmp4.c sync.c stream.c decsub.c deca52.c decdca.c encfaac.c declpcm.c encx264.c decavcodec.c encxvid.c muxavi.c enclame.c muxogm.c encvorbis.c dvd.c muxmkv.c deblock.c deinterlace.c -denoise.c detelecine.c lang.c enctheora.c ; +denoise.c detelecine.c lang.c enctheora.c srt.c ; Library libhb : $(LIBHB_SRC) ; Index: libhb/muxmp4.c =================================================================== --- libhb/muxmp4.c (revision 1399) +++ libhb/muxmp4.c (working copy) @@ -9,6 +9,8 @@ #include "hb.h" +#include "srt.h" + void AddIPodUUID(MP4FileHandle, MP4TrackId); struct hb_mux_object_s @@ -28,6 +30,9 @@ int current_chapter; uint64_t chapter_duration; + /* Subtitle stuff for muxing */ + MP4TrackId subtitle_track; + /* Sample rate of the first audio track. * Used for the timescale */ @@ -130,7 +135,114 @@ } +void MP4SetTrackYTranslation(const MP4FileHandle file, + const MP4TrackId track, + const u_int16_t translation) +{ + uint8_t* val; + uint8_t nval[38]; + uint32_t *ptr32 = (uint32_t*) (nval + 2); + uint32_t size; + MP4GetTrackBytesProperty(file, track, "tkhd.reserved3", &val, &size); + + memcpy(nval, val, size); + + const uint32_t ytranslation = translation * 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(file, track, "tkhd.reserved3", nval, size); +} + /********************************************************************** + * MP4CreateSubtitleTrack + ********************************************************************** + * Creates a new subtitle track + *********************************************************************/ +MP4TrackId MP4CreateSubtitleTrack(const MP4FileHandle file, + const MP4TrackId refTrack, + const uint16_t height, + const uint16_t width, + const uint16_t translation, + const uint16_t language_code) +{ + const MP4TrackId subtitleTrack = MP4AddSubtitleTrack(file, refTrack); + + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.mdhd.language", language_code); + MP4SetTrackFloatProperty(file, subtitleTrack, "tkhd.width", width); + MP4SetTrackFloatProperty(file, subtitleTrack, "tkhd.height", height); + + MP4SetTrackIntegerProperty(file, subtitleTrack, "tkhd.alternate_group", 2); + + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.defTextBoxTop", 0); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.defTextBoxLeft", 0); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.defTextBoxBottom", 50); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.defTextBoxRight", 0); + + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.fontID", 1); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.fontSize", 12); + + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.horizontalJustification", 1); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.verticalJustification", 255); + + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.fontColorRed", 255); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.fontColorGreen", 255); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.fontColorBlue", 255); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.fontColorAlpha", 255); + + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.bgColorRed", 0); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.bgColorGreen", 0); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.bgColorBlue", 0); + MP4SetTrackIntegerProperty(file, subtitleTrack, "mdia.minf.stbl.stsd.tx3g.bgColorAlpha", 255); + + MP4SetTrackYTranslation(file, subtitleTrack, translation); + + MP4SetTrackTimeScale(file, subtitleTrack, 1000); // Set to ms + + return subtitleTrack; +} + +void MP4WriteSubtitleSample(const MP4FileHandle file, + const MP4TrackId track, + const char* string, + const long offset, + const long duration) +{ + if (duration <= 0) + return; + + if (offset) { + // Write the offset... + u_int8_t empty[2] = {0,0}; + MP4WriteSample(file, + track, + empty, + 2, + offset, + 0, true); + } + + // .. and write the actual sample + const size_t stringLength = strlen(string); + u_int8_t buffer[1024]; + memcpy(buffer+2, string, strlen(string)); // strlen > 1024 -> booom + buffer[0] = (stringLength >> 8) & 0xff; + buffer[1] = stringLength & 0xff; + + MP4WriteSample(file, + track, + buffer, + stringLength + 2, + duration, + 0, true); +} + +/********************************************************************** * MP4Init ********************************************************************** * Allocates hb_mux_data_t structures, create file and write headers @@ -350,6 +462,26 @@ m->current_chapter = job->chapter_start; } + printf("[Srt] implementation"); + if (job->srt) + { + const uint16_t subtitleHeight = 60; + m->subtitle_track = MP4CreateSubtitleTrack(m->file, + mux_data->track, + subtitleHeight, + m->job->width, + m->job->height - subtitleHeight, + language_code); + + struct srt_entry_s* subtitles = srt_readfile(job->srt_file); + while (subtitles) { + MP4WriteSubtitleSample(m->file, m->subtitle_track, subtitles->text, subtitles->offset, subtitles->duration); + subtitles = subtitles->next; + } + + srt_free_entries(subtitles); + } + /* Add encoded-by metadata listing version and build date */ char *tool_string; tool_string = (char *)malloc(80); @@ -401,6 +533,7 @@ 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; @@ -481,6 +614,7 @@ hb_error("Failed to write to output file, disk full?"); *job->die = 1; } + free(sample); } @@ -523,4 +657,3 @@ m->job = job; return m; } - Index: libhb/srt.c =================================================================== --- libhb/srt.c (revision 0) +++ libhb/srt.c (revision 0) @@ -0,0 +1,135 @@ +#include "srt.h" +#include +#include +#include + +struct start_and_end { + unsigned long start, end; +}; + +static struct start_and_end read_time_from_string(const char* timeString) { + // for ex. 00:00:15,248 --> 00:00:16,545 + + long houres1, minutes1, seconds1, milliseconds1, + houres2, minutes2, seconds2, milliseconds2; + + sscanf(timeString, "%ld:%ld:%ld,%ld --> %ld:%ld:%ld,%ld\n", &houres1, &minutes1, &seconds1, &milliseconds1, + &houres2, &minutes2, &seconds2, &milliseconds2); + + struct start_and_end result = {milliseconds1 + seconds1*1000 + minutes1*60000 + houres1*36000, + milliseconds2 + seconds2*1000 + minutes2*60000 + houres2*36000}; + return result; +} + +enum +{ + k_state_inEntry, + k_state_potential_new_entry, + k_state_timecode, +}; + +struct srt_entry_s* srt_readfile(const char* filename) +{ + FILE *file = fopen ( filename, "r" ); + + if (file == NULL) + return NULL; + + char line_buffer[1024]; + unsigned long current_time = 0, + number_of_entries = 0, + current_state = k_state_potential_new_entry; + + struct srt_entry_s *first_entry = malloc(sizeof(struct srt_entry_s)), + *current_entry = first_entry; + memset(first_entry, 0, sizeof(struct srt_entry_s)); + + while (fgets(line_buffer,sizeof(line_buffer),file)) { + switch (current_state) + { + case k_state_timecode: + { + struct start_and_end timing = read_time_from_string(line_buffer); + + current_entry->duration = timing.end - timing.start; + current_entry->offset = timing.start - current_time; + + current_time = timing.end; + + current_state = k_state_inEntry; + continue; + } + + case k_state_inEntry: + { + // If the current line is empty, we assume this is the + // seperation betwene two entries. In case we are wrong, + // the mistake is corrected in the next state. + if (strcmp(line_buffer, "\n") == 0 || strcmp(line_buffer, "\r\n") == 0) { + current_state = k_state_potential_new_entry; + continue; + } + + // Append the current line to the entry's text buffer; + strncat(current_entry->text, line_buffer, 1024); + + break; + } + + case k_state_potential_new_entry: + { + char endpoint[] = "\0"; + const unsigned long potential_entry_number = strtol(line_buffer, (char**)&endpoint, 10); + + // Is this really new next entry begin? + if (potential_entry_number == number_of_entries + 1) { + // We actuall found the next entry - or a really rare error condition + + // The first entry was preallocated - all others are alloced here + // plus we overwrite zero-duration entries + if (number_of_entries != 0) { + current_entry->next = malloc(sizeof(struct srt_entry_s)); + current_entry = current_entry->next; + } + + memset(current_entry, 0, sizeof(struct srt_entry_s)); + + ++number_of_entries; + current_state = k_state_timecode; + continue; + } else { + // Well.. looks like we are in the wrong mode.. lets add the + // newline we misinterpreted... + strncat(current_entry->text, "\n", 1024); + current_state = k_state_inEntry; + } + + break; + } + } + } + + fclose ( file ); + + // Since the number of entries is incremented when we find the next entry, + // we missed the lastone + if (strlen(current_entry->text)) { + ++number_of_entries; + } + + // No entries found - free the preallocaed firstone + if (number_of_entries == 0) { + free(first_entry); + return NULL; + } + + return first_entry; +} + +void srt_free_entries(struct srt_entry_s* entries) +{ + if (entries && entries->next) + srt_free_entries(entries->next); + free(entries); +} + Index: libhb/srt.h =================================================================== --- libhb/srt.h (revision 0) +++ libhb/srt.h (revision 0) @@ -0,0 +1,11 @@ +#pragma once + +struct srt_entry_s { + long offset, duration; + char text[1024]; + + struct srt_entry_s* next; +}; + +struct srt_entry_s* srt_readfile(const char* filename); +void srt_free_entries(struct srt_entry_s* entries); Index: libhb/work.c =================================================================== --- libhb/work.c (revision 1399) +++ libhb/work.c (working copy) @@ -337,6 +337,11 @@ } } + if( job->srt ) + { + hb_log(" + SRT subtitles imported: %s", job->srt_file); + } + /* if we are doing passthru, and the input codec is not the same as the output * codec, then remove this audio from the job */ /* otherwise, Bad Things will happen */ Index: libhb/common.h =================================================================== --- libhb/common.h (revision 1399) +++ libhb/common.h (working copy) @@ -193,6 +193,9 @@ int subtitle; int subtitleSmartAdjust; + int srt; + char * srt_file; + /* Muxer settings mux: output file format file: file path */ Index: macosx/HandBrake.xcodeproj/project.pbxproj =================================================================== --- macosx/HandBrake.xcodeproj/project.pbxproj (revision 1399) +++ macosx/HandBrake.xcodeproj/project.pbxproj (working copy) @@ -90,6 +90,10 @@ 593034EC0BBA39A100172349 /* ChapterTitles.m in Sources */ = {isa = PBXBuildFile; fileRef = 593034EA0BBA39A100172349 /* ChapterTitles.m */; }; 59CBD2370BBB44DA004A3BE3 /* parsecsv.c in Sources */ = {isa = PBXBuildFile; fileRef = 59CBD2360BBB44DA004A3BE3 /* parsecsv.c */; }; 59CBD2650BBB4D1B004A3BE3 /* ChapterTitles.m in Sources */ = {isa = PBXBuildFile; fileRef = 593034EA0BBA39A100172349 /* ChapterTitles.m */; }; + 7495D9E40DA024CC00CDC473 /* srt.c in Sources */ = {isa = PBXBuildFile; fileRef = 7495D9E20DA024CC00CDC473 /* srt.c */; }; + 7495D9E50DA024CC00CDC473 /* srt.h in Headers */ = {isa = PBXBuildFile; fileRef = 7495D9E30DA024CC00CDC473 /* srt.h */; }; + 7495D9E60DA024CC00CDC473 /* srt.c in Sources */ = {isa = PBXBuildFile; fileRef = 7495D9E20DA024CC00CDC473 /* srt.c */; }; + 7495D9E70DA024CC00CDC473 /* srt.h in Headers */ = {isa = PBXBuildFile; fileRef = 7495D9E30DA024CC00CDC473 /* srt.h */; }; A22C85EC0D05D35000C10E36 /* HBPresets.h in Headers */ = {isa = PBXBuildFile; fileRef = A22C85EA0D05D35000C10E36 /* HBPresets.h */; }; A22C85ED0D05D35100C10E36 /* HBPresets.m in Sources */ = {isa = PBXBuildFile; fileRef = A22C85EB0D05D35000C10E36 /* HBPresets.m */; }; A25289E60D87A27D00461D5B /* enctheora.c in Sources */ = {isa = PBXBuildFile; fileRef = A25289E50D87A27D00461D5B /* enctheora.c */; }; @@ -258,6 +262,8 @@ 593034E90BBA39A100172349 /* ChapterTitles.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChapterTitles.h; sourceTree = ""; }; 593034EA0BBA39A100172349 /* ChapterTitles.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ChapterTitles.m; sourceTree = ""; }; 59CBD2360BBB44DA004A3BE3 /* parsecsv.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = parsecsv.c; path = ../test/parsecsv.c; sourceTree = SOURCE_ROOT; }; + 7495D9E20DA024CC00CDC473 /* srt.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srt.c; path = ../libhb/srt.c; sourceTree = SOURCE_ROOT; }; + 7495D9E30DA024CC00CDC473 /* srt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = srt.h; path = ../libhb/srt.h; sourceTree = SOURCE_ROOT; }; A22C85EA0D05D35000C10E36 /* HBPresets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBPresets.h; sourceTree = ""; }; A22C85EB0D05D35000C10E36 /* HBPresets.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBPresets.m; sourceTree = ""; }; A25289E50D87A27D00461D5B /* enctheora.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = enctheora.c; path = ../libhb/enctheora.c; sourceTree = SOURCE_ROOT; }; @@ -432,6 +438,8 @@ 526FBC8D0B4CA9F90064E04C /* libhb Sources */ = { isa = PBXGroup; children = ( + 7495D9E20DA024CC00CDC473 /* srt.c */, + 7495D9E30DA024CC00CDC473 /* srt.h */, A25289E50D87A27D00461D5B /* enctheora.c */, B48359A70C82960500E04440 /* lang.c */, EAA526920C3B25D200944FF2 /* stream.c */, @@ -567,6 +575,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 7495D9E70DA024CC00CDC473 /* srt.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -575,6 +584,7 @@ buildActionMask = 2147483647; files = ( 593034EB0BBA39A100172349 /* ChapterTitles.h in Headers */, + 7495D9E50DA024CC00CDC473 /* srt.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -789,6 +799,7 @@ FC8519550C59A02C0073812C /* deinterlace.c in Sources */, FC8519560C59A02C0073812C /* deblock.c in Sources */, FC8519570C59A02C0073812C /* detelecine.c in Sources */, + 7495D9E60DA024CC00CDC473 /* srt.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -834,6 +845,7 @@ FC8519530C59A02C0073812C /* detelecine.c in Sources */, B48359A80C82960500E04440 /* lang.c in Sources */, A25289E60D87A27D00461D5B /* enctheora.c in Sources */, + 7495D9E40DA024CC00CDC473 /* srt.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; Index: contrib/Jamfile =================================================================== --- contrib/Jamfile (revision 1399) +++ contrib/Jamfile (working copy) @@ -227,6 +227,7 @@ LIBMP4V2_PATCH += "$(PATCH) -p1 < ../patch-mpeg4ip.patch && " ; LIBMP4V2_PATCH += "$(PATCH) -p1 < ../patch-mpeg4ip-nasm-2.00-configure.patch && " ; LIBMP4V2_PATCH += "$(PATCH) -p1 < ../patch-mpeg4ip-ac3.patch && " ; + LIBMP4V2_PATCH += "$(PATCH) -p1 < ../patch-mpeg4ip-srt.patch && " ; Depends $(<) : $(>) ; Depends lib : $(<) ; } Index: contrib/patch-mpeg4ip-srt.patch =================================================================== --- contrib/patch-mpeg4ip-srt.patch (revision 0) +++ contrib/patch-mpeg4ip-srt.patch (revision 0) @@ -0,0 +1,406 @@ +--- mpeg4ip/lib/mp4v2/Makefile.in 2008-03-30 16:58:59.000000000 -0400 ++++ mpeg4ip-patched/lib/mp4v2/Makefile.in 2008-03-30 16:56:50.000000000 -0400 +@@ -55,7 +55,7 @@ + am_libmp4v2_la_OBJECTS = 3gp.lo atom_amr.lo atom_avc1.lo atom_avcC.lo \ + atom_d263.lo atom_damr.lo atom_dref.lo atom_elst.lo \ + atom_enca.lo atom_encv.lo atom_free.lo atom_ftyp.lo \ +- atom_gmin.lo atom_text.lo atom_ac3.lo \ ++ atom_gmin.lo atom_text.lo atom_ac3.lo atom_tx3g.lo atom_ftab.lo atom_nmhd.lo \ + atom_hdlr.lo atom_hinf.lo atom_hnti.lo atom_href.lo \ + atom_mdat.lo atom_mdhd.lo atom_meta.lo atom_mp4a.lo \ + atom_mp4s.lo atom_mp4v.lo atom_mvhd.lo atom_pasp.lo atom_root.lo \ +@@ -83,6 +83,7 @@ + @AMDEP_TRUE@ ./$(DEPDIR)/atom_enca.Plo \ + @AMDEP_TRUE@ ./$(DEPDIR)/atom_encv.Plo \ + @AMDEP_TRUE@ ./$(DEPDIR)/atom_free.Plo \ ++@AMDEP_TRUE@ ./$(DEPDIR)/atom_ftab.Plo \ + @AMDEP_TRUE@ ./$(DEPDIR)/atom_ftyp.Plo \ + @AMDEP_TRUE@ ./$(DEPDIR)/atom_gmin.Plo \ + @AMDEP_TRUE@ ./$(DEPDIR)/atom_hdlr.Plo \ +@@ -96,6 +97,7 @@ + @AMDEP_TRUE@ ./$(DEPDIR)/atom_mp4s.Plo \ + @AMDEP_TRUE@ ./$(DEPDIR)/atom_mp4v.Plo \ + @AMDEP_TRUE@ ./$(DEPDIR)/atom_mvhd.Plo \ ++@AMDEP_TRUE@ ./$(DEPDIR)/atom_nmhd.Plo \ + @AMDEP_TRUE@ ./$(DEPDIR)/atom_root.Plo ./$(DEPDIR)/atom_rtp.Plo \ + @AMDEP_TRUE@ ./$(DEPDIR)/atom_pasp.Plo \ + @AMDEP_TRUE@ ./$(DEPDIR)/atom_s263.Plo ./$(DEPDIR)/atom_sdp.Plo \ +@@ -112,6 +114,7 @@ + @AMDEP_TRUE@ ./$(DEPDIR)/atom_tkhd.Plo \ + @AMDEP_TRUE@ ./$(DEPDIR)/atom_treftype.Plo \ + @AMDEP_TRUE@ ./$(DEPDIR)/atom_trun.Plo \ ++@AMDEP_TRUE@ ./$(DEPDIR)/atom_tx3g.Plo \ + @AMDEP_TRUE@ ./$(DEPDIR)/atom_udta.Plo ./$(DEPDIR)/atom_url.Plo \ + @AMDEP_TRUE@ ./$(DEPDIR)/atom_urn.Plo \ + @AMDEP_TRUE@ ./$(DEPDIR)/atom_video.Plo \ +@@ -354,6 +357,7 @@ + atom_enca.cpp \ + atom_encv.cpp \ + atom_free.cpp \ ++ atom_ftab.cpp \ + atom_ftyp.cpp \ + atom_gmin.cpp \ + atom_hdlr.cpp \ +@@ -367,6 +371,7 @@ + atom_mp4s.cpp \ + atom_mp4v.cpp \ + atom_mvhd.cpp \ ++ atom_nmhd.cpp \ + atom_pasp.cpp \ + atom_root.cpp \ + atom_rtp.cpp \ +@@ -386,6 +391,7 @@ + atom_tkhd.cpp \ + atom_treftype.cpp \ + atom_trun.cpp \ ++ atom_tx3g.cpp \ + atom_udta.cpp \ + atom_url.cpp \ + atom_urn.cpp \ +@@ -510,6 +516,7 @@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_enca.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_encv.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_free.Plo@am__quote@ ++@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_ftab.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_ftyp.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_gmin.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_hdlr.Plo@am__quote@ +@@ -523,6 +530,7 @@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_mp4s.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_mp4v.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_mvhd.Plo@am__quote@ ++@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_nmhd.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_pasp.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_root.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_rtp.Plo@am__quote@ +@@ -541,6 +549,7 @@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_tkhd.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_treftype.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_trun.Plo@am__quote@ ++@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_tx3g.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_udta.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_url.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom_urn.Plo@am__quote@ +--- mpeg4ip/lib/mp4v2/Makefile.am 2008-03-30 16:58:59.000000000 -0400 ++++ mpeg4ip-patched/lib/mp4v2/Makefile.am 2008-03-30 16:47:28.000000000 -0400 +@@ -22,6 +22,7 @@ + atom_encv.cpp \ + atom_free.cpp \ + atom_ftyp.cpp \ ++ atom_ftab.cpp \ + atom_gmin.cpp \ + atom_hdlr.cpp \ + atom_hinf.cpp \ +@@ -35,6 +36,7 @@ + atom_mp4v.cpp \ + atom_mvhd.cpp \ + atom_name.cpp \ ++ atom_nmhd.cpp \ + atom_pasp.cpp \ + atom_root.cpp \ + atom_rtp.cpp \ +@@ -53,6 +55,7 @@ + atom_tfhd.cpp \ + atom_tkhd.cpp \ + atom_treftype.cpp \ ++ atom_tx3g.cpp \ + atom_trun.cpp \ + atom_udta.cpp \ + atom_url.cpp \ +Index: mpeg4ip/lib/mp4v2/mp4file.h +=================================================================== +--- mpeg4ip/lib/mp4v2/mp4file.h (revision 4) ++++ mpeg4ip/lib/mp4v2/mp4file.h (working copy) +@@ -303,6 +303,7 @@ + MP4TrackId AddHintTrack(MP4TrackId refTrackId); + MP4TrackId AddTextTrack(MP4TrackId refTrackId); + MP4TrackId AddChapterTextTrack(MP4TrackId refTrackId); ++ MP4TrackId AddSubtitleTrack(MP4TrackId refTrackId); + + MP4TrackId AddPixelAspectRatio(MP4TrackId trackId, u_int32_t hSpacing, u_int32_t vSpacing); + +Index: mpeg4ip/lib/mp4v2/atom_nmhd.cpp +=================================================================== +--- mpeg4ip/lib/mp4v2/atom_nmhd.cpp (revision 0) ++++ mpeg4ip/lib/mp4v2/atom_nmhd.cpp (revision 0) +@@ -0,0 +1,25 @@ ++/* ++ * The contents of this file are subject to the Mozilla Public ++ * License Version 1.1 (the "License"); you may not use this file ++ * except in compliance with the License. You may obtain a copy of ++ * the License at http://www.mozilla.org/MPL/ ++ * ++ * Software distributed under the License is distributed on an "AS ++ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or ++ * implied. See the License for the specific language governing ++ * rights and limitations under the License. ++ * ++ * The Original Code is MPEG4IP. ++ * ++ * The Initial Developer of the Original Code is Cisco Systems Inc. ++ * Portions created by Cisco Systems Inc. are ++ * Copyright (C) Cisco Systems Inc. 2001. All Rights Reserved. ++ */ ++ ++#include "mp4common.h" ++ ++MP4NmhdAtom::MP4NmhdAtom() ++ : MP4Atom("nmhd") ++{ ++ AddVersionAndFlags(); ++} +\ No newline at end of file +Index: mp4atom.cpp +=================================================================== +--- mpeg4ip/lib/mp4v2/mp4atom.cpp (revision 4) ++++ mpeg4ip/lib/mp4v2/mp4atom.cpp (working copy) +@@ -120,6 +120,8 @@ + pAtom = new MP4FreeAtom(); + } else if (ATOMID(type) == ATOMID("ftyp")) { + pAtom = new MP4FtypAtom(); ++ } else if (ATOMID(type) == ATOMID("ftab")) { ++ pAtom = new MP4FtabAtom(); + } + break; + case 'g': +@@ -176,6 +178,8 @@ + case 'n': + if (ATOMID(type) == ATOMID("name")) { // iTunes + pAtom = new MP4NameAtom(); ++ } else if (ATOMID(type) == ATOMID("nmhd")) { ++ pAtom = new MP4NmhdAtom(); + } + break; + case 'r': +@@ -216,6 +220,8 @@ + case 't': + if (ATOMID(type) == ATOMID("text")) { + pAtom = new MP4TextAtom(); ++ } else if (ATOMID(type) == ATOMID("tx3g")) { ++ pAtom = new MP4Tx3gAtom(); + } else if (ATOMID(type) == ATOMID("tkhd")) { + pAtom = new MP4TkhdAtom(); + } else if (ATOMID(type) == ATOMID("tfhd")) { +Index: mpeg4ip/lib/mp4v2/mp4.cpp +=================================================================== +--- mpeg4ip/lib/mp4v2/mp4.cpp (revision 4) ++++ mpeg4ip/lib/mp4v2/mp4.cpp (working copy) +@@ -973,6 +973,21 @@ + return MP4_INVALID_TRACK_ID; + } + ++extern "C" MP4TrackId MP4AddSubtitleTrack( ++ MP4FileHandle hFile, MP4TrackId refTrackId) ++{ ++ if (MP4_IS_VALID_FILE_HANDLE(hFile)) { ++ try { ++ return ((MP4File*)hFile)->AddSubtitleTrack(refTrackId); ++ } ++ catch (MP4Error* e) { ++ PRINT_ERROR(e); ++ delete e; ++ } ++ } ++ return MP4_INVALID_TRACK_ID; ++} ++ + extern "C" MP4TrackId MP4AddChapterTextTrack( + MP4FileHandle hFile, MP4TrackId refTrackId) + { +Index: mpeg4ip/lib/mp4v2/atom_tx3g.cpp +=================================================================== +--- mpeg4ip/lib/mp4v2/atom_tx3g.cpp (revision 0) ++++ mpeg4ip/lib/mp4v2/atom_tx3g.cpp (revision 0) +@@ -0,0 +1,55 @@ ++/* ++ * The contents of this file are subject to the Mozilla Public ++ * License Version 1.1 (the "License"); you may not use this file ++ * except in compliance with the License. You may obtain a copy of ++ * the License at http://www.mozilla.org/MPL/ ++ * ++ * Software distributed under the License is distributed on an "AS ++ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or ++ * implied. See the License for the specific language governing ++ * rights and limitations under the License. ++ * ++ * The Original Code is MPEG4IP. ++ * ++ * The Initial Developer of the Original Code is Cisco Systems Inc. ++ * Portions created by Cisco Systems Inc. are ++ * Copyright (C) Cisco Systems Inc. 2001. All Rights Reserved. ++ */ ++ ++#include "mp4common.h" ++ ++MP4Tx3gAtom::MP4Tx3gAtom() ++ : MP4Atom("tx3g") ++{ ++ AddReserved("reserved1", 4); /* 0 */ ++ AddReserved("reserved2", 2); /* 1 */ ++ ++ AddProperty(new MP4Integer16Property("dataReferenceIndex"));/* 2 */ ++ ++ AddProperty(new MP4Integer32Property("displayFlags")); /* 3 */ ++ AddProperty(new MP4Integer8Property("horizontalJustification")); /* 4 */ ++ AddProperty(new MP4Integer8Property("verticalJustification")); /* 5 */ ++ ++ AddProperty(new MP4Integer8Property("bgColorRed")); /* 6 */ ++ AddProperty(new MP4Integer8Property("bgColorGreen")); /* 7 */ ++ AddProperty(new MP4Integer8Property("bgColorBlue")); /* 8 */ ++ AddProperty(new MP4Integer8Property("bgColorAlpha")); /* 9 */ ++ ++ AddProperty(new MP4Integer16Property("defTextBoxTop")); /* 10 */ ++ AddProperty(new MP4Integer16Property("defTextBoxLeft")); /* 11 */ ++ AddProperty(new MP4Integer16Property("defTextBoxBottom")); /* 12 */ ++ AddProperty(new MP4Integer16Property("defTextBoxRight")); /* 13 */ ++ ++ AddProperty(new MP4Integer16Property("startChar")); /* 14 */ ++ AddProperty(new MP4Integer16Property("endChar")); /* 15 */ ++ AddProperty(new MP4Integer16Property("fontID")); /* 16 */ ++ AddProperty(new MP4Integer8Property("fontFace")); /* 17 */ ++ AddProperty(new MP4Integer8Property("fontSize")); /* 18 */ ++ ++ AddProperty(new MP4Integer8Property("fontColorRed")); /* 19 */ ++ AddProperty(new MP4Integer8Property("fontColorGreen")); /* 20 */ ++ AddProperty(new MP4Integer8Property("fontColorBlue")); /* 21 */ ++ AddProperty(new MP4Integer8Property("fontColorAlpha")); /* 22 */ ++ ++ ExpectChildAtom("ftab", Optional, Many); ++} +Index: mpeg4ip/lib/mp4v2/atoms.h +=================================================================== +--- mpeg4ip/lib/mp4v2/atoms.h (revision 4) ++++ mpeg4ip/lib/mp4v2/atoms.h (working copy) +@@ -353,6 +353,16 @@ + void GenerateGmhdType(); + }; + ++class MP4Tx3gAtom : public MP4Atom { ++public: ++ MP4Tx3gAtom(); ++}; ++ ++class MP4FtabAtom : public MP4Atom { ++public: ++ MP4FtabAtom(); ++}; ++ + class MP4TfhdAtom : public MP4Atom { + public: + MP4TfhdAtom(); +@@ -406,4 +416,9 @@ + void Generate(); + }; + ++class MP4NmhdAtom : public MP4Atom { ++public: ++ MP4NmhdAtom(); ++}; ++ + #endif /* __MP4_ATOMS_INCLUDED__ */ +Index: mpeg4ip/lib/mp4v2/atom_ftab.cpp +=================================================================== +--- mpeg4ip/lib/mp4v2/atom_ftab.cpp (revision 0) ++++ mpeg4ip/lib/mp4v2/atom_ftab.cpp (revision 0) +@@ -0,0 +1,32 @@ ++/* ++ * The contents of this file are subject to the Mozilla Public ++ * License Version 1.1 (the "License"); you may not use this file ++ * except in compliance with the License. You may obtain a copy of ++ * the License at http://www.mozilla.org/MPL/ ++ * ++ * Software distributed under the License is distributed on an "AS ++ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or ++ * implied. See the License for the specific language governing ++ * rights and limitations under the License. ++ * ++ * The Original Code is MPEG4IP. ++ * ++ * The Initial Developer of the Original Code is Cisco Systems Inc. ++ * Portions created by Cisco Systems Inc. are ++ * Copyright (C) Cisco Systems Inc. 2001. All Rights Reserved. ++ */ ++ ++#include "mp4common.h" ++ ++MP4FtabAtom::MP4FtabAtom() ++ : MP4Atom("ftab") ++{ ++ MP4Integer16Property* pCount = new MP4Integer16Property("entryCount"); /* 0 */ ++ AddProperty(pCount); ++ ++ MP4TableProperty* pTable = new MP4TableProperty("fontEntries", pCount); /* 1 */ ++ AddProperty(pTable); ++ ++ pTable->AddProperty(new MP4Integer16Property("fontID")); /* 0 */ ++ pTable->AddProperty(new MP4StringProperty("name", true)); /* 1 */ ++} +Index: mpeg4ip/lib/mp4v2/mp4file.cpp +=================================================================== +--- mpeg4ip/lib/mp4v2/mp4file.cpp (revision 4) ++++ mpeg4ip/lib/mp4v2/mp4file.cpp (working copy) +@@ -1948,6 +1948,49 @@ + return trackId; + } + ++MP4TrackId MP4File::AddSubtitleTrack(MP4TrackId refTrackId) ++{ ++ // validate reference track id ++ FindTrackIndex(refTrackId); ++ ++ MP4TrackId trackId = ++ AddTrack(MP4_TEXT_TRACK_TYPE, GetTrackTimeScale(refTrackId)); ++ ++ InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "nmhd", 0); ++ ++ AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "tx3g"); ++ ++ MP4StringProperty* pHandlerTypeProperty; ++ FindStringProperty(MakeTrackName(trackId, "mdia.hdlr.handlerType"), ++ (MP4Property**)&pHandlerTypeProperty); ++ pHandlerTypeProperty->SetValue("sbtl"); ++ ++ // Hardcoded crap... add the ftab atom and add one font entry ++ MP4Atom* pFtabAtom = AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.tx3g"), "ftab"); ++ ++ ((MP4Integer16Property*)pFtabAtom->GetProperty(0))->IncrementValue(); ++ ++ MP4Integer16Property* pfontID = (MP4Integer16Property*)((MP4TableProperty*)pFtabAtom->GetProperty(1))->GetProperty(0); ++ pfontID->AddValue(1); ++ ++ MP4StringProperty* pName = (MP4StringProperty*)((MP4TableProperty*)pFtabAtom->GetProperty(1))->GetProperty(1); ++ pName->AddValue("Arial"); ++ ++ ++ // stsd is a unique beast in that it has a count of the number ++ // of child atoms that needs to be incremented after we add the text atom ++ MP4Integer32Property* pStsdCountProperty; ++ FindIntegerProperty( ++ MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"), ++ (MP4Property**)&pStsdCountProperty); ++ pStsdCountProperty->IncrementValue(); ++ ++ /* add the magic "text" atom to the generic media header */ ++// AddChildAtom(MakeTrackName(trackId, "mdia.minf.gmhd"), "text"); ++ ++ return trackId; ++} ++ + MP4TrackId MP4File::AddChapterTextTrack(MP4TrackId refTrackId) + { + // validate reference track id +Index: mpeg4ip/lib/mp4v2/mp4.h +=================================================================== +--- mpeg4ip/lib/mp4v2/mp4.h (revision 4) ++++ mpeg4ip/lib/mp4v2/mp4.h (working copy) +@@ -553,6 +553,10 @@ + MP4FileHandle hFile, + MP4TrackId refTrackId); + ++MP4TrackId MP4AddSubtitleTrack( ++ MP4FileHandle hFile, ++ MP4TrackId refTrackId); ++ + MP4TrackId MP4AddPixelAspectRatio( + MP4FileHandle hFile, + MP4TrackId refTrackId,