1 /*******************************************************************************
2 texttrack.c
3
4 libquicktime - A library for reading and writing quicktime/avi/mp4 files.
5 http://libquicktime.sourceforge.net
6
7 Copyright (C) 2002 Heroine Virtual Ltd.
8 Copyright (C) 2002-2011 Members of the libquicktime project.
9
10 This library is free software; you can redistribute it and/or modify it under
11 the terms of the GNU Lesser General Public License as published by the Free
12 Software Foundation; either version 2.1 of the License, or (at your option)
13 any later version.
14
15 This library is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
18 details.
19
20 You should have received a copy of the GNU Lesser General Public License along
21 with this library; if not, write to the Free Software Foundation, Inc., 51
22 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 *******************************************************************************/
24
25 #include "lqt_private.h"
26 #include "charset.h"
27 #include <stdlib.h>
28 #include <string.h>
29
30 #define LOG_DOMAIN "texttrack"
31
32 /* Common functions */
33
lqt_init_text_map(quicktime_t * file,quicktime_text_map_t * map,quicktime_trak_t * trak,int encode)34 void lqt_init_text_map(quicktime_t * file,
35 quicktime_text_map_t * map, quicktime_trak_t * trak,
36 int encode)
37 {
38 const char * charset;
39 const char * charset_fallback;
40 map->track = trak;
41 map->cur_chunk = 0;
42 if(!encode)
43 {
44 charset = lqt_get_charset(trak->mdia.mdhd.language, file->file_type);
45 charset_fallback = lqt_get_charset_fallback(trak->mdia.mdhd.language, file->file_type);
46
47 if(!charset && !charset_fallback)
48 {
49 lqt_log(file, LQT_LOG_WARNING, LOG_DOMAIN,
50 "Cannot determine character set of text track, will copy the strings verbatim");
51 return;
52 }
53 else
54 {
55 if(charset)
56 map->cnv = lqt_charset_converter_create(file, charset, "UTF-8");
57 if(!map->cnv && charset_fallback)
58 map->cnv = lqt_charset_converter_create(file, charset_fallback, "UTF-8");
59 }
60 if(!map->cnv)
61 lqt_log(file, LQT_LOG_WARNING, LOG_DOMAIN,
62 "Unsupported charset in text track, will copy the strings verbatim");
63 }
64 }
65
lqt_delete_text_map(quicktime_t * file,quicktime_text_map_t * map)66 void lqt_delete_text_map(quicktime_t * file,
67 quicktime_text_map_t * map)
68 {
69 if(map->text_buffer) free(map->text_buffer);
70 if(map->cnv) lqt_charset_converter_destroy(map->cnv);
71 }
72
73 /* Decoding */
74
75
lqt_text_tracks(quicktime_t * file)76 int lqt_text_tracks(quicktime_t * file)
77 {
78 int i, result = 0;
79 quicktime_minf_t *minf;
80 for(i = 0; i < file->moov.total_tracks; i++)
81 {
82 minf = &file->moov.trak[i]->mdia.minf;
83 if(minf->is_text)
84 result++;
85 }
86 return result;
87 }
88
lqt_text_time_scale(quicktime_t * file,int track)89 int lqt_text_time_scale(quicktime_t * file, int track)
90 {
91 return file->ttracks[track].track->mdia.mdhd.time_scale;
92 }
93
94
lqt_read_text(quicktime_t * file,int track,char ** text,int * text_alloc,int64_t * timestamp,int64_t * duration)95 int lqt_read_text(quicktime_t * file, int track, char ** text, int * text_alloc,
96 int64_t * timestamp, int64_t * duration)
97 {
98 int64_t file_position, stts_index = 0, stts_count = 0;
99 char * ptr;
100
101 int string_length;
102
103 quicktime_text_map_t * ttrack = &file->ttracks[track];
104 quicktime_trak_t * trak = ttrack->track;
105 quicktime_stts_t * stts = &trak->mdia.minf.stbl.stts;
106
107 if(ttrack->current_position >= quicktime_track_samples(file, trak))
108 return 0; // EOF
109
110 /* Get the file position */
111 file_position = quicktime_sample_to_offset(file, trak, ttrack->current_position);
112 quicktime_set_position(file, file_position);
113
114 string_length = quicktime_read_int16(file);
115
116 if(string_length)
117 {
118
119 /* Read whole sample */
120 if(ttrack->text_buffer_alloc < string_length)
121 {
122 ttrack->text_buffer_alloc = string_length + 128;
123 ttrack->text_buffer = realloc(ttrack->text_buffer, ttrack->text_buffer_alloc);
124 }
125 quicktime_read_data(file, (uint8_t*)ttrack->text_buffer, string_length);
126
127 if(ttrack->cnv)
128 {
129 /* Convert character set */
130 lqt_charset_convert_realloc(ttrack->cnv,
131 ttrack->text_buffer, string_length,
132 text, text_alloc, (int*)0);
133 }
134 else /* Copy verbatim */
135 {
136 if(*text_alloc < string_length)
137 {
138 *text_alloc = string_length + 64;
139 *text = realloc(*text, *text_alloc);
140 memcpy(*text, ttrack->text_buffer, string_length);
141 }
142 }
143 }
144 else /* Empty string */
145 {
146 if(*text_alloc < 1)
147 {
148 *text_alloc = 1;
149 *text = realloc(*text, 1);
150 }
151 (*text)[0] = '\0';
152 }
153
154 *timestamp = quicktime_sample_to_time(stts, ttrack->current_position,
155 &stts_index, &stts_count);
156 *duration = stts->table[stts_index].sample_duration;
157
158 /* de-macify linebreaks */
159 ptr = *text;
160 while(*ptr != '\0')
161 {
162 if(*ptr == '\r')
163 *ptr = '\n';
164 ptr++;
165 }
166 ttrack->current_position++;
167 return 1;
168 }
169
170
lqt_is_chapter_track(quicktime_t * file,int track)171 int lqt_is_chapter_track(quicktime_t * file, int track)
172 {
173 int i, j, k;
174
175 quicktime_trak_t * trak = file->ttracks[track].track;
176
177 for(i = 0; i < file->moov.total_tracks; i++)
178 {
179 if(file->moov.trak[i] == trak)
180 continue;
181
182 /* Track reference present */
183 if(file->moov.trak[i]->has_tref)
184 {
185 for(j = 0; j < file->moov.trak[i]->tref.num_references; j++)
186 {
187 /* Track reference has type chap */
188 if(quicktime_match_32(file->moov.trak[i]->tref.references[j].type, "chap"))
189 {
190 for(k = 0; k < file->moov.trak[i]->tref.references[j].num_tracks; k++)
191 {
192 /* Track reference points to us */
193 if(file->moov.trak[i]->tref.references[j].tracks[k] == trak->tkhd.track_id)
194 return 1;
195 }
196 }
197 }
198 }
199 }
200 return 0;
201 }
202
lqt_text_samples(quicktime_t * file,int track)203 int64_t lqt_text_samples(quicktime_t * file, int track)
204 {
205 return quicktime_track_samples(file, file->ttracks[track].track);
206 }
207
208
lqt_set_text_position(quicktime_t * file,int track,int64_t position)209 void lqt_set_text_position(quicktime_t * file, int track, int64_t position)
210 {
211 file->ttracks[track].current_position = position;
212 }
213
214
lqt_set_text_time(quicktime_t * file,int track,int64_t time)215 void lqt_set_text_time(quicktime_t * file, int track, int64_t time)
216 {
217 int64_t stts_index, stts_count;
218 quicktime_text_map_t * ttrack = &file->ttracks[track];
219 quicktime_trak_t * trak = ttrack->track;
220
221 file->ttracks[track].current_position =
222 quicktime_time_to_sample(&trak->mdia.minf.stbl.stts,
223 &time,
224 &stts_index,
225 &stts_count);
226
227 }
228
229 /* Encoding */
230
lqt_add_text_track(quicktime_t * file,int timescale)231 int lqt_add_text_track(quicktime_t * file, int timescale)
232 {
233 quicktime_trak_t * trak;
234 file->ttracks =
235 realloc(file->ttracks, (file->total_ttracks+1)*sizeof(quicktime_text_map_t));
236 memset(&file->ttracks[file->total_ttracks], 0, sizeof(quicktime_text_map_t));
237
238 trak = quicktime_add_track(file);
239
240 if(IS_MP4(file->file_type))
241 quicktime_trak_init_tx3g(file, trak, timescale);
242 else if(file->file_type & (LQT_FILE_QT | LQT_FILE_QT_OLD))
243 quicktime_trak_init_text(file, trak, timescale);
244 else
245 lqt_log(file, LQT_LOG_ERROR, LOG_DOMAIN,
246 "Text track not supported for this file");
247
248 lqt_init_text_map(file, &file->ttracks[file->total_ttracks],
249 trak, 1);
250 file->total_ttracks++;
251 return 0;
252 }
253
lqt_set_chapter_track(quicktime_t * file,int track)254 void lqt_set_chapter_track(quicktime_t * file, int track)
255 {
256 file->ttracks[track].is_chapter_track = 1;
257 }
258
make_chapter_track(quicktime_t * file,quicktime_trak_t * trak)259 static void make_chapter_track(quicktime_t * file,
260 quicktime_trak_t * trak)
261 {
262 quicktime_trak_t * ref_track;
263
264 /* We create one "chap" reference in one other track. Choose the first
265 video track if possible */
266
267 if(file->total_vtracks)
268 ref_track = file->vtracks[0].track;
269 else if(file->total_atracks)
270 ref_track = file->atracks[0].track;
271 else
272 {
273 lqt_log(file, LQT_LOG_ERROR, LOG_DOMAIN,
274 "Need at least one audio or video stream for chapters");
275 return;
276 }
277 quicktime_tref_init_chap(&ref_track->tref, trak->tkhd.track_id);
278 ref_track->has_tref = 1;
279 }
280
lqt_write_text(quicktime_t * file,int track,const char * text,int64_t duration)281 int lqt_write_text(quicktime_t * file, int track, const char * text,
282 int64_t duration)
283 {
284 const char * charset;
285 const char * charset_fallback;
286 quicktime_text_map_t * ttrack;
287 quicktime_trak_t * trak;
288 int out_len;
289
290 ttrack = &file->ttracks[track];
291 trak = ttrack->track;
292
293 /* Issue a warning for AVI files (No subitles are supported here) */
294 if(file->file_type & (LQT_FILE_AVI|LQT_FILE_AVI_ODML))
295 {
296 lqt_log(file, LQT_LOG_ERROR, LOG_DOMAIN,
297 "Subtitles are not supported in AVI files");
298 return 1;
299 }
300
301 if(!ttrack->initialized)
302 {
303 /* Create character set converter */
304 if(file->file_type & (LQT_FILE_QT_OLD|LQT_FILE_QT))
305 {
306 charset = lqt_get_charset(trak->mdia.mdhd.language,
307 file->file_type);
308 charset_fallback = lqt_get_charset_fallback(trak->mdia.mdhd.language,
309 file->file_type);
310 if(!charset && !charset_fallback)
311 {
312 lqt_log(file, LQT_LOG_ERROR, LOG_DOMAIN,
313 "Subtitles character set could not be determined, string will be copied verbatim");
314 }
315 else
316 {
317 if(charset)
318 ttrack->cnv = lqt_charset_converter_create(file, "UTF-8", charset);
319 if(!ttrack->cnv && charset_fallback)
320 ttrack->cnv = lqt_charset_converter_create(file, "UTF-8", charset_fallback);
321
322 if(!ttrack->cnv)
323 {
324 lqt_log(file, LQT_LOG_ERROR, LOG_DOMAIN,
325 "Unsupported character set in text track, string will be copied verbatim");
326 }
327 }
328 }
329
330 /* Set up chapter track */
331 if(ttrack->is_chapter_track)
332 make_chapter_track(file, trak);
333 ttrack->initialized = 1;
334 }
335
336 quicktime_write_chunk_header(file, trak);
337
338 if(text)
339 {
340 if(ttrack->cnv)
341 {
342 lqt_charset_convert_realloc(ttrack->cnv,
343 text, -1,
344 &ttrack->text_buffer, &ttrack->text_buffer_alloc,
345 &out_len);
346 quicktime_write_int16(file, out_len);
347 quicktime_write_data(file, (uint8_t*)ttrack->text_buffer, out_len);
348 }
349 else
350 {
351 out_len = strlen(text);
352
353 quicktime_write_int16(file, out_len);
354 quicktime_write_data(file, (uint8_t*)text, out_len);
355 }
356 }
357 else
358 {
359 quicktime_write_int16(file, 0);
360 }
361
362 trak->chunk_samples = 1;
363 quicktime_write_chunk_footer(file, trak);
364
365 quicktime_update_stts(&trak->mdia.minf.stbl.stts, ttrack->current_position, duration);
366
367 ttrack->cur_chunk++;
368 ttrack->current_position++;
369 return 0;
370 }
371
372 /* Rectangle */
373
lqt_set_text_box(quicktime_t * file,int track,uint16_t top,uint16_t left,uint16_t bottom,uint16_t right)374 void lqt_set_text_box(quicktime_t * file, int track,
375 uint16_t top, uint16_t left,
376 uint16_t bottom, uint16_t right)
377 {
378 quicktime_trak_t * trak;
379 quicktime_stsd_table_t * stsd;
380 trak = file->ttracks[track].track;
381 stsd = &trak->mdia.minf.stbl.stsd.table[0];
382
383 if(quicktime_match_32(stsd->format, "text"))
384 {
385 trak->tkhd.matrix.values[6] += (float)left;
386 trak->tkhd.matrix.values[7] += (float)top;
387 trak->tkhd.track_width = right - left;
388 trak->tkhd.track_height = bottom - top;
389 }
390 else if(quicktime_match_32(stsd->format, "tx3g"))
391 {
392 trak->tkhd.track_width = right - left;
393 trak->tkhd.track_height = bottom - top;
394
395 stsd->tx3g.defaultTextBox[0] = top;
396 stsd->tx3g.defaultTextBox[1] = left;
397 stsd->tx3g.defaultTextBox[2] = bottom;
398 stsd->tx3g.defaultTextBox[3] = right;
399 }
400 }
401
lqt_get_text_box(quicktime_t * file,int track,uint16_t * top,uint16_t * left,uint16_t * bottom,uint16_t * right)402 void lqt_get_text_box(quicktime_t * file, int track,
403 uint16_t * top, uint16_t * left,
404 uint16_t * bottom, uint16_t * right)
405 {
406 quicktime_trak_t * trak;
407 quicktime_stsd_table_t * stsd;
408 trak = file->ttracks[track].track;
409 stsd = &trak->mdia.minf.stbl.stsd.table[0];
410
411 if(quicktime_match_32(stsd->format, "text"))
412 {
413 *top = stsd->text.defaultTextBox[0];
414 *left = stsd->text.defaultTextBox[1];
415 *bottom = stsd->text.defaultTextBox[2];
416 *right = stsd->text.defaultTextBox[3];
417 }
418 else if(quicktime_match_32(stsd->format, "tx3g"))
419 {
420 *top = stsd->tx3g.defaultTextBox[0];
421 *left = stsd->tx3g.defaultTextBox[1];
422 *bottom = stsd->tx3g.defaultTextBox[2];
423 *right = stsd->tx3g.defaultTextBox[3];
424 }
425 }
426
lqt_set_text_fg_color(quicktime_t * file,int track,uint16_t r,uint16_t g,uint16_t b,uint16_t a)427 void lqt_set_text_fg_color(quicktime_t * file, int track,
428 uint16_t r, uint16_t g,
429 uint16_t b, uint16_t a)
430 {
431 quicktime_trak_t * trak;
432 quicktime_stsd_table_t * stsd;
433 trak = file->ttracks[track].track;
434 stsd = &trak->mdia.minf.stbl.stsd.table[0];
435
436 if(quicktime_match_32(stsd->format, "text"))
437 {
438 stsd->text.scrpColor[0] = r;
439 stsd->text.scrpColor[1] = g;
440 stsd->text.scrpColor[2] = b;
441 }
442 else if(quicktime_match_32(stsd->format, "tx3g"))
443 {
444 stsd->tx3g.text_color[0] = r >> 8;
445 stsd->tx3g.text_color[1] = g >> 8;
446 stsd->tx3g.text_color[2] = b >> 8;
447 stsd->tx3g.text_color[3] = a >> 8;
448 }
449 }
450
451
lqt_set_text_bg_color(quicktime_t * file,int track,uint16_t r,uint16_t g,uint16_t b,uint16_t a)452 void lqt_set_text_bg_color(quicktime_t * file, int track,
453 uint16_t r, uint16_t g,
454 uint16_t b, uint16_t a)
455 {
456 quicktime_trak_t * trak;
457 quicktime_stsd_table_t * stsd;
458 trak = file->ttracks[track].track;
459 stsd = &trak->mdia.minf.stbl.stsd.table[0];
460
461 if(quicktime_match_32(stsd->format, "text"))
462 {
463 stsd->text.bgColor[0] = r;
464 stsd->text.bgColor[1] = g;
465 stsd->text.bgColor[2] = b;
466 if(a < 0x8000)
467 stsd->text.displayFlags |= 0x4000;
468 }
469 else if(quicktime_match_32(stsd->format, "tx3g"))
470 {
471 stsd->tx3g.back_color[0] = r >> 8;
472 stsd->tx3g.back_color[1] = g >> 8;
473 stsd->tx3g.back_color[2] = b >> 8;
474 stsd->tx3g.back_color[3] = a >> 8;
475 }
476 }
477
478
lqt_get_text_fg_color(quicktime_t * file,int track,uint16_t * r,uint16_t * g,uint16_t * b,uint16_t * a)479 void lqt_get_text_fg_color(quicktime_t * file, int track,
480 uint16_t * r, uint16_t * g,
481 uint16_t * b, uint16_t * a)
482 {
483 quicktime_trak_t * trak;
484 quicktime_stsd_table_t * stsd;
485 trak = file->ttracks[track].track;
486 stsd = &trak->mdia.minf.stbl.stsd.table[0];
487
488 if(quicktime_match_32(stsd->format, "text"))
489 {
490 *r = stsd->text.scrpColor[0];
491 *g = stsd->text.scrpColor[1];
492 *b = stsd->text.scrpColor[2];
493 *a = 0xffff;
494 }
495 else if(quicktime_match_32(stsd->format, "tx3g"))
496 {
497 *r = (stsd->tx3g.text_color[0] << 8) | stsd->tx3g.text_color[0];
498 *g = (stsd->tx3g.text_color[1] << 8) | stsd->tx3g.text_color[1];
499 *b = (stsd->tx3g.text_color[2] << 8) | stsd->tx3g.text_color[2];
500 *a = (stsd->tx3g.text_color[3] << 8) | stsd->tx3g.text_color[3];
501 }
502
503
504 }
505
lqt_get_text_bg_color(quicktime_t * file,int track,uint16_t * r,uint16_t * g,uint16_t * b,uint16_t * a)506 void lqt_get_text_bg_color(quicktime_t * file, int track,
507 uint16_t * r, uint16_t * g,
508 uint16_t * b, uint16_t * a)
509 {
510 quicktime_trak_t * trak;
511 quicktime_stsd_table_t * stsd;
512 trak = file->ttracks[track].track;
513 stsd = &trak->mdia.minf.stbl.stsd.table[0];
514
515 if(quicktime_match_32(stsd->format, "text"))
516 {
517 *r = stsd->text.bgColor[0];
518 *g = stsd->text.bgColor[1];
519 *b = stsd->text.bgColor[2];
520
521 if(stsd->text.displayFlags & 0x4000)
522 *a = 0x0000;
523 else
524 *a = 0xFFFF;
525 }
526 else if(quicktime_match_32(stsd->format, "tx3g"))
527 {
528 *r = (stsd->tx3g.back_color[0] << 8) | stsd->tx3g.back_color[0];
529 *g = (stsd->tx3g.back_color[1] << 8) | stsd->tx3g.back_color[1];
530 *b = (stsd->tx3g.back_color[2] << 8) | stsd->tx3g.back_color[2];
531 *a = (stsd->tx3g.back_color[3] << 8) | stsd->tx3g.back_color[3];
532 }
533
534
535 }
536