FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavcodec/movtextenc.c
Date: 2024-04-19 17:50:32
Exec Total Coverage
Lines: 201 339 59.3%
Functions: 21 28 75.0%
Branches: 62 158 39.2%

Line Branch Exec Source
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/opt.h"
25 #include "libavutil/intreadwrite.h"
26 #include "libavutil/mem.h"
27 #include "libavutil/common.h"
28 #include "ass_split.h"
29 #include "ass.h"
30 #include "bytestream.h"
31 #include "codec_internal.h"
32
33 #define STYLE_FLAG_BOLD (1<<0)
34 #define STYLE_FLAG_ITALIC (1<<1)
35 #define STYLE_FLAG_UNDERLINE (1<<2)
36 #define STYLE_RECORD_SIZE 12
37 #define SIZE_ADD 10
38
39 #define STYL_BOX (1<<0)
40 #define HLIT_BOX (1<<1)
41 #define HCLR_BOX (1<<2)
42
43 #define DEFAULT_STYLE_FONT_ID 0x01
44 #define DEFAULT_STYLE_FONTSIZE 0x12
45 #define DEFAULT_STYLE_COLOR 0xffffffff
46 #define DEFAULT_STYLE_FLAG 0x00
47
48 #define BGR_TO_RGB(c) (((c) & 0xff) << 16 | ((c) & 0xff00) | (((uint32_t)(c) >> 16) & 0xff))
49 #define FONTSIZE_SCALE(s,fs) ((fs) * (s)->font_scale_factor + 0.5)
50 #define av_bprint_append_any(buf, data, size) av_bprint_append_data(buf, ((const char*)data), size)
51
52 typedef struct {
53 uint16_t style_start;
54 uint16_t style_end;
55 uint8_t style_flag;
56 uint16_t style_fontID;
57 uint8_t style_fontsize;
58 uint32_t style_color;
59 } StyleBox;
60
61 typedef struct {
62 uint16_t start;
63 uint16_t end;
64 } HighlightBox;
65
66 typedef struct {
67 uint32_t color;
68 } HilightcolorBox;
69
70 typedef struct {
71 AVClass *class;
72 AVCodecContext *avctx;
73
74 ASSSplitContext *ass_ctx;
75 ASSStyle *ass_dialog_style;
76 StyleBox *style_attributes;
77 unsigned count;
78 unsigned style_attributes_bytes_allocated;
79 StyleBox style_attributes_temp;
80 AVBPrint buffer;
81 HighlightBox hlit;
82 HilightcolorBox hclr;
83 uint8_t box_flags;
84 StyleBox d;
85 uint16_t text_pos;
86 char **fonts;
87 int font_count;
88 double font_scale_factor;
89 int frame_height;
90 } MovTextContext;
91
92 typedef struct {
93 void (*encode)(MovTextContext *s);
94 } Box;
95
96 3 static void mov_text_cleanup(MovTextContext *s)
97 {
98 3 s->count = 0;
99 3 s->style_attributes_temp = s->d;
100 3 }
101
102 3 static void encode_styl(MovTextContext *s)
103 {
104
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3 if ((s->box_flags & STYL_BOX) && s->count) {
105 uint8_t buf[12], *p = buf;
106
107 bytestream_put_be32(&p, s->count * STYLE_RECORD_SIZE + SIZE_ADD);
108 bytestream_put_be32(&p, MKBETAG('s','t','y','l'));
109 bytestream_put_be16(&p, s->count);
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, buf, 10);
113 for (unsigned j = 0; j < s->count; j++) {
114 const StyleBox *style = &s->style_attributes[j];
115
116 p = buf;
117 bytestream_put_be16(&p, style->style_start);
118 bytestream_put_be16(&p, style->style_end);
119 bytestream_put_be16(&p, style->style_fontID);
120 bytestream_put_byte(&p, style->style_flag);
121 bytestream_put_byte(&p, style->style_fontsize);
122 bytestream_put_be32(&p, style->style_color);
123
124 av_bprint_append_any(&s->buffer, buf, 12);
125 }
126 }
127 3 mov_text_cleanup(s);
128 3 }
129
130 3 static void encode_hlit(MovTextContext *s)
131 {
132
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (s->box_flags & HLIT_BOX) {
133 uint8_t buf[12], *p = buf;
134
135 bytestream_put_be32(&p, 12);
136 bytestream_put_be32(&p, MKBETAG('h','l','i','t'));
137 bytestream_put_be16(&p, s->hlit.start);
138 bytestream_put_be16(&p, s->hlit.end);
139
140 av_bprint_append_any(&s->buffer, buf, 12);
141 }
142 3 }
143
144 3 static void encode_hclr(MovTextContext *s)
145 {
146
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (s->box_flags & HCLR_BOX) {
147 uint8_t buf[12], *p = buf;
148
149 bytestream_put_be32(&p, 12);
150 bytestream_put_be32(&p, MKBETAG('h','c','l','r'));
151 bytestream_put_be32(&p, s->hclr.color);
152
153 av_bprint_append_any(&s->buffer, buf, 12);
154 }
155 3 }
156
157 static const Box box_types[] = {
158 { encode_styl },
159 { encode_hlit },
160 { encode_hclr },
161 };
162
163 const static size_t box_count = FF_ARRAY_ELEMS(box_types);
164
165 1 static int mov_text_encode_close(AVCodecContext *avctx)
166 {
167 1 MovTextContext *s = avctx->priv_data;
168
169 1 ff_ass_split_free(s->ass_ctx);
170 1 av_freep(&s->style_attributes);
171 1 av_freep(&s->fonts);
172 1 return 0;
173 }
174
175 1 static int encode_sample_description(AVCodecContext *avctx)
176 {
177 ASS *ass;
178 ASSStyle *style;
179 int i, j;
180 1 uint32_t back_color = 0;
181 1 int font_names_total_len = 0;
182 1 MovTextContext *s = avctx->priv_data;
183 1 uint8_t buf[30], *p = buf;
184 int ret;
185
186 1 av_bprint_init(&s->buffer, 0, INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE + 1);
187
188 // 0x00, 0x00, 0x00, 0x00, // uint32_t displayFlags
189 // 0x01, // int8_t horizontal-justification
190 // 0xFF, // int8_t vertical-justification
191 // 0x00, 0x00, 0x00, 0x00, // uint8_t background-color-rgba[4]
192 // BoxRecord {
193 // 0x00, 0x00, // int16_t top
194 // 0x00, 0x00, // int16_t left
195 // 0x00, 0x00, // int16_t bottom
196 // 0x00, 0x00, // int16_t right
197 // };
198 // StyleRecord {
199 // 0x00, 0x00, // uint16_t startChar
200 // 0x00, 0x00, // uint16_t endChar
201 // 0x00, 0x01, // uint16_t font-ID
202 // 0x00, // uint8_t face-style-flags
203 // 0x12, // uint8_t font-size
204 // 0xFF, 0xFF, 0xFF, 0xFF, // uint8_t text-color-rgba[4]
205 // };
206 // FontTableBox {
207 // 0x00, 0x00, 0x00, 0x12, // uint32_t size
208 // 'f', 't', 'a', 'b', // uint8_t name[4]
209 // 0x00, 0x01, // uint16_t entry-count
210 // FontRecord {
211 // 0x00, 0x01, // uint16_t font-ID
212 // 0x05, // uint8_t font-name-length
213 // 'S', 'e', 'r', 'i', 'f',// uint8_t font[font-name-length]
214 // };
215 // };
216
217 // Populate sample description from ASS header
218 1 ass = (ASS*)s->ass_ctx;
219 // Compute font scaling factor based on (optionally) provided
220 // output video height and ASS script play_res_y
221
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (s->frame_height && ass->script_info.play_res_y)
222 s->font_scale_factor = (double)s->frame_height / ass->script_info.play_res_y;
223 else
224 1 s->font_scale_factor = 1;
225
226 1 style = ff_ass_style_get(s->ass_ctx, "Default");
227
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (!style && ass->styles_count) {
228 style = &ass->styles[0];
229 }
230 1 s->d.style_fontID = DEFAULT_STYLE_FONT_ID;
231 1 s->d.style_fontsize = DEFAULT_STYLE_FONTSIZE;
232 1 s->d.style_color = DEFAULT_STYLE_COLOR;
233 1 s->d.style_flag = DEFAULT_STYLE_FLAG;
234
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (style) {
235 1 s->d.style_fontsize = FONTSIZE_SCALE(s, style->font_size);
236 1 s->d.style_color = BGR_TO_RGB(style->primary_color & 0xffffff) << 8 |
237 1 255 - ((uint32_t)style->primary_color >> 24);
238 1 s->d.style_flag = (!!style->bold * STYLE_FLAG_BOLD) |
239
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
2 (!!style->italic * STYLE_FLAG_ITALIC) |
240
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 (!!style->underline * STYLE_FLAG_UNDERLINE);
241 1 back_color = (BGR_TO_RGB(style->back_color & 0xffffff) << 8) |
242 1 (255 - ((uint32_t)style->back_color >> 24));
243 }
244
245 1 bytestream_put_be32(&p, 0); // displayFlags
246 1 bytestream_put_be16(&p, 0x01FF); // horizontal/vertical justification (2x int8_t)
247 1 bytestream_put_be32(&p, back_color);
248 1 bytestream_put_be64(&p, 0); // BoxRecord - 4xint16_t: top, left, bottom, right
249 // StyleRecord {
250 1 bytestream_put_be16(&p, s->d.style_start);
251 1 bytestream_put_be16(&p, s->d.style_end);
252 1 bytestream_put_be16(&p, s->d.style_fontID);
253 1 bytestream_put_byte(&p, s->d.style_flag);
254 1 bytestream_put_byte(&p, s->d.style_fontsize);
255 1 bytestream_put_be32(&p, s->d.style_color);
256 // };
257 1 av_bprint_append_any(&s->buffer, buf, 30);
258
259 // Build font table
260 // We can't build a complete font table since that would require
261 // scanning all dialogs first. But we can at least fill in what
262 // is avaiable in the ASS header
263
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if (style && ass->styles_count) {
264 // Find unique font names
265
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (style->font_name) {
266 1 av_dynarray_add(&s->fonts, &s->font_count, style->font_name);
267 1 font_names_total_len += strlen(style->font_name);
268 }
269
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (i = 0; i < ass->styles_count; i++) {
270 1 int found = 0;
271
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!ass->styles[i].font_name)
272 continue;
273
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 for (j = 0; j < s->font_count; j++) {
274
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!strcmp(s->fonts[j], ass->styles[i].font_name)) {
275 1 found = 1;
276 1 break;
277 }
278 }
279
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!found) {
280 av_dynarray_add(&s->fonts, &s->font_count,
281 ass->styles[i].font_name);
282 font_names_total_len += strlen(ass->styles[i].font_name);
283 }
284 }
285 } else
286 av_dynarray_add(&s->fonts, &s->font_count, (char*)"Serif");
287
288 // FontTableBox {
289 1 p = buf;
290 1 bytestream_put_be32(&p, SIZE_ADD + 3 * s->font_count + font_names_total_len); // Size
291 1 bytestream_put_be32(&p, MKBETAG('f','t','a','b'));
292 1 bytestream_put_be16(&p, s->font_count);
293
294 1 av_bprint_append_any(&s->buffer, buf, 10);
295 // FontRecord {
296
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (i = 0; i < s->font_count; i++) {
297 1 size_t len = strlen(s->fonts[i]);
298
299 1 p = buf;
300 1 bytestream_put_be16(&p, i + 1); //fontID
301 1 bytestream_put_byte(&p, len);
302
303 1 av_bprint_append_any(&s->buffer, buf, 3);
304 1 av_bprint_append_any(&s->buffer, s->fonts[i], len);
305 }
306 // };
307 // };
308
309
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if (!av_bprint_is_complete(&s->buffer)) {
310 ret = AVERROR(ENOMEM);
311 goto fail;
312 }
313
314 1 avctx->extradata_size = s->buffer.len;
315 1 avctx->extradata = av_mallocz(avctx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
316
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!avctx->extradata) {
317 ret = AVERROR(ENOMEM);
318 goto fail;
319 }
320
321 1 memcpy(avctx->extradata, s->buffer.str, avctx->extradata_size);
322 1 ret = 0;
323 1 fail:
324 1 av_bprint_finalize(&s->buffer, NULL);
325
326 1 return ret;
327 }
328
329 1 static av_cold int mov_text_encode_init(AVCodecContext *avctx)
330 {
331 int ret;
332 1 MovTextContext *s = avctx->priv_data;
333 1 s->avctx = avctx;
334
335 1 s->ass_ctx = ff_ass_split(avctx->subtitle_header);
336
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!s->ass_ctx)
337 return AVERROR_INVALIDDATA;
338 1 ret = encode_sample_description(avctx);
339
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ret < 0)
340 return ret;
341
342 1 return 0;
343 }
344
345 // Start a new style box if needed
346 7 static int mov_text_style_start(MovTextContext *s)
347 {
348 // there's an existing style entry
349
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3 times.
7 if (s->style_attributes_temp.style_start == s->text_pos)
350 // Still at same text pos, use same entry
351 4 return 1;
352
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (s->style_attributes_temp.style_flag != s->d.style_flag ||
353
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 s->style_attributes_temp.style_color != s->d.style_color ||
354
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 s->style_attributes_temp.style_fontID != s->d.style_fontID ||
355
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 s->style_attributes_temp.style_fontsize != s->d.style_fontsize) {
356 StyleBox *tmp;
357
358 // last style != defaults, end the style entry and start a new one
359 if (s->count + 1 > FFMIN(SIZE_MAX / sizeof(*s->style_attributes), UINT16_MAX) ||
360 !(tmp = av_fast_realloc(s->style_attributes,
361 &s->style_attributes_bytes_allocated,
362 (s->count + 1) * sizeof(*s->style_attributes)))) {
363 mov_text_cleanup(s);
364 av_bprint_clear(&s->buffer);
365 s->box_flags &= ~STYL_BOX;
366 return 0;
367 }
368 s->style_attributes = tmp;
369 s->style_attributes_temp.style_end = s->text_pos;
370 s->style_attributes[s->count++] = s->style_attributes_temp;
371 s->box_flags |= STYL_BOX;
372 s->style_attributes_temp = s->d;
373 s->style_attributes_temp.style_start = s->text_pos;
374 } else { // style entry matches defaults, drop entry
375 3 s->style_attributes_temp = s->d;
376 3 s->style_attributes_temp.style_start = s->text_pos;
377 }
378 3 return 1;
379 }
380
381 static uint8_t mov_text_style_to_flag(const char style)
382 {
383 uint8_t style_flag = 0;
384
385 switch (style){
386 case 'b':
387 style_flag = STYLE_FLAG_BOLD;
388 break;
389 case 'i':
390 style_flag = STYLE_FLAG_ITALIC;
391 break;
392 case 'u':
393 style_flag = STYLE_FLAG_UNDERLINE;
394 break;
395 }
396 return style_flag;
397 }
398
399 3 static void mov_text_style_set(MovTextContext *s, uint8_t style_flags)
400 {
401
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (!((s->style_attributes_temp.style_flag & style_flags) ^ style_flags)) {
402 // setting flags that that are already set
403 3 return;
404 }
405 if (mov_text_style_start(s))
406 s->style_attributes_temp.style_flag |= style_flags;
407 }
408
409 static void mov_text_style_cb(void *priv, const char style, int close)
410 {
411 MovTextContext *s = priv;
412 uint8_t style_flag = mov_text_style_to_flag(style);
413
414 if (!!(s->style_attributes_temp.style_flag & style_flag) != close) {
415 // setting flag that is already set
416 return;
417 }
418 if (mov_text_style_start(s)) {
419 if (!close)
420 s->style_attributes_temp.style_flag |= style_flag;
421 else
422 s->style_attributes_temp.style_flag &= ~style_flag;
423 }
424 }
425
426 3 static void mov_text_color_set(MovTextContext *s, uint32_t color)
427 {
428
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if ((s->style_attributes_temp.style_color & 0xffffff00) == color) {
429 // color hasn't changed
430 2 return;
431 }
432
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 if (mov_text_style_start(s))
433 1 s->style_attributes_temp.style_color = (color & 0xffffff00) |
434 1 (s->style_attributes_temp.style_color & 0xff);
435 }
436
437 static void mov_text_color_cb(void *priv, unsigned int color, unsigned int color_id)
438 {
439 MovTextContext *s = priv;
440
441 color = BGR_TO_RGB(color) << 8;
442 if (color_id == 1) { //primary color changes
443 mov_text_color_set(s, color);
444 } else if (color_id == 2) { //secondary color changes
445 if (!(s->box_flags & HCLR_BOX))
446 // Highlight alpha not set yet, use current primary alpha
447 s->hclr.color = s->style_attributes_temp.style_color;
448 if (!(s->box_flags & HLIT_BOX) || s->hlit.start == s->text_pos) {
449 s->box_flags |= HCLR_BOX;
450 s->box_flags |= HLIT_BOX;
451 s->hlit.start = s->text_pos;
452 s->hclr.color = color | (s->hclr.color & 0xFF);
453 }
454 else //close tag
455 s->hlit.end = s->text_pos;
456 /* If there are more than one secondary color changes in ASS,
457 take start of first section and end of last section. Movtext
458 allows only one highlight box per sample.
459 */
460 }
461 // Movtext does not support changes to other color_id (outline, background)
462 }
463
464 3 static void mov_text_alpha_set(MovTextContext *s, uint8_t alpha)
465 {
466
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if ((s->style_attributes_temp.style_color & 0xff) == alpha) {
467 // color hasn't changed
468 2 return;
469 }
470
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 if (mov_text_style_start(s))
471 1 s->style_attributes_temp.style_color =
472 1 (s->style_attributes_temp.style_color & 0xffffff00) | alpha;
473 }
474
475 static void mov_text_alpha_cb(void *priv, int alpha, int alpha_id)
476 {
477 MovTextContext *s = priv;
478
479 alpha = 255 - alpha;
480 if (alpha_id == 1) // primary alpha changes
481 mov_text_alpha_set(s, alpha);
482 else if (alpha_id == 2) { //secondary alpha changes
483 if (!(s->box_flags & HCLR_BOX))
484 // Highlight color not set yet, use current primary color
485 s->hclr.color = s->style_attributes_temp.style_color;
486 if (!(s->box_flags & HLIT_BOX) || s->hlit.start == s->text_pos) {
487 s->box_flags |= HCLR_BOX;
488 s->box_flags |= HLIT_BOX;
489 s->hlit.start = s->text_pos;
490 s->hclr.color = (s->hclr.color & 0xffffff00) | alpha;
491 }
492 else //close tag
493 s->hlit.end = s->text_pos;
494 }
495 // Movtext does not support changes to other alpha_id (outline, background)
496 }
497
498 3 static uint16_t find_font_id(MovTextContext *s, const char *name)
499 {
500
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!name)
501 return 1;
502
503
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 for (int i = 0; i < s->font_count; i++) {
504
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (!strcmp(name, s->fonts[i]))
505 3 return i + 1;
506 }
507 return 1;
508 }
509
510 3 static void mov_text_font_name_set(MovTextContext *s, const char *name)
511 {
512 3 int fontID = find_font_id(s, name);
513
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if (s->style_attributes_temp.style_fontID == fontID) {
514 // color hasn't changed
515 2 return;
516 }
517
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 if (mov_text_style_start(s))
518 1 s->style_attributes_temp.style_fontID = fontID;
519 }
520
521 static void mov_text_font_name_cb(void *priv, const char *name)
522 {
523 mov_text_font_name_set((MovTextContext*)priv, name);
524 }
525
526 3 static void mov_text_font_size_set(MovTextContext *s, int size)
527 {
528 3 size = FONTSIZE_SCALE(s, size);
529
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if (s->style_attributes_temp.style_fontsize == size) {
530 // color hasn't changed
531 2 return;
532 }
533
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 if (mov_text_style_start(s))
534 1 s->style_attributes_temp.style_fontsize = size;
535 }
536
537 static void mov_text_font_size_cb(void *priv, int size)
538 {
539 mov_text_font_size_set((MovTextContext*)priv, size);
540 }
541
542 3 static void mov_text_end_cb(void *priv)
543 {
544 // End of text, close any open style record
545 3 mov_text_style_start((MovTextContext*)priv);
546 3 }
547
548 3 static void mov_text_ass_style_set(MovTextContext *s, ASSStyle *style)
549 {
550 uint8_t style_flags, alpha;
551 uint32_t color;
552
553
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (style) {
554 3 style_flags = (!!style->bold * STYLE_FLAG_BOLD) |
555
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
6 (!!style->italic * STYLE_FLAG_ITALIC) |
556
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 (!!style->underline * STYLE_FLAG_UNDERLINE);
557 3 mov_text_style_set(s, style_flags);
558 3 color = BGR_TO_RGB(style->primary_color & 0xffffff) << 8;
559 3 mov_text_color_set(s, color);
560 3 alpha = 255 - ((uint32_t)style->primary_color >> 24);
561 3 mov_text_alpha_set(s, alpha);
562 3 mov_text_font_size_set(s, style->font_size);
563 3 mov_text_font_name_set(s, style->font_name);
564 } else {
565 // End current style record, go back to defaults
566 mov_text_style_start(s);
567 }
568 3 }
569
570 3 static void mov_text_dialog(MovTextContext *s, ASSDialog *dialog)
571 {
572 3 ASSStyle *style = ff_ass_style_get(s->ass_ctx, dialog->style);
573
574 3 s->ass_dialog_style = style;
575 3 mov_text_ass_style_set(s, style);
576 3 }
577
578 static void mov_text_cancel_overrides_cb(void *priv, const char *style_name)
579 {
580 MovTextContext *s = priv;
581 ASSStyle *style;
582
583 if (!style_name || !*style_name)
584 style = s->ass_dialog_style;
585 else
586 style= ff_ass_style_get(s->ass_ctx, style_name);
587
588 mov_text_ass_style_set(s, style);
589 }
590
591 5 static unsigned utf8_strlen(const char *text, int len)
592 {
593 5 unsigned i = 0, ret = 0;
594
2/2
✓ Branch 0 taken 43 times.
✓ Branch 1 taken 5 times.
48 while (i < len) {
595 43 char c = text[i];
596
1/2
✓ Branch 0 taken 43 times.
✗ Branch 1 not taken.
43 if ((c & 0x80) == 0)
597 43 i += 1;
598 else if ((c & 0xE0) == 0xC0)
599 i += 2;
600 else if ((c & 0xF0) == 0xE0)
601 i += 3;
602 else if ((c & 0xF8) == 0xF0)
603 i += 4;
604 else
605 return 0;
606 43 ret++;
607 }
608 5 return ret;
609 }
610
611 5 static void mov_text_text_cb(void *priv, const char *text, int len)
612 {
613 5 unsigned utf8_len = utf8_strlen(text, len);
614 5 MovTextContext *s = priv;
615 5 av_bprint_append_data(&s->buffer, text, len);
616 // If it's not utf-8, just use the byte length
617
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 s->text_pos += utf8_len ? utf8_len : len;
618 5 }
619
620 2 static void mov_text_new_line_cb(void *priv, int forced)
621 {
622 2 MovTextContext *s = priv;
623 2 s->text_pos += 1;
624 2 av_bprint_chars(&s->buffer, '\n', 1);
625 2 }
626
627 static const ASSCodesCallbacks mov_text_callbacks = {
628 .text = mov_text_text_cb,
629 .new_line = mov_text_new_line_cb,
630 .style = mov_text_style_cb,
631 .color = mov_text_color_cb,
632 .alpha = mov_text_alpha_cb,
633 .font_name = mov_text_font_name_cb,
634 .font_size = mov_text_font_size_cb,
635 .cancel_overrides = mov_text_cancel_overrides_cb,
636 .end = mov_text_end_cb,
637 };
638
639 3 static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf,
640 int bufsize, const AVSubtitle *sub)
641 {
642 3 MovTextContext *s = avctx->priv_data;
643 ASSDialog *dialog;
644 int i, length;
645
646
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (bufsize < 3)
647 goto too_small;
648
649 3 s->text_pos = 0;
650 3 s->count = 0;
651 3 s->box_flags = 0;
652
653 3 av_bprint_init_for_buffer(&s->buffer, buf + 2, bufsize - 2);
654
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 for (i = 0; i < sub->num_rects; i++) {
655 3 const char *ass = sub->rects[i]->ass;
656
657
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (sub->rects[i]->type != SUBTITLE_ASS) {
658 av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
659 return AVERROR(EINVAL);
660 }
661
662 3 dialog = ff_ass_split_dialog(s->ass_ctx, ass);
663
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!dialog)
664 return AVERROR(ENOMEM);
665 3 mov_text_dialog(s, dialog);
666 3 ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
667 3 ff_ass_free_dialog(&dialog);
668 }
669
670
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (s->buffer.len > UINT16_MAX)
671 return AVERROR(ERANGE);
672 3 AV_WB16(buf, s->buffer.len);
673
674
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 3 times.
12 for (size_t j = 0; j < box_count; j++)
675 9 box_types[j].encode(s);
676
677
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!s->buffer.len)
678 return 0;
679
680
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (!av_bprint_is_complete(&s->buffer)) {
681 too_small:
682 av_log(avctx, AV_LOG_ERROR, "Buffer too small for ASS event.\n");
683 return AVERROR_BUFFER_TOO_SMALL;
684 }
685
686 3 length = s->buffer.len + 2;
687
688 3 return length;
689 }
690
691 #define OFFSET(x) offsetof(MovTextContext, x)
692 #define FLAGS AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_SUBTITLE_PARAM
693 static const AVOption options[] = {
694 { "height", "Frame height, usually video height", OFFSET(frame_height), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS },
695 { NULL },
696 };
697
698 static const AVClass mov_text_encoder_class = {
699 .class_name = "MOV text enoder",
700 .item_name = av_default_item_name,
701 .option = options,
702 .version = LIBAVUTIL_VERSION_INT,
703 };
704
705 const FFCodec ff_movtext_encoder = {
706 .p.name = "mov_text",
707 CODEC_LONG_NAME("3GPP Timed Text subtitle"),
708 .p.type = AVMEDIA_TYPE_SUBTITLE,
709 .p.id = AV_CODEC_ID_MOV_TEXT,
710 .priv_data_size = sizeof(MovTextContext),
711 .p.priv_class = &mov_text_encoder_class,
712 .init = mov_text_encode_init,
713 FF_CODEC_ENCODE_SUB_CB(mov_text_encode_frame),
714 .close = mov_text_encode_close,
715 .caps_internal = FF_CODEC_CAP_INIT_CLEANUP,
716 };
717