]> git.sesse.net Git - ffmpeg/blob - libavcodec/movtextenc.c
lavc/movtextenc: simplify style record updates
[ffmpeg] / libavcodec / movtextenc.c
1 /*
2  * 3GPP TS 26.245 Timed Text encoder
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 <stdarg.h>
23 #include "avcodec.h"
24 #include "libavutil/avassert.h"
25 #include "libavutil/avstring.h"
26 #include "libavutil/intreadwrite.h"
27 #include "libavutil/mem.h"
28 #include "libavutil/common.h"
29 #include "ass_split.h"
30 #include "ass.h"
31
32 #define STYLE_FLAG_BOLD         (1<<0)
33 #define STYLE_FLAG_ITALIC       (1<<1)
34 #define STYLE_FLAG_UNDERLINE    (1<<2)
35 #define STYLE_RECORD_SIZE       12
36 #define SIZE_ADD                10
37
38 #define STYL_BOX   (1<<0)
39 #define HLIT_BOX   (1<<1)
40 #define HCLR_BOX   (1<<2)
41
42 #define BGR_TO_RGB(c) (((c) & 0xff) << 16 | ((c) & 0xff00) | (((c) >> 16) & 0xff))
43 #define av_bprint_append_any(buf, data, size)   av_bprint_append_data(buf, ((const char*)data), size)
44
45 typedef struct {
46     uint16_t style_start;
47     uint16_t style_end;
48     uint8_t style_flag;
49 } StyleBox;
50
51 typedef struct {
52     uint16_t start;
53     uint16_t end;
54 } HighlightBox;
55
56 typedef struct {
57    uint32_t color;
58 } HilightcolorBox;
59
60 typedef struct {
61     AVCodecContext *avctx;
62
63     ASSSplitContext *ass_ctx;
64     AVBPrint buffer;
65     StyleBox **style_attributes;
66     StyleBox *style_attributes_temp;
67     HighlightBox hlit;
68     HilightcolorBox hclr;
69     int count;
70     uint8_t box_flags;
71     uint16_t style_fontID;
72     uint8_t style_fontsize;
73     uint32_t style_color;
74     uint16_t text_pos;
75     uint16_t byte_count;
76 } MovTextContext;
77
78 typedef struct {
79     uint32_t type;
80     void (*encode)(MovTextContext *s, uint32_t tsmb_type);
81 } Box;
82
83 static void mov_text_cleanup(MovTextContext *s)
84 {
85     int j;
86     if (s->box_flags & STYL_BOX) {
87         for (j = 0; j < s->count; j++) {
88             av_freep(&s->style_attributes[j]);
89         }
90         av_freep(&s->style_attributes);
91     }
92     if (s->style_attributes_temp) {
93         s->style_attributes_temp->style_flag = 0;
94         s->style_attributes_temp->style_start = 0;
95     }
96 }
97
98 static void encode_styl(MovTextContext *s, uint32_t tsmb_type)
99 {
100     int j;
101     uint32_t tsmb_size;
102     uint16_t style_entries;
103     if ((s->box_flags & STYL_BOX) && s->count) {
104         tsmb_size = s->count * STYLE_RECORD_SIZE + SIZE_ADD;
105         tsmb_size = AV_RB32(&tsmb_size);
106         style_entries = AV_RB16(&s->count);
107         s->style_fontID = 0x00 | 0x01<<8;
108         s->style_fontsize = 0x12;
109         s->style_color = MKTAG(0xFF, 0xFF, 0xFF, 0xFF);
110         /*The above three attributes are hard coded for now
111         but will come from ASS style in the future*/
112         av_bprint_append_any(&s->buffer, &tsmb_size, 4);
113         av_bprint_append_any(&s->buffer, &tsmb_type, 4);
114         av_bprint_append_any(&s->buffer, &style_entries, 2);
115         for (j = 0; j < s->count; j++) {
116             uint16_t style_start, style_end;
117
118             style_start  = AV_RB16(&s->style_attributes[j]->style_start);
119             style_end    = AV_RB16(&s->style_attributes[j]->style_end);
120             av_bprint_append_any(&s->buffer, &style_start, 2);
121             av_bprint_append_any(&s->buffer, &style_end, 2);
122             av_bprint_append_any(&s->buffer, &s->style_fontID, 2);
123             av_bprint_append_any(&s->buffer, &s->style_attributes[j]->style_flag, 1);
124             av_bprint_append_any(&s->buffer, &s->style_fontsize, 1);
125             av_bprint_append_any(&s->buffer, &s->style_color, 4);
126         }
127     }
128     mov_text_cleanup(s);
129 }
130
131 static void encode_hlit(MovTextContext *s, uint32_t tsmb_type)
132 {
133     uint32_t tsmb_size;
134     uint16_t start, end;
135     if (s->box_flags & HLIT_BOX) {
136         tsmb_size = 12;
137         tsmb_size = AV_RB32(&tsmb_size);
138         start     = AV_RB16(&s->hlit.start);
139         end       = AV_RB16(&s->hlit.end);
140         av_bprint_append_any(&s->buffer, &tsmb_size, 4);
141         av_bprint_append_any(&s->buffer, &tsmb_type, 4);
142         av_bprint_append_any(&s->buffer, &start, 2);
143         av_bprint_append_any(&s->buffer, &end, 2);
144     }
145 }
146
147 static void encode_hclr(MovTextContext *s, uint32_t tsmb_type)
148 {
149     uint32_t tsmb_size, color;
150     if (s->box_flags & HCLR_BOX) {
151         tsmb_size = 12;
152         tsmb_size = AV_RB32(&tsmb_size);
153         color     = AV_RB32(&s->hclr.color);
154         av_bprint_append_any(&s->buffer, &tsmb_size, 4);
155         av_bprint_append_any(&s->buffer, &tsmb_type, 4);
156         av_bprint_append_any(&s->buffer, &color, 4);
157     }
158 }
159
160 static const Box box_types[] = {
161     { MKTAG('s','t','y','l'), encode_styl },
162     { MKTAG('h','l','i','t'), encode_hlit },
163     { MKTAG('h','c','l','r'), encode_hclr },
164 };
165
166 const static size_t box_count = FF_ARRAY_ELEMS(box_types);
167
168 static av_cold int mov_text_encode_init(AVCodecContext *avctx)
169 {
170     /*
171      * For now, we'll use a fixed default style. When we add styling
172      * support, this will be generated from the ASS style.
173      */
174     static const uint8_t text_sample_entry[] = {
175         0x00, 0x00, 0x00, 0x00, // uint32_t displayFlags
176         0x01,                   // int8_t horizontal-justification
177         0xFF,                   // int8_t vertical-justification
178         0x00, 0x00, 0x00, 0x00, // uint8_t background-color-rgba[4]
179         // BoxRecord {
180         0x00, 0x00,             // int16_t top
181         0x00, 0x00,             // int16_t left
182         0x00, 0x00,             // int16_t bottom
183         0x00, 0x00,             // int16_t right
184         // };
185         // StyleRecord {
186         0x00, 0x00,             // uint16_t startChar
187         0x00, 0x00,             // uint16_t endChar
188         0x00, 0x01,             // uint16_t font-ID
189         0x00,                   // uint8_t face-style-flags
190         0x12,                   // uint8_t font-size
191         0xFF, 0xFF, 0xFF, 0xFF, // uint8_t text-color-rgba[4]
192         // };
193         // FontTableBox {
194         0x00, 0x00, 0x00, 0x12, // uint32_t size
195         'f', 't', 'a', 'b',     // uint8_t name[4]
196         0x00, 0x01,             // uint16_t entry-count
197         // FontRecord {
198         0x00, 0x01,             // uint16_t font-ID
199         0x05,                   // uint8_t font-name-length
200         'S', 'e', 'r', 'i', 'f',// uint8_t font[font-name-length]
201         // };
202         // };
203     };
204
205     MovTextContext *s = avctx->priv_data;
206     s->avctx = avctx;
207
208     s->style_attributes_temp = av_mallocz(sizeof(*s->style_attributes_temp));
209     if (!s->style_attributes_temp) {
210         return AVERROR(ENOMEM);
211     }
212
213     avctx->extradata_size = sizeof text_sample_entry;
214     avctx->extradata = av_mallocz(avctx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
215     if (!avctx->extradata)
216         return AVERROR(ENOMEM);
217
218     av_bprint_init(&s->buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
219
220     memcpy(avctx->extradata, text_sample_entry, avctx->extradata_size);
221
222     s->ass_ctx = ff_ass_split(avctx->subtitle_header);
223     return s->ass_ctx ? 0 : AVERROR_INVALIDDATA;
224 }
225
226 // Start a new style box if needed
227 static int mov_text_style_start(MovTextContext *s)
228 {
229     // there's an existing style entry
230     if (s->style_attributes_temp->style_start == s->text_pos)
231         // Still at same text pos, use same entry
232         return 1;
233     if (s->style_attributes_temp->style_flag) {
234         // last style != defaults, end the style entry and start a new one
235         s->box_flags |= STYL_BOX;
236         s->style_attributes_temp->style_end = s->text_pos;
237         av_dynarray_add(&s->style_attributes, &s->count, s->style_attributes_temp);
238         s->style_attributes_temp = av_malloc(sizeof(*s->style_attributes_temp));
239         if (!s->style_attributes_temp) {
240             mov_text_cleanup(s);
241             av_bprint_clear(&s->buffer);
242             s->box_flags &= ~STYL_BOX;
243             return 0;
244         }
245
246         s->style_attributes_temp->style_flag = s->style_attributes[s->count - 1]->style_flag;
247         s->style_attributes_temp->style_start = s->text_pos;
248     } else { // style entry matches defaults, drop entry
249         s->style_attributes_temp->style_flag = 0;
250         s->style_attributes_temp->style_start = s->text_pos;
251     }
252     return 1;
253 }
254
255 static uint8_t mov_text_style_to_flag(const char style)
256 {
257     uint8_t style_flag = 0;
258
259     switch (style){
260     case 'b':
261         style_flag = STYLE_FLAG_BOLD;
262         break;
263     case 'i':
264         style_flag = STYLE_FLAG_ITALIC;
265         break;
266     case 'u':
267         style_flag = STYLE_FLAG_UNDERLINE;
268         break;
269     }
270     return style_flag;
271 }
272
273 static void mov_text_style_cb(void *priv, const char style, int close)
274 {
275     MovTextContext *s = priv;
276     uint8_t style_flag = mov_text_style_to_flag(style);
277
278     if (!s->style_attributes_temp ||
279         !!(s->style_attributes_temp->style_flag & style_flag) != close) {
280         // setting flag that is already set
281         return;
282     }
283     if (mov_text_style_start(s)) {
284         if (!close)
285             s->style_attributes_temp->style_flag |= style_flag;
286         else
287             s->style_attributes_temp->style_flag &= ~style_flag;
288     }
289 }
290
291 static void mov_text_color_cb(void *priv, unsigned int color, unsigned int color_id)
292 {
293     MovTextContext *s = priv;
294
295     color = BGR_TO_RGB(color) << 8;
296     if (color_id == 2) {    //secondary color changes
297         if (s->box_flags & HLIT_BOX) {  //close tag
298             s->hlit.end = s->text_pos;
299         } else {
300             s->box_flags |= HCLR_BOX;
301             s->box_flags |= HLIT_BOX;
302             s->hlit.start = s->text_pos;
303             s->hclr.color = color | 0xFF;  //set alpha value to FF
304         }
305     }
306     /* If there are more than one secondary color changes in ASS, take start of
307        first section and end of last section. Movtext allows only one
308        highlight box per sample.
309      */
310 }
311
312 static uint16_t utf8_strlen(const char *text, int len)
313 {
314     uint16_t i = 0, ret = 0;
315     while (i < len) {
316         char c = text[i];
317         if ((c & 0x80) == 0)
318             i += 1;
319         else if ((c & 0xE0) == 0xC0)
320             i += 2;
321         else if ((c & 0xF0) == 0xE0)
322             i += 3;
323         else if ((c & 0xF8) == 0xF0)
324             i += 4;
325         else
326             return 0;
327         ret++;
328     }
329     return ret;
330 }
331
332 static void mov_text_text_cb(void *priv, const char *text, int len)
333 {
334     uint16_t utf8_len = utf8_strlen(text, len);
335     MovTextContext *s = priv;
336     av_bprint_append_data(&s->buffer, text, len);
337     // If it's not utf-8, just use the byte length
338     s->text_pos += utf8_len ? utf8_len : len;
339     s->byte_count += len;
340 }
341
342 static void mov_text_new_line_cb(void *priv, int forced)
343 {
344     MovTextContext *s = priv;
345     av_bprint_append_data(&s->buffer, "\n", 1);
346     s->text_pos += 1;
347     s->byte_count += 1;
348 }
349
350 static const ASSCodesCallbacks mov_text_callbacks = {
351     .text     = mov_text_text_cb,
352     .new_line = mov_text_new_line_cb,
353     .style    = mov_text_style_cb,
354     .color    = mov_text_color_cb,
355 };
356
357 static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf,
358                                  int bufsize, const AVSubtitle *sub)
359 {
360     MovTextContext *s = avctx->priv_data;
361     ASSDialog *dialog;
362     int i, length;
363     size_t j;
364
365     s->byte_count = 0;
366     s->text_pos = 0;
367     s->count = 0;
368     s->box_flags = 0;
369     for (i = 0; i < sub->num_rects; i++) {
370         const char *ass = sub->rects[i]->ass;
371
372         if (sub->rects[i]->type != SUBTITLE_ASS) {
373             av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
374             return AVERROR(ENOSYS);
375         }
376
377 #if FF_API_ASS_TIMING
378         if (!strncmp(ass, "Dialogue: ", 10)) {
379             int num;
380             dialog = ff_ass_split_dialog(s->ass_ctx, ass, 0, &num);
381             for (; dialog && num--; dialog++) {
382                 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
383             }
384         } else {
385 #endif
386             dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
387             if (!dialog)
388                 return AVERROR(ENOMEM);
389             ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
390             ff_ass_free_dialog(&dialog);
391 #if FF_API_ASS_TIMING
392         }
393 #endif
394
395         for (j = 0; j < box_count; j++) {
396             box_types[j].encode(s, box_types[j].type);
397         }
398     }
399
400     AV_WB16(buf, s->byte_count);
401     buf += 2;
402
403     if (!av_bprint_is_complete(&s->buffer)) {
404         length = AVERROR(ENOMEM);
405         goto exit;
406     }
407
408     if (!s->buffer.len) {
409         length = 0;
410         goto exit;
411     }
412
413     if (s->buffer.len > bufsize - 3) {
414         av_log(avctx, AV_LOG_ERROR, "Buffer too small for ASS event.\n");
415         length = AVERROR(EINVAL);
416         goto exit;
417     }
418
419     memcpy(buf, s->buffer.str, s->buffer.len);
420     length = s->buffer.len + 2;
421
422 exit:
423     av_bprint_clear(&s->buffer);
424     return length;
425 }
426
427 static int mov_text_encode_close(AVCodecContext *avctx)
428 {
429     MovTextContext *s = avctx->priv_data;
430     ff_ass_split_free(s->ass_ctx);
431     av_bprint_finalize(&s->buffer, NULL);
432     return 0;
433 }
434
435 AVCodec ff_movtext_encoder = {
436     .name           = "mov_text",
437     .long_name      = NULL_IF_CONFIG_SMALL("3GPP Timed Text subtitle"),
438     .type           = AVMEDIA_TYPE_SUBTITLE,
439     .id             = AV_CODEC_ID_MOV_TEXT,
440     .priv_data_size = sizeof(MovTextContext),
441     .init           = mov_text_encode_init,
442     .encode_sub     = mov_text_encode_frame,
443     .close          = mov_text_encode_close,
444 };