in src/media_tools/text_import.c [272:714]
static GF_Err gf_text_import_srt(GF_MediaImporter *import)
{
FILE *srt_in;
u32 track, timescale, i, count;
GF_TextConfig*cfg;
GF_Err e;
GF_StyleRecord rec;
GF_TextSample * samp;
GF_ISOSample *s;
u32 sh, sm, ss, sms, eh, em, es, ems, txt_line, char_len, char_line, nb_samp, j, duration, rem_styles;
Bool set_start_char, set_end_char, first_samp, rem_color;
u64 start, end, prev_end, file_size;
u32 state, curLine, line, len, ID, OCR_ES_ID, default_color;
s32 unicode_type;
char szLine[2048], szText[2048], *ptr;
unsigned short uniLine[5000], uniText[5000], *sptr;
srt_in = gf_fopen(import->in_name, "rt");
gf_fseek(srt_in, 0, SEEK_END);
file_size = gf_ftell(srt_in);
gf_fseek(srt_in, 0, SEEK_SET);
unicode_type = gf_text_get_utf_type(srt_in);
if (unicode_type<0) {
gf_fclose(srt_in);
return gf_import_message(import, GF_NOT_SUPPORTED, "Unsupported SRT UTF encoding");
}
cfg = NULL;
if (import->esd) {
if (!import->esd->slConfig) {
import->esd->slConfig = (GF_SLConfig *) gf_odf_desc_new(GF_ODF_SLC_TAG);
import->esd->slConfig->predefined = 2;
import->esd->slConfig->timestampResolution = 1000;
}
timescale = import->esd->slConfig->timestampResolution;
if (!timescale) timescale = 1000;
/*explicit text config*/
if (import->esd->decoderConfig && import->esd->decoderConfig->decoderSpecificInfo->tag == GF_ODF_TEXT_CFG_TAG) {
cfg = (GF_TextConfig *) import->esd->decoderConfig->decoderSpecificInfo;
import->esd->decoderConfig->decoderSpecificInfo = NULL;
}
ID = import->esd->ESID;
OCR_ES_ID = import->esd->OCRESID;
} else {
timescale = 1000;
OCR_ES_ID = ID = 0;
}
if (cfg && cfg->timescale) timescale = cfg->timescale;
track = gf_isom_new_track(import->dest, ID, GF_ISOM_MEDIA_TEXT, timescale);
if (!track) {
gf_fclose(srt_in);
return gf_import_message(import, gf_isom_last_error(import->dest), "Error creating text track");
}
gf_isom_set_track_enabled(import->dest, track, 1);
import->final_trackID = gf_isom_get_track_id(import->dest, track);
if (import->esd && !import->esd->ESID) import->esd->ESID = import->final_trackID;
if (OCR_ES_ID) gf_isom_set_track_reference(import->dest, track, GF_ISOM_REF_OCR, OCR_ES_ID);
/*setup track*/
if (cfg) {
char *firstFont = NULL;
/*set track info*/
gf_isom_set_track_layout_info(import->dest, track, cfg->text_width<<16, cfg->text_height<<16, 0, 0, cfg->layer);
/*and set sample descriptions*/
count = gf_list_count(cfg->sample_descriptions);
for (i=0; i<count; i++) {
GF_TextSampleDescriptor *sd= (GF_TextSampleDescriptor *)gf_list_get(cfg->sample_descriptions, i);
if (!sd->font_count) {
sd->fonts = (GF_FontRecord*)gf_malloc(sizeof(GF_FontRecord));
sd->font_count = 1;
sd->fonts[0].fontID = 1;
sd->fonts[0].fontName = gf_strdup("Serif");
}
if (!sd->default_style.fontID) sd->default_style.fontID = sd->fonts[0].fontID;
if (!sd->default_style.font_size) sd->default_style.font_size = 16;
if (!sd->default_style.text_color) sd->default_style.text_color = 0xFF000000;
/*store attribs*/
if (!i) rec = sd->default_style;
gf_isom_new_text_description(import->dest, track, sd, NULL, NULL, &state);
if (!firstFont) firstFont = sd->fonts[0].fontName;
}
gf_import_message(import, GF_OK, "Timed Text (SRT) import - text track %d x %d, font %s (size %d)", cfg->text_width, cfg->text_height, firstFont, rec.font_size);
gf_odf_desc_del((GF_Descriptor *)cfg);
} else {
u32 w, h;
GF_TextSampleDescriptor *sd;
gf_text_get_video_size(import, &w, &h);
/*have to work with default - use max size (if only one video, this means the text region is the
entire display, and with bottom alignment things should be fine...*/
gf_isom_set_track_layout_info(import->dest, track, w<<16, h<<16, 0, 0, 0);
sd = (GF_TextSampleDescriptor*)gf_odf_desc_new(GF_ODF_TX3G_TAG);
sd->fonts = (GF_FontRecord*)gf_malloc(sizeof(GF_FontRecord));
sd->font_count = 1;
sd->fonts[0].fontID = 1;
sd->fonts[0].fontName = gf_strdup(import->fontName ? import->fontName : "Serif");
sd->back_color = 0x00000000; /*transparent*/
sd->default_style.fontID = 1;
sd->default_style.font_size = import->fontSize ? import->fontSize : TTXT_DEFAULT_FONT_SIZE;
sd->default_style.text_color = 0xFFFFFFFF; /*white*/
sd->default_style.style_flags = 0;
sd->horiz_justif = 1; /*center of scene*/
sd->vert_justif = (s8) -1; /*bottom of scene*/
if (import->flags & GF_IMPORT_SKIP_TXT_BOX) {
sd->default_pos.top = sd->default_pos.left = sd->default_pos.right = sd->default_pos.bottom = 0;
} else {
if ((sd->default_pos.bottom==sd->default_pos.top) || (sd->default_pos.right==sd->default_pos.left)) {
sd->default_pos.left = import->text_x;
sd->default_pos.top = import->text_y;
sd->default_pos.right = (import->text_width ? import->text_width : w) + sd->default_pos.left;
sd->default_pos.bottom = (import->text_height ? import->text_height : h) + sd->default_pos.top;
}
}
/*store attribs*/
rec = sd->default_style;
gf_isom_new_text_description(import->dest, track, sd, NULL, NULL, &state);
gf_import_message(import, GF_OK, "Timed Text (SRT) import - text track %d x %d, font %s (size %d)", w, h, sd->fonts[0].fontName, rec.font_size);
gf_odf_desc_del((GF_Descriptor *)sd);
}
gf_text_import_set_language(import, track);
duration = (u32) (((Double) import->duration)*timescale/1000.0);
default_color = rec.text_color;
e = GF_OK;
state = 0;
end = prev_end = 0;
curLine = 0;
txt_line = 0;
set_start_char = set_end_char = GF_FALSE;
char_len = 0;
start = 0;
nb_samp = 0;
samp = gf_isom_new_text_sample();
first_samp = GF_TRUE;
while (1) {
char *sOK = gf_text_get_utf8_line(szLine, 2048, srt_in, unicode_type);
if (sOK) REM_TRAIL_MARKS(szLine, "\r\n\t ")
if (!sOK || !strlen(szLine)) {
rec.style_flags = 0;
rec.startCharOffset = rec.endCharOffset = 0;
if (txt_line) {
if (prev_end && (start != prev_end)) {
GF_TextSample * empty_samp = gf_isom_new_text_sample();
s = gf_isom_text_to_sample(empty_samp);
gf_isom_delete_text_sample(empty_samp);
if (state<=2) {
s->DTS = (u64) ((timescale*prev_end)/1000);
s->IsRAP = RAP;
gf_isom_add_sample(import->dest, track, 1, s);
nb_samp++;
}
gf_isom_sample_del(&s);
}
s = gf_isom_text_to_sample(samp);
if (state<=2) {
s->DTS = (u64) ((timescale*start)/1000);
s->IsRAP = RAP;
gf_isom_add_sample(import->dest, track, 1, s);
gf_isom_sample_del(&s);
nb_samp++;
prev_end = end;
}
txt_line = 0;
char_len = 0;
set_start_char = set_end_char = GF_FALSE;
rec.startCharOffset = rec.endCharOffset = 0;
gf_isom_text_reset(samp);
//gf_import_progress(import, nb_samp, nb_samp+1);
gf_set_progress("Importing SRT", gf_ftell(srt_in), file_size);
if (duration && (end >= duration)) break;
}
state = 0;
if (!sOK) break;
continue;
}
switch (state) {
case 0:
if (sscanf(szLine, "%u", &line) != 1) {
e = gf_import_message(import, GF_CORRUPTED_DATA, "Bad SRT formatting - expecting number got \"%s\"", szLine);
goto exit;
}
if (line != curLine + 1) gf_import_message(import, GF_OK, "WARNING: corrupted SRT frame %d after frame %d", line, curLine);
curLine = line;
state = 1;
break;
case 1:
if (sscanf(szLine, "%u:%u:%u,%u --> %u:%u:%u,%u", &sh, &sm, &ss, &sms, &eh, &em, &es, &ems) != 8) {
sh = eh = 0;
if (sscanf(szLine, "%u:%u,%u --> %u:%u,%u", &sm, &ss, &sms, &em, &es, &ems) != 6) {
e = gf_import_message(import, GF_CORRUPTED_DATA, "Error scanning SRT frame %d timing", curLine);
goto exit;
}
}
start = (3600*sh + 60*sm + ss)*1000 + sms;
if (start<end) {
gf_import_message(import, GF_OK, "WARNING: overlapping SRT frame %d - starts "LLD" ms is before end of previous one "LLD" ms - adjusting time stamps", curLine, start, end);
start = end;
}
end = (3600*eh + 60*em + es)*1000 + ems;
/*make stream start at 0 by inserting a fake AU*/
if (first_samp && (start>0)) {
s = gf_isom_text_to_sample(samp);
s->DTS = 0;
gf_isom_add_sample(import->dest, track, 1, s);
gf_isom_sample_del(&s);
nb_samp++;
}
rec.style_flags = 0;
state = 2;
if (end<=prev_end) {
gf_import_message(import, GF_OK, "WARNING: overlapping SRT frame %d end "LLD" is at or before previous end "LLD" - removing", curLine, end, prev_end);
start = end;
state = 3;
}
break;
default:
/*reset only when text is present*/
first_samp = GF_FALSE;
/*go to line*/
if (txt_line) {
gf_isom_text_add_text(samp, "\n", 1);
char_len += 1;
}
ptr = (char *) szLine;
{
size_t _len = gf_utf8_mbstowcs(uniLine, 5000, (const char **) &ptr);
if (_len == (size_t) -1) {
e = gf_import_message(import, GF_CORRUPTED_DATA, "Invalid UTF data (line %d)", curLine);
goto exit;
}
len = (u32) _len;
}
char_line = 0;
i=j=0;
rem_styles = 0;
rem_color = 0;
while (i<len) {
u32 font_style = 0;
u32 style_nb_chars = 0;
u32 style_def_type = 0;
if ( (uniLine[i]=='<') && (uniLine[i+2]=='>')) {
style_nb_chars = 3;
style_def_type = 1;
}
else if ( (uniLine[i]=='<') && (uniLine[i+1]=='/') && (uniLine[i+3]=='>')) {
style_def_type = 2;
style_nb_chars = 4;
}
else if (uniLine[i]=='<') {
const unsigned short* src = uniLine + i;
size_t alen = gf_utf8_wcstombs(szLine, 2048, (const unsigned short**) & src);
szLine[alen] = 0;
strlwr(szLine);
if (!strncmp(szLine, "<font ", 6) ) {
char *a_sep = strstr(szLine, "color");
if (a_sep) a_sep = strchr(a_sep, '"');
if (a_sep) {
char *e_sep = strchr(a_sep+1, '"');
if (e_sep) {
e_sep[0] = 0;
font_style = gf_color_parse(a_sep+1);
e_sep[0] = '"';
e_sep = strchr(e_sep+1, '>');
if (e_sep) {
style_nb_chars = (u32) (1 + e_sep - szLine);
style_def_type = 1;
}
}
}
}
else if (!strncmp(szLine, "</font>", 7) ) {
style_nb_chars = 7;
style_def_type = 2;
font_style = 0xFFFFFFFF;
}
//skip unknown
else {
char *a_sep = strstr(szLine, ">");
if (a_sep) {
style_nb_chars = (u32) (a_sep - szLine);
i += style_nb_chars;
continue;
}
}
}
/*start of new style*/
if (style_def_type==1) {
/*store prev style*/
if (set_end_char) {
assert(set_start_char);
gf_isom_text_add_style(samp, &rec);
set_end_char = set_start_char = GF_FALSE;
rec.style_flags &= ~rem_styles;
rem_styles = 0;
if (rem_color) {
rec.text_color = default_color;
rem_color = 0;
}
}
if (set_start_char && (rec.startCharOffset != j)) {
rec.endCharOffset = char_len + j;
if (rec.style_flags) gf_isom_text_add_style(samp, &rec);
}
switch (uniLine[i+1]) {
case 'b':
case 'B':
rec.style_flags |= GF_TXT_STYLE_BOLD;
set_start_char = GF_TRUE;
rec.startCharOffset = char_len + j;
break;
case 'i':
case 'I':
rec.style_flags |= GF_TXT_STYLE_ITALIC;
set_start_char = GF_TRUE;
rec.startCharOffset = char_len + j;
break;
case 'u':
case 'U':
rec.style_flags |= GF_TXT_STYLE_UNDERLINED;
set_start_char = GF_TRUE;
rec.startCharOffset = char_len + j;
break;
case 'f':
case 'F':
if (font_style) {
rec.text_color = font_style;
set_start_char = GF_TRUE;
rec.startCharOffset = char_len + j;
}
break;
}
i += style_nb_chars;
continue;
}
/*end of prev style*/
if (style_def_type==2) {
switch (uniLine[i+2]) {
case 'b':
case 'B':
rem_styles |= GF_TXT_STYLE_BOLD;
set_end_char = GF_TRUE;
rec.endCharOffset = char_len + j;
break;
case 'i':
case 'I':
rem_styles |= GF_TXT_STYLE_ITALIC;
set_end_char = GF_TRUE;
rec.endCharOffset = char_len + j;
break;
case 'u':
case 'U':
rem_styles |= GF_TXT_STYLE_UNDERLINED;
set_end_char = GF_TRUE;
rec.endCharOffset = char_len + j;
break;
case 'f':
case 'F':
if (font_style) {
rem_color = 1;
set_end_char = GF_TRUE;
rec.endCharOffset = char_len + j;
}
}
i+=style_nb_chars;
continue;
}
/*store style*/
if (set_end_char) {
gf_isom_text_add_style(samp, &rec);
set_end_char = GF_FALSE;
set_start_char = GF_TRUE;
rec.startCharOffset = char_len + j;
rec.style_flags &= ~rem_styles;
rem_styles = 0;
rec.text_color = default_color;
rem_color = 0;
}
uniText[j] = uniLine[i];
j++;
i++;
}
/*store last style*/
if (set_end_char) {
gf_isom_text_add_style(samp, &rec);
set_end_char = GF_FALSE;
set_start_char = GF_TRUE;
rec.startCharOffset = char_len + j;
rec.style_flags &= ~rem_styles;
rem_styles = 0;
}
char_line = j;
uniText[j] = 0;
sptr = (u16 *) uniText;
len = (u32) gf_utf8_wcstombs(szText, 5000, (const u16 **) &sptr);
gf_isom_text_add_text(samp, szText, len);
char_len += char_line;
txt_line ++;
break;
}
if (duration && (start >= duration)) break;
}
gf_isom_delete_text_sample(samp);
/*do not add any empty sample at the end since it modifies track duration and is not needed - it is the player job
to figure out when to stop displaying the last text sample
However update the last sample duration*/
gf_isom_set_last_sample_duration(import->dest, track, (u32) (end-start) );
gf_set_progress("Importing SRT", nb_samp, nb_samp);
exit:
if (e) gf_isom_remove_track(import->dest, track);
gf_fclose(srt_in);
return e;
}