Browse code

lavfi: add libass based subtitles renderer

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