2 * Copyright (c) 2011 Baptiste Coudurier
3 * Copyright (c) 2011 Stefano Sabatini
4 * Copyright (c) 2012 Clément Bœsch
6 * This file is part of FFmpeg.
8 * FFmpeg is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
13 * FFmpeg is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with FFmpeg; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
25 * Libass subtitles burning filter.
27 * @see{http://www.matroska.org/technical/specs/subtitles/ssa.html}
33 #if CONFIG_SUBTITLES_FILTER
34 # include "libavcodec/avcodec.h"
35 # include "libavformat/avformat.h"
37 #include "libavutil/avstring.h"
38 #include "libavutil/imgutils.h"
39 #include "libavutil/opt.h"
40 #include "libavutil/parseutils.h"
41 #include "drawutils.h"
50 ASS_Renderer
*renderer
;
56 int pix_step
[4]; ///< steps per pixel for each plane of the main output
57 int original_w
, original_h
;
61 #define OFFSET(x) offsetof(AssContext, x)
62 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
64 #define COMMON_OPTIONS \
65 {"filename", "set the filename of file to read", OFFSET(filename), AV_OPT_TYPE_STRING, {.str = NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, \
66 {"f", "set the filename of file to read", OFFSET(filename), AV_OPT_TYPE_STRING, {.str = NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, \
67 {"original_size", "set the size of the original video (used to scale fonts)", OFFSET(original_w), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, \
69 /* libass supports a log level ranging from 0 to 7 */
70 static const int ass_libavfilter_log_level_map
[] = {
75 AV_LOG_WARNING
, /* 4 */
77 AV_LOG_VERBOSE
, /* 6 */
81 static void ass_log(int ass_level
, const char *fmt
, va_list args
, void *ctx
)
83 int level
= ass_libavfilter_log_level_map
[ass_level
];
85 av_vlog(ctx
, level
, fmt
, args
);
86 av_log(ctx
, level
, "\n");
89 static av_cold
int init(AVFilterContext
*ctx
)
91 AssContext
*ass
= ctx
->priv
;
94 av_log(ctx
, AV_LOG_ERROR
, "No filename provided!\n");
95 return AVERROR(EINVAL
);
98 ass
->library
= ass_library_init();
100 av_log(ctx
, AV_LOG_ERROR
, "Could not initialize libass.\n");
101 return AVERROR(EINVAL
);
103 ass_set_message_cb(ass
->library
, ass_log
, ctx
);
105 ass
->renderer
= ass_renderer_init(ass
->library
);
106 if (!ass
->renderer
) {
107 av_log(ctx
, AV_LOG_ERROR
, "Could not initialize libass renderer.\n");
108 return AVERROR(EINVAL
);
114 static av_cold
void uninit(AVFilterContext
*ctx
)
116 AssContext
*ass
= ctx
->priv
;
119 ass_free_track(ass
->track
);
121 ass_renderer_done(ass
->renderer
);
123 ass_library_done(ass
->library
);
126 static int query_formats(AVFilterContext
*ctx
)
128 ff_set_common_formats(ctx
, ff_draw_supported_pixel_formats(0));
132 static int config_input(AVFilterLink
*inlink
)
134 AssContext
*ass
= inlink
->dst
->priv
;
136 ff_draw_init(&ass
->draw
, inlink
->format
, 0);
138 ass_set_frame_size (ass
->renderer
, inlink
->w
, inlink
->h
);
139 if (ass
->original_w
&& ass
->original_h
)
140 ass_set_aspect_ratio(ass
->renderer
, (double)inlink
->w
/ inlink
->h
,
141 (double)ass
->original_w
/ ass
->original_h
);
146 /* libass stores an RGBA color in the format RRGGBBTT, where TT is the transparency level */
147 #define AR(c) ( (c)>>24)
148 #define AG(c) (((c)>>16)&0xFF)
149 #define AB(c) (((c)>>8) &0xFF)
150 #define AA(c) ((0xFF-c) &0xFF)
152 static void overlay_ass_image(AssContext
*ass
, AVFrame
*picref
,
153 const ASS_Image
*image
)
155 for (; image
; image
= image
->next
) {
156 uint8_t rgba_color
[] = {AR(image
->color
), AG(image
->color
), AB(image
->color
), AA(image
->color
)};
158 ff_draw_color(&ass
->draw
, &color
, rgba_color
);
159 ff_blend_mask(&ass
->draw
, &color
,
160 picref
->data
, picref
->linesize
,
161 picref
->width
, picref
->height
,
162 image
->bitmap
, image
->stride
, image
->w
, image
->h
,
163 3, 0, image
->dst_x
, image
->dst_y
);
167 static int filter_frame(AVFilterLink
*inlink
, AVFrame
*picref
)
169 AVFilterContext
*ctx
= inlink
->dst
;
170 AVFilterLink
*outlink
= ctx
->outputs
[0];
171 AssContext
*ass
= ctx
->priv
;
172 int detect_change
= 0;
173 double time_ms
= picref
->pts
* av_q2d(inlink
->time_base
) * 1000;
174 ASS_Image
*image
= ass_render_frame(ass
->renderer
, ass
->track
,
175 time_ms
, &detect_change
);
178 av_log(ctx
, AV_LOG_DEBUG
, "Change happened at time ms:%f\n", time_ms
);
180 overlay_ass_image(ass
, picref
, image
);
182 return ff_filter_frame(outlink
, picref
);
185 static const AVFilterPad ass_inputs
[] = {
188 .type
= AVMEDIA_TYPE_VIDEO
,
189 .filter_frame
= filter_frame
,
190 .config_props
= config_input
,
196 static const AVFilterPad ass_outputs
[] = {
199 .type
= AVMEDIA_TYPE_VIDEO
,
204 #if CONFIG_ASS_FILTER
206 static const AVOption ass_options
[] = {
211 AVFILTER_DEFINE_CLASS(ass
);
213 static av_cold
int init_ass(AVFilterContext
*ctx
)
215 AssContext
*ass
= ctx
->priv
;
221 /* Initialize fonts */
222 ass_set_fonts(ass
->renderer
, NULL
, NULL
, 1, NULL
, 1);
224 ass
->track
= ass_read_file(ass
->library
, ass
->filename
, NULL
);
226 av_log(ctx
, AV_LOG_ERROR
,
227 "Could not create a libass track when reading file '%s'\n",
229 return AVERROR(EINVAL
);
234 AVFilter ff_vf_ass
= {
236 .description
= NULL_IF_CONFIG_SMALL("Render ASS subtitles onto input video using the libass library."),
237 .priv_size
= sizeof(AssContext
),
240 .query_formats
= query_formats
,
241 .inputs
= ass_inputs
,
242 .outputs
= ass_outputs
,
243 .priv_class
= &ass_class
,
247 #if CONFIG_SUBTITLES_FILTER
249 static const AVOption subtitles_options
[] = {
251 {"charenc", "set input character encoding", OFFSET(charenc
), AV_OPT_TYPE_STRING
, {.str
= NULL
}, CHAR_MIN
, CHAR_MAX
, FLAGS
},
252 {"stream_index", "set stream index", OFFSET(stream_index
), AV_OPT_TYPE_INT
, { .i64
= -1 }, -1, INT_MAX
, FLAGS
},
253 {"si", "set stream index", OFFSET(stream_index
), AV_OPT_TYPE_INT
, { .i64
= -1 }, -1, INT_MAX
, FLAGS
},
257 static const char * const font_mimetypes
[] = {
258 "application/x-truetype-font",
259 "application/vnd.ms-opentype",
260 "application/x-font-ttf",
264 static int attachment_is_font(AVStream
* st
)
266 const AVDictionaryEntry
*tag
= NULL
;
269 tag
= av_dict_get(st
->metadata
, "mimetype", NULL
, AV_DICT_MATCH_CASE
);
272 for (n
= 0; font_mimetypes
[n
]; n
++) {
273 if (av_strcasecmp(font_mimetypes
[n
], tag
->value
) == 0)
280 AVFILTER_DEFINE_CLASS(subtitles
);
282 static av_cold
int init_subtitles(AVFilterContext
*ctx
)
286 AVDictionary
*codec_opts
= NULL
;
287 AVFormatContext
*fmt
= NULL
;
288 AVCodecContext
*dec_ctx
= NULL
;
290 const AVCodecDescriptor
*dec_desc
;
293 AssContext
*ass
= ctx
->priv
;
299 ass
->track
= ass_new_track(ass
->library
);
301 av_log(ctx
, AV_LOG_ERROR
, "Could not create a libass track\n");
302 return AVERROR(EINVAL
);
305 /* Open subtitles file */
306 ret
= avformat_open_input(&fmt
, ass
->filename
, NULL
, NULL
);
308 av_log(ctx
, AV_LOG_ERROR
, "Unable to open %s\n", ass
->filename
);
311 ret
= avformat_find_stream_info(fmt
, NULL
);
315 /* Locate subtitles stream */
316 if (ass
->stream_index
< 0)
317 ret
= av_find_best_stream(fmt
, AVMEDIA_TYPE_SUBTITLE
, -1, -1, NULL
, 0);
320 if (ass
->stream_index
< fmt
->nb_streams
) {
321 for (j
= 0; j
< fmt
->nb_streams
; j
++) {
322 if (fmt
->streams
[j
]->codec
->codec_type
== AVMEDIA_TYPE_SUBTITLE
) {
323 if (ass
->stream_index
== k
) {
334 av_log(ctx
, AV_LOG_ERROR
, "Unable to locate subtitle stream in %s\n",
339 st
= fmt
->streams
[sid
];
341 /* Load attached fonts */
342 for (j
= 0; j
< fmt
->nb_streams
; j
++) {
343 AVStream
*st
= fmt
->streams
[j
];
344 if (st
->codec
->codec_type
== AVMEDIA_TYPE_ATTACHMENT
&&
345 attachment_is_font(st
)) {
346 const AVDictionaryEntry
*tag
= NULL
;
347 tag
= av_dict_get(st
->metadata
, "filename", NULL
,
351 av_log(ctx
, AV_LOG_DEBUG
, "Loading attached font: %s\n",
353 ass_add_font(ass
->library
, tag
->value
,
354 st
->codec
->extradata
,
355 st
->codec
->extradata_size
);
357 av_log(ctx
, AV_LOG_WARNING
,
358 "Font attachment has no filename, ignored.\n");
363 /* Initialize fonts */
364 ass_set_fonts(ass
->renderer
, NULL
, NULL
, 1, NULL
, 1);
368 dec
= avcodec_find_decoder(dec_ctx
->codec_id
);
370 av_log(ctx
, AV_LOG_ERROR
, "Failed to find subtitle codec %s\n",
371 avcodec_get_name(dec_ctx
->codec_id
));
372 return AVERROR(EINVAL
);
374 dec_desc
= avcodec_descriptor_get(dec_ctx
->codec_id
);
375 if (dec_desc
&& !(dec_desc
->props
& AV_CODEC_PROP_TEXT_SUB
)) {
376 av_log(ctx
, AV_LOG_ERROR
,
377 "Only text based subtitles are currently supported\n");
378 return AVERROR_PATCHWELCOME
;
381 av_dict_set(&codec_opts
, "sub_charenc", ass
->charenc
, 0);
382 ret
= avcodec_open2(dec_ctx
, dec
, &codec_opts
);
386 /* Decode subtitles and push them into the renderer (libass) */
387 if (dec_ctx
->subtitle_header
)
388 ass_process_codec_private(ass
->track
,
389 dec_ctx
->subtitle_header
,
390 dec_ctx
->subtitle_header_size
);
391 av_init_packet(&pkt
);
394 while (av_read_frame(fmt
, &pkt
) >= 0) {
396 AVSubtitle sub
= {0};
398 if (pkt
.stream_index
== sid
) {
399 ret
= avcodec_decode_subtitle2(dec_ctx
, &sub
, &got_subtitle
, &pkt
);
401 av_log(ctx
, AV_LOG_WARNING
, "Error decoding: %s (ignored)\n",
403 } else if (got_subtitle
) {
404 for (i
= 0; i
< sub
.num_rects
; i
++) {
405 char *ass_line
= sub
.rects
[i
]->ass
;
408 ass_process_data(ass
->track
, ass_line
, strlen(ass_line
));
412 av_free_packet(&pkt
);
413 avsubtitle_free(&sub
);
417 av_dict_free(&codec_opts
);
419 avcodec_close(dec_ctx
);
421 avformat_close_input(&fmt
);
425 AVFilter ff_vf_subtitles
= {
427 .description
= NULL_IF_CONFIG_SMALL("Render text subtitles onto input video using the libass library."),
428 .priv_size
= sizeof(AssContext
),
429 .init
= init_subtitles
,
431 .query_formats
= query_formats
,
432 .inputs
= ass_inputs
,
433 .outputs
= ass_outputs
,
434 .priv_class
= &subtitles_class
,