in src/media_tools/text_import.c [1785:2123]
static GF_Err gf_text_import_ttxt(GF_MediaImporter *import)
{
GF_Err e;
Bool last_sample_empty;
u32 i, j, k, track, ID, nb_samples, nb_descs, nb_children;
u64 last_sample_duration;
GF_XMLAttribute *att;
GF_DOMParser *parser;
GF_XMLNode *root, *node, *ext;
if (import->flags==GF_IMPORT_PROBE_ONLY) return GF_OK;
parser = gf_xml_dom_new();
e = gf_xml_dom_parse(parser, import->in_name, ttxt_import_progress, import);
if (e) {
gf_import_message(import, e, "Error parsing TTXT file: Line %d - %s", gf_xml_dom_get_line(parser), gf_xml_dom_get_error(parser));
gf_xml_dom_del(parser);
return e;
}
root = gf_xml_dom_get_root(parser);
e = GF_OK;
if (strcmp(root->name, "TextStream")) {
e = gf_import_message(import, GF_BAD_PARAM, "Invalid Timed Text file - expecting \"TextStream\" got %s", "TextStream", root->name);
goto exit;
}
/*setup track in 3GP format directly (no ES desc)*/
ID = (import->esd) ? import->esd->ESID : 0;
track = gf_isom_new_track(import->dest, ID, GF_ISOM_MEDIA_TEXT, 1000);
if (!track) {
e = gf_isom_last_error(import->dest);
goto exit;
}
gf_isom_set_track_enabled(import->dest, track, 1);
/*some MPEG-4 setup*/
if (import->esd) {
if (!import->esd->ESID) import->esd->ESID = gf_isom_get_track_id(import->dest, track);
if (!import->esd->decoderConfig) import->esd->decoderConfig = (GF_DecoderConfig *) gf_odf_desc_new(GF_ODF_DCD_TAG);
if (!import->esd->slConfig) import->esd->slConfig = (GF_SLConfig *) gf_odf_desc_new(GF_ODF_SLC_TAG);
import->esd->slConfig->timestampResolution = 1000;
import->esd->decoderConfig->streamType = GF_STREAM_TEXT;
import->esd->decoderConfig->objectTypeIndication = GPAC_OTI_TEXT_MPEG4;
if (import->esd->OCRESID) gf_isom_set_track_reference(import->dest, track, GF_ISOM_REF_OCR, import->esd->OCRESID);
}
gf_text_import_set_language(import, track);
gf_import_message(import, GF_OK, "Timed Text (GPAC TTXT) Import");
last_sample_empty = GF_FALSE;
last_sample_duration = 0;
nb_descs = 0;
nb_samples = 0;
nb_children = gf_list_count(root->content);
i=0;
while ( (node = (GF_XMLNode*)gf_list_enum(root->content, &i))) {
if (node->type) {
nb_children--;
continue;
}
if (!strcmp(node->name, "TextStreamHeader")) {
GF_XMLNode *sdesc;
s32 w, h, tx, ty, layer;
u32 tref_id;
w = TTXT_DEFAULT_WIDTH;
h = TTXT_DEFAULT_HEIGHT;
tx = ty = layer = 0;
nb_children--;
tref_id = 0;
j=0;
while ( (att=(GF_XMLAttribute *)gf_list_enum(node->attributes, &j))) {
if (!strcmp(att->name, "width")) w = atoi(att->value);
else if (!strcmp(att->name, "height")) h = atoi(att->value);
else if (!strcmp(att->name, "layer")) layer = atoi(att->value);
else if (!strcmp(att->name, "translation_x")) tx = atoi(att->value);
else if (!strcmp(att->name, "translation_y")) ty = atoi(att->value);
else if (!strcmp(att->name, "trefID")) tref_id = atoi(att->value);
}
if (tref_id)
gf_isom_set_track_reference(import->dest, track, GF_ISOM_BOX_TYPE_CHAP, tref_id);
gf_isom_set_track_layout_info(import->dest, track, w<<16, h<<16, tx<<16, ty<<16, (s16) layer);
j=0;
while ( (sdesc=(GF_XMLNode*)gf_list_enum(node->content, &j))) {
if (sdesc->type) continue;
if (!strcmp(sdesc->name, "TextSampleDescription")) {
GF_TextSampleDescriptor td;
u32 idx;
memset(&td, 0, sizeof(GF_TextSampleDescriptor));
td.tag = GF_ODF_TEXT_CFG_TAG;
td.vert_justif = (s8) -1;
td.default_style.fontID = 1;
td.default_style.font_size = TTXT_DEFAULT_FONT_SIZE;
k=0;
while ( (att=(GF_XMLAttribute *)gf_list_enum(sdesc->attributes, &k))) {
if (!strcmp(att->name, "horizontalJustification")) {
if (!stricmp(att->value, "center")) td.horiz_justif = 1;
else if (!stricmp(att->value, "right")) td.horiz_justif = (s8) -1;
else if (!stricmp(att->value, "left")) td.horiz_justif = 0;
}
else if (!strcmp(att->name, "verticalJustification")) {
if (!stricmp(att->value, "center")) td.vert_justif = 1;
else if (!stricmp(att->value, "bottom")) td.vert_justif = (s8) -1;
else if (!stricmp(att->value, "top")) td.vert_justif = 0;
}
else if (!strcmp(att->name, "backColor")) td.back_color = ttxt_get_color(import, att->value);
else if (!strcmp(att->name, "verticalText") && !stricmp(att->value, "yes") ) td.displayFlags |= GF_TXT_VERTICAL;
else if (!strcmp(att->name, "fillTextRegion") && !stricmp(att->value, "yes") ) td.displayFlags |= GF_TXT_FILL_REGION;
else if (!strcmp(att->name, "continuousKaraoke") && !stricmp(att->value, "yes") ) td.displayFlags |= GF_TXT_KARAOKE;
else if (!strcmp(att->name, "scroll")) {
if (!stricmp(att->value, "inout")) td.displayFlags |= GF_TXT_SCROLL_IN | GF_TXT_SCROLL_OUT;
else if (!stricmp(att->value, "in")) td.displayFlags |= GF_TXT_SCROLL_IN;
else if (!stricmp(att->value, "out")) td.displayFlags |= GF_TXT_SCROLL_OUT;
}
else if (!strcmp(att->name, "scrollMode")) {
u32 scroll_mode = GF_TXT_SCROLL_CREDITS;
if (!stricmp(att->value, "Credits")) scroll_mode = GF_TXT_SCROLL_CREDITS;
else if (!stricmp(att->value, "Marquee")) scroll_mode = GF_TXT_SCROLL_MARQUEE;
else if (!stricmp(att->value, "Right")) scroll_mode = GF_TXT_SCROLL_RIGHT;
else if (!stricmp(att->value, "Down")) scroll_mode = GF_TXT_SCROLL_DOWN;
td.displayFlags |= ((scroll_mode<<7) & GF_TXT_SCROLL_DIRECTION);
}
}
k=0;
while ( (ext=(GF_XMLNode*)gf_list_enum(sdesc->content, &k))) {
if (ext->type) continue;
if (!strcmp(ext->name, "TextBox")) ttxt_parse_text_box(import, ext, &td.default_pos);
else if (!strcmp(ext->name, "Style")) ttxt_parse_text_style(import, ext, &td.default_style);
else if (!strcmp(ext->name, "FontTable")) {
GF_XMLNode *ftable;
u32 z=0;
while ( (ftable=(GF_XMLNode*)gf_list_enum(ext->content, &z))) {
u32 m;
if (ftable->type || strcmp(ftable->name, "FontTableEntry")) continue;
td.font_count += 1;
td.fonts = (GF_FontRecord*)gf_realloc(td.fonts, sizeof(GF_FontRecord)*td.font_count);
m=0;
while ( (att=(GF_XMLAttribute *)gf_list_enum(ftable->attributes, &m))) {
if (!stricmp(att->name, "fontID")) td.fonts[td.font_count-1].fontID = atoi(att->value);
else if (!stricmp(att->name, "fontName")) td.fonts[td.font_count-1].fontName = gf_strdup(att->value);
}
}
}
}
if (import->flags & GF_IMPORT_SKIP_TXT_BOX) {
td.default_pos.top = td.default_pos.left = td.default_pos.right = td.default_pos.bottom = 0;
} else {
if ((td.default_pos.bottom==td.default_pos.top) || (td.default_pos.right==td.default_pos.left)) {
td.default_pos.top = td.default_pos.left = 0;
td.default_pos.right = w;
td.default_pos.bottom = h;
}
}
if (!td.fonts) {
td.font_count = 1;
td.fonts = (GF_FontRecord*)gf_malloc(sizeof(GF_FontRecord));
td.fonts[0].fontID = 1;
td.fonts[0].fontName = gf_strdup("Serif");
}
gf_isom_new_text_description(import->dest, track, &td, NULL, NULL, &idx);
for (k=0; k<td.font_count; k++) gf_free(td.fonts[k].fontName);
gf_free(td.fonts);
nb_descs ++;
}
}
}
/*sample text*/
else if (!strcmp(node->name, "TextSample")) {
GF_ISOSample *s;
GF_TextSample * samp;
u32 ts, descIndex;
Bool has_text = GF_FALSE;
if (!nb_descs) {
e = gf_import_message(import, GF_BAD_PARAM, "Invalid Timed Text file - text stream header not found or empty");
goto exit;
}
samp = gf_isom_new_text_sample();
ts = 0;
descIndex = 1;
last_sample_empty = GF_TRUE;
j=0;
while ( (att=(GF_XMLAttribute*)gf_list_enum(node->attributes, &j))) {
if (!strcmp(att->name, "sampleTime")) {
u32 h, m, s, ms;
if (sscanf(att->value, "%u:%u:%u.%u", &h, &m, &s, &ms) == 4) {
ts = (h*3600 + m*60 + s)*1000 + ms;
} else {
ts = (u32) (atof(att->value) * 1000);
}
}
else if (!strcmp(att->name, "sampleDescriptionIndex")) descIndex = atoi(att->value);
else if (!strcmp(att->name, "text")) {
u32 len;
char *str = ttxt_parse_string(import, att->value, GF_TRUE);
len = (u32) strlen(str);
gf_isom_text_add_text(samp, str, len);
last_sample_empty = len ? GF_FALSE : GF_TRUE;
has_text = GF_TRUE;
}
else if (!strcmp(att->name, "scrollDelay")) gf_isom_text_set_scroll_delay(samp, (u32) (1000*atoi(att->value)));
else if (!strcmp(att->name, "highlightColor")) gf_isom_text_set_highlight_color_argb(samp, ttxt_get_color(import, att->value));
else if (!strcmp(att->name, "wrap") && !strcmp(att->value, "Automatic")) gf_isom_text_set_wrap(samp, 0x01);
}
/*get all modifiers*/
j=0;
while ( (ext=(GF_XMLNode*)gf_list_enum(node->content, &j))) {
if (!has_text && (ext->type==GF_XML_TEXT_TYPE)) {
u32 len;
char *str = ttxt_parse_string(import, ext->name, GF_FALSE);
len = (u32) strlen(str);
gf_isom_text_add_text(samp, str, len);
last_sample_empty = len ? GF_FALSE : GF_TRUE;
has_text = GF_TRUE;
}
if (ext->type) continue;
if (!stricmp(ext->name, "Style")) {
GF_StyleRecord r;
ttxt_parse_text_style(import, ext, &r);
gf_isom_text_add_style(samp, &r);
}
else if (!stricmp(ext->name, "TextBox")) {
GF_BoxRecord r;
ttxt_parse_text_box(import, ext, &r);
gf_isom_text_set_box(samp, r.top, r.left, r.bottom, r.right);
}
else if (!stricmp(ext->name, "Highlight")) {
u16 start, end;
start = end = 0;
k=0;
while ( (att=(GF_XMLAttribute *)gf_list_enum(ext->attributes, &k))) {
if (!strcmp(att->name, "fromChar")) start = atoi(att->value);
else if (!strcmp(att->name, "toChar")) end = atoi(att->value);
}
gf_isom_text_add_highlight(samp, start, end);
}
else if (!stricmp(ext->name, "Blinking")) {
u16 start, end;
start = end = 0;
k=0;
while ( (att=(GF_XMLAttribute *)gf_list_enum(ext->attributes, &k))) {
if (!strcmp(att->name, "fromChar")) start = atoi(att->value);
else if (!strcmp(att->name, "toChar")) end = atoi(att->value);
}
gf_isom_text_add_blink(samp, start, end);
}
else if (!stricmp(ext->name, "HyperLink")) {
u16 start, end;
char *url, *url_tt;
start = end = 0;
url = url_tt = NULL;
k=0;
while ( (att=(GF_XMLAttribute *)gf_list_enum(ext->attributes, &k))) {
if (!strcmp(att->name, "fromChar")) start = atoi(att->value);
else if (!strcmp(att->name, "toChar")) end = atoi(att->value);
else if (!strcmp(att->name, "URL")) url = gf_strdup(att->value);
else if (!strcmp(att->name, "URLToolTip")) url_tt = gf_strdup(att->value);
}
gf_isom_text_add_hyperlink(samp, url, url_tt, start, end);
if (url) gf_free(url);
if (url_tt) gf_free(url_tt);
}
else if (!stricmp(ext->name, "Karaoke")) {
u32 startTime;
GF_XMLNode *krok;
startTime = 0;
k=0;
while ( (att=(GF_XMLAttribute *)gf_list_enum(ext->attributes, &k))) {
if (!strcmp(att->name, "startTime")) startTime = (u32) (1000*atof(att->value));
}
gf_isom_text_add_karaoke(samp, startTime);
k=0;
while ( (krok=(GF_XMLNode*)gf_list_enum(ext->content, &k))) {
u16 start, end;
u32 endTime, m;
if (krok->type) continue;
if (strcmp(krok->name, "KaraokeRange")) continue;
start = end = 0;
endTime = 0;
m=0;
while ( (att=(GF_XMLAttribute *)gf_list_enum(krok->attributes, &m))) {
if (!strcmp(att->name, "fromChar")) start = atoi(att->value);
else if (!strcmp(att->name, "toChar")) end = atoi(att->value);
else if (!strcmp(att->name, "endTime")) endTime = (u32) (1000*atof(att->value));
}
gf_isom_text_set_karaoke_segment(samp, endTime, start, end);
}
}
}
/*in MP4 we must start at T=0, so add an empty sample*/
if (ts && !nb_samples) {
GF_TextSample * firstsamp = gf_isom_new_text_sample();
s = gf_isom_text_to_sample(firstsamp);
s->DTS = 0;
gf_isom_add_sample(import->dest, track, 1, s);
nb_samples++;
gf_isom_delete_text_sample(firstsamp);
gf_isom_sample_del(&s);
}
s = gf_isom_text_to_sample(samp);
gf_isom_delete_text_sample(samp);
s->DTS = ts;
if (last_sample_empty) {
last_sample_duration = s->DTS - last_sample_duration;
} else {
last_sample_duration = s->DTS;
}
e = gf_isom_add_sample(import->dest, track, descIndex, s);
if (e) goto exit;
gf_isom_sample_del(&s);
nb_samples++;
gf_set_progress("Importing TTXT", nb_samples, nb_children);
if (import->duration && (ts>import->duration)) break;
}
}
if (last_sample_empty) {
gf_isom_remove_sample(import->dest, track, nb_samples);
gf_isom_set_last_sample_duration(import->dest, track, (u32) last_sample_duration);
}
gf_set_progress("Importing TTXT", nb_samples, nb_samples);
exit:
gf_xml_dom_del(parser);
return e;
}