Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
6147 | serge | 1 | /* |
2 | * Copyright (c) 2011 Baptiste Coudurier |
||
3 | * Copyright (c) 2011 Stefano Sabatini |
||
4 | * Copyright (c) 2012 Clément Bœsch |
||
5 | * |
||
6 | * This file is part of FFmpeg. |
||
7 | * |
||
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. |
||
12 | * |
||
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. |
||
17 | * |
||
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 |
||
21 | */ |
||
22 | |||
23 | /** |
||
24 | * @file |
||
25 | * Libass subtitles burning filter. |
||
26 | * |
||
27 | * @see{http://www.matroska.org/technical/specs/subtitles/ssa.html} |
||
28 | */ |
||
29 | |||
30 | #include |
||
31 | |||
32 | #include "config.h" |
||
33 | #if CONFIG_SUBTITLES_FILTER |
||
34 | # include "libavcodec/avcodec.h" |
||
35 | # include "libavformat/avformat.h" |
||
36 | #endif |
||
37 | #include "libavutil/avstring.h" |
||
38 | #include "libavutil/imgutils.h" |
||
39 | #include "libavutil/opt.h" |
||
40 | #include "libavutil/parseutils.h" |
||
41 | #include "drawutils.h" |
||
42 | #include "avfilter.h" |
||
43 | #include "internal.h" |
||
44 | #include "formats.h" |
||
45 | #include "video.h" |
||
46 | |||
47 | typedef struct { |
||
48 | const AVClass *class; |
||
49 | ASS_Library *library; |
||
50 | ASS_Renderer *renderer; |
||
51 | ASS_Track *track; |
||
52 | char *filename; |
||
53 | char *fontsdir; |
||
54 | char *charenc; |
||
55 | char *force_style; |
||
56 | int stream_index; |
||
57 | uint8_t rgba_map[4]; |
||
58 | int pix_step[4]; ///< steps per pixel for each plane of the main output |
||
59 | int original_w, original_h; |
||
60 | int shaping; |
||
61 | FFDrawContext draw; |
||
62 | } AssContext; |
||
63 | |||
64 | #define OFFSET(x) offsetof(AssContext, x) |
||
65 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM |
||
66 | |||
67 | #define COMMON_OPTIONS \ |
||
68 | {"filename", "set the filename of file to read", OFFSET(filename), AV_OPT_TYPE_STRING, {.str = NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, \ |
||
69 | {"f", "set the filename of file to read", OFFSET(filename), AV_OPT_TYPE_STRING, {.str = NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, \ |
||
70 | {"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 }, \ |
||
71 | {"fontsdir", "set the directory containing the fonts to read", OFFSET(fontsdir), AV_OPT_TYPE_STRING, {.str = NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, \ |
||
72 | |||
73 | /* libass supports a log level ranging from 0 to 7 */ |
||
74 | static const int ass_libavfilter_log_level_map[] = { |
||
75 | [0] = AV_LOG_FATAL, /* MSGL_FATAL */ |
||
76 | [1] = AV_LOG_ERROR, /* MSGL_ERR */ |
||
77 | [2] = AV_LOG_WARNING, /* MSGL_WARN */ |
||
78 | [3] = AV_LOG_WARNING, /* |
||
79 | [4] = AV_LOG_INFO, /* MSGL_INFO */ |
||
80 | [5] = AV_LOG_INFO, /* |
||
81 | [6] = AV_LOG_VERBOSE, /* MSGL_V */ |
||
82 | [7] = AV_LOG_DEBUG, /* MSGL_DBG2 */ |
||
83 | }; |
||
84 | |||
85 | static void ass_log(int ass_level, const char *fmt, va_list args, void *ctx) |
||
86 | { |
||
87 | const int ass_level_clip = av_clip(ass_level, 0, |
||
88 | FF_ARRAY_ELEMS(ass_libavfilter_log_level_map) - 1); |
||
89 | const int level = ass_libavfilter_log_level_map[ass_level_clip]; |
||
90 | |||
91 | av_vlog(ctx, level, fmt, args); |
||
92 | av_log(ctx, level, "\n"); |
||
93 | } |
||
94 | |||
95 | static av_cold int init(AVFilterContext *ctx) |
||
96 | { |
||
97 | AssContext *ass = ctx->priv; |
||
98 | |||
99 | if (!ass->filename) { |
||
100 | av_log(ctx, AV_LOG_ERROR, "No filename provided!\n"); |
||
101 | return AVERROR(EINVAL); |
||
102 | } |
||
103 | |||
104 | ass->library = ass_library_init(); |
||
105 | if (!ass->library) { |
||
106 | av_log(ctx, AV_LOG_ERROR, "Could not initialize libass.\n"); |
||
107 | return AVERROR(EINVAL); |
||
108 | } |
||
109 | ass_set_message_cb(ass->library, ass_log, ctx); |
||
110 | |||
111 | ass_set_fonts_dir(ass->library, ass->fontsdir); |
||
112 | |||
113 | ass->renderer = ass_renderer_init(ass->library); |
||
114 | if (!ass->renderer) { |
||
115 | av_log(ctx, AV_LOG_ERROR, "Could not initialize libass renderer.\n"); |
||
116 | return AVERROR(EINVAL); |
||
117 | } |
||
118 | |||
119 | return 0; |
||
120 | } |
||
121 | |||
122 | static av_cold void uninit(AVFilterContext *ctx) |
||
123 | { |
||
124 | AssContext *ass = ctx->priv; |
||
125 | |||
126 | if (ass->track) |
||
127 | ass_free_track(ass->track); |
||
128 | if (ass->renderer) |
||
129 | ass_renderer_done(ass->renderer); |
||
130 | if (ass->library) |
||
131 | ass_library_done(ass->library); |
||
132 | } |
||
133 | |||
134 | static int query_formats(AVFilterContext *ctx) |
||
135 | { |
||
136 | return ff_set_common_formats(ctx, ff_draw_supported_pixel_formats(0)); |
||
137 | } |
||
138 | |||
139 | static int config_input(AVFilterLink *inlink) |
||
140 | { |
||
141 | AssContext *ass = inlink->dst->priv; |
||
142 | |||
143 | ff_draw_init(&ass->draw, inlink->format, 0); |
||
144 | |||
145 | ass_set_frame_size (ass->renderer, inlink->w, inlink->h); |
||
146 | if (ass->original_w && ass->original_h) |
||
147 | ass_set_aspect_ratio(ass->renderer, (double)inlink->w / inlink->h, |
||
148 | (double)ass->original_w / ass->original_h); |
||
149 | if (ass->shaping != -1) |
||
150 | ass_set_shaper(ass->renderer, ass->shaping); |
||
151 | |||
152 | return 0; |
||
153 | } |
||
154 | |||
155 | /* libass stores an RGBA color in the format RRGGBBTT, where TT is the transparency level */ |
||
156 | #define AR(c) ( (c)>>24) |
||
157 | #define AG(c) (((c)>>16)&0xFF) |
||
158 | #define AB(c) (((c)>>8) &0xFF) |
||
159 | #define AA(c) ((0xFF-(c)) &0xFF) |
||
160 | |||
161 | static void overlay_ass_image(AssContext *ass, AVFrame *picref, |
||
162 | const ASS_Image *image) |
||
163 | { |
||
164 | for (; image; image = image->next) { |
||
165 | uint8_t rgba_color[] = {AR(image->color), AG(image->color), AB(image->color), AA(image->color)}; |
||
166 | FFDrawColor color; |
||
167 | ff_draw_color(&ass->draw, &color, rgba_color); |
||
168 | ff_blend_mask(&ass->draw, &color, |
||
169 | picref->data, picref->linesize, |
||
170 | picref->width, picref->height, |
||
171 | image->bitmap, image->stride, image->w, image->h, |
||
172 | 3, 0, image->dst_x, image->dst_y); |
||
173 | } |
||
174 | } |
||
175 | |||
176 | static int filter_frame(AVFilterLink *inlink, AVFrame *picref) |
||
177 | { |
||
178 | AVFilterContext *ctx = inlink->dst; |
||
179 | AVFilterLink *outlink = ctx->outputs[0]; |
||
180 | AssContext *ass = ctx->priv; |
||
181 | int detect_change = 0; |
||
182 | double time_ms = picref->pts * av_q2d(inlink->time_base) * 1000; |
||
183 | ASS_Image *image = ass_render_frame(ass->renderer, ass->track, |
||
184 | time_ms, &detect_change); |
||
185 | |||
186 | if (detect_change) |
||
187 | av_log(ctx, AV_LOG_DEBUG, "Change happened at time ms:%f\n", time_ms); |
||
188 | |||
189 | overlay_ass_image(ass, picref, image); |
||
190 | |||
191 | return ff_filter_frame(outlink, picref); |
||
192 | } |
||
193 | |||
194 | static const AVFilterPad ass_inputs[] = { |
||
195 | { |
||
196 | .name = "default", |
||
197 | .type = AVMEDIA_TYPE_VIDEO, |
||
198 | .filter_frame = filter_frame, |
||
199 | .config_props = config_input, |
||
200 | .needs_writable = 1, |
||
201 | }, |
||
202 | { NULL } |
||
203 | }; |
||
204 | |||
205 | static const AVFilterPad ass_outputs[] = { |
||
206 | { |
||
207 | .name = "default", |
||
208 | .type = AVMEDIA_TYPE_VIDEO, |
||
209 | }, |
||
210 | { NULL } |
||
211 | }; |
||
212 | |||
213 | #if CONFIG_ASS_FILTER |
||
214 | |||
215 | static const AVOption ass_options[] = { |
||
216 | COMMON_OPTIONS |
||
217 | {"shaping", "set shaping engine", OFFSET(shaping), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 1, FLAGS, "shaping_mode"}, |
||
218 | {"auto", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = -1}, INT_MIN, INT_MAX, FLAGS, "shaping_mode"}, |
||
219 | {"simple", "simple shaping", 0, AV_OPT_TYPE_CONST, {.i64 = ASS_SHAPING_SIMPLE}, INT_MIN, INT_MAX, FLAGS, "shaping_mode"}, |
||
220 | {"complex", "complex shaping", 0, AV_OPT_TYPE_CONST, {.i64 = ASS_SHAPING_COMPLEX}, INT_MIN, INT_MAX, FLAGS, "shaping_mode"}, |
||
221 | {NULL}, |
||
222 | }; |
||
223 | |||
224 | AVFILTER_DEFINE_CLASS(ass); |
||
225 | |||
226 | static av_cold int init_ass(AVFilterContext *ctx) |
||
227 | { |
||
228 | AssContext *ass = ctx->priv; |
||
229 | int ret = init(ctx); |
||
230 | |||
231 | if (ret < 0) |
||
232 | return ret; |
||
233 | |||
234 | /* Initialize fonts */ |
||
235 | ass_set_fonts(ass->renderer, NULL, NULL, 1, NULL, 1); |
||
236 | |||
237 | ass->track = ass_read_file(ass->library, ass->filename, NULL); |
||
238 | if (!ass->track) { |
||
239 | av_log(ctx, AV_LOG_ERROR, |
||
240 | "Could not create a libass track when reading file '%s'\n", |
||
241 | ass->filename); |
||
242 | return AVERROR(EINVAL); |
||
243 | } |
||
244 | return 0; |
||
245 | } |
||
246 | |||
247 | AVFilter ff_vf_ass = { |
||
248 | .name = "ass", |
||
249 | .description = NULL_IF_CONFIG_SMALL("Render ASS subtitles onto input video using the libass library."), |
||
250 | .priv_size = sizeof(AssContext), |
||
251 | .init = init_ass, |
||
252 | .uninit = uninit, |
||
253 | .query_formats = query_formats, |
||
254 | .inputs = ass_inputs, |
||
255 | .outputs = ass_outputs, |
||
256 | .priv_class = &ass_class, |
||
257 | }; |
||
258 | #endif |
||
259 | |||
260 | #if CONFIG_SUBTITLES_FILTER |
||
261 | |||
262 | static const AVOption subtitles_options[] = { |
||
263 | COMMON_OPTIONS |
||
264 | {"charenc", "set input character encoding", OFFSET(charenc), AV_OPT_TYPE_STRING, {.str = NULL}, CHAR_MIN, CHAR_MAX, FLAGS}, |
||
265 | {"stream_index", "set stream index", OFFSET(stream_index), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, FLAGS}, |
||
266 | {"si", "set stream index", OFFSET(stream_index), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, FLAGS}, |
||
267 | {"force_style", "force subtitle style", OFFSET(force_style), AV_OPT_TYPE_STRING, {.str = NULL}, CHAR_MIN, CHAR_MAX, FLAGS}, |
||
268 | {NULL}, |
||
269 | }; |
||
270 | |||
271 | static const char * const font_mimetypes[] = { |
||
272 | "application/x-truetype-font", |
||
273 | "application/vnd.ms-opentype", |
||
274 | "application/x-font-ttf", |
||
275 | NULL |
||
276 | }; |
||
277 | |||
278 | static int attachment_is_font(AVStream * st) |
||
279 | { |
||
280 | const AVDictionaryEntry *tag = NULL; |
||
281 | int n; |
||
282 | |||
283 | tag = av_dict_get(st->metadata, "mimetype", NULL, AV_DICT_MATCH_CASE); |
||
284 | |||
285 | if (tag) { |
||
286 | for (n = 0; font_mimetypes[n]; n++) { |
||
287 | if (av_strcasecmp(font_mimetypes[n], tag->value) == 0) |
||
288 | return 1; |
||
289 | } |
||
290 | } |
||
291 | return 0; |
||
292 | } |
||
293 | |||
294 | AVFILTER_DEFINE_CLASS(subtitles); |
||
295 | |||
296 | static av_cold int init_subtitles(AVFilterContext *ctx) |
||
297 | { |
||
298 | int j, ret, sid; |
||
299 | int k = 0; |
||
300 | AVDictionary *codec_opts = NULL; |
||
301 | AVFormatContext *fmt = NULL; |
||
302 | AVCodecContext *dec_ctx = NULL; |
||
303 | AVCodec *dec = NULL; |
||
304 | const AVCodecDescriptor *dec_desc; |
||
305 | AVStream *st; |
||
306 | AVPacket pkt; |
||
307 | AssContext *ass = ctx->priv; |
||
308 | |||
309 | /* Init libass */ |
||
310 | ret = init(ctx); |
||
311 | if (ret < 0) |
||
312 | return ret; |
||
313 | ass->track = ass_new_track(ass->library); |
||
314 | if (!ass->track) { |
||
315 | av_log(ctx, AV_LOG_ERROR, "Could not create a libass track\n"); |
||
316 | return AVERROR(EINVAL); |
||
317 | } |
||
318 | |||
319 | /* Open subtitles file */ |
||
320 | ret = avformat_open_input(&fmt, ass->filename, NULL, NULL); |
||
321 | if (ret < 0) { |
||
322 | av_log(ctx, AV_LOG_ERROR, "Unable to open %s\n", ass->filename); |
||
323 | goto end; |
||
324 | } |
||
325 | ret = avformat_find_stream_info(fmt, NULL); |
||
326 | if (ret < 0) |
||
327 | goto end; |
||
328 | |||
329 | /* Locate subtitles stream */ |
||
330 | if (ass->stream_index < 0) |
||
331 | ret = av_find_best_stream(fmt, AVMEDIA_TYPE_SUBTITLE, -1, -1, NULL, 0); |
||
332 | else { |
||
333 | ret = -1; |
||
334 | if (ass->stream_index < fmt->nb_streams) { |
||
335 | for (j = 0; j < fmt->nb_streams; j++) { |
||
336 | if (fmt->streams[j]->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) { |
||
337 | if (ass->stream_index == k) { |
||
338 | ret = j; |
||
339 | break; |
||
340 | } |
||
341 | k++; |
||
342 | } |
||
343 | } |
||
344 | } |
||
345 | } |
||
346 | |||
347 | if (ret < 0) { |
||
348 | av_log(ctx, AV_LOG_ERROR, "Unable to locate subtitle stream in %s\n", |
||
349 | ass->filename); |
||
350 | goto end; |
||
351 | } |
||
352 | sid = ret; |
||
353 | st = fmt->streams[sid]; |
||
354 | |||
355 | /* Load attached fonts */ |
||
356 | for (j = 0; j < fmt->nb_streams; j++) { |
||
357 | AVStream *st = fmt->streams[j]; |
||
358 | if (st->codec->codec_type == AVMEDIA_TYPE_ATTACHMENT && |
||
359 | attachment_is_font(st)) { |
||
360 | const AVDictionaryEntry *tag = NULL; |
||
361 | tag = av_dict_get(st->metadata, "filename", NULL, |
||
362 | AV_DICT_MATCH_CASE); |
||
363 | |||
364 | if (tag) { |
||
365 | av_log(ctx, AV_LOG_DEBUG, "Loading attached font: %s\n", |
||
366 | tag->value); |
||
367 | ass_add_font(ass->library, tag->value, |
||
368 | st->codec->extradata, |
||
369 | st->codec->extradata_size); |
||
370 | } else { |
||
371 | av_log(ctx, AV_LOG_WARNING, |
||
372 | "Font attachment has no filename, ignored.\n"); |
||
373 | } |
||
374 | } |
||
375 | } |
||
376 | |||
377 | /* Initialize fonts */ |
||
378 | ass_set_fonts(ass->renderer, NULL, NULL, 1, NULL, 1); |
||
379 | |||
380 | /* Open decoder */ |
||
381 | dec_ctx = st->codec; |
||
382 | dec = avcodec_find_decoder(dec_ctx->codec_id); |
||
383 | if (!dec) { |
||
384 | av_log(ctx, AV_LOG_ERROR, "Failed to find subtitle codec %s\n", |
||
385 | avcodec_get_name(dec_ctx->codec_id)); |
||
386 | return AVERROR(EINVAL); |
||
387 | } |
||
388 | dec_desc = avcodec_descriptor_get(dec_ctx->codec_id); |
||
389 | if (dec_desc && !(dec_desc->props & AV_CODEC_PROP_TEXT_SUB)) { |
||
390 | av_log(ctx, AV_LOG_ERROR, |
||
391 | "Only text based subtitles are currently supported\n"); |
||
392 | return AVERROR_PATCHWELCOME; |
||
393 | } |
||
394 | if (ass->charenc) |
||
395 | av_dict_set(&codec_opts, "sub_charenc", ass->charenc, 0); |
||
396 | ret = avcodec_open2(dec_ctx, dec, &codec_opts); |
||
397 | if (ret < 0) |
||
398 | goto end; |
||
399 | |||
400 | if (ass->force_style) { |
||
401 | char **list = NULL; |
||
402 | char *temp = NULL; |
||
403 | char *ptr = av_strtok(ass->force_style, ",", &temp); |
||
404 | int i = 0; |
||
405 | while (ptr) { |
||
406 | av_dynarray_add(&list, &i, ptr); |
||
407 | if (!list) { |
||
408 | ret = AVERROR(ENOMEM); |
||
409 | goto end; |
||
410 | } |
||
411 | ptr = av_strtok(NULL, ",", &temp); |
||
412 | } |
||
413 | av_dynarray_add(&list, &i, NULL); |
||
414 | if (!list) { |
||
415 | ret = AVERROR(ENOMEM); |
||
416 | goto end; |
||
417 | } |
||
418 | ass_set_style_overrides(ass->library, list); |
||
419 | av_free(list); |
||
420 | } |
||
421 | /* Decode subtitles and push them into the renderer (libass) */ |
||
422 | if (dec_ctx->subtitle_header) |
||
423 | ass_process_codec_private(ass->track, |
||
424 | dec_ctx->subtitle_header, |
||
425 | dec_ctx->subtitle_header_size); |
||
426 | av_init_packet(&pkt); |
||
427 | pkt.data = NULL; |
||
428 | pkt.size = 0; |
||
429 | while (av_read_frame(fmt, &pkt) >= 0) { |
||
430 | int i, got_subtitle; |
||
431 | AVSubtitle sub = {0}; |
||
432 | |||
433 | if (pkt.stream_index == sid) { |
||
434 | ret = avcodec_decode_subtitle2(dec_ctx, &sub, &got_subtitle, &pkt); |
||
435 | if (ret < 0) { |
||
436 | av_log(ctx, AV_LOG_WARNING, "Error decoding: %s (ignored)\n", |
||
437 | av_err2str(ret)); |
||
438 | } else if (got_subtitle) { |
||
439 | for (i = 0; i < sub.num_rects; i++) { |
||
440 | char *ass_line = sub.rects[i]->ass; |
||
441 | if (!ass_line) |
||
442 | break; |
||
443 | ass_process_data(ass->track, ass_line, strlen(ass_line)); |
||
444 | } |
||
445 | } |
||
446 | } |
||
447 | av_free_packet(&pkt); |
||
448 | avsubtitle_free(&sub); |
||
449 | } |
||
450 | |||
451 | end: |
||
452 | av_dict_free(&codec_opts); |
||
453 | if (dec_ctx) |
||
454 | avcodec_close(dec_ctx); |
||
455 | if (fmt) |
||
456 | avformat_close_input(&fmt); |
||
457 | return ret; |
||
458 | } |
||
459 | |||
460 | AVFilter ff_vf_subtitles = { |
||
461 | .name = "subtitles", |
||
462 | .description = NULL_IF_CONFIG_SMALL("Render text subtitles onto input video using the libass library."), |
||
463 | .priv_size = sizeof(AssContext), |
||
464 | .init = init_subtitles, |
||
465 | .uninit = uninit, |
||
466 | .query_formats = query_formats, |
||
467 | .inputs = ass_inputs, |
||
468 | .outputs = ass_outputs, |
||
469 | .priv_class = &subtitles_class, |
||
470 | }; |
||
471 | #endif>>>>>>>>>>>>> |