PageRenderTime 58ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/src/media_tools/text_import.c

https://github.com/svettom/gpac-1
C | 1712 lines | 1487 code | 156 blank | 69 comment | 582 complexity | b358e5d584d2c54dcbdee15d0f4b83a9 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. /*
  2. * GPAC - Multimedia Framework C SDK
  3. *
  4. * Copyright (c) Jean Le Feuvre 2000-2005
  5. * All rights reserved
  6. *
  7. * This file is part of GPAC / Media Tools sub-project
  8. *
  9. * GPAC is free software; you can redistribute it and/or modify
  10. * it under the terms of the GNU Lesser General Public License as published by
  11. * the Free Software Foundation; either version 2, or (at your option)
  12. * any later version.
  13. *
  14. * GPAC is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU Lesser General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Lesser General Public
  20. * License along with this library; see the file COPYING. If not, write to
  21. * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
  22. *
  23. */
  24. #include <gpac/constants.h>
  25. #include <gpac/utf.h>
  26. #include <gpac/xml.h>
  27. #include <gpac/token.h>
  28. #include <gpac/internal/media_dev.h>
  29. #include <gpac/internal/isomedia_dev.h>
  30. #ifndef GPAC_DISABLE_ISOM_WRITE
  31. enum
  32. {
  33. GF_TEXT_IMPORT_NONE = 0,
  34. GF_TEXT_IMPORT_SRT,
  35. GF_TEXT_IMPORT_SUB,
  36. GF_TEXT_IMPORT_TTXT,
  37. GF_TEXT_IMPORT_TEXML,
  38. };
  39. #define REM_TRAIL_MARKS(__str, __sep) while (1) { \
  40. u32 _len = strlen(__str); \
  41. if (!_len) break; \
  42. _len--; \
  43. if (strchr(__sep, __str[_len])) __str[_len] = 0; \
  44. else break; \
  45. } \
  46. static s32 gf_text_get_utf_type(FILE *in_src)
  47. {
  48. u32 readen;
  49. unsigned char BOM[5];
  50. readen = fread(BOM, sizeof(char), 5, in_src);
  51. if (readen < 1)
  52. return -1;
  53. if ((BOM[0]==0xFF) && (BOM[1]==0xFE)) {
  54. /*UTF32 not supported*/
  55. if (!BOM[2] && !BOM[3]) return -1;
  56. gf_f64_seek(in_src, 2, SEEK_SET);
  57. return 3;
  58. }
  59. if ((BOM[0]==0xFE) && (BOM[1]==0xFF)) {
  60. /*UTF32 not supported*/
  61. if (!BOM[2] && !BOM[3]) return -1;
  62. gf_f64_seek(in_src, 2, SEEK_SET);
  63. return 2;
  64. } else if ((BOM[0]==0xEF) && (BOM[1]==0xBB) && (BOM[2]==0xBF)) {
  65. gf_f64_seek(in_src, 3, SEEK_SET);
  66. return 1;
  67. }
  68. if (BOM[0]<0x80) {
  69. gf_f64_seek(in_src, 0, SEEK_SET);
  70. return 0;
  71. }
  72. return -1;
  73. }
  74. static GF_Err gf_text_guess_format(char *filename, u32 *fmt)
  75. {
  76. char szLine[2048];
  77. u32 val;
  78. s32 uni_type;
  79. FILE *test = gf_f64_open(filename, "rb");
  80. if (!test) return GF_URL_ERROR;
  81. uni_type = gf_text_get_utf_type(test);
  82. if (uni_type>1) {
  83. const u16 *sptr;
  84. char szUTF[1024];
  85. u32 read = fread(szUTF, 1, 1023, test);
  86. szUTF[read]=0;
  87. sptr = (u16*)szUTF;
  88. read = gf_utf8_wcstombs(szLine, read, &sptr);
  89. } else {
  90. val = fread(szLine, 1, 1024, test);
  91. szLine[val]=0;
  92. }
  93. REM_TRAIL_MARKS(szLine, "\r\n\t ")
  94. *fmt = GF_TEXT_IMPORT_NONE;
  95. if ((szLine[0]=='{') && strstr(szLine, "}{")) *fmt = GF_TEXT_IMPORT_SUB;
  96. else if (!strnicmp(szLine, "<?xml ", 6)) {
  97. char *ext = strrchr(filename, '.');
  98. if (!strnicmp(ext, ".ttxt", 5)) *fmt = GF_TEXT_IMPORT_TTXT;
  99. ext = strstr(szLine, "?>");
  100. if (ext) ext += 2;
  101. if (!ext[0]){
  102. if (!fgets(szLine, 2048, test))
  103. szLine[0] = '\0';
  104. }
  105. if (strstr(szLine, "x-quicktime-tx3g") || strstr(szLine, "text3GTrack")) *fmt = GF_TEXT_IMPORT_TEXML;
  106. else if (strstr(szLine, "TextStream")) *fmt = GF_TEXT_IMPORT_TTXT;
  107. }
  108. else if (strstr(szLine, " --> ") )
  109. *fmt = GF_TEXT_IMPORT_SRT;
  110. fclose(test);
  111. return GF_OK;
  112. }
  113. #define TTXT_DEFAULT_WIDTH 400
  114. #define TTXT_DEFAULT_HEIGHT 60
  115. #define TTXT_DEFAULT_FONT_SIZE 18
  116. static void gf_text_get_video_size(GF_ISOFile *dest, u32 *width, u32 *height)
  117. {
  118. u32 w, h, f_w, f_h, i;
  119. f_w = f_h = 0;
  120. for (i=0; i<gf_isom_get_track_count(dest); i++) {
  121. switch (gf_isom_get_media_type(dest, i+1)) {
  122. case GF_ISOM_MEDIA_SCENE:
  123. case GF_ISOM_MEDIA_VISUAL:
  124. gf_isom_get_visual_info(dest, i+1, 1, &w, &h);
  125. if (w > f_w) f_w = w;
  126. if (h > f_h) f_h = h;
  127. gf_isom_get_track_layout_info(dest, i+1, &w, &h, NULL, NULL, NULL);
  128. if (w > f_w) f_w = w;
  129. if (h > f_h) f_h = h;
  130. break;
  131. }
  132. }
  133. (*width) = f_w ? f_w : TTXT_DEFAULT_WIDTH;
  134. (*height) = f_h ? f_h : TTXT_DEFAULT_HEIGHT;
  135. }
  136. #ifndef GPAC_DISABLE_MEDIA_IMPORT
  137. static void gf_text_import_set_language(GF_MediaImporter *import, u32 track)
  138. {
  139. if (import->esd && import->esd->langDesc) {
  140. char lang[4];
  141. lang[0] = (import->esd->langDesc->langCode>>16) & 0xFF;
  142. lang[1] = (import->esd->langDesc->langCode>>8) & 0xFF;
  143. lang[2] = (import->esd->langDesc->langCode) & 0xFF;
  144. lang[3] = 0;
  145. gf_isom_set_media_language(import->dest, track, lang);
  146. }
  147. }
  148. #endif
  149. static char *gf_text_get_utf8_line(char *szLine, u32 lineSize, FILE *txt_in, s32 unicode_type)
  150. {
  151. u32 i, j, len;
  152. char *sOK;
  153. char szLineConv[1024];
  154. unsigned short *sptr;
  155. memset(szLine, 0, sizeof(char)*lineSize);
  156. sOK = fgets(szLine, lineSize, txt_in);
  157. if (!sOK) return NULL;
  158. if (unicode_type<=1) {
  159. j=0;
  160. len = strlen(szLine);
  161. for (i=0; i<len; i++) {
  162. if (!unicode_type && (szLine[i] & 0x80)) {
  163. /*non UTF8 (likely some win-CP)*/
  164. if ((szLine[i+1] & 0xc0) != 0x80) {
  165. szLineConv[j] = 0xc0 | ( (szLine[i] >> 6) & 0x3 );
  166. j++;
  167. szLine[i] &= 0xbf;
  168. }
  169. /*UTF8 2 bytes char*/
  170. else if ( (szLine[i] & 0xe0) == 0xc0) {
  171. szLineConv[j] = szLine[i]; i++; j++;
  172. }
  173. /*UTF8 3 bytes char*/
  174. else if ( (szLine[i] & 0xf0) == 0xe0) {
  175. szLineConv[j] = szLine[i]; i++; j++;
  176. szLineConv[j] = szLine[i]; i++; j++;
  177. }
  178. /*UTF8 4 bytes char*/
  179. else if ( (szLine[i] & 0xf8) == 0xf0) {
  180. szLineConv[j] = szLine[i]; i++; j++;
  181. szLineConv[j] = szLine[i]; i++; j++;
  182. szLineConv[j] = szLine[i]; i++; j++;
  183. } else {
  184. i+=1;
  185. continue;
  186. }
  187. }
  188. szLineConv[j] = szLine[i];
  189. j++;
  190. }
  191. szLineConv[j] = 0;
  192. strcpy(szLine, szLineConv);
  193. return sOK;
  194. }
  195. #ifdef GPAC_BIG_ENDIAN
  196. if (unicode_type==3) {
  197. #else
  198. if (unicode_type==2) {
  199. #endif
  200. i=0;
  201. while (1) {
  202. char c;
  203. if (!szLine[i] && !szLine[i+1]) break;
  204. c = szLine[i+1];
  205. szLine[i+1] = szLine[i];
  206. szLine[i] = c;
  207. i+=2;
  208. }
  209. }
  210. sptr = (u16 *)szLine;
  211. i = gf_utf8_wcstombs(szLineConv, 1024, (const unsigned short **) &sptr);
  212. szLineConv[i] = 0;
  213. strcpy(szLine, szLineConv);
  214. /*this is ugly indeed: since input is UTF16-LE, there are many chances the fgets never reads the \0 after a \n*/
  215. if (unicode_type==3) fgetc(txt_in);
  216. return sOK;
  217. }
  218. #ifndef GPAC_DISABLE_MEDIA_IMPORT
  219. static GF_Err gf_text_import_srt(GF_MediaImporter *import)
  220. {
  221. FILE *srt_in;
  222. u32 track, timescale, i, count;
  223. GF_TextConfig*cfg;
  224. GF_Err e;
  225. GF_StyleRecord rec;
  226. GF_TextSample * samp;
  227. GF_ISOSample *s;
  228. u32 sh, sm, ss, sms, eh, em, es, ems, txt_line, char_len, char_line, nb_samp, j, duration, rem_styles;
  229. Bool set_start_char, set_end_char, first_samp;
  230. u64 start, end, prev_end, file_size;
  231. u32 state, curLine, line, len, ID, OCR_ES_ID;
  232. s32 unicode_type;
  233. char szLine[2048], szText[2048], *ptr;
  234. unsigned short uniLine[5000], uniText[5000], *sptr;
  235. srt_in = gf_f64_open(import->in_name, "rt");
  236. gf_f64_seek(srt_in, 0, SEEK_END);
  237. file_size = gf_f64_tell(srt_in);
  238. gf_f64_seek(srt_in, 0, SEEK_SET);
  239. unicode_type = gf_text_get_utf_type(srt_in);
  240. if (unicode_type<0) {
  241. fclose(srt_in);
  242. return gf_import_message(import, GF_NOT_SUPPORTED, "Unsupported SRT UTF encoding");
  243. }
  244. cfg = NULL;
  245. if (import->esd) {
  246. if (!import->esd->slConfig) {
  247. import->esd->slConfig = (GF_SLConfig *) gf_odf_desc_new(GF_ODF_SLC_TAG);
  248. import->esd->slConfig->predefined = 2;
  249. import->esd->slConfig->timestampResolution = 1000;
  250. }
  251. timescale = import->esd->slConfig->timestampResolution;
  252. if (!timescale) timescale = 1000;
  253. /*explicit text config*/
  254. if (import->esd->decoderConfig && import->esd->decoderConfig->decoderSpecificInfo->tag == GF_ODF_TEXT_CFG_TAG) {
  255. cfg = (GF_TextConfig *) import->esd->decoderConfig->decoderSpecificInfo;
  256. import->esd->decoderConfig->decoderSpecificInfo = NULL;
  257. }
  258. ID = import->esd->ESID;
  259. OCR_ES_ID = import->esd->OCRESID;
  260. } else {
  261. timescale = 1000;
  262. OCR_ES_ID = ID = 0;
  263. }
  264. if (cfg && cfg->timescale) timescale = cfg->timescale;
  265. track = gf_isom_new_track(import->dest, ID, GF_ISOM_MEDIA_TEXT, timescale);
  266. if (!track) {
  267. fclose(srt_in);
  268. return gf_import_message(import, gf_isom_last_error(import->dest), "Error creating text track");
  269. }
  270. gf_isom_set_track_enabled(import->dest, track, 1);
  271. if (import->esd && !import->esd->ESID) import->esd->ESID = gf_isom_get_track_id(import->dest, track);
  272. if (OCR_ES_ID) gf_isom_set_track_reference(import->dest, track, GF_ISOM_REF_OCR, OCR_ES_ID);
  273. /*setup track*/
  274. if (cfg) {
  275. char *firstFont = NULL;
  276. /*set track info*/
  277. gf_isom_set_track_layout_info(import->dest, track, cfg->text_width<<16, cfg->text_height<<16, 0, 0, cfg->layer);
  278. /*and set sample descriptions*/
  279. count = gf_list_count(cfg->sample_descriptions);
  280. for (i=0; i<count; i++) {
  281. GF_TextSampleDescriptor *sd= (GF_TextSampleDescriptor *)gf_list_get(cfg->sample_descriptions, i);
  282. if (!sd->font_count) {
  283. sd->fonts = (GF_FontRecord*)gf_malloc(sizeof(GF_FontRecord));
  284. sd->font_count = 1;
  285. sd->fonts[0].fontID = 1;
  286. sd->fonts[0].fontName = gf_strdup("Serif");
  287. }
  288. if (!sd->default_style.fontID) sd->default_style.fontID = sd->fonts[0].fontID;
  289. if (!sd->default_style.font_size) sd->default_style.font_size = 16;
  290. if (!sd->default_style.text_color) sd->default_style.text_color = 0xFF000000;
  291. /*store attribs*/
  292. if (!i) rec = sd->default_style;
  293. gf_isom_new_text_description(import->dest, track, sd, NULL, NULL, &state);
  294. if (!firstFont) firstFont = sd->fonts[0].fontName;
  295. }
  296. 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);
  297. gf_odf_desc_del((GF_Descriptor *)cfg);
  298. } else {
  299. u32 w, h;
  300. GF_TextSampleDescriptor *sd;
  301. gf_text_get_video_size(import->dest, &w, &h);
  302. /*have to work with default - use max size (if only one video, this means the text region is the
  303. entire display, and with bottom alignment things should be fine...*/
  304. gf_isom_set_track_layout_info(import->dest, track, w<<16, h<<16, 0, 0, 0);
  305. sd = (GF_TextSampleDescriptor*)gf_odf_desc_new(GF_ODF_TX3G_TAG);
  306. sd->fonts = (GF_FontRecord*)gf_malloc(sizeof(GF_FontRecord));
  307. sd->font_count = 1;
  308. sd->fonts[0].fontID = 1;
  309. sd->fonts[0].fontName = gf_strdup(import->fontName ? import->fontName : "Serif");
  310. sd->back_color = 0x00000000; /*transparent*/
  311. sd->default_style.fontID = 1;
  312. sd->default_style.font_size = import->fontSize ? import->fontSize : TTXT_DEFAULT_FONT_SIZE;
  313. sd->default_style.text_color = 0xFFFFFFFF; /*white*/
  314. sd->default_style.style_flags = 0;
  315. sd->horiz_justif = 1; /*center of scene*/
  316. sd->vert_justif = (s8) -1; /*bottom of scene*/
  317. if (import->flags & GF_IMPORT_SKIP_TXT_BOX) {
  318. sd->default_pos.top = sd->default_pos.left = sd->default_pos.right = sd->default_pos.bottom = 0;
  319. } else {
  320. if ((sd->default_pos.bottom==sd->default_pos.top) || (sd->default_pos.right==sd->default_pos.left)) {
  321. sd->default_pos.top = sd->default_pos.left = 0;
  322. sd->default_pos.right = import->twidth ? import->twidth : w;
  323. sd->default_pos.bottom = import->theight ? import->theight : h;
  324. }
  325. }
  326. /*store attribs*/
  327. rec = sd->default_style;
  328. gf_isom_new_text_description(import->dest, track, sd, NULL, NULL, &state);
  329. 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);
  330. gf_odf_desc_del((GF_Descriptor *)sd);
  331. }
  332. gf_text_import_set_language(import, track);
  333. duration = (u32) (((Double) import->duration)*timescale/1000.0);
  334. e = GF_OK;
  335. state = 0;
  336. end = prev_end = 0;
  337. curLine = 0;
  338. txt_line = 0;
  339. set_start_char = set_end_char = 0;
  340. char_len = 0;
  341. start = 0;
  342. nb_samp = 0;
  343. samp = gf_isom_new_text_sample();
  344. first_samp = 1;
  345. while (1) {
  346. char *sOK = gf_text_get_utf8_line(szLine, 2048, srt_in, unicode_type);
  347. if (sOK) REM_TRAIL_MARKS(szLine, "\r\n\t ")
  348. if (!sOK || !strlen(szLine)) {
  349. state = 0;
  350. rec.style_flags = 0;
  351. rec.startCharOffset = rec.endCharOffset = 0;
  352. if (txt_line) {
  353. if (prev_end && (start != prev_end)) {
  354. GF_TextSample * empty_samp = gf_isom_new_text_sample();
  355. s = gf_isom_text_to_sample(empty_samp);
  356. gf_isom_delete_text_sample(empty_samp);
  357. s->DTS = (u64) ((timescale*prev_end)/1000);
  358. s->IsRAP = 1;
  359. gf_isom_add_sample(import->dest, track, 1, s);
  360. gf_isom_sample_del(&s);
  361. nb_samp++;
  362. }
  363. s = gf_isom_text_to_sample(samp);
  364. s->DTS = (u64) ((timescale*start)/1000);
  365. s->IsRAP = 1;
  366. gf_isom_add_sample(import->dest, track, 1, s);
  367. gf_isom_sample_del(&s);
  368. nb_samp++;
  369. prev_end = end;
  370. txt_line = 0;
  371. char_len = 0;
  372. set_start_char = set_end_char = 0;
  373. rec.startCharOffset = rec.endCharOffset = 0;
  374. gf_isom_text_reset(samp);
  375. //gf_import_progress(import, nb_samp, nb_samp+1);
  376. gf_set_progress("Importing SRT", gf_f64_tell(srt_in), file_size);
  377. if (duration && (end >= duration)) break;
  378. }
  379. if (!sOK) break;
  380. continue;
  381. }
  382. switch (state) {
  383. case 0:
  384. if (sscanf(szLine, "%u", &line) != 1) {
  385. e = gf_import_message(import, GF_CORRUPTED_DATA, "Bad SRT formatting - expecting number got \"%s\"", szLine);
  386. goto exit;
  387. }
  388. if (line != curLine + 1) gf_import_message(import, GF_OK, "WARNING: corrupted SRT frame %d after frame %d", line, curLine);
  389. curLine = line;
  390. state = 1;
  391. break;
  392. case 1:
  393. if (sscanf(szLine, "%u:%u:%u,%u --> %u:%u:%u,%u", &sh, &sm, &ss, &sms, &eh, &em, &es, &ems) != 8) {
  394. e = gf_import_message(import, GF_CORRUPTED_DATA, "Error scanning SRT frame %d timing", curLine);
  395. goto exit;
  396. }
  397. start = (3600*sh + 60*sm + ss)*1000 + sms;
  398. if (start<end) {
  399. gf_import_message(import, GF_OK, "WARNING: corrupted SRT frame %d - starts (at %d ms) before end of previous one (%d ms) - adjusting time stamps", curLine, start, end);
  400. start = end;
  401. }
  402. end = (3600*eh + 60*em + es)*1000 + ems;
  403. /*make stream start at 0 by inserting a fake AU*/
  404. if (first_samp && (start>0)) {
  405. s = gf_isom_text_to_sample(samp);
  406. s->DTS = 0;
  407. gf_isom_add_sample(import->dest, track, 1, s);
  408. gf_isom_sample_del(&s);
  409. nb_samp++;
  410. }
  411. rec.style_flags = 0;
  412. state = 2;
  413. break;
  414. default:
  415. /*reset only when text is present*/
  416. first_samp = 0;
  417. /*go to line*/
  418. if (txt_line) {
  419. gf_isom_text_add_text(samp, "\n", 1);
  420. char_len += 1;
  421. }
  422. ptr = (char *) szLine;
  423. len = gf_utf8_mbstowcs(uniLine, 5000, (const char **) &ptr);
  424. if (len == (u32) -1) {
  425. e = gf_import_message(import, GF_CORRUPTED_DATA, "Invalid UTF data (line %d)", curLine);
  426. goto exit;
  427. }
  428. char_line = 0;
  429. i=j=0;
  430. rem_styles = 0;
  431. while (i<len) {
  432. /*start of new style*/
  433. if ( (uniLine[i]=='<') && (uniLine[i+2]=='>')) {
  434. /*store prev style*/
  435. if (set_end_char) {
  436. assert(set_start_char);
  437. gf_isom_text_add_style(samp, &rec);
  438. set_end_char = set_start_char = 0;
  439. rec.style_flags &= ~rem_styles;
  440. rem_styles = 0;
  441. }
  442. if (set_start_char && (rec.startCharOffset != j)) {
  443. rec.endCharOffset = char_len + j;
  444. if (rec.style_flags) gf_isom_text_add_style(samp, &rec);
  445. }
  446. switch (uniLine[i+1]) {
  447. case 'b': case 'B':
  448. rec.style_flags |= GF_TXT_STYLE_BOLD;
  449. set_start_char = 1;
  450. rec.startCharOffset = char_len + j;
  451. break;
  452. case 'i': case 'I':
  453. rec.style_flags |= GF_TXT_STYLE_ITALIC;
  454. set_start_char = 1;
  455. rec.startCharOffset = char_len + j;
  456. break;
  457. case 'u': case 'U':
  458. rec.style_flags |= GF_TXT_STYLE_UNDERLINED;
  459. set_start_char = 1;
  460. rec.startCharOffset = char_len + j;
  461. break;
  462. }
  463. i+=3;
  464. continue;
  465. }
  466. /*end of prev style*/
  467. if ( (uniLine[i]=='<') && (uniLine[i+1]=='/') && (uniLine[i+3]=='>')) {
  468. switch (uniLine[i+2]) {
  469. case 'b': case 'B':
  470. rem_styles |= GF_TXT_STYLE_BOLD;
  471. set_end_char = 1;
  472. rec.endCharOffset = char_len + j;
  473. break;
  474. case 'i': case 'I':
  475. rem_styles |= GF_TXT_STYLE_ITALIC;
  476. set_end_char = 1;
  477. rec.endCharOffset = char_len + j;
  478. break;
  479. case 'u': case 'U':
  480. rem_styles |= GF_TXT_STYLE_UNDERLINED;
  481. set_end_char = 1;
  482. rec.endCharOffset = char_len + j;
  483. break;
  484. }
  485. i+=4;
  486. continue;
  487. }
  488. /*store style*/
  489. if (set_end_char) {
  490. gf_isom_text_add_style(samp, &rec);
  491. set_end_char = 0;
  492. set_start_char = 1;
  493. rec.startCharOffset = char_len + j;
  494. rec.style_flags &= ~rem_styles;
  495. rem_styles = 0;
  496. }
  497. uniText[j] = uniLine[i];
  498. j++;
  499. i++;
  500. }
  501. /*store last style*/
  502. if (set_end_char) {
  503. gf_isom_text_add_style(samp, &rec);
  504. set_end_char = 0;
  505. set_start_char = 1;
  506. rec.startCharOffset = char_len + j;
  507. rec.style_flags &= ~rem_styles;
  508. rem_styles = 0;
  509. }
  510. char_line = j;
  511. uniText[j] = 0;
  512. sptr = (u16 *) uniText;
  513. len = gf_utf8_wcstombs(szText, 5000, (const u16 **) &sptr);
  514. gf_isom_text_add_text(samp, szText, len);
  515. char_len += char_line;
  516. txt_line ++;
  517. break;
  518. }
  519. if (duration && (start >= duration)) break;
  520. }
  521. gf_isom_delete_text_sample(samp);
  522. /*do not add any empty sample at the end since it modifies track duration and is not needed - it is the player job
  523. to figure out when to stop displaying the last text sample
  524. However update the last sample duration*/
  525. gf_isom_set_last_sample_duration(import->dest, track, (u32) (end-start) );
  526. gf_set_progress("Importing SRT", nb_samp, nb_samp);
  527. exit:
  528. if (e) gf_isom_remove_track(import->dest, track);
  529. fclose(srt_in);
  530. return e;
  531. }
  532. static GF_Err gf_text_import_sub(GF_MediaImporter *import)
  533. {
  534. FILE *sub_in;
  535. u32 track, ID, timescale, i, j, desc_idx, start, end, prev_end, nb_samp, duration, len, line;
  536. u64 file_size;
  537. GF_TextConfig*cfg;
  538. GF_Err e;
  539. Double FPS;
  540. GF_TextSample * samp;
  541. Bool first_samp;
  542. s32 unicode_type;
  543. char szLine[2048], szTime[20], szText[2048];
  544. GF_ISOSample *s;
  545. sub_in = gf_f64_open(import->in_name, "rt");
  546. gf_f64_seek(sub_in, 0, SEEK_END);
  547. file_size = gf_f64_tell(sub_in);
  548. gf_f64_seek(sub_in, 0, SEEK_SET);
  549. unicode_type = gf_text_get_utf_type(sub_in);
  550. if (unicode_type<0) {
  551. fclose(sub_in);
  552. return gf_import_message(import, GF_NOT_SUPPORTED, "Unsupported SUB UTF encoding");
  553. }
  554. FPS = GF_IMPORT_DEFAULT_FPS;
  555. if (import->video_fps) FPS = import->video_fps;
  556. cfg = NULL;
  557. if (import->esd) {
  558. if (!import->esd->slConfig) {
  559. import->esd->slConfig = (GF_SLConfig *) gf_odf_desc_new(GF_ODF_SLC_TAG);
  560. import->esd->slConfig->predefined = 2;
  561. import->esd->slConfig->timestampResolution = 1000;
  562. }
  563. timescale = import->esd->slConfig->timestampResolution;
  564. if (!timescale) timescale = 1000;
  565. /*explicit text config*/
  566. if (import->esd->decoderConfig && import->esd->decoderConfig->decoderSpecificInfo->tag == GF_ODF_TEXT_CFG_TAG) {
  567. cfg = (GF_TextConfig *) import->esd->decoderConfig->decoderSpecificInfo;
  568. import->esd->decoderConfig->decoderSpecificInfo = NULL;
  569. }
  570. ID = import->esd->ESID;
  571. } else {
  572. timescale = 1000;
  573. ID = 0;
  574. }
  575. if (cfg && cfg->timescale) timescale = cfg->timescale;
  576. track = gf_isom_new_track(import->dest, ID, GF_ISOM_MEDIA_TEXT, timescale);
  577. if (!track) {
  578. fclose(sub_in);
  579. return gf_import_message(import, gf_isom_last_error(import->dest), "Error creating text track");
  580. }
  581. gf_isom_set_track_enabled(import->dest, track, 1);
  582. if (import->esd && !import->esd->ESID) import->esd->ESID = gf_isom_get_track_id(import->dest, track);
  583. gf_text_import_set_language(import, track);
  584. file_size = 0;
  585. /*setup track*/
  586. if (cfg) {
  587. u32 count;
  588. char *firstFont = NULL;
  589. /*set track info*/
  590. gf_isom_set_track_layout_info(import->dest, track, cfg->text_width<<16, cfg->text_height<<16, 0, 0, cfg->layer);
  591. /*and set sample descriptions*/
  592. count = gf_list_count(cfg->sample_descriptions);
  593. for (i=0; i<count; i++) {
  594. GF_TextSampleDescriptor *sd= (GF_TextSampleDescriptor *)gf_list_get(cfg->sample_descriptions, i);
  595. if (!sd->font_count) {
  596. sd->fonts = (GF_FontRecord*)gf_malloc(sizeof(GF_FontRecord));
  597. sd->font_count = 1;
  598. sd->fonts[0].fontID = 1;
  599. sd->fonts[0].fontName = gf_strdup("Serif");
  600. }
  601. if (!sd->default_style.fontID) sd->default_style.fontID = sd->fonts[0].fontID;
  602. if (!sd->default_style.font_size) sd->default_style.font_size = 16;
  603. if (!sd->default_style.text_color) sd->default_style.text_color = 0xFF000000;
  604. file_size = sd->default_style.font_size;
  605. gf_isom_new_text_description(import->dest, track, sd, NULL, NULL, &desc_idx);
  606. if (!firstFont) firstFont = sd->fonts[0].fontName;
  607. }
  608. gf_import_message(import, GF_OK, "Timed Text (SUB @ %02.2f) import - text track %d x %d, font %s (size %d)", FPS, cfg->text_width, cfg->text_height, firstFont, file_size);
  609. gf_odf_desc_del((GF_Descriptor *)cfg);
  610. } else {
  611. u32 w, h;
  612. GF_TextSampleDescriptor *sd;
  613. gf_text_get_video_size(import->dest, &w, &h);
  614. /*have to work with default - use max size (if only one video, this means the text region is the
  615. entire display, and with bottom alignment things should be fine...*/
  616. gf_isom_set_track_layout_info(import->dest, track, w<<16, h<<16, 0, 0, 0);
  617. sd = (GF_TextSampleDescriptor*)gf_odf_desc_new(GF_ODF_TX3G_TAG);
  618. sd->fonts = (GF_FontRecord*)gf_malloc(sizeof(GF_FontRecord));
  619. sd->font_count = 1;
  620. sd->fonts[0].fontID = 1;
  621. sd->fonts[0].fontName = gf_strdup("Serif");
  622. sd->back_color = 0x00000000; /*transparent*/
  623. sd->default_style.fontID = 1;
  624. sd->default_style.font_size = TTXT_DEFAULT_FONT_SIZE;
  625. sd->default_style.text_color = 0xFFFFFFFF; /*white*/
  626. sd->default_style.style_flags = 0;
  627. sd->horiz_justif = 1; /*center of scene*/
  628. sd->vert_justif = (s8) -1; /*bottom of scene*/
  629. if (import->flags & GF_IMPORT_SKIP_TXT_BOX) {
  630. sd->default_pos.top = sd->default_pos.left = sd->default_pos.right = sd->default_pos.bottom = 0;
  631. } else {
  632. if ((sd->default_pos.bottom==sd->default_pos.top) || (sd->default_pos.right==sd->default_pos.left)) {
  633. sd->default_pos.top = sd->default_pos.left = 0;
  634. sd->default_pos.right = import->twidth ? import->twidth : w;
  635. sd->default_pos.bottom = import->theight ? import->theight : h;
  636. }
  637. }
  638. gf_isom_new_text_description(import->dest, track, sd, NULL, NULL, &desc_idx);
  639. gf_import_message(import, GF_OK, "Timed Text (SUB @ %02.2f) import - text track %d x %d, font %s (size %d)", FPS, w, h, sd->fonts[0].fontName, TTXT_DEFAULT_FONT_SIZE);
  640. gf_odf_desc_del((GF_Descriptor *)sd);
  641. }
  642. duration = (u32) (((Double) import->duration)*timescale/1000.0);
  643. e = GF_OK;
  644. nb_samp = 0;
  645. samp = gf_isom_new_text_sample();
  646. FPS = ((Double) timescale ) / FPS;
  647. start = end = prev_end = 0;
  648. line = 0;
  649. first_samp = 1;
  650. while (1) {
  651. char *sOK = gf_text_get_utf8_line(szLine, 2048, sub_in, unicode_type);
  652. if (!sOK) break;
  653. REM_TRAIL_MARKS(szLine, "\r\n\t ")
  654. line++;
  655. len = strlen(szLine);
  656. if (!len) continue;
  657. i=0;
  658. if (szLine[i] != '{') {
  659. e = gf_import_message(import, GF_NON_COMPLIANT_BITSTREAM, "Bad SUB file (line %d): expecting \"{\" got \"%c\"", line, szLine[i]);
  660. goto exit;
  661. }
  662. while (szLine[i+1] && szLine[i+1]!='}') { szTime[i] = szLine[i+1]; i++; }
  663. szTime[i] = 0;
  664. start = atoi(szTime);
  665. if (start<end) {
  666. gf_import_message(import, GF_OK, "WARNING: corrupted SUB frame (line %d) - starts (at %d ms) before end of previous one (%d ms) - adjusting time stamps", line, start, end);
  667. start = end;
  668. }
  669. j=i+2;
  670. i=0;
  671. if (szLine[i+j] != '{') {
  672. e = gf_import_message(import, GF_NON_COMPLIANT_BITSTREAM, "Bad SUB file - expecting \"{\" got \"%c\"", szLine[i]);
  673. goto exit;
  674. }
  675. while (szLine[i+1+j] && szLine[i+1+j]!='}') { szTime[i] = szLine[i+1+j]; i++; }
  676. szTime[i] = 0;
  677. end = atoi(szTime);
  678. j+=i+2;
  679. if (start>end) {
  680. gf_import_message(import, GF_OK, "WARNING: corrupted SUB frame (line %d) - ends (at %d ms) before start of current frame (%d ms) - skipping", line, end, start);
  681. continue;
  682. }
  683. gf_isom_text_reset(samp);
  684. if (start && first_samp) {
  685. s = gf_isom_text_to_sample(samp);
  686. s->DTS = 0;
  687. s->IsRAP = 1;
  688. gf_isom_add_sample(import->dest, track, 1, s);
  689. gf_isom_sample_del(&s);
  690. first_samp = 0;
  691. nb_samp++;
  692. }
  693. for (i=j; i<len; i++) {
  694. if (szLine[i]=='|') {
  695. szText[i-j] = '\n';
  696. } else {
  697. szText[i-j] = szLine[i];
  698. }
  699. }
  700. szText[i-j] = 0;
  701. gf_isom_text_add_text(samp, szText, strlen(szText) );
  702. if (prev_end) {
  703. GF_TextSample * empty_samp = gf_isom_new_text_sample();
  704. s = gf_isom_text_to_sample(empty_samp);
  705. s->DTS = (u64) (FPS*(s64)prev_end);
  706. gf_isom_add_sample(import->dest, track, 1, s);
  707. gf_isom_sample_del(&s);
  708. nb_samp++;
  709. gf_isom_delete_text_sample(empty_samp);
  710. }
  711. s = gf_isom_text_to_sample(samp);
  712. s->DTS = (u64) (FPS*(s64)start);
  713. gf_isom_add_sample(import->dest, track, 1, s);
  714. gf_isom_sample_del(&s);
  715. nb_samp++;
  716. gf_isom_text_reset(samp);
  717. prev_end = end;
  718. gf_set_progress("Importing SUB", gf_f64_tell(sub_in), file_size);
  719. if (duration && (end >= duration)) break;
  720. }
  721. gf_isom_delete_text_sample(samp);
  722. /*do not add any empty sample at the end since it modifies track duration and is not needed - it is the player job
  723. to figure out when to stop displaying the last text sample
  724. However update the last sample duration*/
  725. gf_isom_set_last_sample_duration(import->dest, track, (u32) (end-start) );
  726. gf_set_progress("Importing SUB", nb_samp, nb_samp);
  727. exit:
  728. if (e) gf_isom_remove_track(import->dest, track);
  729. fclose(sub_in);
  730. return e;
  731. }
  732. #define CHECK_STR(__str) \
  733. if (!__str) { \
  734. e = gf_import_message(import, GF_BAD_PARAM, "Invalid XML formatting (line %d)", parser.line); \
  735. goto exit; \
  736. } \
  737. u32 ttxt_get_color(GF_MediaImporter *import, char *val)
  738. {
  739. u32 r, g, b, a, res;
  740. r = g = b = a = 0;
  741. if (sscanf(val, "%x %x %x %x", &r, &g, &b, &a) != 4) {
  742. gf_import_message(import, GF_OK, "Warning: color badly formatted");
  743. }
  744. res = (a&0xFF); res<<=8;
  745. res |= (r&0xFF); res<<=8;
  746. res |= (g&0xFF); res<<=8;
  747. res |= (b&0xFF);
  748. return res;
  749. }
  750. void ttxt_parse_text_box(GF_MediaImporter *import, GF_XMLNode *n, GF_BoxRecord *box)
  751. {
  752. u32 i=0;
  753. GF_XMLAttribute *att;
  754. memset(box, 0, sizeof(GF_BoxRecord));
  755. while ( (att=(GF_XMLAttribute *)gf_list_enum(n->attributes, &i))) {
  756. if (!stricmp(att->name, "top")) box->top = atoi(att->value);
  757. else if (!stricmp(att->name, "bottom")) box->bottom = atoi(att->value);
  758. else if (!stricmp(att->name, "left")) box->left = atoi(att->value);
  759. else if (!stricmp(att->name, "right")) box->right = atoi(att->value);
  760. }
  761. }
  762. void ttxt_parse_text_style(GF_MediaImporter *import, GF_XMLNode *n, GF_StyleRecord *style)
  763. {
  764. u32 i=0;
  765. GF_XMLAttribute *att;
  766. memset(style, 0, sizeof(GF_StyleRecord));
  767. style->fontID = 1;
  768. style->font_size = TTXT_DEFAULT_FONT_SIZE;
  769. style->text_color = 0xFFFFFFFF;
  770. while ( (att=(GF_XMLAttribute *)gf_list_enum(n->attributes, &i))) {
  771. if (!stricmp(att->name, "fromChar")) style->startCharOffset = atoi(att->value);
  772. else if (!stricmp(att->name, "toChar")) style->endCharOffset = atoi(att->value);
  773. else if (!stricmp(att->name, "fontID")) style->fontID = atoi(att->value);
  774. else if (!stricmp(att->name, "fontSize")) style->font_size = atoi(att->value);
  775. else if (!stricmp(att->name, "color")) style->text_color = ttxt_get_color(import, att->value);
  776. else if (!stricmp(att->name, "styles")) {
  777. if (strstr(att->value, "Bold")) style->style_flags |= GF_TXT_STYLE_BOLD;
  778. if (strstr(att->value, "Italic")) style->style_flags |= GF_TXT_STYLE_ITALIC;
  779. if (strstr(att->value, "Underlined")) style->style_flags |= GF_TXT_STYLE_UNDERLINED;
  780. }
  781. }
  782. }
  783. char *ttxt_parse_string(GF_MediaImporter *import, char *str, Bool strip_lines)
  784. {
  785. u32 i=0;
  786. u32 k=0;
  787. u32 len = strlen(str);
  788. u32 state = 0;
  789. if (!strip_lines) {
  790. for (i=0; i<len; i++) {
  791. if ((str[i] == '\r') && (str[i+1] == '\n')) {
  792. i++;
  793. }
  794. str[k] = str[i];
  795. k++;
  796. }
  797. str[k]=0;
  798. return str;
  799. }
  800. if (str[0]!='\'') return str;
  801. for (i=0; i<len; i++) {
  802. if (str[i] == '\'') {
  803. if (!state) {
  804. if (k) {
  805. str[k]='\n';
  806. k++;
  807. }
  808. state = !state;
  809. } else if (state) {
  810. if ( (i+1==len) ||
  811. ((str[i+1]==' ') || (str[i+1]=='\n') || (str[i+1]=='\r') || (str[i+1]=='\t') || (str[i+1]=='\''))
  812. ) {
  813. state = !state;
  814. } else {
  815. str[k] = str[i];
  816. k++;
  817. }
  818. }
  819. } else if (state) {
  820. str[k] = str[i];
  821. k++;
  822. }
  823. }
  824. str[k]=0;
  825. return str;
  826. }
  827. static void ttxt_import_progress(void *cbk, u64 cur_samp, u64 count)
  828. {
  829. gf_set_progress("TTXT Loading", cur_samp, count);
  830. }
  831. static GF_Err gf_text_import_ttxt(GF_MediaImporter *import)
  832. {
  833. GF_Err e;
  834. Bool last_sample_empty;
  835. u32 i, j, k, track, ID, nb_samples, nb_descs, nb_children;
  836. u64 last_sample_duration;
  837. GF_XMLAttribute *att;
  838. GF_DOMParser *parser;
  839. GF_XMLNode *root, *node, *ext;
  840. if (import->flags==GF_IMPORT_PROBE_ONLY) return GF_OK;
  841. parser = gf_xml_dom_new();
  842. e = gf_xml_dom_parse(parser, import->in_name, ttxt_import_progress, import);
  843. if (e) {
  844. gf_import_message(import, e, "Error parsing TTXT file: Line %d - %s", gf_xml_dom_get_line(parser), gf_xml_dom_get_error(parser));
  845. gf_xml_dom_del(parser);
  846. return e;
  847. }
  848. root = gf_xml_dom_get_root(parser);
  849. e = GF_OK;
  850. if (strcmp(root->name, "TextStream")) {
  851. e = gf_import_message(import, GF_BAD_PARAM, "Invalid Timed Text file - expecting \"TextStream\" got %s", "TextStream", root->name);
  852. goto exit;
  853. }
  854. /*setup track in 3GP format directly (no ES desc)*/
  855. ID = (import->esd) ? import->esd->ESID : 0;
  856. track = gf_isom_new_track(import->dest, ID, GF_ISOM_MEDIA_TEXT, 1000);
  857. if (!track) {
  858. e = gf_isom_last_error(import->dest);
  859. goto exit;
  860. }
  861. gf_isom_set_track_enabled(import->dest, track, 1);
  862. /*some MPEG-4 setup*/
  863. if (import->esd) {
  864. if (!import->esd->ESID) import->esd->ESID = gf_isom_get_track_id(import->dest, track);
  865. if (!import->esd->decoderConfig) import->esd->decoderConfig = (GF_DecoderConfig *) gf_odf_desc_new(GF_ODF_DCD_TAG);
  866. if (!import->esd->slConfig) import->esd->slConfig = (GF_SLConfig *) gf_odf_desc_new(GF_ODF_SLC_TAG);
  867. import->esd->slConfig->timestampResolution = 1000;
  868. import->esd->decoderConfig->streamType = GF_STREAM_TEXT;
  869. import->esd->decoderConfig->objectTypeIndication = 0x08;
  870. if (import->esd->OCRESID) gf_isom_set_track_reference(import->dest, track, GF_ISOM_REF_OCR, import->esd->OCRESID);
  871. }
  872. gf_text_import_set_language(import, track);
  873. gf_import_message(import, GF_OK, "Timed Text (GPAC TTXT) Import");
  874. last_sample_empty = 0;
  875. last_sample_duration = 0;
  876. nb_descs = 0;
  877. nb_samples = 0;
  878. nb_children = gf_list_count(root->content);
  879. i=0;
  880. while ( (node = (GF_XMLNode*)gf_list_enum(root->content, &i))) {
  881. if (node->type) { nb_children--; continue; }
  882. if (!strcmp(node->name, "TextStreamHeader")) {
  883. GF_XMLNode *sdesc;
  884. s32 w, h, tx, ty, layer;
  885. u32 tref_id;
  886. w = TTXT_DEFAULT_WIDTH;
  887. h = TTXT_DEFAULT_HEIGHT;
  888. tx = ty = layer = 0;
  889. nb_children--;
  890. tref_id = 0;
  891. j=0;
  892. while ( (att=(GF_XMLAttribute *)gf_list_enum(node->attributes, &j))) {
  893. if (!strcmp(att->name, "width")) w = atoi(att->value);
  894. else if (!strcmp(att->name, "height")) h = atoi(att->value);
  895. else if (!strcmp(att->name, "layer")) layer = atoi(att->value);
  896. else if (!strcmp(att->name, "translation_x")) tx = atoi(att->value);
  897. else if (!strcmp(att->name, "translation_y")) ty = atoi(att->value);
  898. else if (!strcmp(att->name, "trefID")) tref_id = atoi(att->value);
  899. }
  900. if (tref_id)
  901. gf_isom_set_track_reference(import->dest, track, GF_ISOM_BOX_TYPE_CHAP, tref_id);
  902. gf_isom_set_track_layout_info(import->dest, track, w<<16, h<<16, tx<<16, ty<<16, (s16) layer);
  903. j=0;
  904. while ( (sdesc=(GF_XMLNode*)gf_list_enum(node->content, &j))) {
  905. if (sdesc->type) continue;
  906. if (!strcmp(sdesc->name, "TextSampleDescription")) {
  907. GF_TextSampleDescriptor td;
  908. u32 idx;
  909. memset(&td, 0, sizeof(GF_TextSampleDescriptor));
  910. td.tag = GF_ODF_TEXT_CFG_TAG;
  911. td.vert_justif = (s8) -1;
  912. td.default_style.fontID = 1;
  913. td.default_style.font_size = TTXT_DEFAULT_FONT_SIZE;
  914. k=0;
  915. while ( (att=(GF_XMLAttribute *)gf_list_enum(sdesc->attributes, &k))) {
  916. if (!strcmp(att->name, "horizontalJustification")) {
  917. if (!stricmp(att->value, "center")) td.horiz_justif = 1;
  918. else if (!stricmp(att->value, "right")) td.horiz_justif = (s8) -1;
  919. else if (!stricmp(att->value, "left")) td.horiz_justif = 0;
  920. }
  921. else if (!strcmp(att->name, "verticalJustification")) {
  922. if (!stricmp(att->value, "center")) td.vert_justif = 1;
  923. else if (!stricmp(att->value, "bottom")) td.vert_justif = (s8) -1;
  924. else if (!stricmp(att->value, "top")) td.vert_justif = 0;
  925. }
  926. else if (!strcmp(att->name, "backColor")) td.back_color = ttxt_get_color(import, att->value);
  927. else if (!strcmp(att->name, "verticalText") && !stricmp(att->value, "yes") ) td.displayFlags |= GF_TXT_VERTICAL;
  928. else if (!strcmp(att->name, "fillTextRegion") && !stricmp(att->value, "yes") ) td.displayFlags |= GF_TXT_FILL_REGION;
  929. else if (!strcmp(att->name, "continuousKaraoke") && !stricmp(att->value, "yes") ) td.displayFlags |= GF_TXT_KARAOKE;
  930. else if (!strcmp(att->name, "scroll")) {
  931. if (!stricmp(att->value, "inout")) td.displayFlags |= GF_TXT_SCROLL_IN | GF_TXT_SCROLL_OUT;
  932. else if (!stricmp(att->value, "in")) td.displayFlags |= GF_TXT_SCROLL_IN;
  933. else if (!stricmp(att->value, "out")) td.displayFlags |= GF_TXT_SCROLL_OUT;
  934. }
  935. else if (!strcmp(att->name, "scrollMode")) {
  936. u32 scroll_mode = GF_TXT_SCROLL_CREDITS;
  937. if (!stricmp(att->value, "Credits")) scroll_mode = GF_TXT_SCROLL_CREDITS;
  938. else if (!stricmp(att->value, "Marquee")) scroll_mode = GF_TXT_SCROLL_MARQUEE;
  939. else if (!stricmp(att->value, "Right")) scroll_mode = GF_TXT_SCROLL_RIGHT;
  940. else if (!stricmp(att->value, "Down")) scroll_mode = GF_TXT_SCROLL_DOWN;
  941. td.displayFlags |= ((scroll_mode<<7) & GF_TXT_SCROLL_DIRECTION);
  942. }
  943. }
  944. k=0;
  945. while ( (ext=(GF_XMLNode*)gf_list_enum(sdesc->content, &k))) {
  946. if (ext->type) continue;
  947. if (!strcmp(ext->name, "TextBox")) ttxt_parse_text_box(import, ext, &td.default_pos);
  948. else if (!strcmp(ext->name, "Style")) ttxt_parse_text_style(import, ext, &td.default_style);
  949. else if (!strcmp(ext->name, "FontTable")) {
  950. GF_XMLNode *ftable;
  951. u32 z=0;
  952. while ( (ftable=(GF_XMLNode*)gf_list_enum(ext->content, &z))) {
  953. u32 m;
  954. if (ftable->type || strcmp(ftable->name, "FontTableEntry")) continue;
  955. td.font_count += 1;
  956. td.fonts = (GF_FontRecord*)gf_realloc(td.fonts, sizeof(GF_FontRecord)*td.font_count);
  957. m=0;
  958. while ( (att=(GF_XMLAttribute *)gf_list_enum(ftable->attributes, &m))) {
  959. if (!stricmp(att->name, "fontID")) td.fonts[td.font_count-1].fontID = atoi(att->value);
  960. else if (!stricmp(att->name, "fontName")) td.fonts[td.font_count-1].fontName = gf_strdup(att->value);
  961. }
  962. }
  963. }
  964. }
  965. if (import->flags & GF_IMPORT_SKIP_TXT_BOX) {
  966. td.default_pos.top = td.default_pos.left = td.default_pos.right = td.default_pos.bottom = 0;
  967. } else {
  968. if ((td.default_pos.bottom==td.default_pos.top) || (td.default_pos.right==td.default_pos.left)) {
  969. td.default_pos.top = td.default_pos.left = 0;
  970. td.default_pos.right = w;
  971. td.default_pos.bottom = h;
  972. }
  973. }
  974. if (!td.fonts) {
  975. td.font_count = 1;
  976. td.fonts = (GF_FontRecord*)gf_malloc(sizeof(GF_FontRecord));
  977. td.fonts[0].fontID = 1;
  978. td.fonts[0].fontName = gf_strdup("Serif");
  979. }
  980. gf_isom_new_text_description(import->dest, track, &td, NULL, NULL, &idx);
  981. for (k=0; k<td.font_count; k++) gf_free(td.fonts[k].fontName);
  982. gf_free(td.fonts);
  983. nb_descs ++;
  984. }
  985. }
  986. }
  987. /*sample text*/
  988. else if (!strcmp(node->name, "TextSample")) {
  989. GF_ISOSample *s;
  990. GF_TextSample * samp;
  991. u32 ts, descIndex;
  992. Bool has_text = 0;
  993. if (!nb_descs) {
  994. e = gf_import_message(import, GF_BAD_PARAM, "Invalid Timed Text file - text stream header not found or empty");
  995. goto exit;
  996. }
  997. samp = gf_isom_new_text_sample();
  998. ts = 0;
  999. descIndex = 1;
  1000. last_sample_empty = 0;
  1001. j=0;
  1002. while ( (att=(GF_XMLAttribute*)gf_list_enum(node->attributes, &j))) {
  1003. if (!strcmp(att->name, "sampleTime")) {
  1004. u32 h, m, s, ms;
  1005. if (sscanf(att->value, "%u:%u:%u.%u", &h, &m, &s, &ms) == 4) {
  1006. ts = (h*3600 + m*60 + s)*1000 + ms;
  1007. } else {
  1008. ts = (u32) (atof(att->value) * 1000);
  1009. }
  1010. }
  1011. else if (!strcmp(att->name, "sampleDescriptionIndex")) descIndex = atoi(att->value);
  1012. else if (!strcmp(att->name, "text")) {
  1013. u32 len;
  1014. char *str = ttxt_parse_string(import, att->value, 1);
  1015. len = strlen(str);
  1016. gf_isom_text_add_text(samp, str, len);
  1017. last_sample_empty = len ? 0 : 1;
  1018. has_text = 1;
  1019. }
  1020. else if (!strcmp(att->name, "scrollDelay")) gf_isom_text_set_scroll_delay(samp, (u32) (1000*atoi(att->value)));
  1021. else if (!strcmp(att->name, "highlightColor")) gf_isom_text_set_highlight_color_argb(samp, ttxt_get_color(import, att->value));
  1022. else if (!strcmp(att->name, "wrap") && !strcmp(att->value, "Automatic")) gf_isom_text_set_wrap(samp, 0x01);
  1023. }
  1024. /*get all modifiers*/
  1025. j=0;
  1026. while ( (ext=(GF_XMLNode*)gf_list_enum(node->content, &j))) {
  1027. if (!has_text && (ext->type==GF_XML_TEXT_TYPE)) {
  1028. u32 len;
  1029. char *str = ttxt_parse_string(import, ext->name, 0);
  1030. len = strlen(str);
  1031. gf_isom_text_add_text(samp, str, len);
  1032. last_sample_empty = len ? 0 : 1;
  1033. has_text = 1;
  1034. }
  1035. if (ext->type) continue;
  1036. if (!stricmp(ext->name, "Style")) {
  1037. GF_StyleRecord r;
  1038. ttxt_parse_text_style(import, ext, &r);
  1039. gf_isom_text_add_style(samp, &r);
  1040. }
  1041. else if (!stricmp(ext->name, "TextBox")) {
  1042. GF_BoxRecord r;
  1043. ttxt_parse_text_box(import, ext, &r);
  1044. gf_isom_text_set_box(samp, r.top, r.left, r.bottom, r.right);
  1045. }
  1046. else if (!stricmp(ext->name, "Highlight")) {
  1047. u16 start, end;
  1048. start = end = 0;
  1049. k=0;
  1050. while ( (att=(GF_XMLAttribute *)gf_list_enum(ext->attributes, &k))) {
  1051. if (!strcmp(att->name, "fromChar")) start = atoi(att->value);
  1052. else if (!strcmp(att->name, "toChar")) end = atoi(att->value);
  1053. }
  1054. gf_isom_text_add_highlight(samp, start, end);
  1055. }
  1056. else if (!stricmp(ext->name, "Blinking")) {
  1057. u16 start, end;
  1058. start = end = 0;
  1059. k=0;
  1060. while ( (att=(GF_XMLAttribute *)gf_list_enum(ext->attributes, &k))) {
  1061. if (!strcmp(att->name, "fromChar")) start = atoi(att->value);
  1062. else if (!strcmp(att->name, "toChar")) end = atoi(att->value);
  1063. }
  1064. gf_isom_text_add_blink(samp, start, end);
  1065. }
  1066. else if (!stricmp(ext->name, "HyperLink")) {
  1067. u16 start, end;
  1068. char *url, *url_tt;
  1069. start = end = 0;
  1070. url = url_tt = NULL;
  1071. k=0;
  1072. while ( (att=(GF_XMLAttribute *)gf_list_enum(ext->attributes, &k))) {
  1073. if (!strcmp(att->name, "fromChar")) start = atoi(att->value);
  1074. else if (!strcmp(att->name, "toChar")) end = atoi(att->value);
  1075. else if (!strcmp(att->name, "URL")) url = gf_strdup(att->value);
  1076. else if (!strcmp(att->name, "URLToolTip")) url_tt = gf_strdup(att->value);
  1077. }
  1078. gf_isom_text_add_hyperlink(samp, url, url_tt, start, end);
  1079. if (url) gf_free(url);
  1080. if (url_tt) gf_free(url_tt);
  1081. }
  1082. else if (!stricmp(ext->name, "Karaoke")) {
  1083. u32 startTime;
  1084. GF_XMLNode *krok;
  1085. startTime = 0;
  1086. k=0;
  1087. while ( (att=(GF_XMLAttribute *)gf_list_enum(ext->attributes, &k))) {
  1088. if (!strcmp(att->name, "startTime")) startTime = (u32) (1000*atof(att->value));
  1089. }
  1090. gf_isom_text_add_karaoke(samp, startTime);
  1091. k=0;
  1092. while ( (krok=(GF_XMLNode*)gf_list_enum(ext->content, &k))) {
  1093. u16 start, end;
  1094. u32 endTime, m;
  1095. if (krok->type) continue;
  1096. if (strcmp(krok->name, "KaraokeRange")) continue;
  1097. start = end = 0;
  1098. endTime = 0;
  1099. m=0;
  1100. while ( (att=(GF_XMLAttribute *)gf_list_enum(krok->attributes, &m))) {
  1101. if (!strcmp(att->name, "fromChar")) start = atoi(att->value);
  1102. else if (!strcmp(att->name, "toChar")) end = atoi(att->value);
  1103. else if (!strcmp(att->name, "endTime")) endTime = (u32) (1000*atof(att->value));
  1104. }
  1105. gf_isom_text_set_karaoke_segment(samp, endTime, start, end);
  1106. }
  1107. }
  1108. }
  1109. /*in MP4 we must start at T=0, so add an empty sample*/
  1110. if (ts && !nb_samples) {
  1111. GF_TextSample * firstsamp = gf_isom_new_text_sample();
  1112. s = gf_isom_text_to_sample(firstsamp);
  1113. s->DTS = 0;
  1114. gf_isom_add_sample(import->dest, track, 1, s);
  1115. nb_samples++;
  1116. gf_isom_delete_text_sample(firstsamp);
  1117. gf_isom_sample_del(&s);
  1118. }
  1119. s = gf_isom_text_to_sample(samp);
  1120. gf_isom_delete_text_sample(samp);
  1121. s->DTS = ts;
  1122. if (last_sample_empty) {
  1123. last_sample_duration = s->DTS - last_sample_duration;
  1124. } else {
  1125. last_sample_duration = s->DTS;
  1126. }
  1127. e = gf_isom_add_sample(import->dest, track, descIndex, s);
  1128. if (e) goto exit;
  1129. gf_isom_sample_del(&s);
  1130. nb_samples++;
  1131. gf_set_progress("Importing TTXT", nb_samples, nb_children);
  1132. if (import->duration && (ts>import->duration)) break;
  1133. }
  1134. }
  1135. if (last_sample_empty) {
  1136. gf_isom_remove_sample(import->dest, track, nb_samples);
  1137. gf_isom_set_last_sample_duration(import->dest, track, (u32) last_sample_duration);
  1138. }
  1139. gf_set_progress("Importing TTXT", nb_samples, nb_samples);
  1140. exit:
  1141. gf_xml_dom_del(parser);
  1142. return e;
  1143. }
  1144. u32 tx3g_get_color(GF_MediaImporter *import, char *value)
  1145. {
  1146. u32 r, g, b, a;
  1147. u32 res, v;
  1148. r = g = b = a = 0;
  1149. if (sscanf(value, "%u%%, %u%%, %u%%, %u%%", &r, &g, &b, &a) != 4) {
  1150. gf_import_message(import, GF_OK, "Warning: color badly formatted");
  1151. }
  1152. v = (u32) (a*255/100);
  1153. res = (v&0xFF); res<<=8;
  1154. v = (u32) (r*255/100);
  1155. res |= (v&0xFF); res<<=8;
  1156. v = (u32) (g*255/100);
  1157. res |= (v&0xFF); res<<=8;
  1158. v = (u32) (b*255/100);
  1159. res |= (v&0xFF);
  1160. return res;
  1161. }
  1162. void tx3g_parse_text_box(GF_MediaImporter *import, GF_XMLNode *n, GF_BoxRecord *box)
  1163. {
  1164. u32 i=0;
  1165. GF_XMLAttribute *att;
  1166. memset(box, 0, sizeof(GF_BoxRecord));
  1167. while ((att=(GF_XMLAttribute *)gf_list_enum(n->attributes, &i))) {
  1168. if (!stricmp(att->name, "x")) box->left = atoi(att->value);
  1169. else if (!stricmp(att->name, "y")) box->top = atoi(att->value);
  1170. else if (!stricmp(att->name, "height")) box->bottom = atoi(att->value);
  1171. else if (!stricmp(att->name, "width")) box->right = atoi(att->value);
  1172. }
  1173. }
  1174. typedef struct
  1175. {
  1176. u32 id;
  1177. u32 pos;
  1178. } Marker;
  1179. #define GET_MARKER_POS(_val, __isend) \
  1180. { \
  1181. u32 i, __m = atoi(att->value); \
  1182. _val = 0; \
  1183. for (i=0; i<nb_marks; i++) { if (__m==marks[i].id) { _val = marks[i].pos; /*if (__isend) _val--; */break; } } \
  1184. }
  1185. static void texml_import_progress(void *cbk, u64 cur_samp, u64 count)
  1186. {
  1187. gf_set_progress("TeXML Loading", cur_samp, count);
  1188. }
  1189. static GF_Err gf_text_import_texml(GF_MediaImporter *import)
  1190. {
  1191. GF_Err e;
  1192. u32 track, ID, nb_samples, nb_children, nb_descs, timescale, w, h, i, j, k;
  1193. u64 DTS;
  1194. s32 tx, ty, layer;
  1195. GF_StyleRecord styles[50];
  1196. Marker marks[50];
  1197. GF_XMLAttribute *att;
  1198. GF_DOMParser *parser;
  1199. GF_XMLNode *root, *node;
  1200. if (import->flags==GF_IMPORT_PROBE_ONLY) return GF_OK;
  1201. parser = gf_xml_dom_new();
  1202. e = gf_xml_dom_parse(parser, import->in_name, texml_import_progress, import);
  1203. if (e) {
  1204. gf_import_message(import, e, "Error parsing TeXML file: Line %d - %s", gf_xml_dom_get_line(parser), gf_xml_dom_get_error(parser));
  1205. gf_xml_dom_del(parser);
  1206. return e;
  1207. }
  1208. root = gf_xml_dom_get_root(parser);
  1209. if (strcmp(root->name, "text3GTrack")) {
  1210. e = gf_import_message(import, GF_BAD_PARAM, "Invalid QT TeXML file - expecting root \"text3GTrack\" got \"%s\"", root->name);
  1211. goto exit;
  1212. }
  1213. w = TTXT_DEFAULT_WIDTH;
  1214. h = TTXT_DEFAULT_HEIGHT;
  1215. tx = ty = 0;
  1216. layer = 0;
  1217. timescale = 1000;
  1218. i=0;
  1219. while ( (att=(GF_XMLAttribute *)gf_list_enum(root->attributes, &i))) {
  1220. if (!strcmp(att->name, "trackWidth")) w = atoi(att->value);
  1221. else if (!strcmp(att->name, "trackHeight")) h = atoi(att->value);
  1222. else if (!strcmp(att->name, "layer")) layer = atoi(att->value);
  1223. else if (!strcmp(att->name, "timescale")) timescale = atoi(att->value);
  1224. else if (!strcmp(att->name, "transform")) {
  1225. Float fx, fy;
  1226. sscanf(att->value, "translate(%f,%f)", &fx, &fy);
  1227. tx = (u32) fx; ty = (u32) fy;
  1228. }
  1229. }
  1230. /*setup track in 3GP format directly (no ES desc)*/
  1231. ID = (import->esd) ? import->esd->ESID : 0;
  1232. track = gf_isom_new_track(import->dest, ID, GF_ISOM_MEDIA_TEXT, timescale);
  1233. if (!track) {
  1234. e = gf_isom_last_error(import->dest);
  1235. goto exit;
  1236. }
  1237. gf_isom_set_track_enabled(import->dest, track, 1);
  1238. /*some MPEG-4 setup*/
  1239. if (import->esd) {
  1240. if (!import->esd->ESID) import->esd->ESID = gf_isom_get_track_id(import->dest, track);
  1241. if (!import->esd->decoderConfig) import->esd->decoderConfig = (GF_DecoderConfig *) gf_odf_desc_new(GF_ODF_DCD_TAG);
  1242. if (!import->esd->slConfig) import->esd->slConfig = (GF_SLConfig *) gf_odf_desc_new(GF_ODF_SLC_TAG);
  1243. import->esd->slConfig->timestampResolution = timescale;
  1244. import->esd->decoderConfig->streamType = GF_STREAM_TEXT;
  1245. import->esd->decoderConfig->objectTypeIndication = 0x08;
  1246. if (import->esd->OCRESID) gf_isom_set_track_reference(import->dest, track, GF_ISOM_REF_OCR, import->esd->OCRESID);
  1247. }
  1248. DTS = 0;
  1249. gf_isom_set_track_layout_info(import->dest, track, w<<16, h<<16, tx<<16, ty<<16, (s16) layer);
  1250. gf_text_import_set_language(import, track);
  1251. e = GF_OK;
  1252. gf_import_message(import, GF_OK, "Timed Text (QT TeXML) Import - Track Size %d x %d", w, h);
  1253. nb_children = gf_list_count(root->content);
  1254. nb_descs = 0;
  1255. nb_samples = 0;
  1256. i=0;
  1257. while ( (node=(GF_XMLNode*)gf_list_enum(root->content, &i))) {
  1258. GF_XMLNode *desc;
  1259. GF_TextSampleDescriptor td;
  1260. GF_TextSample * samp = NULL;
  1261. GF_ISOSample *s;
  1262. u32 duration, descIndex, nb_styles, nb_marks;
  1263. Bool isRAP, same_style, same_box;
  1264. if (node->type) continue;
  1265. if (strcmp(node->name, "sample")) continue;
  1266. isRAP = 0;
  1267. duration = 1000;
  1268. j=0;
  1269. while ((att=(GF_XMLAttribute *)gf_list_enum(node->attributes, &j))) {
  1270. if (!strcmp(att->name, "duration")) duration = atoi(att->value);
  1271. else if (!strcmp(att->name, "keyframe")) isRAP = !stricmp(att->value, "true");
  1272. }
  1273. nb_styles = 0;
  1274. nb_marks = 0;
  1275. same_style = same_box = 0;
  1276. descIndex = 1;
  1277. j=0;
  1278. while ((desc=(GF_XMLNode*)gf_list_enum(node->content, &j))) {
  1279. if (desc->type) continue;
  1280. if (!strcmp(desc->name, "description")) {
  1281. GF_XMLNode *sub;
  1282. memset(&td, 0, sizeof(GF_TextSampleDescriptor));
  1283. td.tag = GF_ODF_TEXT_CFG_TAG;
  1284. td.vert_justif = (s8) -1;
  1285. td.default_style.fontID = 1;
  1286. td.default_style.font_size = TTXT_DEFAULT_FONT_SIZE;
  1287. k=0;
  1288. while ((att=(GF_XMLAttribute *)gf_list_enum(desc->attributes, &k))) {
  1289. if (!strcmp(att->name, "horizontalJustification")) {
  1290. if (!stricmp(att->value, "center")) td.horiz_justif = 1;
  1291. else if (!stricmp(att->value, "right")) td.horiz_justif = (s8) -1;
  1292. else if (!stricmp(att->value, "left")) td.horiz_justif = 0;
  1293. }
  1294. else if (!strcmp(att->name, "verticalJustification")) {
  1295. if (!stricmp(att->value, "center")) td.vert_justif = 1;
  1296. else if (!stricmp(att->value, "bottom")) td.vert_justif = (s8) -1;
  1297. else if (!stricmp(att->value, "top")) td.vert_justif = 0;
  1298. }
  1299. else if (!strcmp(att->name, "backgroundColor")) td.back_color = tx3g_get_color(import, att->value);
  1300. else if (!strcmp(att->name, "displayFlags")) {
  1301. Bool rev_scroll = 0;
  1302. if (strstr(att->value, "scroll")) {
  1303. u32 scroll_mode = 0;
  1304. if (strstr(att->value, "scrollIn")) td.displayFlags |= GF_TXT_SCROLL_IN;
  1305. if (strstr(att->value, "scrollOut")) td.displayFlags |= GF_TXT_SCROLL_OUT;
  1306. if (strstr(att->value, "reverse")) rev_scroll = 1;
  1307. if (strstr(att->value, "horizontal")) scroll_mode = rev_scroll ? GF_TXT_SCROLL_RIGHT : GF_TXT_SCROLL_MARQUEE;
  1308. else scroll_mode = (rev_scroll ? GF_TXT_SCROLL_DOWN : GF_TXT_SCROLL_CREDITS);
  1309. td.displayFlags |= (scroll_mode<<7) & GF_TXT_SCROLL_DIRECTION;
  1310. }
  1311. /*TODO FIXME: check in QT doc !!*/
  1312. if (strstr(att->value, "writeTextVertically")) td.displayFlags |= GF_TXT_VERTICAL;
  1313. if (!strcmp(att->name, "continuousKaraoke")) td.displayFlags |= GF_TXT_KARAOKE;
  1314. }
  1315. }
  1316. k=0;
  1317. while ((sub=(GF_XMLNode*)gf_list_enum(desc->content, &k))) {
  1318. if (sub->type) continue;
  1319. if (!strcmp(sub->name, "defaultTextBox")) tx3g_parse_text_box(import, sub, &td.default_pos);
  1320. else if (!strcmp(sub->name, "fontTable")) {
  1321. GF_XMLNode *ftable;
  1322. u32 m=0;
  1323. while ((ftable=(GF_XMLNode*)gf_list_enum(sub->content, &m))) {
  1324. if (ftable->type) continue;
  1325. if (!strcmp(ftable->name, "font")) {
  1326. u32 n=0;
  1327. td.font_count += 1;
  1328. td.fonts = (GF_FontRecord*)gf_realloc(td.fonts, sizeof(GF_FontRecord)*td.font_count);
  1329. while ((att=(GF_XMLAttribute *)gf_list_enum(ftable->attributes, &n))) {
  1330. if (!stricmp(att->name, "id")) td.fonts[td.font_count-1].fontID = atoi(att->value);
  1331. else if (!stricmp(att->name, "name")) td.fonts[td.font_count-1].fontName = gf_strdup(att->value);
  1332. }
  1333. }
  1334. }
  1335. }
  1336. else if (!strcmp(sub->name, "sharedStyles")) {
  1337. GF_XMLNode *style, *ftable;
  1338. u32 m=0;
  1339. while ((style=(GF_XMLNode*)gf_list_enum(sub->content, &m))) {
  1340. if (style->type) continue;
  1341. if (!strcmp(style->name, "style")) break;
  1342. }
  1343. if (style) {
  1344. char *cur;
  1345. s32 start=0;
  1346. char css_style[1024], css_val[1024];
  1347. memset(&styles[nb_styles], 0, sizeof(GF_StyleRecord));
  1348. m=0;
  1349. while ( (att=(GF_XMLAttribute *)gf_list_enum(style->attributes, &m))) {
  1350. if (!strcmp(att->name, "id")) styles[nb_styles].startCharOffset = atoi(att->value);
  1351. }
  1352. m=0;
  1353. while ( (ftable=(GF_XMLNode*)gf_list_enum(style->content, &m))) {
  1354. if (ftable->type) break;
  1355. }
  1356. cur = ftable->name;
  1357. while (cur) {
  1358. start = gf_token_get_strip(cur, 0, "{:", " ", css_style, 1024);
  1359. if (start <0) break;
  1360. start = gf_token_get_strip(cur, start, ":}", " ", css_val, 1024);
  1361. if (start <0) break;
  1362. cur = strchr(cur+start, '{');
  1363. if (!strcmp(css_style, "font-table")) {
  1364. u32 z;
  1365. styles[nb_styles].fontI

Large files files are truncated, but you can click here to view the full file