From 3313e46c4ae29899a76e85c1f59524f64ce43095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 29 Nov 2012 03:28:37 +0100 Subject: [PATCH] lavfi: add subtitles filter. --- Changelog | 1 + configure | 1 + doc/filters.texi | 72 ++++++++++-------- libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/version.h | 4 +- libavfilter/vf_ass.c | 157 +++++++++++++++++++++++++++++++++++---- 7 files changed, 187 insertions(+), 50 deletions(-) diff --git a/Changelog b/Changelog index 31981d2a2c..c1feaa5fdd 100644 --- a/Changelog +++ b/Changelog @@ -33,6 +33,7 @@ version : - BRSTM demuxer - animated GIF decoder and demuxer - PVF demuxer +- subtitles filter version 1.0: diff --git a/configure b/configure index 588104f323..26280359d7 100755 --- a/configure +++ b/configure @@ -1970,6 +1970,7 @@ removelogo_filter_deps="avcodec avformat swscale" scale_filter_deps="swscale" smartblur_filter_deps="gpl swscale" showspectrum_filter_deps="avcodec rdft" +subtitles_filter_deps="avformat avcodec libass" super2xsai_filter_deps="gpl" tinterlace_filter_deps="gpl" yadif_filter_deps="gpl" diff --git a/doc/filters.texi b/doc/filters.texi index e25f548c98..cf920bfbd4 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -1274,38 +1274,9 @@ overlay to a video stream, consider the @var{overlay} filter instead. @section ass -Draw ASS (Advanced Substation Alpha) subtitles on top of input video -using the libass library. - -To enable compilation of this filter you need to configure FFmpeg with -@code{--enable-libass}. - -This filter accepts the following named options, expressed as a -sequence of @var{key}=@var{value} pairs, separated by ":". - -@table @option -@item filename, f -Set the filename of the ASS file to read. It must be specified. - -@item original_size -Specify the size of the original video, the video for which the ASS file -was composed. Due to a misdesign in ASS aspect ratio arithmetic, this is -necessary to correctly scale the fonts if the aspect ratio has been changed. -@end table - -If the first key is not specified, it is assumed that the first value -specifies the @option{filename}. - -For example, to render the file @file{sub.ass} on top of the input -video, use the command: -@example -ass=sub.ass -@end example - -which is equivalent to: -@example -ass=filename=sub.ass -@end example +Same as the @ref{subtitles} filter, except that it doesn't require libavcodec +and libavformat to work. On the other hand, it is limited to ASS (Advanced +Substation Alpha) subtitles files. @section bbox @@ -3745,6 +3716,43 @@ a pixel should be blurred or not. A value of 0 will filter all the image, a value included in [0,30] will filter flat areas and a value included in [-30,0] will filter edges. +@anchor{subtitles} +@section subtitles + +Draw subtitles on top of input video using the libass library. + +To enable compilation of this filter you need to configure FFmpeg with +@code{--enable-libass}. This filter also requires a build with libavcodec and +libavformat to convert the passed subtitles file to ASS (Advanced Substation +Alpha) subtitles format. + +This filter accepts the following named options, expressed as a +sequence of @var{key}=@var{value} pairs, separated by ":". + +@table @option +@item filename, f +Set the filename of the subtitle file to read. It must be specified. + +@item original_size +Specify the size of the original video, the video for which the ASS file +was composed. Due to a misdesign in ASS aspect ratio arithmetic, this is +necessary to correctly scale the fonts if the aspect ratio has been changed. +@end table + +If the first key is not specified, it is assumed that the first value +specifies the @option{filename}. + +For example, to render the file @file{sub.srt} on top of the input +video, use the command: +@example +subtitles=sub.srt +@end example + +which is equivalent to: +@example +subtitles=filename=sub.srt +@end example + @section split Split input video into several identical outputs. diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 0e4991212a..377bd4d701 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -134,6 +134,7 @@ OBJS-$(CONFIG_SETTB_FILTER) += f_settb.o OBJS-$(CONFIG_SHOWINFO_FILTER) += vf_showinfo.o OBJS-$(CONFIG_SMARTBLUR_FILTER) += vf_smartblur.o OBJS-$(CONFIG_SPLIT_FILTER) += split.o +OBJS-$(CONFIG_SUBTITLES_FILTER) += vf_ass.o OBJS-$(CONFIG_SUPER2XSAI_FILTER) += vf_super2xsai.o OBJS-$(CONFIG_SWAPUV_FILTER) += vf_swapuv.o OBJS-$(CONFIG_THUMBNAIL_FILTER) += vf_thumbnail.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 746e713287..5a1f939781 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -126,6 +126,7 @@ void avfilter_register_all(void) REGISTER_FILTER (SHOWINFO, showinfo, vf); REGISTER_FILTER (SMARTBLUR, smartblur, vf); REGISTER_FILTER (SPLIT, split, vf); + REGISTER_FILTER (SUBTITLES, subtitles, vf); REGISTER_FILTER (SUPER2XSAI, super2xsai, vf); REGISTER_FILTER (SWAPUV, swapuv, vf); REGISTER_FILTER (THUMBNAIL, thumbnail, vf); diff --git a/libavfilter/version.h b/libavfilter/version.h index a4a2e37644..694f4f0ab4 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -29,8 +29,8 @@ #include "libavutil/avutil.h" #define LIBAVFILTER_VERSION_MAJOR 3 -#define LIBAVFILTER_VERSION_MINOR 23 -#define LIBAVFILTER_VERSION_MICRO 105 +#define LIBAVFILTER_VERSION_MINOR 24 +#define LIBAVFILTER_VERSION_MICRO 100 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ LIBAVFILTER_VERSION_MINOR, \ diff --git a/libavfilter/vf_ass.c b/libavfilter/vf_ass.c index 5f96202477..3f0d3a994c 100644 --- a/libavfilter/vf_ass.c +++ b/libavfilter/vf_ass.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2011 Baptiste Coudurier * Copyright (c) 2011 Stefano Sabatini + * Copyright (c) 2012 Clément Bœsch * * This file is part of FFmpeg. * @@ -28,6 +29,11 @@ #include +#include "config.h" +#if CONFIG_SUBTITLES_FILTER +# include "libavcodec/avcodec.h" +# include "libavformat/avformat.h" +#endif #include "libavutil/avstring.h" #include "libavutil/imgutils.h" #include "libavutil/opt.h" @@ -53,15 +59,13 @@ typedef struct { #define OFFSET(x) offsetof(AssContext, x) #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM -static const AVOption ass_options[] = { - {"filename", "set the filename of the ASS file to read", OFFSET(filename), AV_OPT_TYPE_STRING, {.str = NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, - {"f", "set the filename of the ASS file to read", OFFSET(filename), AV_OPT_TYPE_STRING, {.str = NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, +static const AVOption options[] = { + {"filename", "set the filename of file to read", OFFSET(filename), AV_OPT_TYPE_STRING, {.str = NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, + {"f", "set the filename of file to read", OFFSET(filename), AV_OPT_TYPE_STRING, {.str = NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, {"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 }, {NULL}, }; -AVFILTER_DEFINE_CLASS(ass); - /* libass supports a log level ranging from 0 to 7 */ static const int ass_libavfilter_log_level_map[] = { AV_LOG_QUIET, /* 0 */ @@ -82,13 +86,13 @@ static void ass_log(int ass_level, const char *fmt, va_list args, void *ctx) av_log(ctx, level, "\n"); } -static av_cold int init(AVFilterContext *ctx, const char *args) +static av_cold int init(AVFilterContext *ctx, const char *args, const AVClass *class) { AssContext *ass = ctx->priv; static const char *shorthand[] = { "filename", NULL }; int ret; - ass->class = &ass_class; + ass->class = class; av_opt_set_defaults(ass); if ((ret = av_opt_set_from_string(ass, args, shorthand, "=", ":")) < 0) @@ -112,14 +116,6 @@ static av_cold int init(AVFilterContext *ctx, const char *args) return AVERROR(EINVAL); } - ass->track = ass_read_file(ass->library, ass->filename, NULL); - if (!ass->track) { - av_log(ctx, AV_LOG_ERROR, - "Could not create a libass track when reading file '%s'\n", - ass->filename); - return AVERROR(EINVAL); - } - ass_set_fonts(ass->renderer, NULL, NULL, 1, NULL, 1); return 0; } @@ -215,14 +211,143 @@ static const AVFilterPad ass_outputs[] = { { NULL } }; +#if CONFIG_ASS_FILTER + +#define ass_options options +AVFILTER_DEFINE_CLASS(ass); + +static av_cold int init_ass(AVFilterContext *ctx, const char *args) +{ + AssContext *ass = ctx->priv; + int ret = init(ctx, args, &ass_class); + + if (ret < 0) + return ret; + + ass->track = ass_read_file(ass->library, ass->filename, NULL); + if (!ass->track) { + av_log(ctx, AV_LOG_ERROR, + "Could not create a libass track when reading file '%s'\n", + ass->filename); + return AVERROR(EINVAL); + } + return 0; +} + AVFilter avfilter_vf_ass = { .name = "ass", .description = NULL_IF_CONFIG_SMALL("Render subtitles onto input video using the libass library."), .priv_size = sizeof(AssContext), - .init = init, + .init = init_ass, .uninit = uninit, .query_formats = query_formats, .inputs = ass_inputs, .outputs = ass_outputs, .priv_class = &ass_class, }; +#endif + +#if CONFIG_SUBTITLES_FILTER + +#define subtitles_options options +AVFILTER_DEFINE_CLASS(subtitles); + +static av_cold int init_subtitles(AVFilterContext *ctx, const char *args) +{ + int ret, sid; + AVFormatContext *fmt = NULL; + AVCodecContext *dec_ctx = NULL; + AVCodec *dec = NULL; + AVStream *st; + AVPacket pkt; + AssContext *ass = ctx->priv; + + /* Init libass */ + ret = init(ctx, args, &subtitles_class); + if (ret < 0) + return ret; + ass->track = ass_new_track(ass->library); + if (!ass->track) { + av_log(ctx, AV_LOG_ERROR, "Could not create a libass track\n"); + return AVERROR(EINVAL); + } + + /* Open subtitles file */ + ret = avformat_open_input(&fmt, ass->filename, NULL, NULL); + if (ret < 0) { + av_log(ctx, AV_LOG_ERROR, "Unable to open %s\n", ass->filename); + goto end; + } + ret = avformat_find_stream_info(fmt, NULL); + if (ret < 0) + goto end; + + /* Locate subtitles stream */ + ret = av_find_best_stream(fmt, AVMEDIA_TYPE_SUBTITLE, -1, -1, NULL, 0); + if (ret < 0) { + av_log(ctx, AV_LOG_ERROR, "Unable to locate subtitle stream in %s\n", + ass->filename); + goto end; + } + sid = ret; + st = fmt->streams[sid]; + + /* Open decoder */ + dec_ctx = st->codec; + dec = avcodec_find_decoder(dec_ctx->codec_id); + if (!dec) { + av_log(ctx, AV_LOG_ERROR, "Failed to find subtitle codec %s\n", + avcodec_get_name(dec_ctx->codec_id)); + return AVERROR(EINVAL); + } + ret = avcodec_open2(dec_ctx, dec, NULL); + if (ret < 0) + goto end; + + /* Decode subtitles and push them into the renderer (libass) */ + if (dec_ctx->subtitle_header) + ass_process_codec_private(ass->track, + dec_ctx->subtitle_header, + dec_ctx->subtitle_header_size); + av_init_packet(&pkt); + pkt.data = NULL; + pkt.size = 0; + while (av_read_frame(fmt, &pkt) >= 0) { + int i, got_subtitle; + AVSubtitle sub; + + if (pkt.stream_index == sid) { + ret = avcodec_decode_subtitle2(dec_ctx, &sub, &got_subtitle, &pkt); + if (ret < 0 || !got_subtitle) + break; + for (i = 0; i < sub.num_rects; i++) { + char *ass_line = sub.rects[i]->ass; + if (!ass_line) + break; + ass_process_data(ass->track, ass_line, strlen(ass_line)); + } + } + av_free_packet(&pkt); + avsubtitle_free(&sub); + } + +end: + if (fmt) + avformat_close_input(&fmt); + if (dec_ctx) + avcodec_close(dec_ctx); + return ret; +} + +AVFilter avfilter_vf_subtitles = { + .name = "subtitles", + .description = NULL_IF_CONFIG_SMALL("Render subtitles onto input video using the libass library."), + .priv_size = sizeof(AssContext), + .init = init_subtitles, + .uninit = uninit, + .query_formats = query_formats, + .inputs = ass_inputs, + .outputs = ass_outputs, + .priv_class = &subtitles_class, +}; +#endif