diff options
Diffstat (limited to 'sub')
-rw-r--r-- | sub/dec_sub.c | 25 | ||||
-rw-r--r-- | sub/dec_sub.h | 1 | ||||
-rw-r--r-- | sub/filter_jsre.c | 2 | ||||
-rw-r--r-- | sub/filter_regex.c | 2 | ||||
-rw-r--r-- | sub/lavc_conv.c | 56 | ||||
-rw-r--r-- | sub/osd.c | 14 | ||||
-rw-r--r-- | sub/osd.h | 1 | ||||
-rw-r--r-- | sub/osd_font.otf | bin | 4460 -> 4504 bytes | |||
-rw-r--r-- | sub/sd.h | 12 | ||||
-rw-r--r-- | sub/sd_ass.c | 153 | ||||
-rw-r--r-- | sub/sd_lavc.c | 26 |
11 files changed, 188 insertions, 104 deletions
diff --git a/sub/dec_sub.c b/sub/dec_sub.c index 94ff3ba4dd..c03393a007 100644 --- a/sub/dec_sub.c +++ b/sub/dec_sub.c @@ -123,7 +123,7 @@ static void wakeup_demux(void *ctx) mp_dispatch_interrupt(q); } -static void sub_destroy_cached_pkts(struct dec_sub *sub) +static void destroy_cached_pkts(struct dec_sub *sub) { int index = 0; while (index < sub->num_cached_pkts) { @@ -204,7 +204,7 @@ struct dec_sub *sub_create(struct mpv_global *global, struct track *track, }; sub->opts = sub->opts_cache->opts; sub->shared_opts = sub->shared_opts_cache->opts; - mp_mutex_init_type(&sub->lock, MP_MUTEX_RECURSIVE); + mp_mutex_init(&sub->lock); sub->sd = init_decoder(sub); if (sub->sd) { @@ -445,11 +445,16 @@ char *sub_get_text(struct dec_sub *sub, double pts, enum sd_text_type type) char *sub_ass_get_extradata(struct dec_sub *sub) { + char *data = NULL; + mp_mutex_lock(&sub->lock); if (strcmp(sub->sd->codec->codec, "ass") != 0) - return NULL; + goto done; char *extradata = sub->sd->codec->extradata; int extradata_size = sub->sd->codec->extradata_size; - return talloc_strndup(NULL, extradata, extradata_size); + data = talloc_strndup(NULL, extradata, extradata_size); +done: + mp_mutex_unlock(&sub->lock); + return data; } struct sd_times sub_get_times(struct dec_sub *sub, double pts) @@ -476,7 +481,7 @@ void sub_reset(struct dec_sub *sub) sub->sd->driver->reset(sub->sd); sub->last_pkt_pts = MP_NOPTS_VALUE; sub->last_vo_pts = MP_NOPTS_VALUE; - sub_destroy_cached_pkts(sub); + destroy_cached_pkts(sub); TA_FREEP(&sub->new_segment); mp_mutex_unlock(&sub->lock); } @@ -548,10 +553,16 @@ void sub_set_play_dir(struct dec_sub *sub, int dir) bool sub_is_primary_visible(struct dec_sub *sub) { - return sub->shared_opts->sub_visibility[0]; + mp_mutex_lock(&sub->lock); + bool ret = sub->shared_opts->sub_visibility[0]; + mp_mutex_unlock(&sub->lock); + return ret; } bool sub_is_secondary_visible(struct dec_sub *sub) { - return sub->shared_opts->sub_visibility[1]; + mp_mutex_lock(&sub->lock); + bool ret = sub->shared_opts->sub_visibility[1]; + mp_mutex_unlock(&sub->lock); + return ret; } diff --git a/sub/dec_sub.h b/sub/dec_sub.h index eb8406cb14..a40aa9bbfd 100644 --- a/sub/dec_sub.h +++ b/sub/dec_sub.h @@ -24,6 +24,7 @@ enum sd_ctrl { enum sd_text_type { SD_TEXT_TYPE_PLAIN, SD_TEXT_TYPE_ASS, + SD_TEXT_TYPE_ASS_FULL, }; struct sd_times { diff --git a/sub/filter_jsre.c b/sub/filter_jsre.c index f956000d55..fb711814e3 100644 --- a/sub/filter_jsre.c +++ b/sub/filter_jsre.c @@ -115,7 +115,7 @@ static struct demux_packet *jsre_filter(struct sd_filter *ft, bool drop = false; if (ft->opts->rf_plain) - sd_ass_to_plaintext(text, strlen(text), text); + sd_ass_to_plaintext(&text, text); for (int n = 0; n < p->num_regexes; n++) { int found, err = p_regexec(p->J, n, text, &found); diff --git a/sub/filter_regex.c b/sub/filter_regex.c index 8e299918ce..1d29ea1ac1 100644 --- a/sub/filter_regex.c +++ b/sub/filter_regex.c @@ -64,7 +64,7 @@ static struct demux_packet *rf_filter(struct sd_filter *ft, bool drop = false; if (ft->opts->rf_plain) - sd_ass_to_plaintext(text, strlen(text), text); + sd_ass_to_plaintext(&text, text); for (int n = 0; n < p->num_regexes; n++) { int err = regexec(&p->regexes[n], text, 0, NULL, 0); diff --git a/sub/lavc_conv.c b/sub/lavc_conv.c index 532e91d508..3a33933605 100644 --- a/sub/lavc_conv.c +++ b/sub/lavc_conv.c @@ -32,6 +32,8 @@ struct lavc_conv { struct mp_log *log; + struct mp_subtitle_opts *opts; + bool styled; AVCodecContext *avctx; AVPacket *avpkt; AVPacket *avpkt_vtt; @@ -52,27 +54,13 @@ static const char *get_lavc_format(const char *format) return format; } -// Disable style definitions generated by the libavcodec converter. -// We always want the user defined style instead. -static void disable_styles(bstr header) -{ - bstr style = bstr0("\nStyle: "); - while (header.len) { - int n = bstr_find(header, style); - if (n < 0) - break; - header.start[n + 1] = '#'; // turn into a comment - header = bstr_cut(header, n + style.len); - } -} - -struct lavc_conv *lavc_conv_create(struct mp_log *log, - const struct mp_codec_params *mp_codec) +struct lavc_conv *lavc_conv_create(struct sd *sd) { struct lavc_conv *priv = talloc_zero(NULL, struct lavc_conv); - priv->log = log; + priv->log = sd->log; + priv->opts = sd->opts; priv->cur_list = talloc_array(priv, char*, 0); - priv->codec = talloc_strdup(priv, mp_codec->codec); + priv->codec = talloc_strdup(priv, sd->codec->codec); AVCodecContext *avctx = NULL; AVDictionary *opts = NULL; const char *fmt = get_lavc_format(priv->codec); @@ -82,7 +70,7 @@ struct lavc_conv *lavc_conv_create(struct mp_log *log, avctx = avcodec_alloc_context3(codec); if (!avctx) goto error; - if (mp_set_avctx_codec_headers(avctx, mp_codec) < 0) + if (mp_set_avctx_codec_headers(avctx, sd->codec) < 0) goto error; priv->avpkt = av_packet_alloc(); @@ -90,6 +78,15 @@ struct lavc_conv *lavc_conv_create(struct mp_log *log, if (!priv->avpkt || !priv->avpkt_vtt) goto error; + switch (codec->id) { + case AV_CODEC_ID_DVB_TELETEXT: + av_dict_set_int(&opts, "txt_format", 2, 0); + break; + case AV_CODEC_ID_ARIB_CAPTION: + av_dict_set_int(&opts, "sub_type", SUBTITLE_ASS, 0); + break; + } + #if LIBAVCODEC_VERSION_MAJOR < 59 av_dict_set(&opts, "sub_text_format", "ass", 0); #endif @@ -106,7 +103,6 @@ struct lavc_conv *lavc_conv_create(struct mp_log *log, priv->avctx = avctx; priv->extradata = talloc_strndup(priv, avctx->subtitle_header, avctx->subtitle_header_size); - disable_styles(bstr0(priv->extradata)); return priv; error: @@ -250,6 +246,21 @@ char **lavc_conv_decode(struct lavc_conv *priv, struct demux_packet *packet, curr_pkt = priv->avpkt_vtt; } + priv->styled = avctx->codec_id == AV_CODEC_ID_DVB_TELETEXT; + + if (avctx->codec_id == AV_CODEC_ID_DVB_TELETEXT) { + if (!priv->opts->teletext_page) { + av_opt_set(avctx, "txt_page", "subtitle", AV_OPT_SEARCH_CHILDREN); + priv->styled = false; + } else if (priv->opts->teletext_page == -1) { + av_opt_set(avctx, "txt_page", "*", AV_OPT_SEARCH_CHILDREN); + } else { + char page[4]; + snprintf(page, sizeof(page), "%d", priv->opts->teletext_page); + av_opt_set(avctx, "txt_page", page, AV_OPT_SEARCH_CHILDREN); + } + } + ret = avcodec_decode_subtitle2(avctx, &priv->cur, &got_sub, curr_pkt); if (ret < 0) { MP_ERR(priv, "Error decoding subtitle\n"); @@ -278,6 +289,11 @@ done: return priv->cur_list; } +bool lavc_conv_is_styled(struct lavc_conv *priv) +{ + return priv->styled; +} + void lavc_conv_reset(struct lavc_conv *priv) { avcodec_flush_buffers(priv->avctx); @@ -515,10 +515,16 @@ void osd_rescale_bitmaps(struct sub_bitmaps *imgs, int frame_w, int frame_h, int cy = vidh / 2 - (int)(frame_h * yscale) / 2; for (int i = 0; i < imgs->num_parts; i++) { struct sub_bitmap *bi = &imgs->parts[i]; - bi->x = (int)(bi->x * xscale) + cx + res.ml; - bi->y = (int)(bi->y * yscale) + cy + res.mt; - bi->dw = (int)(bi->w * xscale + 0.5); - bi->dh = (int)(bi->h * yscale + 0.5); + struct mp_rect rc = { + .x0 = lrint(bi->x * xscale), + .y0 = lrint(bi->y * yscale), + .x1 = lrint((bi->x + bi->w) * xscale), + .y1 = lrint((bi->y + bi->h) * yscale), + }; + bi->x = rc.x0 + cx + res.ml; + bi->y = rc.y0 + cy + res.mt; + bi->dw = mp_rect_w(rc); + bi->dh = mp_rect_h(rc); } } @@ -126,6 +126,7 @@ enum mp_osd_font_codepoints { OSD_BRIGHTNESS = 0x0A, OSD_HUE = 0x0B, OSD_BALANCE = 0x0C, + OSD_REV = 0x0D, OSD_PANSCAN = 0x50, OSD_PB_START = 0x10, diff --git a/sub/osd_font.otf b/sub/osd_font.otf Binary files differindex 70b9b21f40..c5eff7c93d 100644 --- a/sub/osd_font.otf +++ b/sub/osd_font.otf @@ -51,11 +51,11 @@ struct sd_functions { // lavc_conv.c struct lavc_conv; -struct lavc_conv *lavc_conv_create(struct mp_log *log, - const struct mp_codec_params *mp_codec); +struct lavc_conv *lavc_conv_create(struct sd *sd); char *lavc_conv_get_extradata(struct lavc_conv *priv); char **lavc_conv_decode(struct lavc_conv *priv, struct demux_packet *packet, double *sub_pts, double *sub_duration); +bool lavc_conv_is_styled(struct lavc_conv *priv); void lavc_conv_reset(struct lavc_conv *priv); void lavc_conv_uninit(struct lavc_conv *priv); @@ -107,8 +107,10 @@ int sd_ass_fmt_offset(const char *event_format); bstr sd_ass_pkt_text(struct sd_filter *ft, struct demux_packet *pkt, int offset); // convert \0-terminated "Text" (ass) content to plaintext, possibly in-place. -// result.start is out, result.len is MIN(out_siz, strlen(in)) or smaller. -// if there's room: out[result.len] is set to \0. out == in is allowed. -bstr sd_ass_to_plaintext(char *out, size_t out_siz, const char *in); +// result.start is *out, result.len is strlen(in) or smaller. +// (*out)[result.len] is always set to \0. *out == in is allowed. +// *out must be a talloc-allocated buffer or NULL, and will be reallocated if needed. +// *out will not be reallocated if *out == in. +bstr sd_ass_to_plaintext(char **out, const char *in); #endif diff --git a/sub/sd_ass.c b/sub/sd_ass.c index 6fa4d1bb52..baaf5c6905 100644 --- a/sub/sd_ass.c +++ b/sub/sd_ass.c @@ -51,7 +51,7 @@ struct sd_ass_priv { bool clear_once; struct mp_ass_packer *packer; struct sub_bitmap_copy_cache *copy_cache; - char last_text[500]; + bstr last_text; struct mp_image_params video_params; struct mp_image_params last_params; struct mp_osd_res osd; @@ -271,7 +271,7 @@ static int init(struct sd *sd) strcmp(sd->codec->codec, "null") != 0) { ctx->is_converted = true; - ctx->converter = lavc_conv_create(sd->log, sd->codec); + ctx->converter = lavc_conv_create(sd); if (!ctx->converter) return -1; @@ -420,7 +420,7 @@ static void decode(struct sd *sd, struct demux_packet *packet) if (sd->opts->sub_stretch_durations || packet->duration < 0 || sub_duration == UINT32_MAX) { if (!ctx->duration_unknown) { - MP_WARN(sd, "Subtitle with unknown duration.\n"); + MP_VERBOSE(sd, "Subtitle with unknown duration.\n"); ctx->duration_unknown = true; } sub_duration = UNKNOWN_DURATION; @@ -510,7 +510,7 @@ static void configure_ass(struct sd *sd, struct mp_osd_res *dim, set_force_flags |= ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE; if (converted) set_force_flags |= ASS_OVERRIDE_BIT_ALIGNMENT; -#ifdef ASS_JUSTIFY_AUTO +#if LIBASS_VERSION >= 0x01306000 if ((converted || shared_opts->ass_style_override[sd->order]) && opts->ass_justify) set_force_flags |= ASS_OVERRIDE_BIT_JUSTIFY; #endif @@ -527,8 +527,16 @@ static void configure_ass(struct sd *sd, struct mp_osd_res *dim, ass_set_hinting(priv, set_hinting); ass_set_line_spacing(priv, set_line_spacing); #if LIBASS_VERSION >= 0x01600010 - if (converted) + if (converted) { ass_track_set_feature(track, ASS_FEATURE_WRAP_UNICODE, 1); + if (!opts->sub_vsfilter_bidi_compat) { + for (int n = 0; n < track->n_styles; n++) { + track->styles[n].Encoding = -1; + } + ass_track_set_feature(track, ASS_FEATURE_BIDI_BRACKETS, 1); + ass_track_set_feature(track, ASS_FEATURE_WHOLE_TEXT_LAYOUT, 1); + } + } #endif if (converted) { bool override_playres = true; @@ -550,12 +558,13 @@ static void configure_ass(struct sd *sd, struct mp_osd_res *dim, if (override_playres) { int vidw = dim->w - (dim->ml + dim->mr); int vidh = dim->h - (dim->mt + dim->mb); + int old_playresx = track->PlayResX; track->PlayResX = track->PlayResY * (double)vidw / MPMAX(vidh, 1); - // ffmpeg and mpv use a default PlayResX of 384 when it is not known, - // this comes from VSFilter. - double fix_margins = track->PlayResX / (double)MP_ASS_FONT_PLAYRESX; - track->styles->MarginL = round(track->styles->MarginL * fix_margins); - track->styles->MarginR = round(track->styles->MarginR * fix_margins); + double fix_margins = track->PlayResX / (double)old_playresx; + for (int n = 0; n < track->n_styles; n++) { + track->styles[n].MarginL = round(track->styles[n].MarginL * fix_margins); + track->styles[n].MarginR = round(track->styles[n].MarginR * fix_margins); + } } } } @@ -642,7 +651,7 @@ static struct sub_bitmaps *get_bitmaps(struct sd *sd, struct mp_osd_res dim, struct mp_subtitle_opts *opts = sd->opts; struct mp_subtitle_shared_opts *shared_opts = sd->shared_opts; bool no_ass = !opts->ass_enabled || shared_opts->ass_style_override[sd->order] == 5; - bool converted = ctx->is_converted || no_ass; + bool converted = (ctx->is_converted && !lavc_conv_is_styled(ctx->converter)) || no_ass; ASS_Track *track = no_ass ? ctx->shadow_track : ctx->ass_track; ASS_Renderer *renderer = ctx->ass_renderer; struct sub_bitmaps *res = &(struct sub_bitmaps){0}; @@ -700,30 +709,23 @@ done: return res; } -struct buf { - char *start; - int size; - int len; -}; +#define MAX_BUF_SIZE 1024 * 1024 +#define MIN_EXPAND_SIZE 4096 -static void append(struct buf *b, char c) +static void append(bstr *b, char c) { - if (b->len < b->size) { - b->start[b->len] = c; - b->len++; - } + bstr_xappend(NULL, b, (bstr){&c, 1}); } -static void ass_to_plaintext(struct buf *b, const char *in) +static void ass_to_plaintext(bstr *b, const char *in) { - bool in_tag = false; const char *open_tag_pos = NULL; bool in_drawing = false; while (*in) { - if (in_tag) { + if (open_tag_pos) { if (in[0] == '}') { in += 1; - in_tag = false; + open_tag_pos = NULL; } else if (in[0] == '\\' && in[1] == 'p' && in[2] != 'o') { in += 2; // Skip text between \pN and \p0 tags. A \p without a number @@ -747,7 +749,6 @@ static void ass_to_plaintext(struct buf *b, const char *in) } else if (in[0] == '{') { open_tag_pos = in; in += 1; - in_tag = true; } else { if (!in_drawing) append(b, in[0]); @@ -756,65 +757,86 @@ static void ass_to_plaintext(struct buf *b, const char *in) } } // A '{' without a closing '}' is always visible. - if (in_tag) { - while (*open_tag_pos) - append(b, *open_tag_pos++); + if (open_tag_pos) { + bstr_xappend(NULL, b, bstr0(open_tag_pos)); } } -// Empty string counts as whitespace. Reads s[len-1] even if there are \0s. -static bool is_whitespace_only(char *s, int len) +// Empty string counts as whitespace. +static bool is_whitespace_only(bstr b) { - for (int n = 0; n < len; n++) { - if (s[n] != ' ' && s[n] != '\t') + for (int n = 0; n < b.len; n++) { + if (b.start[n] != ' ' && b.start[n] != '\t') return false; } return true; } -static char *get_text_buf(struct sd *sd, double pts, enum sd_text_type type) +static bstr get_text_buf(struct sd *sd, double pts, enum sd_text_type type) { struct sd_ass_priv *ctx = sd->priv; ASS_Track *track = ctx->ass_track; if (pts == MP_NOPTS_VALUE) - return NULL; + return (bstr){0}; long long ipts = find_timestamp(sd, pts); - struct buf b = {ctx->last_text, sizeof(ctx->last_text) - 1}; + bstr *b = &ctx->last_text; + + if (!b->start) + b->start = talloc_size(ctx, 4096); + + b->len = 0; for (int i = 0; i < track->n_events; ++i) { ASS_Event *event = track->events + i; if (ipts >= event->Start && ipts < event->Start + event->Duration) { if (event->Text) { - int start = b.len; + int start = b->len; if (type == SD_TEXT_TYPE_PLAIN) { - ass_to_plaintext(&b, event->Text); + ass_to_plaintext(b, event->Text); + } else if (type == SD_TEXT_TYPE_ASS_FULL) { + long long s = event->Start; + long long e = s + event->Duration; + + ASS_Style *style = (event->Style < 0 || event->Style >= track->n_styles) ? NULL : &track->styles[event->Style]; + + int sh = (s / 60 / 60 / 1000); + int sm = (s / 60 / 1000) % 60; + int ss = (s / 1000) % 60; + int sc = (s / 10) % 100; + int eh = (e / 60 / 60 / 1000); + int em = (e / 60 / 1000) % 60; + int es = (e / 1000) % 60; + int ec = (e / 10) % 100; + + bstr_xappend_asprintf(NULL, b, "Dialogue: %d,%d:%02d:%02d.%02d,%d:%02d:%02d.%02d,%s,%s,%04d,%04d,%04d,%s,%s", + event->Layer, + sh, sm, ss, sc, + eh, em, es, ec, + (style && style->Name) ? style->Name : "", event->Name, + event->MarginL, event->MarginR, event->MarginV, + event->Effect, event->Text); } else { - char *t = event->Text; - while (*t) - append(&b, *t++); + bstr_xappend(NULL, b, bstr0(event->Text)); } - if (is_whitespace_only(&b.start[start], b.len - start)) { - b.len = start; + if (is_whitespace_only(bstr_cut(*b, start))) { + b->len = start; } else { - append(&b, '\n'); + append(b, '\n'); } } } } - b.start[b.len] = '\0'; + bstr_eatend(b, (bstr)bstr0_lit("\n")); - if (b.len > 0 && b.start[b.len - 1] == '\n') - b.start[b.len - 1] = '\0'; - - return ctx->last_text; + return *b; } static char *get_text(struct sd *sd, double pts, enum sd_text_type type) { - return talloc_strdup(NULL, get_text_buf(sd, pts, type)); + return bstrto0(NULL, get_text_buf(sd, pts, type)); } static struct sd_times get_times(struct sd *sd, double pts) @@ -853,20 +875,26 @@ static void fill_plaintext(struct sd *sd, double pts) ass_flush_events(track); - char *text = get_text_buf(sd, pts, SD_TEXT_TYPE_PLAIN); - if (!text) + bstr text = get_text_buf(sd, pts, SD_TEXT_TYPE_PLAIN); + if (!text.len) return; bstr dst = {0}; - while (*text) { - if (*text == '{') + while (text.len) { + if (*text.start == '{') { + bstr_xappend(NULL, &dst, bstr0("\\{")); + text = bstr_cut(text, 1); + } else if (*text.start == '\\') { bstr_xappend(NULL, &dst, bstr0("\\")); - bstr_xappend(NULL, &dst, (bstr){text, 1}); - // Break ASS escapes with U+2060 WORD JOINER - if (*text == '\\') + // Break ASS escapes with U+2060 WORD JOINER mp_append_utf8_bstr(NULL, &dst, 0x2060); - text++; + text = bstr_cut(text, 1); + } + + int i = bstrcspn(text, "{\\"); + bstr_xappend(NULL, &dst, (bstr){text.start, i}); + text = bstr_cut(text, i); } if (!dst.start) @@ -1094,11 +1122,10 @@ bstr sd_ass_pkt_text(struct sd_filter *ft, struct demux_packet *pkt, int offset) return txt; } -bstr sd_ass_to_plaintext(char *out, size_t out_siz, const char *in) +bstr sd_ass_to_plaintext(char **out, const char *in) { - struct buf b = {out, out_siz, 0}; + bstr b = {*out}; ass_to_plaintext(&b, in); - if (b.len < out_siz) - out[b.len] = 0; - return (bstr){out, b.len}; + *out = b.start; + return b; } diff --git a/sub/sd_lavc.c b/sub/sd_lavc.c index cf49f2d8f1..6c1bdf550e 100644 --- a/sub/sd_lavc.c +++ b/sub/sd_lavc.c @@ -83,6 +83,7 @@ static int init(struct sd *sd) case AV_CODEC_ID_HDMV_PGS_SUBTITLE: case AV_CODEC_ID_XSUB: case AV_CODEC_ID_DVD_SUBTITLE: + case AV_CODEC_ID_ARIB_CAPTION: break; default: return -1; @@ -92,13 +93,31 @@ static int init(struct sd *sd) AVCodecContext *ctx = NULL; const AVCodec *sub_codec = avcodec_find_decoder(cid); if (!sub_codec) - goto error; + goto error_probe; ctx = avcodec_alloc_context3(sub_codec); if (!ctx) - goto error; + goto error_probe; mp_set_avopts(sd->log, ctx, sd->opts->sub_avopts); + switch (cid) { + case AV_CODEC_ID_DVB_TELETEXT: { + int64_t format; + int ret = av_opt_get_int(ctx, "txt_format", AV_OPT_SEARCH_CHILDREN, &format); + // format == 0 is bitmap + if (!ret && format) + goto error_probe; + break; + } + case AV_CODEC_ID_ARIB_CAPTION: { + int64_t format; + int ret = av_opt_get_int(ctx, "sub_type", AV_OPT_SEARCH_CHILDREN, &format); + if (!ret && format != SUBTITLE_BITMAP) + goto error_probe; + break; + } + } + priv->avpkt = av_packet_alloc(); if (!priv->avpkt) goto error; @@ -115,8 +134,9 @@ static int init(struct sd *sd) priv->packer = talloc_zero(priv, struct bitmap_packer); return 0; - error: +error: MP_FATAL(sd, "Could not open libavcodec subtitle decoder\n"); +error_probe: avcodec_free_context(&ctx); mp_free_av_packet(&priv->avpkt); talloc_free(priv); |