PageRenderTime 54ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/src/media_tools/text_import.c

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

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