00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00029 #include <ass/ass.h>
00030
00031 #include "libavutil/avstring.h"
00032 #include "libavutil/imgutils.h"
00033 #include "libavutil/opt.h"
00034 #include "libavutil/parseutils.h"
00035 #include "drawutils.h"
00036 #include "avfilter.h"
00037
00038 typedef struct {
00039 const AVClass *class;
00040 ASS_Library *library;
00041 ASS_Renderer *renderer;
00042 ASS_Track *track;
00043 char *filename;
00044 uint8_t rgba_map[4];
00045 int pix_step[4];
00046 int original_w, original_h;
00047 FFDrawContext draw;
00048 } AssContext;
00049
00050 #define OFFSET(x) offsetof(AssContext, x)
00051
00052 static const AVOption ass_options[] = {
00053 {"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 },
00054 {NULL},
00055 };
00056
00057 static const char *ass_get_name(void *ctx)
00058 {
00059 return "ass";
00060 }
00061
00062 static const AVClass ass_class = {
00063 "AssContext",
00064 ass_get_name,
00065 ass_options
00066 };
00067
00068
00069 int ass_libav_log_level_map[] = {
00070 AV_LOG_QUIET,
00071 AV_LOG_PANIC,
00072 AV_LOG_FATAL,
00073 AV_LOG_ERROR,
00074 AV_LOG_WARNING,
00075 AV_LOG_INFO,
00076 AV_LOG_VERBOSE,
00077 AV_LOG_DEBUG,
00078 };
00079
00080 static void ass_log(int ass_level, const char *fmt, va_list args, void *ctx)
00081 {
00082 int level = ass_libav_log_level_map[ass_level];
00083
00084 av_vlog(ctx, level, fmt, args);
00085 av_log(ctx, level, "\n");
00086 }
00087
00088 static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
00089 {
00090 AssContext *ass = ctx->priv;
00091 int ret;
00092
00093 ass->class = &ass_class;
00094 av_opt_set_defaults(ass);
00095
00096 if (args)
00097 ass->filename = av_get_token(&args, ":");
00098 if (!ass->filename || !*ass->filename) {
00099 av_log(ctx, AV_LOG_ERROR, "No filename provided!\n");
00100 return AVERROR(EINVAL);
00101 }
00102
00103 if (*args++ == ':' && (ret = av_set_options_string(ass, args, "=", ":")) < 0) {
00104 av_log(ctx, AV_LOG_ERROR, "Error parsing options string: '%s'\n", args);
00105 return ret;
00106 }
00107
00108 ass->library = ass_library_init();
00109 if (!ass->library) {
00110 av_log(ctx, AV_LOG_ERROR, "Could not initialize libass.\n");
00111 return AVERROR(EINVAL);
00112 }
00113 ass_set_message_cb(ass->library, ass_log, ctx);
00114
00115 ass->renderer = ass_renderer_init(ass->library);
00116 if (!ass->renderer) {
00117 av_log(ctx, AV_LOG_ERROR, "Could not initialize libass renderer.\n");
00118 return AVERROR(EINVAL);
00119 }
00120
00121 ass->track = ass_read_file(ass->library, ass->filename, NULL);
00122 if (!ass->track) {
00123 av_log(ctx, AV_LOG_ERROR,
00124 "Could not create a libass track when reading file '%s'\n",
00125 ass->filename);
00126 return AVERROR(EINVAL);
00127 }
00128
00129 ass_set_fonts(ass->renderer, NULL, NULL, 1, NULL, 1);
00130 return 0;
00131 }
00132
00133 static av_cold void uninit(AVFilterContext *ctx)
00134 {
00135 AssContext *ass = ctx->priv;
00136
00137 av_freep(&ass->filename);
00138 if (ass->track)
00139 ass_free_track(ass->track);
00140 if (ass->renderer)
00141 ass_renderer_done(ass->renderer);
00142 if (ass->library)
00143 ass_library_done(ass->library);
00144 }
00145
00146 static int query_formats(AVFilterContext *ctx)
00147 {
00148 avfilter_set_common_pixel_formats(ctx, ff_draw_supported_pixel_formats(0));
00149 return 0;
00150 }
00151
00152 static int config_input(AVFilterLink *inlink)
00153 {
00154 AssContext *ass = inlink->dst->priv;
00155
00156 ff_draw_init(&ass->draw, inlink->format, 0);
00157
00158 ass_set_frame_size (ass->renderer, inlink->w, inlink->h);
00159 if (ass->original_w && ass->original_h)
00160 ass_set_aspect_ratio(ass->renderer, (double)inlink->w / inlink->h,
00161 (double)ass->original_w / ass->original_h);
00162
00163 return 0;
00164 }
00165
00166 static void null_draw_slice(AVFilterLink *link, int y, int h, int slice_dir) { }
00167
00168
00169 #define AR(c) ( (c)>>24)
00170 #define AG(c) (((c)>>16)&0xFF)
00171 #define AB(c) (((c)>>8) &0xFF)
00172 #define AA(c) ((0xFF-c) &0xFF)
00173
00174 static void overlay_ass_image(AssContext *ass, AVFilterBufferRef *picref,
00175 const ASS_Image *image)
00176 {
00177 for (; image; image = image->next) {
00178 uint8_t rgba_color[] = {AR(image->color), AG(image->color), AB(image->color), AA(image->color)};
00179 FFDrawColor color;
00180 ff_draw_color(&ass->draw, &color, rgba_color);
00181 ff_blend_mask(&ass->draw, &color,
00182 picref->data, picref->linesize,
00183 picref->video->w, picref->video->h,
00184 image->bitmap, image->stride, image->w, image->h,
00185 3, 0, image->dst_x, image->dst_y);
00186 }
00187 }
00188
00189 static void end_frame(AVFilterLink *inlink)
00190 {
00191 AVFilterContext *ctx = inlink->dst;
00192 AVFilterLink *outlink = ctx->outputs[0];
00193 AssContext *ass = ctx->priv;
00194 AVFilterBufferRef *picref = inlink->cur_buf;
00195 int detect_change = 0;
00196 double time_ms = picref->pts * av_q2d(inlink->time_base) * 1000;
00197 ASS_Image *image = ass_render_frame(ass->renderer, ass->track,
00198 time_ms, &detect_change);
00199
00200 if (detect_change)
00201 av_log(ctx, AV_LOG_DEBUG, "Change happened at time ms:%f\n", time_ms);
00202
00203 overlay_ass_image(ass, picref, image);
00204
00205 avfilter_draw_slice(outlink, 0, picref->video->h, 1);
00206 avfilter_end_frame(outlink);
00207 }
00208
00209 AVFilter avfilter_vf_ass = {
00210 .name = "ass",
00211 .description = NULL_IF_CONFIG_SMALL("Render subtitles onto input video using the libass library."),
00212 .priv_size = sizeof(AssContext),
00213 .init = init,
00214 .uninit = uninit,
00215 .query_formats = query_formats,
00216
00217 .inputs = (const AVFilterPad[]) {
00218 { .name = "default",
00219 .type = AVMEDIA_TYPE_VIDEO,
00220 .get_video_buffer = avfilter_null_get_video_buffer,
00221 .start_frame = avfilter_null_start_frame,
00222 .draw_slice = null_draw_slice,
00223 .end_frame = end_frame,
00224 .config_props = config_input,
00225 .min_perms = AV_PERM_WRITE | AV_PERM_READ,
00226 .rej_perms = AV_PERM_PRESERVE },
00227 { .name = NULL}
00228 },
00229 .outputs = (const AVFilterPad[]) {
00230 { .name = "default",
00231 .type = AVMEDIA_TYPE_VIDEO, },
00232 { .name = NULL}
00233 },
00234 };