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