diff --git a/Makefile b/Makefile index d3e203a852..389a519055 100644 --- a/Makefile +++ b/Makefile @@ -231,6 +231,7 @@ SOURCES = talloc.c \ sub/find_subfiles.c \ sub/img_convert.c \ sub/sd_lavc.c \ + sub/sd_lavc_conv.c \ sub/sd_microdvd.c \ sub/sd_movtext.c \ sub/sd_spu.c \ diff --git a/sub/dec_sub.c b/sub/dec_sub.c index 49ecd5c009..ec9af94abb 100644 --- a/sub/dec_sub.c +++ b/sub/dec_sub.c @@ -35,6 +35,7 @@ extern const struct sd_functions sd_spu; extern const struct sd_functions sd_movtext; extern const struct sd_functions sd_srt; extern const struct sd_functions sd_microdvd; +extern const struct sd_functions sd_lavc_conv; static const struct sd_functions *sd_list[] = { #ifdef CONFIG_ASS @@ -45,6 +46,7 @@ static const struct sd_functions *sd_list[] = { &sd_movtext, &sd_srt, &sd_microdvd, + &sd_lavc_conv, NULL }; @@ -159,6 +161,14 @@ static void read_sub_data(struct dec_sub *sub, struct sub_data *subdata) sub_decode(sub, &pkt); } + struct sd *sd = sub_get_last_sd(sub); + // Hack for broken FFmpeg packet format: make sd_ass keep the subtitle + // events on reset(), even though broken FFmpeg ASS packets were received + // (from sd_lavc_conv.c). Normally, these events are removed on seek/reset, + // but this is obviously unwanted in this case. + if (sd && sd->driver->fix_events) + sd->driver->fix_events(sd); + talloc_free(temp); } diff --git a/sub/sd.h b/sub/sd.h index dbb6af835f..20dce8003d 100644 --- a/sub/sd.h +++ b/sub/sd.h @@ -44,6 +44,8 @@ struct sd_functions { void (*reset)(struct sd *sd); void (*uninit)(struct sd *sd); + void (*fix_events)(struct sd *sd); + // decoder void (*get_bitmaps)(struct sd *sd, struct mp_osd_res dim, double pts, struct sub_bitmaps *res); diff --git a/sub/sd_ass.c b/sub/sd_ass.c index 405cef323a..d8951df96f 100644 --- a/sub/sd_ass.c +++ b/sub/sd_ass.c @@ -254,6 +254,12 @@ static char *get_text(struct sd *sd, double pts) return ctx->last_text; } +static void fix_events(struct sd *sd) +{ + struct sd_ass_priv *ctx = sd->priv; + ctx->flush_on_seek = false; +} + static void reset(struct sd *sd) { struct sd_ass_priv *ctx = sd->priv; @@ -281,6 +287,7 @@ const struct sd_functions sd_ass = { .decode = decode, .get_bitmaps = get_bitmaps, .get_text = get_text, + .fix_events = fix_events, .reset = reset, .uninit = uninit, }; diff --git a/sub/sd_lavc_conv.c b/sub/sd_lavc_conv.c new file mode 100644 index 0000000000..e45e743499 --- /dev/null +++ b/sub/sd_lavc_conv.c @@ -0,0 +1,132 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mpv. If not, see . + */ + +#include +#include + +#include +#include +#include + +#include "talloc.h" +#include "core/mp_msg.h" +#include "core/av_common.h" +#include "sd.h" + +struct sd_lavc_priv { + AVCodecContext *avctx; +}; + +static bool supports_format(const char *format) +{ + enum AVCodecID cid = mp_codec_to_av_codec_id(format); + const AVCodecDescriptor *desc = avcodec_descriptor_get(cid); + // These are documented to support AVSubtitleRect->ass. + return desc && (desc->props & AV_CODEC_PROP_TEXT_SUB); +} + +static int init(struct sd *sd) +{ + struct sd_lavc_priv *priv = talloc_zero(NULL, struct sd_lavc_priv); + AVCodecContext *avctx = NULL; + AVCodec *codec = avcodec_find_decoder(mp_codec_to_av_codec_id(sd->codec)); + if (!codec) + goto error; + avctx = avcodec_alloc_context3(codec); + if (!avctx) + goto error; + avctx->extradata_size = sd->extradata_len; + avctx->extradata = sd->extradata; + if (avcodec_open2(avctx, codec, NULL) < 0) + goto error; + // Documented as "set by libavcodec", but there is no other way + avctx->time_base = (AVRational) {1, 1000}; + priv->avctx = avctx; + sd->priv = priv; + sd->output_codec = "ass"; + sd->output_extradata = avctx->subtitle_header; + sd->output_extradata_len = avctx->subtitle_header_size; + return 0; + + error: + mp_msg(MSGT_SUBREADER, MSGL_ERR, + "Could not open libavcodec subtitle converter\n"); + av_free(avctx); + talloc_free(priv); + return -1; +} + +static void decode(struct sd *sd, struct demux_packet *packet) +{ + struct sd_lavc_priv *priv = sd->priv; + AVCodecContext *avctx = priv->avctx; + double ts = av_q2d(av_inv_q(avctx->time_base)); + AVSubtitle sub = {0}; + AVPacket pkt; + int ret, got_sub; + + av_init_packet(&pkt); + pkt.data = packet->buffer; + pkt.size = packet->len; + pkt.pts = packet->pts == MP_NOPTS_VALUE ? AV_NOPTS_VALUE : packet->pts * ts; + pkt.duration = packet->duration * ts; + + ret = avcodec_decode_subtitle2(avctx, &sub, &got_sub, &pkt); + if (ret < 0) { + mp_msg(MSGT_OSD, MSGL_ERR, "Error decoding subtitle\n"); + } else if (got_sub) { + for (int i = 0; i < sub.num_rects; i++) { + char *ass_line = sub.rects[i]->ass; + if (!ass_line) + break; + // This might contain embedded timestamps, using the "old" ffmpeg + // ASS packet format, in which case pts/duration might be ignored + // at a later point. + sd_conv_add_packet(sd, ass_line, strlen(ass_line), + packet->pts, packet->duration); + } + } + + av_free_packet(&pkt); + avsubtitle_free(&sub); +} + +static void reset(struct sd *sd) +{ + struct sd_lavc_priv *priv = sd->priv; + + avcodec_flush_buffers(priv->avctx); + sd_conv_def_reset(sd); +} + +static void uninit(struct sd *sd) +{ + struct sd_lavc_priv *priv = sd->priv; + + avcodec_close(priv->avctx); + av_free(priv->avctx); + talloc_free(priv); +} + +const struct sd_functions sd_lavc_conv = { + .supports_format = supports_format, + .init = init, + .decode = decode, + .get_converted = sd_conv_def_get_converted, + .reset = reset, + .uninit = uninit, +};