yading@11: /* yading@11: * Copyright (c) 2011 Baptiste Coudurier yading@11: * Copyright (c) 2011 Stefano Sabatini yading@11: * Copyright (c) 2012 Clément Bœsch yading@11: * yading@11: * This file is part of FFmpeg. yading@11: * yading@11: * FFmpeg is free software; you can redistribute it and/or yading@11: * modify it under the terms of the GNU Lesser General Public yading@11: * License as published by the Free Software Foundation; either yading@11: * version 2.1 of the License, or (at your option) any later version. yading@11: * yading@11: * FFmpeg is distributed in the hope that it will be useful, yading@11: * but WITHOUT ANY WARRANTY; without even the implied warranty of yading@11: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU yading@11: * Lesser General Public License for more details. yading@11: * yading@11: * You should have received a copy of the GNU Lesser General Public yading@11: * License along with FFmpeg; if not, write to the Free Software yading@11: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA yading@11: */ yading@11: yading@11: /** yading@11: * @file yading@11: * Libass subtitles burning filter. yading@11: * yading@11: * @see{http://www.matroska.org/technical/specs/subtitles/ssa.html} yading@11: */ yading@11: yading@11: #include yading@11: yading@11: #include "config.h" yading@11: #if CONFIG_SUBTITLES_FILTER yading@11: # include "libavcodec/avcodec.h" yading@11: # include "libavformat/avformat.h" yading@11: #endif yading@11: #include "libavutil/avstring.h" yading@11: #include "libavutil/imgutils.h" yading@11: #include "libavutil/opt.h" yading@11: #include "libavutil/parseutils.h" yading@11: #include "drawutils.h" yading@11: #include "avfilter.h" yading@11: #include "internal.h" yading@11: #include "formats.h" yading@11: #include "video.h" yading@11: yading@11: typedef struct { yading@11: const AVClass *class; yading@11: ASS_Library *library; yading@11: ASS_Renderer *renderer; yading@11: ASS_Track *track; yading@11: char *filename; yading@11: char *charenc; yading@11: uint8_t rgba_map[4]; yading@11: int pix_step[4]; ///< steps per pixel for each plane of the main output yading@11: int original_w, original_h; yading@11: FFDrawContext draw; yading@11: } AssContext; yading@11: yading@11: #define OFFSET(x) offsetof(AssContext, x) yading@11: #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM yading@11: yading@11: #define COMMON_OPTIONS \ yading@11: {"filename", "set the filename of file to read", OFFSET(filename), AV_OPT_TYPE_STRING, {.str = NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, \ yading@11: {"f", "set the filename of file to read", OFFSET(filename), AV_OPT_TYPE_STRING, {.str = NULL}, CHAR_MIN, CHAR_MAX, FLAGS }, \ yading@11: {"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 }, \ yading@11: yading@11: /* libass supports a log level ranging from 0 to 7 */ yading@11: static const int ass_libavfilter_log_level_map[] = { yading@11: AV_LOG_QUIET, /* 0 */ yading@11: AV_LOG_PANIC, /* 1 */ yading@11: AV_LOG_FATAL, /* 2 */ yading@11: AV_LOG_ERROR, /* 3 */ yading@11: AV_LOG_WARNING, /* 4 */ yading@11: AV_LOG_INFO, /* 5 */ yading@11: AV_LOG_VERBOSE, /* 6 */ yading@11: AV_LOG_DEBUG, /* 7 */ yading@11: }; yading@11: yading@11: static void ass_log(int ass_level, const char *fmt, va_list args, void *ctx) yading@11: { yading@11: int level = ass_libavfilter_log_level_map[ass_level]; yading@11: yading@11: av_vlog(ctx, level, fmt, args); yading@11: av_log(ctx, level, "\n"); yading@11: } yading@11: yading@11: static av_cold int init(AVFilterContext *ctx) yading@11: { yading@11: AssContext *ass = ctx->priv; yading@11: yading@11: if (!ass->filename) { yading@11: av_log(ctx, AV_LOG_ERROR, "No filename provided!\n"); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: yading@11: ass->library = ass_library_init(); yading@11: if (!ass->library) { yading@11: av_log(ctx, AV_LOG_ERROR, "Could not initialize libass.\n"); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: ass_set_message_cb(ass->library, ass_log, ctx); yading@11: yading@11: ass->renderer = ass_renderer_init(ass->library); yading@11: if (!ass->renderer) { yading@11: av_log(ctx, AV_LOG_ERROR, "Could not initialize libass renderer.\n"); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: yading@11: ass_set_fonts(ass->renderer, NULL, NULL, 1, NULL, 1); yading@11: return 0; yading@11: } yading@11: yading@11: static av_cold void uninit(AVFilterContext *ctx) yading@11: { yading@11: AssContext *ass = ctx->priv; yading@11: yading@11: if (ass->track) yading@11: ass_free_track(ass->track); yading@11: if (ass->renderer) yading@11: ass_renderer_done(ass->renderer); yading@11: if (ass->library) yading@11: ass_library_done(ass->library); yading@11: } yading@11: yading@11: static int query_formats(AVFilterContext *ctx) yading@11: { yading@11: ff_set_common_formats(ctx, ff_draw_supported_pixel_formats(0)); yading@11: return 0; yading@11: } yading@11: yading@11: static int config_input(AVFilterLink *inlink) yading@11: { yading@11: AssContext *ass = inlink->dst->priv; yading@11: yading@11: ff_draw_init(&ass->draw, inlink->format, 0); yading@11: yading@11: ass_set_frame_size (ass->renderer, inlink->w, inlink->h); yading@11: if (ass->original_w && ass->original_h) yading@11: ass_set_aspect_ratio(ass->renderer, (double)inlink->w / inlink->h, yading@11: (double)ass->original_w / ass->original_h); yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: /* libass stores an RGBA color in the format RRGGBBTT, where TT is the transparency level */ yading@11: #define AR(c) ( (c)>>24) yading@11: #define AG(c) (((c)>>16)&0xFF) yading@11: #define AB(c) (((c)>>8) &0xFF) yading@11: #define AA(c) ((0xFF-c) &0xFF) yading@11: yading@11: static void overlay_ass_image(AssContext *ass, AVFrame *picref, yading@11: const ASS_Image *image) yading@11: { yading@11: for (; image; image = image->next) { yading@11: uint8_t rgba_color[] = {AR(image->color), AG(image->color), AB(image->color), AA(image->color)}; yading@11: FFDrawColor color; yading@11: ff_draw_color(&ass->draw, &color, rgba_color); yading@11: ff_blend_mask(&ass->draw, &color, yading@11: picref->data, picref->linesize, yading@11: picref->width, picref->height, yading@11: image->bitmap, image->stride, image->w, image->h, yading@11: 3, 0, image->dst_x, image->dst_y); yading@11: } yading@11: } yading@11: yading@11: static int filter_frame(AVFilterLink *inlink, AVFrame *picref) yading@11: { yading@11: AVFilterContext *ctx = inlink->dst; yading@11: AVFilterLink *outlink = ctx->outputs[0]; yading@11: AssContext *ass = ctx->priv; yading@11: int detect_change = 0; yading@11: double time_ms = picref->pts * av_q2d(inlink->time_base) * 1000; yading@11: ASS_Image *image = ass_render_frame(ass->renderer, ass->track, yading@11: time_ms, &detect_change); yading@11: yading@11: if (detect_change) yading@11: av_log(ctx, AV_LOG_DEBUG, "Change happened at time ms:%f\n", time_ms); yading@11: yading@11: overlay_ass_image(ass, picref, image); yading@11: yading@11: return ff_filter_frame(outlink, picref); yading@11: } yading@11: yading@11: static const AVFilterPad ass_inputs[] = { yading@11: { yading@11: .name = "default", yading@11: .type = AVMEDIA_TYPE_VIDEO, yading@11: .filter_frame = filter_frame, yading@11: .config_props = config_input, yading@11: .needs_writable = 1, yading@11: }, yading@11: { NULL } yading@11: }; yading@11: yading@11: static const AVFilterPad ass_outputs[] = { yading@11: { yading@11: .name = "default", yading@11: .type = AVMEDIA_TYPE_VIDEO, yading@11: }, yading@11: { NULL } yading@11: }; yading@11: yading@11: #if CONFIG_ASS_FILTER yading@11: yading@11: static const AVOption ass_options[] = { yading@11: COMMON_OPTIONS yading@11: {NULL}, yading@11: }; yading@11: yading@11: AVFILTER_DEFINE_CLASS(ass); yading@11: yading@11: static av_cold int init_ass(AVFilterContext *ctx) yading@11: { yading@11: AssContext *ass = ctx->priv; yading@11: int ret = init(ctx); yading@11: yading@11: if (ret < 0) yading@11: return ret; yading@11: yading@11: ass->track = ass_read_file(ass->library, ass->filename, NULL); yading@11: if (!ass->track) { yading@11: av_log(ctx, AV_LOG_ERROR, yading@11: "Could not create a libass track when reading file '%s'\n", yading@11: ass->filename); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: return 0; yading@11: } yading@11: yading@11: AVFilter avfilter_vf_ass = { yading@11: .name = "ass", yading@11: .description = NULL_IF_CONFIG_SMALL("Render ASS subtitles onto input video using the libass library."), yading@11: .priv_size = sizeof(AssContext), yading@11: .init = init_ass, yading@11: .uninit = uninit, yading@11: .query_formats = query_formats, yading@11: .inputs = ass_inputs, yading@11: .outputs = ass_outputs, yading@11: .priv_class = &ass_class, yading@11: }; yading@11: #endif yading@11: yading@11: #if CONFIG_SUBTITLES_FILTER yading@11: yading@11: static const AVOption subtitles_options[] = { yading@11: COMMON_OPTIONS yading@11: {"charenc", "set input character encoding", OFFSET(charenc), AV_OPT_TYPE_STRING, {.str = NULL}, CHAR_MIN, CHAR_MAX, FLAGS}, yading@11: {NULL}, yading@11: }; yading@11: yading@11: AVFILTER_DEFINE_CLASS(subtitles); yading@11: yading@11: static av_cold int init_subtitles(AVFilterContext *ctx) yading@11: { yading@11: int ret, sid; yading@11: AVDictionary *codec_opts = NULL; yading@11: AVFormatContext *fmt = NULL; yading@11: AVCodecContext *dec_ctx = NULL; yading@11: AVCodec *dec = NULL; yading@11: const AVCodecDescriptor *dec_desc; yading@11: AVStream *st; yading@11: AVPacket pkt; yading@11: AssContext *ass = ctx->priv; yading@11: yading@11: /* Init libass */ yading@11: ret = init(ctx); yading@11: if (ret < 0) yading@11: return ret; yading@11: ass->track = ass_new_track(ass->library); yading@11: if (!ass->track) { yading@11: av_log(ctx, AV_LOG_ERROR, "Could not create a libass track\n"); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: yading@11: /* Open subtitles file */ yading@11: ret = avformat_open_input(&fmt, ass->filename, NULL, NULL); yading@11: if (ret < 0) { yading@11: av_log(ctx, AV_LOG_ERROR, "Unable to open %s\n", ass->filename); yading@11: goto end; yading@11: } yading@11: ret = avformat_find_stream_info(fmt, NULL); yading@11: if (ret < 0) yading@11: goto end; yading@11: yading@11: /* Locate subtitles stream */ yading@11: ret = av_find_best_stream(fmt, AVMEDIA_TYPE_SUBTITLE, -1, -1, NULL, 0); yading@11: if (ret < 0) { yading@11: av_log(ctx, AV_LOG_ERROR, "Unable to locate subtitle stream in %s\n", yading@11: ass->filename); yading@11: goto end; yading@11: } yading@11: sid = ret; yading@11: st = fmt->streams[sid]; yading@11: yading@11: /* Open decoder */ yading@11: dec_ctx = st->codec; yading@11: dec = avcodec_find_decoder(dec_ctx->codec_id); yading@11: if (!dec) { yading@11: av_log(ctx, AV_LOG_ERROR, "Failed to find subtitle codec %s\n", yading@11: avcodec_get_name(dec_ctx->codec_id)); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: dec_desc = avcodec_descriptor_get(dec_ctx->codec_id); yading@11: if (dec_desc && !(dec_desc->props & AV_CODEC_PROP_TEXT_SUB)) { yading@11: av_log(ctx, AV_LOG_ERROR, yading@11: "Only text based subtitles are currently supported\n"); yading@11: return AVERROR_PATCHWELCOME; yading@11: } yading@11: if (ass->charenc) yading@11: av_dict_set(&codec_opts, "sub_charenc", ass->charenc, 0); yading@11: ret = avcodec_open2(dec_ctx, dec, &codec_opts); yading@11: if (ret < 0) yading@11: goto end; yading@11: yading@11: /* Decode subtitles and push them into the renderer (libass) */ yading@11: if (dec_ctx->subtitle_header) yading@11: ass_process_codec_private(ass->track, yading@11: dec_ctx->subtitle_header, yading@11: dec_ctx->subtitle_header_size); yading@11: av_init_packet(&pkt); yading@11: pkt.data = NULL; yading@11: pkt.size = 0; yading@11: while (av_read_frame(fmt, &pkt) >= 0) { yading@11: int i, got_subtitle; yading@11: AVSubtitle sub; yading@11: yading@11: if (pkt.stream_index == sid) { yading@11: ret = avcodec_decode_subtitle2(dec_ctx, &sub, &got_subtitle, &pkt); yading@11: if (ret < 0) { yading@11: av_log(ctx, AV_LOG_WARNING, "Error decoding: %s (ignored)\n", yading@11: av_err2str(ret)); yading@11: } else if (got_subtitle) { yading@11: for (i = 0; i < sub.num_rects; i++) { yading@11: char *ass_line = sub.rects[i]->ass; yading@11: if (!ass_line) yading@11: break; yading@11: ass_process_data(ass->track, ass_line, strlen(ass_line)); yading@11: } yading@11: } yading@11: } yading@11: av_free_packet(&pkt); yading@11: avsubtitle_free(&sub); yading@11: } yading@11: yading@11: end: yading@11: av_dict_free(&codec_opts); yading@11: if (dec_ctx) yading@11: avcodec_close(dec_ctx); yading@11: if (fmt) yading@11: avformat_close_input(&fmt); yading@11: return ret; yading@11: } yading@11: yading@11: AVFilter avfilter_vf_subtitles = { yading@11: .name = "subtitles", yading@11: .description = NULL_IF_CONFIG_SMALL("Render text subtitles onto input video using the libass library."), yading@11: .priv_size = sizeof(AssContext), yading@11: .init = init_subtitles, yading@11: .uninit = uninit, yading@11: .query_formats = query_formats, yading@11: .inputs = ass_inputs, yading@11: .outputs = ass_outputs, yading@11: .priv_class = &subtitles_class, yading@11: }; yading@11: #endif