]> git.sesse.net Git - ffmpeg/blob - libavcodec/libaribb24.c
add libaribb24 ARIB STD-B24 caption decoder
[ffmpeg] / libavcodec / libaribb24.c
1 /*
2  * ARIB STD-B24 caption decoder using the libaribb24 library
3  * Copyright (c) 2019 Jan Ekström
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 "libavcodec/ass.h"
24 #include "libavutil/log.h"
25 #include "libavutil/opt.h"
26
27 #include <aribb24/aribb24.h>
28 #include <aribb24/parser.h>
29 #include <aribb24/decoder.h>
30
31 typedef struct Libaribb24Context {
32     AVClass *class;
33
34     arib_instance_t *lib_instance;
35     arib_parser_t *parser;
36     arib_decoder_t *decoder;
37
38     int read_order;
39
40     char        *aribb24_base_path;
41     unsigned int aribb24_skip_ruby;
42 } Libaribb24Context;
43
44 static unsigned int get_profile_font_size(int profile)
45 {
46     switch (profile) {
47     case FF_PROFILE_ARIB_PROFILE_A:
48         return 36;
49     case FF_PROFILE_ARIB_PROFILE_C:
50         return 18;
51     default:
52         return 0;
53     }
54 }
55
56 static void libaribb24_log(void *p, const char *msg)
57 {
58     av_log((AVCodecContext *)p, AV_LOG_INFO, "%s\n", msg);
59 }
60
61 static int libaribb24_generate_ass_header(AVCodecContext *avctx)
62 {
63     unsigned int plane_width = 0;
64     unsigned int plane_height = 0;
65     unsigned int font_size = 0;
66
67     switch (avctx->profile) {
68     case FF_PROFILE_ARIB_PROFILE_A:
69         plane_width = 960;
70         plane_height = 540;
71         font_size = get_profile_font_size(avctx->profile);
72         break;
73     case FF_PROFILE_ARIB_PROFILE_C:
74         plane_width = 320;
75         plane_height = 180;
76         font_size = get_profile_font_size(avctx->profile);
77         break;
78     default:
79         av_log(avctx, AV_LOG_ERROR, "Unknown or unsupported profile set!\n");
80         return AVERROR(EINVAL);
81     }
82
83     avctx->subtitle_header = av_asprintf(
84              "[Script Info]\r\n"
85              "; Script generated by FFmpeg/Lavc%s\r\n"
86              "ScriptType: v4.00+\r\n"
87              "PlayResX: %d\r\n"
88              "PlayResY: %d\r\n"
89              "\r\n"
90              "[V4+ Styles]\r\n"
91
92              /* ASSv4 header */
93              "Format: Name, "
94              "Fontname, Fontsize, "
95              "PrimaryColour, SecondaryColour, OutlineColour, BackColour, "
96              "Bold, Italic, Underline, StrikeOut, "
97              "ScaleX, ScaleY, "
98              "Spacing, Angle, "
99              "BorderStyle, Outline, Shadow, "
100              "Alignment, MarginL, MarginR, MarginV, "
101              "Encoding\r\n"
102
103              "Style: "
104              "Default,"             /* Name */
105              "%s,%d,"               /* Font{name,size} */
106              "&H%x,&H%x,&H%x,&H%x," /* {Primary,Secondary,Outline,Back}Colour */
107              "%d,%d,%d,0,"          /* Bold, Italic, Underline, StrikeOut */
108              "100,100,"             /* Scale{X,Y} */
109              "0,0,"                 /* Spacing, Angle */
110              "%d,1,0,"              /* BorderStyle, Outline, Shadow */
111              "%d,10,10,10,"         /* Alignment, Margin[LRV] */
112              "0\r\n"                /* Encoding */
113
114              "\r\n"
115              "[Events]\r\n"
116              "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n",
117              !(avctx->flags & AV_CODEC_FLAG_BITEXACT) ? AV_STRINGIFY(LIBAVCODEC_VERSION) : "",
118              plane_width, plane_height,
119              ASS_DEFAULT_FONT, font_size, ASS_DEFAULT_COLOR,
120              ASS_DEFAULT_COLOR, ASS_DEFAULT_BACK_COLOR, ASS_DEFAULT_BACK_COLOR,
121              -ASS_DEFAULT_BOLD, -ASS_DEFAULT_ITALIC, -ASS_DEFAULT_UNDERLINE,
122              ASS_DEFAULT_BORDERSTYLE, ASS_DEFAULT_ALIGNMENT);
123
124     if (!avctx->subtitle_header)
125         return AVERROR(ENOMEM);
126
127     avctx->subtitle_header_size = strlen(avctx->subtitle_header);
128
129     return 0;
130 }
131
132 static int libaribb24_init(AVCodecContext *avctx)
133 {
134     Libaribb24Context *b24 = avctx->priv_data;
135     void(* arib_dec_init)(arib_decoder_t* decoder) = NULL;
136     int ret_code = AVERROR_EXTERNAL;
137
138     if (!(b24->lib_instance = arib_instance_new(avctx))) {
139         av_log(avctx, AV_LOG_ERROR, "Failed to initialize libaribb24!\n");
140         goto init_fail;
141     }
142
143     if (b24->aribb24_base_path) {
144         av_log(avctx, AV_LOG_INFO, "Setting the libaribb24 base path to '%s'\n",
145                b24->aribb24_base_path);
146         arib_set_base_path(b24->lib_instance, b24->aribb24_base_path);
147     }
148
149     arib_register_messages_callback(b24->lib_instance, libaribb24_log);
150
151     if (!(b24->parser = arib_get_parser(b24->lib_instance))) {
152         av_log(avctx, AV_LOG_ERROR, "Failed to initialize libaribb24 PES parser!\n");
153         goto init_fail;
154     }
155     if (!(b24->decoder = arib_get_decoder(b24->lib_instance))) {
156         av_log(avctx, AV_LOG_ERROR, "Failed to initialize libaribb24 decoder!\n");
157         goto init_fail;
158     }
159
160     switch (avctx->profile) {
161     case FF_PROFILE_ARIB_PROFILE_A:
162         arib_dec_init = arib_initialize_decoder_a_profile;
163         break;
164     case FF_PROFILE_ARIB_PROFILE_C:
165         arib_dec_init = arib_initialize_decoder_c_profile;
166         break;
167     default:
168         av_log(avctx, AV_LOG_ERROR, "Unknown or unsupported profile set!\n");
169         ret_code = AVERROR(EINVAL);
170         goto init_fail;
171     }
172
173     arib_dec_init(b24->decoder);
174
175     if (libaribb24_generate_ass_header(avctx) < 0) {
176         ret_code = AVERROR(ENOMEM);
177         goto init_fail;
178     }
179
180     return 0;
181
182 init_fail:
183     if (b24->decoder)
184         arib_finalize_decoder(b24->decoder);
185
186     if (b24->lib_instance)
187         arib_instance_destroy(b24->lib_instance);
188
189     return ret_code;
190 }
191
192 static int libaribb24_close(AVCodecContext *avctx)
193 {
194     Libaribb24Context *b24 = avctx->priv_data;
195
196     if (b24->decoder)
197         arib_finalize_decoder(b24->decoder);
198
199     if (b24->lib_instance)
200         arib_instance_destroy(b24->lib_instance);
201
202     return 0;
203 }
204
205 #define RGB_TO_BGR(c) ((c & 0xff) << 16 | (c & 0xff00) | ((c >> 16) & 0xff))
206
207 static void libaribb24_handle_regions(AVCodecContext *avctx, AVSubtitle *sub)
208 {
209     Libaribb24Context *b24 = avctx->priv_data;
210     const arib_buf_region_t *region = arib_decoder_get_regions(b24->decoder);
211     unsigned int profile_font_size = get_profile_font_size(avctx->profile);
212     AVBPrint buf = { 0 };
213
214     av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
215
216     while (region) {
217         ptrdiff_t region_length = region->p_end - region->p_start;
218         unsigned int ruby_region =
219             region->i_fontheight == (profile_font_size / 2);
220
221         // ASS requires us to make the colors BGR, so we convert here
222         int foreground_bgr_color = RGB_TO_BGR(region->i_foreground_color);
223         int background_bgr_color = RGB_TO_BGR(region->i_background_color);
224
225         if (region_length < 0) {
226             av_log(avctx, AV_LOG_ERROR, "Invalid negative region length!\n");
227             break;
228         }
229
230         if (region_length == 0 || (ruby_region && b24->aribb24_skip_ruby)) {
231             goto next_region;
232         }
233
234         // color and alpha
235         if (foreground_bgr_color != ASS_DEFAULT_COLOR)
236             av_bprintf(&buf, "{\\1c&H%06x&}", foreground_bgr_color);
237
238         if (region->i_foreground_alpha != 0)
239             av_bprintf(&buf, "{\\1a&H%02x&}", region->i_foreground_alpha);
240
241         if (background_bgr_color != ASS_DEFAULT_BACK_COLOR)
242             av_bprintf(&buf, "{\\3c&H%06x&}", background_bgr_color);
243
244         if (region->i_background_alpha != 0)
245             av_bprintf(&buf, "{\\3a&H%02x&}", region->i_background_alpha);
246
247         // font size
248         if (region->i_fontwidth  != profile_font_size ||
249             region->i_fontheight != profile_font_size) {
250             av_bprintf(&buf, "{\\fscx%d\\fscy%d}",
251                        (int)round(((double)region->i_fontwidth /
252                                    (double)profile_font_size) * 100),
253                        (int)round(((double)region->i_fontheight /
254                                    (double)profile_font_size) * 100));
255         }
256
257         // TODO: positioning
258
259         av_bprint_append_data(&buf, region->p_start, region_length);
260
261         av_bprintf(&buf, "{\\r}");
262
263 next_region:
264         region = region->p_next;
265     }
266
267     av_log(avctx, AV_LOG_DEBUG, "Styled ASS line: %s\n",
268            buf.str);
269     ff_ass_add_rect(sub, buf.str, b24->read_order++,
270                     0, NULL, NULL);
271
272     av_bprint_finalize(&buf, NULL);
273 }
274
275 static int libaribb24_decode(AVCodecContext *avctx, void *data, int *got_sub_ptr, AVPacket *pkt)
276 {
277     Libaribb24Context *b24 = avctx->priv_data;
278     AVSubtitle *sub = data;
279     size_t parsed_data_size = 0;
280     size_t decoded_subtitle_size = 0;
281     const unsigned char *parsed_data = NULL;
282     char *decoded_subtitle = NULL;
283     time_t subtitle_duration = 0;
284
285     if (pkt->size <= 0)
286         return pkt->size;
287
288     arib_parse_pes(b24->parser, pkt->data, pkt->size);
289
290     parsed_data = arib_parser_get_data(b24->parser,
291                                        &parsed_data_size);
292     if (!parsed_data || !parsed_data_size) {
293         av_log(avctx, AV_LOG_DEBUG, "No decode'able data was received from "
294                                     "packet (dts: %"PRId64", pts: %"PRId64").\n",
295                pkt->dts, pkt->pts);
296         return pkt->size;
297     }
298
299     decoded_subtitle_size = parsed_data_size * 4;
300     if (!(decoded_subtitle = av_mallocz(decoded_subtitle_size + 1))) {
301         av_log(avctx, AV_LOG_ERROR,
302                "Failed to allocate buffer for decoded subtitle!\n");
303         return AVERROR(ENOMEM);
304     }
305
306     decoded_subtitle_size = arib_decode_buffer(b24->decoder,
307                                                parsed_data,
308                                                parsed_data_size,
309                                                decoded_subtitle,
310                                                decoded_subtitle_size);
311
312     subtitle_duration = arib_decoder_get_time(b24->decoder);
313
314     if (avctx->pkt_timebase.num && pkt->pts != AV_NOPTS_VALUE)
315         sub->pts = av_rescale_q(pkt->pts,
316                                 avctx->pkt_timebase, AV_TIME_BASE_Q);
317
318     sub->end_display_time = subtitle_duration ?
319                             av_rescale_q(subtitle_duration,
320                                          AV_TIME_BASE_Q,
321                                          (AVRational){1, 1000}) :
322                             UINT32_MAX;
323
324     av_log(avctx, AV_LOG_DEBUG,
325            "Result: '%s' (size: %zu, pkt_pts: %"PRId64", sub_pts: %"PRId64" "
326            "duration: %"PRIu32", pkt_timebase: %d/%d, time_base: %d/%d')\n",
327            decoded_subtitle ? decoded_subtitle : "<no subtitle>",
328            decoded_subtitle_size,
329            pkt->pts, sub->pts,
330            sub->end_display_time,
331            avctx->pkt_timebase.num, avctx->pkt_timebase.den,
332            avctx->time_base.num, avctx->time_base.den);
333
334     if (decoded_subtitle)
335         libaribb24_handle_regions(avctx, sub);
336
337     *got_sub_ptr = sub->num_rects > 0;
338
339     av_free(decoded_subtitle);
340
341     // flush the region buffers, otherwise the linked list keeps getting
342     // longer and longer...
343     arib_finalize_decoder(b24->decoder);
344
345     return pkt->size;
346 }
347
348 static void libaribb24_flush(AVCodecContext *avctx)
349 {
350     Libaribb24Context *b24 = avctx->priv_data;
351     if (!(avctx->flags2 & AV_CODEC_FLAG2_RO_FLUSH_NOOP))
352         b24->read_order = 0;
353 }
354
355 #define OFFSET(x) offsetof(Libaribb24Context, x)
356 #define SD AV_OPT_FLAG_SUBTITLE_PARAM | AV_OPT_FLAG_DECODING_PARAM
357 static const AVOption options[] = {
358     { "aribb24-base-path", "set the base path for the libaribb24 library",
359       OFFSET(aribb24_base_path), AV_OPT_TYPE_STRING, { 0 }, 0, 0, SD },
360     { "aribb24-skip-ruby-text", "skip ruby text blocks during decoding",
361       OFFSET(aribb24_skip_ruby), AV_OPT_TYPE_BOOL, { 1 }, 0, 1, SD },
362     { NULL }
363 };
364
365 static const AVClass aribb24_class = {
366     .class_name = "libaribb24 decoder",
367     .item_name  = av_default_item_name,
368     .option     = options,
369     .version    = LIBAVUTIL_VERSION_INT,
370 };
371
372 AVCodec ff_libaribb24_decoder = {
373     .name      = "libaribb24",
374     .long_name = NULL_IF_CONFIG_SMALL("libaribb24 ARIB STD-B24 caption decoder"),
375     .type      = AVMEDIA_TYPE_SUBTITLE,
376     .id        = AV_CODEC_ID_ARIB_CAPTION,
377     .priv_data_size = sizeof(Libaribb24Context),
378     .init      = libaribb24_init,
379     .close     = libaribb24_close,
380     .decode    = libaribb24_decode,
381     .flush     = libaribb24_flush,
382     .priv_class= &aribb24_class,
383     .wrapper_name = "libaribb24",
384 };