Commit 3313e46c authored by Clément Bœsch's avatar Clément Bœsch

lavfi: add subtitles filter.

parent b5eb630e
...@@ -33,6 +33,7 @@ version <next>: ...@@ -33,6 +33,7 @@ version <next>:
- BRSTM demuxer - BRSTM demuxer
- animated GIF decoder and demuxer - animated GIF decoder and demuxer
- PVF demuxer - PVF demuxer
- subtitles filter
version 1.0: version 1.0:
......
...@@ -1970,6 +1970,7 @@ removelogo_filter_deps="avcodec avformat swscale" ...@@ -1970,6 +1970,7 @@ removelogo_filter_deps="avcodec avformat swscale"
scale_filter_deps="swscale" scale_filter_deps="swscale"
smartblur_filter_deps="gpl swscale" smartblur_filter_deps="gpl swscale"
showspectrum_filter_deps="avcodec rdft" showspectrum_filter_deps="avcodec rdft"
subtitles_filter_deps="avformat avcodec libass"
super2xsai_filter_deps="gpl" super2xsai_filter_deps="gpl"
tinterlace_filter_deps="gpl" tinterlace_filter_deps="gpl"
yadif_filter_deps="gpl" yadif_filter_deps="gpl"
......
...@@ -1274,38 +1274,9 @@ overlay to a video stream, consider the @var{overlay} filter instead. ...@@ -1274,38 +1274,9 @@ overlay to a video stream, consider the @var{overlay} filter instead.
@section ass @section ass
Draw ASS (Advanced Substation Alpha) subtitles on top of input video Same as the @ref{subtitles} filter, except that it doesn't require libavcodec
using the libass library. and libavformat to work. On the other hand, it is limited to ASS (Advanced
Substation Alpha) subtitles files.
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
@section bbox @section bbox
...@@ -3745,6 +3716,43 @@ a pixel should be blurred or not. A value of 0 will filter all the ...@@ -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 image, a value included in [0,30] will filter flat areas and a value
included in [-30,0] will filter edges. 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 @section split
Split input video into several identical outputs. Split input video into several identical outputs.
......
...@@ -134,6 +134,7 @@ OBJS-$(CONFIG_SETTB_FILTER) += f_settb.o ...@@ -134,6 +134,7 @@ OBJS-$(CONFIG_SETTB_FILTER) += f_settb.o
OBJS-$(CONFIG_SHOWINFO_FILTER) += vf_showinfo.o OBJS-$(CONFIG_SHOWINFO_FILTER) += vf_showinfo.o
OBJS-$(CONFIG_SMARTBLUR_FILTER) += vf_smartblur.o OBJS-$(CONFIG_SMARTBLUR_FILTER) += vf_smartblur.o
OBJS-$(CONFIG_SPLIT_FILTER) += split.o OBJS-$(CONFIG_SPLIT_FILTER) += split.o
OBJS-$(CONFIG_SUBTITLES_FILTER) += vf_ass.o
OBJS-$(CONFIG_SUPER2XSAI_FILTER) += vf_super2xsai.o OBJS-$(CONFIG_SUPER2XSAI_FILTER) += vf_super2xsai.o
OBJS-$(CONFIG_SWAPUV_FILTER) += vf_swapuv.o OBJS-$(CONFIG_SWAPUV_FILTER) += vf_swapuv.o
OBJS-$(CONFIG_THUMBNAIL_FILTER) += vf_thumbnail.o OBJS-$(CONFIG_THUMBNAIL_FILTER) += vf_thumbnail.o
......
...@@ -126,6 +126,7 @@ void avfilter_register_all(void) ...@@ -126,6 +126,7 @@ void avfilter_register_all(void)
REGISTER_FILTER (SHOWINFO, showinfo, vf); REGISTER_FILTER (SHOWINFO, showinfo, vf);
REGISTER_FILTER (SMARTBLUR, smartblur, vf); REGISTER_FILTER (SMARTBLUR, smartblur, vf);
REGISTER_FILTER (SPLIT, split, vf); REGISTER_FILTER (SPLIT, split, vf);
REGISTER_FILTER (SUBTITLES, subtitles, vf);
REGISTER_FILTER (SUPER2XSAI, super2xsai, vf); REGISTER_FILTER (SUPER2XSAI, super2xsai, vf);
REGISTER_FILTER (SWAPUV, swapuv, vf); REGISTER_FILTER (SWAPUV, swapuv, vf);
REGISTER_FILTER (THUMBNAIL, thumbnail, vf); REGISTER_FILTER (THUMBNAIL, thumbnail, vf);
......
...@@ -29,8 +29,8 @@ ...@@ -29,8 +29,8 @@
#include "libavutil/avutil.h" #include "libavutil/avutil.h"
#define LIBAVFILTER_VERSION_MAJOR 3 #define LIBAVFILTER_VERSION_MAJOR 3
#define LIBAVFILTER_VERSION_MINOR 23 #define LIBAVFILTER_VERSION_MINOR 24
#define LIBAVFILTER_VERSION_MICRO 105 #define LIBAVFILTER_VERSION_MICRO 100
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
LIBAVFILTER_VERSION_MINOR, \ LIBAVFILTER_VERSION_MINOR, \
......
/* /*
* Copyright (c) 2011 Baptiste Coudurier * Copyright (c) 2011 Baptiste Coudurier
* Copyright (c) 2011 Stefano Sabatini * Copyright (c) 2011 Stefano Sabatini
* Copyright (c) 2012 Clément Bœsch
* *
* This file is part of FFmpeg. * This file is part of FFmpeg.
* *
...@@ -28,6 +29,11 @@ ...@@ -28,6 +29,11 @@
#include <ass/ass.h> #include <ass/ass.h>
#include "config.h"
#if CONFIG_SUBTITLES_FILTER
# include "libavcodec/avcodec.h"
# include "libavformat/avformat.h"
#endif
#include "libavutil/avstring.h" #include "libavutil/avstring.h"
#include "libavutil/imgutils.h" #include "libavutil/imgutils.h"
#include "libavutil/opt.h" #include "libavutil/opt.h"
...@@ -53,15 +59,13 @@ typedef struct { ...@@ -53,15 +59,13 @@ typedef struct {
#define OFFSET(x) offsetof(AssContext, x) #define OFFSET(x) offsetof(AssContext, x)
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
static const AVOption ass_options[] = { static const AVOption options[] = {
{"filename", "set the filename of the ASS file to read", OFFSET(filename), AV_OPT_TYPE_STRING, {.str = NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, {"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 the ASS 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 }, {"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}, {NULL},
}; };
AVFILTER_DEFINE_CLASS(ass);
/* libass supports a log level ranging from 0 to 7 */ /* libass supports a log level ranging from 0 to 7 */
static const int ass_libavfilter_log_level_map[] = { static const int ass_libavfilter_log_level_map[] = {
AV_LOG_QUIET, /* 0 */ AV_LOG_QUIET, /* 0 */
...@@ -82,13 +86,13 @@ static void ass_log(int ass_level, const char *fmt, va_list args, void *ctx) ...@@ -82,13 +86,13 @@ static void ass_log(int ass_level, const char *fmt, va_list args, void *ctx)
av_log(ctx, level, "\n"); 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; AssContext *ass = ctx->priv;
static const char *shorthand[] = { "filename", NULL }; static const char *shorthand[] = { "filename", NULL };
int ret; int ret;
ass->class = &ass_class; ass->class = class;
av_opt_set_defaults(ass); av_opt_set_defaults(ass);
if ((ret = av_opt_set_from_string(ass, args, shorthand, "=", ":")) < 0) 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) ...@@ -112,14 +116,6 @@ static av_cold int init(AVFilterContext *ctx, const char *args)
return AVERROR(EINVAL); 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); ass_set_fonts(ass->renderer, NULL, NULL, 1, NULL, 1);
return 0; return 0;
} }
...@@ -215,14 +211,143 @@ static const AVFilterPad ass_outputs[] = { ...@@ -215,14 +211,143 @@ static const AVFilterPad ass_outputs[] = {
{ NULL } { 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 = { AVFilter avfilter_vf_ass = {
.name = "ass", .name = "ass",
.description = NULL_IF_CONFIG_SMALL("Render subtitles onto input video using the libass library."), .description = NULL_IF_CONFIG_SMALL("Render subtitles onto input video using the libass library."),
.priv_size = sizeof(AssContext), .priv_size = sizeof(AssContext),
.init = init, .init = init_ass,
.uninit = uninit, .uninit = uninit,
.query_formats = query_formats, .query_formats = query_formats,
.inputs = ass_inputs, .inputs = ass_inputs,
.outputs = ass_outputs, .outputs = ass_outputs,
.priv_class = &ass_class, .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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment