History

lavfi: add libass based subtitles renderer

Stefano Sabatini committed 15 years ago

Commit 28338bc1 parent 31a13427 files · +268 −2Browse files

Unified · Split

7 files
  1. Changelog +1 −0
  2. configure +5 −0
  3. doc/filters.texi +16 −0
  4. libavfilter/Makefile +1 −0
  5. libavfilter/allfilters.c +1 −0
  6. libavfilter/avfilter.h +2 −2
  7. libavfilter/vf_ass.c +242 −0
Changelog+1−0
@@ -124,6 +124,7 @@ easier to use. The changes are: · 123 unmodified lines
124- compact and csv output in ffprobe124- compact and csv output in ffprobe
125- pan audio filter125- pan audio filter
126- IFF Amiga Continuous Bitmap (ACBM) decoder126- IFF Amiga Continuous Bitmap (ACBM) decoder
127- ass filter
127 128
128 129
129version 0.8:130version 0.8:
configure+5−0
@@ -168,6 +168,7 @@ External library support: · 167 unmodified lines
168 --enable-frei0r enable frei0r video filtering168 --enable-frei0r enable frei0r video filtering
169 --enable-gnutls enable gnutls [no]169 --enable-gnutls enable gnutls [no]
170 --enable-libaacplus enable AAC+ encoding via libaacplus [no]170 --enable-libaacplus enable AAC+ encoding via libaacplus [no]
171 --enable-libass enable libass subtitles rendering [no]
171 --enable-libcelt enable CELT decoding via libcelt [no]172 --enable-libcelt enable CELT decoding via libcelt [no]
172 --enable-libopencore-amrnb enable AMR-NB de/encoding via libopencore-amrnb [no]173 --enable-libopencore-amrnb enable AMR-NB de/encoding via libopencore-amrnb [no]
173 --enable-libopencore-amrwb enable AMR-WB decoding via libopencore-amrwb [no]174 --enable-libopencore-amrwb enable AMR-WB decoding via libopencore-amrwb [no]
@@ -1008,6 +1009,7 @@ CONFIG_LIST=" · 834 unmodified lines
1008 hardcoded_tables1009 hardcoded_tables
1009 huffman1010 huffman
1010 libaacplus1011 libaacplus
1012 libass
1011 libcdio1013 libcdio
1012 libcelt1014 libcelt
1013 libdc13941015 libdc1394
@@ -1613,6 +1615,7 @@ udp_protocol_deps="network" · 599 unmodified lines
1613 1615
1614# filters1616# filters
1615amovie_filter_deps="avcodec avformat"1617amovie_filter_deps="avcodec avformat"
1618ass_filter_deps="libass"
1616blackframe_filter_deps="gpl"1619blackframe_filter_deps="gpl"
1617boxblur_filter_deps="gpl"1620boxblur_filter_deps="gpl"
1618cropdetect_filter_deps="gpl"1621cropdetect_filter_deps="gpl"
@@ -3057,6 +3060,7 @@ enabled avisynth && require2 vfw32 "windows.h vfw.h" AVIFileInit -lavifil32 · 1438 unmodified lines
3057enabled frei0r && { check_header frei0r.h || die "ERROR: frei0r.h header not found"; }3060enabled frei0r && { check_header frei0r.h || die "ERROR: frei0r.h header not found"; }
3058enabled gnutls && require_pkg_config gnutls gnutls/gnutls.h gnutls_global_init3061enabled gnutls && require_pkg_config gnutls gnutls/gnutls.h gnutls_global_init
3059enabled libaacplus && require "libaacplus >= 2.0.0" aacplus.h aacplusEncOpen -laacplus3062enabled libaacplus && require "libaacplus >= 2.0.0" aacplus.h aacplusEncOpen -laacplus
3063enabled libass && require_pkg_config libass ass/ass.h ass_library_init
3060enabled libcelt && require libcelt celt/celt.h celt_decode -lcelt03064enabled libcelt && require libcelt celt/celt.h celt_decode -lcelt0
3061enabled libdc1394 && require_pkg_config libdc1394-2 dc1394/dc1394.h dc1394_new3065enabled libdc1394 && require_pkg_config libdc1394-2 dc1394/dc1394.h dc1394_new
3062enabled libdirac && require_pkg_config dirac \3066enabled libdirac && require_pkg_config dirac \
@@ -3395,6 +3399,7 @@ echo "AVISynth enabled ${avisynth-no}" · 332 unmodified lines
3395echo "frei0r enabled ${frei0r-no}"3399echo "frei0r enabled ${frei0r-no}"
3396echo "gnutls enabled ${gnutls-no}"3400echo "gnutls enabled ${gnutls-no}"
3397echo "libaacplus enabled ${libaacplus-no}"3401echo "libaacplus enabled ${libaacplus-no}"
3402echo "libass enabled ${libass-no}"
3398echo "libcdio support ${libcdio-no}"3403echo "libcdio support ${libcdio-no}"
3399echo "libcelt enabled ${libcelt-no}"3404echo "libcelt enabled ${libcelt-no}"
3400echo "libdc1394 support ${libdc1394-no}"3405echo "libdc1394 support ${libdc1394-no}"
doc/filters.texi+16−0
@@ -574,6 +574,22 @@ build. · 573 unmodified lines
574 574
575Below is a description of the currently available video filters.575Below is a description of the currently available video filters.
576 576
577@section ass
578
579Draw ASS (Advanced Substation Alpha) subtitles on top of input video
580using the libass library.
581
582To enable compilation of this filter you need to configure FFmpeg with
583@code{--enable-libass}.
584
585This filter accepts in input the name of the ass file to render.
586
587For example, to render the file @file{sub.ass} on top of the input
588video, use the command:
589@example
590ass=sub.ass
591@end example
592
577@section blackframe593@section blackframe
578 594
579Detect frames that are (almost) completely black. Can be useful to595Detect frames that are (almost) completely black. Can be useful to
libavfilter/Makefile+1−0
@@ -40,6 +40,7 @@ OBJS-$(CONFIG_ANULLSRC_FILTER) += asrc_anullsrc.o · 39 unmodified lines
40OBJS-$(CONFIG_ABUFFERSINK_FILTER) += sink_buffer.o40OBJS-$(CONFIG_ABUFFERSINK_FILTER) += sink_buffer.o
41OBJS-$(CONFIG_ANULLSINK_FILTER) += asink_anullsink.o41OBJS-$(CONFIG_ANULLSINK_FILTER) += asink_anullsink.o
42 42
43OBJS-$(CONFIG_ASS_FILTER) += vf_ass.o
43OBJS-$(CONFIG_BLACKFRAME_FILTER) += vf_blackframe.o44OBJS-$(CONFIG_BLACKFRAME_FILTER) += vf_blackframe.o
44OBJS-$(CONFIG_BOXBLUR_FILTER) += vf_boxblur.o45OBJS-$(CONFIG_BOXBLUR_FILTER) += vf_boxblur.o
45OBJS-$(CONFIG_COPY_FILTER) += vf_copy.o46OBJS-$(CONFIG_COPY_FILTER) += vf_copy.o
libavfilter/allfilters.c+1−0
@@ -51,6 +51,7 @@ void avfilter_register_all(void) · 50 unmodified lines
51 REGISTER_FILTER (ABUFFERSINK, abuffersink, asink);51 REGISTER_FILTER (ABUFFERSINK, abuffersink, asink);
52 REGISTER_FILTER (ANULLSINK, anullsink, asink);52 REGISTER_FILTER (ANULLSINK, anullsink, asink);
53 53
54 REGISTER_FILTER (ASS, ass, vf);
54 REGISTER_FILTER (BLACKFRAME, blackframe, vf);55 REGISTER_FILTER (BLACKFRAME, blackframe, vf);
55 REGISTER_FILTER (BOXBLUR, boxblur, vf);56 REGISTER_FILTER (BOXBLUR, boxblur, vf);
56 REGISTER_FILTER (COPY, copy, vf);57 REGISTER_FILTER (COPY, copy, vf);
libavfilter/avfilter.h+2−2
@@ -29,8 +29,8 @@ · 28 unmodified lines
29#include "libavutil/rational.h"29#include "libavutil/rational.h"
30 30
31#define LIBAVFILTER_VERSION_MAJOR 231#define LIBAVFILTER_VERSION_MAJOR 2
32#define LIBAVFILTER_VERSION_MINOR 4932#define LIBAVFILTER_VERSION_MINOR 50
33#define LIBAVFILTER_VERSION_MICRO 133#define LIBAVFILTER_VERSION_MICRO 0
34 34
35#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \35#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
36 LIBAVFILTER_VERSION_MINOR, \36 LIBAVFILTER_VERSION_MINOR, \
libavfilter/vf_ass.c+242−0
@@ -0,0 +1,242 @@
1/*
2 * Copyright (c) 2011 Baptiste Coudurier
3 * Copyright (c) 2011 Stefano Sabatini
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/**
23 * @file
24 * Libass subtitles burning filter.
25 *
26 * @see{http://www.matroska.org/technical/specs/subtitles/ssa.html}
27 */
28
29#include <ass/ass.h>
30
31#include "libavutil/avstring.h"
32#include "libavutil/imgutils.h"
33#include "libavutil/pixdesc.h"
34#include "drawutils.h"
35#include "avfilter.h"
36
37typedef struct {
38 ASS_Library *library;
39 ASS_Renderer *renderer;
40 ASS_Track *track;
41 int hsub, vsub;
42 char *filename;
43 uint8_t rgba_map[4];
44 int pix_step[4]; ///< steps per pixel for each plane of the main output
45} AssContext;
46
47/* libass supports a log level ranging from 0 to 7 */
48int ass_libav_log_level_map[] = {
49 AV_LOG_QUIET, /* 0 */
50 AV_LOG_PANIC, /* 1 */
51 AV_LOG_FATAL, /* 2 */
52 AV_LOG_ERROR, /* 3 */
53 AV_LOG_WARNING, /* 4 */
54 AV_LOG_INFO, /* 5 */
55 AV_LOG_VERBOSE, /* 6 */
56 AV_LOG_DEBUG, /* 7 */
57};
58
59static void ass_log(int ass_level, const char *fmt, va_list args, void *ctx)
60{
61 int level = ass_libav_log_level_map[ass_level];
62
63 av_vlog(ctx, level, fmt, args);
64 av_log(ctx, level, "\n");
65}
66
67static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
68{
69 AssContext *ass = ctx->priv;
70
71 if (args)
72 ass->filename = av_get_token(&args, ":");
73 if (!ass->filename || !*ass->filename) {
74 av_log(ctx, AV_LOG_ERROR, "No filename provided!\n");
75 return AVERROR(EINVAL);
76 }
77
78 ass->library = ass_library_init();
79 if (!ass->library) {
80 av_log(ctx, AV_LOG_ERROR, "Could not initialize libass.\n");
81 return AVERROR(EINVAL);
82 }
83 ass_set_message_cb(ass->library, ass_log, ctx);
84
85 ass->renderer = ass_renderer_init(ass->library);
86 if (!ass->renderer) {
87 av_log(ctx, AV_LOG_ERROR, "Could not initialize libass renderer.\n");
88 return AVERROR(EINVAL);
89 }
90
91 ass->track = ass_read_file(ass->library, ass->filename, NULL);
92 if (!ass->track) {
93 av_log(ctx, AV_LOG_ERROR,
94 "Could not create a libass track when reading file '%s'\n",
95 ass->filename);
96 return AVERROR(EINVAL);
97 }
98
99 ass_set_fonts(ass->renderer, NULL, NULL, 1, NULL, 1);
100
101 return 0;
102}
103
104static av_cold void uninit(AVFilterContext *ctx)
105{
106 AssContext *ass = ctx->priv;
107
108 av_freep(&ass->filename);
109 if (ass->track)
110 ass_free_track(ass->track);
111 if (ass->renderer)
112 ass_renderer_done(ass->renderer);
113 if (ass->library)
114 ass_library_done(ass->library);
115}
116
117static int query_formats(AVFilterContext *ctx)
118{
119 static const enum PixelFormat pix_fmts[] = {
120 PIX_FMT_ARGB, PIX_FMT_RGBA,
121 PIX_FMT_ABGR, PIX_FMT_BGRA,
122 PIX_FMT_RGB24, PIX_FMT_BGR24,
123 PIX_FMT_NONE
124 };
125
126 avfilter_set_common_pixel_formats(ctx, avfilter_make_format_list(pix_fmts));
127
128 return 0;
129}
130
131static int config_input(AVFilterLink *inlink)
132{
133 AssContext *ass = inlink->dst->priv;
134 const AVPixFmtDescriptor *pix_desc = &av_pix_fmt_descriptors[inlink->format];
135 double sar = inlink->sample_aspect_ratio.num ?
136 av_q2d(inlink->sample_aspect_ratio) : 1;
137 double dar = inlink->w / inlink->h * sar;
138
139 av_image_fill_max_pixsteps(ass->pix_step, NULL, pix_desc);
140 ff_fill_rgba_map(ass->rgba_map, inlink->format);
141
142 ass->hsub = pix_desc->log2_chroma_w;
143 ass->vsub = pix_desc->log2_chroma_h;
144
145 ass_set_frame_size (ass->renderer, inlink->w, inlink->h);
146 ass_set_aspect_ratio(ass->renderer, dar, sar);
147
148 return 0;
149}
150
151static void null_draw_slice(AVFilterLink *link, int y, int h, int slice_dir) { }
152
153#define R 0
154#define G 1
155#define B 2
156#define A 3
157
158#define SET_PIXEL_RGB(picref, rgba_color, val, x, y, pixel_step, r_off, g_off, b_off) { \
159 p = picref->data[0] + (x) * pixel_step + ((y) * picref->linesize[0]); \
160 alpha = rgba_color[A] * (val) * 129; \
161 *(p+r_off) = (alpha * rgba_color[R] + (255*255*129 - alpha) * *(p+r_off)) >> 23; \
162 *(p+g_off) = (alpha * rgba_color[G] + (255*255*129 - alpha) * *(p+g_off)) >> 23; \
163 *(p+b_off) = (alpha * rgba_color[B] + (255*255*129 - alpha) * *(p+b_off)) >> 23; \
164}
165
166/* libass stores an RGBA color in the format RRGGBBTT, where TT is the transparency level */
167#define AR(c) ( (c)>>24)
168#define AG(c) (((c)>>16)&0xFF)
169#define AB(c) (((c)>>8) &0xFF)
170#define AA(c) ((0xFF-c) &0xFF)
171
172static void overlay_ass_image(AVFilterBufferRef *picref, const uint8_t *rgba_map, const int pix_step,
173 const ASS_Image *image)
174{
175 int i, j;
176 int alpha;
177 uint8_t *p;
178 const int ro = rgba_map[R];
179 const int go = rgba_map[G];
180 const int bo = rgba_map[B];
181
182 for (; image; image = image->next) {
183 uint8_t rgba_color[] = {AR(image->color), AG(image->color), AB(image->color), AA(image->color)};
184 unsigned char *row = image->bitmap;
185
186 for (i = 0; i < image->h; i++) {
187 for (j = 0; j < image->w; j++) {
188 if (row[j]) {
189 SET_PIXEL_RGB(picref, rgba_color, row[j], image->dst_x + j, image->dst_y + i, pix_step, ro, go, bo);
190 }
191 }
192 row += image->stride;
193 }
194 }
195}
196
197static void end_frame(AVFilterLink *inlink)
198{
199 AVFilterContext *ctx = inlink->dst;
200 AVFilterLink *outlink = ctx->outputs[0];
201 AssContext *ass = ctx->priv;
202 AVFilterBufferRef *picref = inlink->cur_buf;
203 int detect_change = 0;
204 double time_ms = picref->pts * av_q2d(inlink->time_base) * 1000;
205 ASS_Image *image = ass_render_frame(ass->renderer, ass->track,
206 time_ms, &detect_change);
207
208 if (detect_change)
209 av_log(ctx, AV_LOG_DEBUG, "Change happened at time ms:%f\n", time_ms);
210
211 overlay_ass_image(picref, ass->rgba_map, ass->pix_step[0], image);
212
213 avfilter_draw_slice(outlink, 0, picref->video->h, 1);
214 avfilter_end_frame(outlink);
215}
216
217AVFilter avfilter_vf_ass = {
218 .name = "ass",
219 .description = NULL_IF_CONFIG_SMALL("Render subtitles onto input video using the libass library."),
220 .priv_size = sizeof(AssContext),
221 .init = init,
222 .uninit = uninit,
223 .query_formats = query_formats,
224
225 .inputs = (const AVFilterPad[]) {
226 { .name = "default",
227 .type = AVMEDIA_TYPE_VIDEO,
228 .get_video_buffer = avfilter_null_get_video_buffer,
229 .start_frame = avfilter_null_start_frame,
230 .draw_slice = null_draw_slice,
231 .end_frame = end_frame,
232 .config_props = config_input,
233 .min_perms = AV_PERM_WRITE | AV_PERM_READ,
234 .rej_perms = AV_PERM_PRESERVE },
235 { .name = NULL}
236 },
237 .outputs = (const AVFilterPad[]) {
238 { .name = "default",
239 .type = AVMEDIA_TYPE_VIDEO, },
240 { .name = NULL}
241 },
242};

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t