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