]> git.sesse.net Git - ffmpeg/blob - libavcodec/movtextdec.c
avcodec/movtextdec: Skip empty styles
[ffmpeg] / libavcodec / movtextdec.c
1 /*
2  * 3GPP TS 26.245 Timed Text decoder
3  * Copyright (c) 2012  Philip Langdale <philipl@overt.org>
4  *
5  * This file is part of FFmpeg.
6  *
7  * FFmpeg is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * FFmpeg is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with FFmpeg; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20  */
21
22 #include "avcodec.h"
23 #include "ass.h"
24 #include "libavutil/opt.h"
25 #include "libavutil/avstring.h"
26 #include "libavutil/common.h"
27 #include "libavutil/bprint.h"
28 #include "libavutil/intreadwrite.h"
29 #include "libavutil/mem.h"
30
31 #define STYLE_FLAG_BOLD         (1<<0)
32 #define STYLE_FLAG_ITALIC       (1<<1)
33 #define STYLE_FLAG_UNDERLINE    (1<<2)
34
35 #define BOX_SIZE_INITIAL    40
36
37 #define STYL_BOX   (1<<0)
38 #define HLIT_BOX   (1<<1)
39 #define HCLR_BOX   (1<<2)
40 #define TWRP_BOX   (1<<3)
41
42 #define BOTTOM_LEFT     1
43 #define BOTTOM_CENTER   2
44 #define BOTTOM_RIGHT    3
45 #define MIDDLE_LEFT     4
46 #define MIDDLE_CENTER   5
47 #define MIDDLE_RIGHT    6
48 #define TOP_LEFT        7
49 #define TOP_CENTER      8
50 #define TOP_RIGHT       9
51
52 #define RGB_TO_BGR(c) (((c) & 0xff) << 16 | ((c) & 0xff00) | (((c) >> 16) & 0xff))
53
54 typedef struct {
55     uint16_t fontID;
56     const char *font;
57     uint8_t fontsize;
58     int color;
59     uint8_t alpha;
60     int back_color;
61     uint8_t back_alpha;
62     uint8_t bold;
63     uint8_t italic;
64     uint8_t underline;
65     int alignment;
66 } MovTextDefault;
67
68 typedef struct {
69     uint16_t fontID;
70     char *font;
71 } FontRecord;
72
73 typedef struct {
74     uint16_t style_start;
75     uint16_t style_end;
76     uint8_t style_flag;
77     uint8_t bold;
78     uint8_t italic;
79     uint8_t underline;
80     int color;
81     uint8_t alpha;
82     uint8_t fontsize;
83     uint16_t style_fontID;
84 } StyleBox;
85
86 typedef struct {
87     uint16_t hlit_start;
88     uint16_t hlit_end;
89 } HighlightBox;
90
91 typedef struct {
92    uint8_t hlit_color[4];
93 } HilightcolorBox;
94
95 typedef struct {
96     uint8_t wrap_flag;
97 } TextWrapBox;
98
99 typedef struct {
100     AVClass *class;
101     StyleBox *s;
102     HighlightBox h;
103     HilightcolorBox c;
104     FontRecord *ftab;
105     TextWrapBox w;
106     MovTextDefault d;
107     uint8_t box_flags;
108     uint16_t style_entries, ftab_entries;
109     uint64_t tracksize;
110     int size_var;
111     int readorder;
112     int frame_width;
113     int frame_height;
114 } MovTextContext;
115
116 typedef struct {
117     uint32_t type;
118     size_t base_size;
119     int (*decode)(const uint8_t *tsmb, MovTextContext *m, AVPacket *avpkt);
120 } Box;
121
122 static void mov_text_cleanup(MovTextContext *m)
123 {
124     if (m->box_flags & STYL_BOX) {
125         av_freep(&m->s);
126         m->style_entries = 0;
127     }
128 }
129
130 static void mov_text_cleanup_ftab(MovTextContext *m)
131 {
132     for (unsigned i = 0; i < m->ftab_entries; i++)
133         av_freep(&m->ftab[i].font);
134     av_freep(&m->ftab);
135     m->ftab_entries = 0;
136 }
137
138 static int mov_text_tx3g(AVCodecContext *avctx, MovTextContext *m)
139 {
140     uint8_t *tx3g_ptr = avctx->extradata;
141     int i, j = -1, font_length, remaining = avctx->extradata_size - BOX_SIZE_INITIAL;
142     int8_t v_align, h_align;
143     unsigned ftab_entries;
144     StyleBox s_default;
145
146     m->ftab_entries = 0;
147     if (remaining < 0)
148         return -1;
149
150     // Display Flags
151     tx3g_ptr += 4;
152     // Alignment
153     h_align = *tx3g_ptr++;
154     v_align = *tx3g_ptr++;
155     if (h_align == 0) {
156         if (v_align == 0)
157             m->d.alignment = TOP_LEFT;
158         if (v_align == 1)
159             m->d.alignment = MIDDLE_LEFT;
160         if (v_align == -1)
161             m->d.alignment = BOTTOM_LEFT;
162     }
163     if (h_align == 1) {
164         if (v_align == 0)
165             m->d.alignment = TOP_CENTER;
166         if (v_align == 1)
167             m->d.alignment = MIDDLE_CENTER;
168         if (v_align == -1)
169             m->d.alignment = BOTTOM_CENTER;
170     }
171     if (h_align == -1) {
172         if (v_align == 0)
173             m->d.alignment = TOP_RIGHT;
174         if (v_align == 1)
175             m->d.alignment = MIDDLE_RIGHT;
176         if (v_align == -1)
177             m->d.alignment = BOTTOM_RIGHT;
178     }
179     // Background Color
180     m->d.back_color = AV_RB24(tx3g_ptr);
181     tx3g_ptr += 3;
182     m->d.back_alpha = AV_RB8(tx3g_ptr);
183     tx3g_ptr += 1;
184     // BoxRecord
185     tx3g_ptr += 8;
186     // StyleRecord
187     tx3g_ptr += 4;
188     // fontID
189     m->d.fontID = AV_RB16(tx3g_ptr);
190     tx3g_ptr += 2;
191     // face-style-flags
192     s_default.style_flag = *tx3g_ptr++;
193     m->d.bold = !!(s_default.style_flag & STYLE_FLAG_BOLD);
194     m->d.italic = !!(s_default.style_flag & STYLE_FLAG_ITALIC);
195     m->d.underline = !!(s_default.style_flag & STYLE_FLAG_UNDERLINE);
196     // fontsize
197     m->d.fontsize = *tx3g_ptr++;
198     // Primary color
199     m->d.color = AV_RB24(tx3g_ptr);
200     tx3g_ptr += 3;
201     m->d.alpha = AV_RB8(tx3g_ptr);
202     tx3g_ptr += 1;
203     // FontRecord
204     // FontRecord Size
205     tx3g_ptr += 4;
206     // ftab
207     tx3g_ptr += 4;
208
209     // In case of broken header, init default font
210     m->d.font = ASS_DEFAULT_FONT;
211
212     ftab_entries = AV_RB16(tx3g_ptr);
213     if (!ftab_entries)
214         return 0;
215     remaining   -= 3 * ftab_entries;
216     if (remaining < 0)
217         return AVERROR_INVALIDDATA;
218     m->ftab = av_calloc(ftab_entries, sizeof(*m->ftab));
219     if (!m->ftab)
220         return AVERROR(ENOMEM);
221     m->ftab_entries = ftab_entries;
222     tx3g_ptr += 2;
223
224     for (i = 0; i < m->ftab_entries; i++) {
225         m->ftab[i].fontID = AV_RB16(tx3g_ptr);
226         if (m->ftab[i].fontID == m->d.fontID)
227             j = i;
228         tx3g_ptr += 2;
229         font_length = *tx3g_ptr++;
230
231         remaining  -= font_length;
232         if (remaining < 0) {
233             mov_text_cleanup_ftab(m);
234             return -1;
235         }
236         m->ftab[i].font = av_malloc(font_length + 1);
237         if (!m->ftab[i].font) {
238             mov_text_cleanup_ftab(m);
239             return AVERROR(ENOMEM);
240         }
241         memcpy(m->ftab[i].font, tx3g_ptr, font_length);
242         m->ftab[i].font[font_length] = '\0';
243         tx3g_ptr = tx3g_ptr + font_length;
244     }
245     if (j >= 0)
246         m->d.font = m->ftab[j].font;
247     return 0;
248 }
249
250 static int decode_twrp(const uint8_t *tsmb, MovTextContext *m, AVPacket *avpkt)
251 {
252     m->box_flags |= TWRP_BOX;
253     m->w.wrap_flag = *tsmb++;
254     return 0;
255 }
256
257 static int decode_hlit(const uint8_t *tsmb, MovTextContext *m, AVPacket *avpkt)
258 {
259     m->box_flags |= HLIT_BOX;
260     m->h.hlit_start = AV_RB16(tsmb);
261     tsmb += 2;
262     m->h.hlit_end = AV_RB16(tsmb);
263     tsmb += 2;
264     return 0;
265 }
266
267 static int decode_hclr(const uint8_t *tsmb, MovTextContext *m, AVPacket *avpkt)
268 {
269     m->box_flags |= HCLR_BOX;
270     memcpy(m->c.hlit_color, tsmb, 4);
271     tsmb += 4;
272     return 0;
273 }
274
275 static int decode_styl(const uint8_t *tsmb, MovTextContext *m, AVPacket *avpkt)
276 {
277     int i;
278     int style_entries = AV_RB16(tsmb);
279     StyleBox *tmp;
280     tsmb += 2;
281     // A single style record is of length 12 bytes.
282     if (m->tracksize + m->size_var + 2 + style_entries * 12 > avpkt->size)
283         return -1;
284
285     tmp = av_realloc_array(m->s, style_entries, sizeof(*m->s));
286     if (!tmp)
287         return AVERROR(ENOMEM);
288     m->s             = tmp;
289     m->style_entries = style_entries;
290
291     m->box_flags |= STYL_BOX;
292     for(i = 0; i < m->style_entries; i++) {
293         StyleBox *style = &m->s[i];
294         style->style_start = AV_RB16(tsmb);
295         tsmb += 2;
296         style->style_end = AV_RB16(tsmb);
297
298         if (   style->style_end < style->style_start
299             || (i && style->style_start < m->s[i - 1].style_end)) {
300             mov_text_cleanup(m);
301             return AVERROR(ENOMEM);
302         }
303
304         tsmb += 2;
305         if (style->style_start == style->style_end) {
306             /* Skip this style as it applies to no character */
307             tsmb += 8;
308             m->style_entries--;
309             i--;
310             continue;
311         }
312
313         style->style_fontID = AV_RB16(tsmb);
314         tsmb += 2;
315         style->style_flag = AV_RB8(tsmb);
316         style->bold      = !!(style->style_flag & STYLE_FLAG_BOLD);
317         style->italic    = !!(style->style_flag & STYLE_FLAG_ITALIC);
318         style->underline = !!(style->style_flag & STYLE_FLAG_UNDERLINE);
319         tsmb++;
320         style->fontsize = AV_RB8(tsmb);
321         tsmb++;
322         style->color = AV_RB24(tsmb);
323         tsmb += 3;
324         style->alpha = AV_RB8(tsmb);
325         tsmb++;
326     }
327     return 0;
328 }
329
330 static const Box box_types[] = {
331     { MKBETAG('s','t','y','l'), 2, decode_styl },
332     { MKBETAG('h','l','i','t'), 4, decode_hlit },
333     { MKBETAG('h','c','l','r'), 4, decode_hclr },
334     { MKBETAG('t','w','r','p'), 1, decode_twrp }
335 };
336
337 const static size_t box_count = FF_ARRAY_ELEMS(box_types);
338
339 // Return byte length of the UTF-8 sequence starting at text[0]. 0 on error.
340 static int get_utf8_length_at(const char *text, const char *text_end)
341 {
342     const char *start = text;
343     int err = 0;
344     uint32_t c;
345     GET_UTF8(c, text < text_end ? (uint8_t)*text++ : (err = 1, 0), goto error;);
346     if (err)
347         goto error;
348     return text - start;
349 error:
350     return 0;
351 }
352
353 static int text_to_ass(AVBPrint *buf, const char *text, const char *text_end,
354                        AVCodecContext *avctx)
355 {
356     MovTextContext *m = avctx->priv_data;
357     int i = 0;
358     int text_pos = 0;
359     int style_active = 0;
360     int entry = 0;
361     int color = m->d.color;
362
363     if (text < text_end && m->box_flags & TWRP_BOX) {
364         if (m->w.wrap_flag == 1) {
365             av_bprintf(buf, "{\\q1}"); /* End of line wrap */
366         } else {
367             av_bprintf(buf, "{\\q2}"); /* No wrap */
368         }
369     }
370
371     while (text < text_end) {
372         int len;
373
374         if ((m->box_flags & STYL_BOX) && entry < m->style_entries) {
375             const StyleBox *style = &m->s[entry];
376             if (text_pos == style->style_start) {
377                 style_active = 1;
378                 if (style->bold ^ m->d.bold)
379                     av_bprintf(buf, "{\\b%d}", style->bold);
380                 if (style->italic ^ m->d.italic)
381                     av_bprintf(buf, "{\\i%d}", style->italic);
382                 if (style->underline ^ m->d.underline)
383                     av_bprintf(buf, "{\\u%d}", style->underline);
384                 if (style->fontsize != m->d.fontsize)
385                     av_bprintf(buf, "{\\fs%d}", style->fontsize);
386                 if (style->style_fontID != m->d.fontID)
387                     for (i = 0; i < m->ftab_entries; i++) {
388                         if (style->style_fontID == m->ftab[i].fontID)
389                             av_bprintf(buf, "{\\fn%s}", m->ftab[i].font);
390                     }
391                 if (m->d.color != style->color) {
392                     color = style->color;
393                     av_bprintf(buf, "{\\1c&H%X&}", RGB_TO_BGR(color));
394                 }
395                 if (m->d.alpha != style->alpha)
396                     av_bprintf(buf, "{\\1a&H%02X&}", 255 - style->alpha);
397             }
398             if (text_pos == style->style_end) {
399                 if (style_active) {
400                     av_bprintf(buf, "{\\r}");
401                     style_active = 0;
402                     color = m->d.color;
403                 }
404                 entry++;
405             }
406         }
407         if (m->box_flags & HLIT_BOX) {
408             if (text_pos == m->h.hlit_start) {
409                 /* If hclr box is present, set the secondary color to the color
410                  * specified. Otherwise, set primary color to white and secondary
411                  * color to black. These colors will come from TextSampleModifier
412                  * boxes in future and inverse video technique for highlight will
413                  * be implemented.
414                  */
415                 if (m->box_flags & HCLR_BOX) {
416                     av_bprintf(buf, "{\\2c&H%02x%02x%02x&}", m->c.hlit_color[2],
417                                 m->c.hlit_color[1], m->c.hlit_color[0]);
418                 } else {
419                     av_bprintf(buf, "{\\1c&H000000&}{\\2c&HFFFFFF&}");
420                 }
421             }
422             if (text_pos == m->h.hlit_end) {
423                 if (m->box_flags & HCLR_BOX) {
424                     av_bprintf(buf, "{\\2c&H%X&}", RGB_TO_BGR(m->d.color));
425                 } else {
426                     av_bprintf(buf, "{\\1c&H%X&}{\\2c&H%X&}",
427                                RGB_TO_BGR(color), RGB_TO_BGR(m->d.color));
428                 }
429             }
430         }
431
432         len = get_utf8_length_at(text, text_end);
433         if (len < 1) {
434             av_log(avctx, AV_LOG_ERROR, "invalid UTF-8 byte in subtitle\n");
435             len = 1;
436         }
437         for (i = 0; i < len; i++) {
438             switch (*text) {
439             case '\r':
440                 break;
441             case '\n':
442                 av_bprintf(buf, "\\N");
443                 break;
444             default:
445                 av_bprint_chars(buf, *text, 1);
446                 break;
447             }
448             text++;
449         }
450         text_pos++;
451     }
452
453     return 0;
454 }
455
456 static int mov_text_init(AVCodecContext *avctx) {
457     /*
458      * TODO: Handle the default text style.
459      * NB: Most players ignore styles completely, with the result that
460      * it's very common to find files where the default style is broken
461      * and respecting it results in a worse experience than ignoring it.
462      */
463     int ret;
464     MovTextContext *m = avctx->priv_data;
465     ret = mov_text_tx3g(avctx, m);
466     if (ret == 0) {
467         if (!m->frame_width || !m->frame_height) {
468             m->frame_width = ASS_DEFAULT_PLAYRESX;
469             m->frame_height = ASS_DEFAULT_PLAYRESY;
470         }
471         return ff_ass_subtitle_header_full(avctx,
472                     m->frame_width, m->frame_height,
473                     m->d.font, m->d.fontsize,
474                     (255U - m->d.alpha) << 24 | RGB_TO_BGR(m->d.color),
475                     (255U - m->d.alpha) << 24 | RGB_TO_BGR(m->d.color),
476                     (255U - m->d.back_alpha) << 24 | RGB_TO_BGR(m->d.back_color),
477                     (255U - m->d.back_alpha) << 24 | RGB_TO_BGR(m->d.back_color),
478                     m->d.bold, m->d.italic, m->d.underline,
479                     ASS_DEFAULT_BORDERSTYLE, m->d.alignment);
480     } else
481         return ff_ass_subtitle_header_default(avctx);
482 }
483
484 static int mov_text_decode_frame(AVCodecContext *avctx,
485                             void *data, int *got_sub_ptr, AVPacket *avpkt)
486 {
487     AVSubtitle *sub = data;
488     MovTextContext *m = avctx->priv_data;
489     int ret;
490     AVBPrint buf;
491     char *ptr = avpkt->data;
492     char *end;
493     int text_length, tsmb_type, ret_tsmb;
494     uint64_t tsmb_size;
495     const uint8_t *tsmb;
496     size_t i;
497
498     if (!ptr || avpkt->size < 2)
499         return AVERROR_INVALIDDATA;
500
501     /*
502      * A packet of size two with value zero is an empty subtitle
503      * used to mark the end of the previous non-empty subtitle.
504      * We can just drop them here as we have duration information
505      * already. If the value is non-zero, then it's technically a
506      * bad packet.
507      */
508     if (avpkt->size == 2)
509         return AV_RB16(ptr) == 0 ? 0 : AVERROR_INVALIDDATA;
510
511     /*
512      * The first two bytes of the packet are the length of the text string
513      * In complex cases, there are style descriptors appended to the string
514      * so we can't just assume the packet size is the string size.
515      */
516     text_length = AV_RB16(ptr);
517     end = ptr + FFMIN(2 + text_length, avpkt->size);
518     ptr += 2;
519
520     mov_text_cleanup(m);
521
522     tsmb_size = 0;
523     m->tracksize = 2 + text_length;
524     m->style_entries = 0;
525     m->box_flags = 0;
526     // Note that the spec recommends lines be no longer than 2048 characters.
527     av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
528     if (text_length + 2 != avpkt->size) {
529         while (m->tracksize + 8 <= avpkt->size) {
530             // A box is a minimum of 8 bytes.
531             tsmb = ptr + m->tracksize - 2;
532             tsmb_size = AV_RB32(tsmb);
533             tsmb += 4;
534             tsmb_type = AV_RB32(tsmb);
535             tsmb += 4;
536
537             if (tsmb_size == 1) {
538                 if (m->tracksize + 16 > avpkt->size)
539                     break;
540                 tsmb_size = AV_RB64(tsmb);
541                 tsmb += 8;
542                 m->size_var = 16;
543             } else
544                 m->size_var = 8;
545             //size_var is equal to 8 or 16 depending on the size of box
546
547             if (tsmb_size == 0) {
548                 av_log(avctx, AV_LOG_ERROR, "tsmb_size is 0\n");
549                 return AVERROR_INVALIDDATA;
550             }
551
552             if (tsmb_size > avpkt->size - m->tracksize)
553                 break;
554
555             for (i = 0; i < box_count; i++) {
556                 if (tsmb_type == box_types[i].type) {
557                     if (m->tracksize + m->size_var + box_types[i].base_size > avpkt->size)
558                         break;
559                     ret_tsmb = box_types[i].decode(tsmb, m, avpkt);
560                     if (ret_tsmb == -1)
561                         break;
562                 }
563             }
564             m->tracksize = m->tracksize + tsmb_size;
565         }
566         text_to_ass(&buf, ptr, end, avctx);
567         mov_text_cleanup(m);
568     } else
569         text_to_ass(&buf, ptr, end, avctx);
570
571     ret = ff_ass_add_rect(sub, buf.str, m->readorder++, 0, NULL, NULL);
572     av_bprint_finalize(&buf, NULL);
573     if (ret < 0)
574         return ret;
575     *got_sub_ptr = sub->num_rects > 0;
576     return avpkt->size;
577 }
578
579 static int mov_text_decode_close(AVCodecContext *avctx)
580 {
581     MovTextContext *m = avctx->priv_data;
582     mov_text_cleanup_ftab(m);
583     mov_text_cleanup(m);
584     return 0;
585 }
586
587 static void mov_text_flush(AVCodecContext *avctx)
588 {
589     MovTextContext *m = avctx->priv_data;
590     if (!(avctx->flags2 & AV_CODEC_FLAG2_RO_FLUSH_NOOP))
591         m->readorder = 0;
592 }
593
594 #define OFFSET(x) offsetof(MovTextContext, x)
595 #define FLAGS AV_OPT_FLAG_DECODING_PARAM | AV_OPT_FLAG_SUBTITLE_PARAM
596 static const AVOption options[] = {
597     { "width", "Frame width, usually video width", OFFSET(frame_width), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS },
598     { "height", "Frame height, usually video height", OFFSET(frame_height), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS },
599     { NULL },
600 };
601
602 static const AVClass mov_text_decoder_class = {
603     .class_name = "MOV text decoder",
604     .item_name  = av_default_item_name,
605     .option     = options,
606     .version    = LIBAVUTIL_VERSION_INT,
607 };
608
609 AVCodec ff_movtext_decoder = {
610     .name         = "mov_text",
611     .long_name    = NULL_IF_CONFIG_SMALL("3GPP Timed Text subtitle"),
612     .type         = AVMEDIA_TYPE_SUBTITLE,
613     .id           = AV_CODEC_ID_MOV_TEXT,
614     .priv_data_size = sizeof(MovTextContext),
615     .priv_class   = &mov_text_decoder_class,
616     .init         = mov_text_init,
617     .decode       = mov_text_decode_frame,
618     .close        = mov_text_decode_close,
619     .flush        = mov_text_flush,
620 };