First of all, the stuff I implemented is in a -very- early stage. I was hoping that I have some time this weeked to get it ready - but in the end i barely had enough time to add an srt reader. Ahh.. If only I had no life... and no job...
Anyway... the options were either wait till i can finally squeeze out enough time to get this done - or just post what i got so far now.
So, where are we now...
libmp4v2 has been extened to fully support tx3g, ftab and nmhd atoms. A new method has been added MP4AddSubtitleTrack. This method adds the tx3g atom and the ftab with hardcoded values... (which is required on the iphone/ipod touch - but not in qt... and both seem to ignore its properties anyway).
I didn't get as far as i wanted this weekend with libhb... but there we go:
- I added the files srt.h/.c (not sure if they should really be in libhb) which contain a really simple srt reader. (Can't handle any malformed srts, style tages, etc...)
- Some new helper methods in muxmp4.c:
MP4SetTrackYTranslation: required for QT but not the iphone/ipod touch (since defTextBox is ignored(??) for sbtl streams)
MP4CreateSubtitleTrack: creates the track and sets all kinds of properties and sets the tracks timescale to ms.
MP4WriteSubtitleSample
Now the really ugly stuff...
In mp4init i call MP4CreateSubtitleTrack, call srt_readfile - with a hardcoded path ( ) - and call MP4WriteSubtitleSample() for each entry.
So... as long as you set the hardcoded file path - and start ripping at the first chapter we got mostly correct subtitles. (There are a couple of syncing issues it seems tho)
Asides from really integrating it in libhb and the GUI, there is still enough stuff missing... like support to add subtitles starting at a custom chapter or a more flexible srt reader, etc....
libhb-subtitles.patch: http://pastebin.ca/963559
mp4v2-subtitles.patch: http://pastebin.ca/963569
I'll continue working on it as soon as possible - but feel free to help...
I really want to get this feature finished asap - but my time is -very- limited right now....
Ben
ps
Awesome... just found a typo in read_time_from_string() in srt.cpp... an houre is 36000ms on my planet it seems. Sorry about that.. Might explain some of my syncing issues....
libhb patch backup
Code: Select all
Index: libhb/muxmp4.c
===================================================================
--- libhb/muxmp4.c (revision 1364)
+++ libhb/muxmp4.c (working copy)
@@ -9,6 +9,8 @@
#include "hb.h"
+#include "srt.h"
+
void AddIPodUUID(MP4FileHandle, MP4TrackId);
struct hb_mux_object_s
@@ -27,6 +29,9 @@
MP4TrackId chapter_track;
int current_chapter;
uint64_t chapter_duration;
+
+ /* Subtitle stuff for muxing */
+ MP4TrackId subtitle_track;
};
struct hb_mux_data_s
@@ -119,7 +124,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 duration,
+ const long offset)
+{
+ if (duration <= 0)
+ return;
+
+ if (offset) {
+ // Write the offset...
+ u_int8_t empty[2] = {0,0};
+ MP4WriteSample(file,
+ track,
+ empty,
+ 2,
+ duration,
+ 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
@@ -335,7 +447,26 @@
m->chapter_duration = 0;
m->current_chapter = job->chapter_start;
}
-
+
+ const uint16_t subtitleHeight = 60;
+ m->subtitle_track = MP4CreateSubtitleTrack(m->file,
+ mux_data->track,
+ subtitleHeight,
+ m->job->width,
+ m->job->height - subtitleHeight,
+ language_code);
+
+
+ char filename[] = "/Users/ben/Developer/SrtParser/test.srt";
+ struct srt_entry_s* subtitles = srt_readfile(filename);
+ 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);
@@ -387,7 +518,8 @@
hb_error("Failed to write to output file, disk full?");
*job->die = 1;
}
- free(sample);
+
+ free(sample);
m->current_chapter++;
m->chapter_duration += duration;
}
@@ -465,9 +597,10 @@
hb_error("Failed to write to output file, disk full?");
*job->die = 1;
}
- free(sample);
+
+ free(sample);
}
-
+
if (job->areBframes)
{
// Insert track edit to get A/V back in sync. The edit amount is
@@ -507,4 +640,3 @@
m->job = job;
return m;
}
-
Index: libhb/srt.c
===================================================================
--- libhb/srt.c (revision 0)
+++ libhb/srt.c (revision 0)
@@ -0,0 +1,134 @@
+#include "srt.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+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, "%d:%d:%d,%d --> %d:%d:%d,%d\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);
+}
\ No newline at end of file
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);
\ No newline at end of file
Code: Select all
Index: mp4file.h
===================================================================
--- mp4file.h (revision 4)
+++ 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: atom_nmhd.cpp
===================================================================
--- atom_nmhd.cpp (revision 0)
+++ 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
===================================================================
--- mp4atom.cpp (revision 4)
+++ 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: mp4.cpp
===================================================================
--- mp4.cpp (revision 4)
+++ 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: atom_tx3g.cpp
===================================================================
--- atom_tx3g.cpp (revision 0)
+++ 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: atoms.h
===================================================================
--- atoms.h (revision 4)
+++ 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: atom_ftab.cpp
===================================================================
--- atom_ftab.cpp (revision 0)
+++ 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: mp4file.cpp
===================================================================
--- mp4file.cpp (revision 4)
+++ 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: mp4.h
===================================================================
--- mp4.h (revision 4)
+++ mp4.h (working copy)
@@ -553,6 +553,10 @@
MP4FileHandle hFile,
MP4TrackId refTrackId);
+MP4TrackId MP4AddSubtitleTrack(
+ MP4FileHandle hFile,
+ MP4TrackId refTrackId);
+
MP4TrackId MP4AddPixelAspectRatio(
MP4FileHandle hFile,
MP4TrackId refTrackId,