diff options
Diffstat (limited to 'sub/dec_sub.c')
-rw-r--r-- | sub/dec_sub.c | 331 |
1 files changed, 248 insertions, 83 deletions
diff --git a/sub/dec_sub.c b/sub/dec_sub.c index b72630470c..a1392017a2 100644 --- a/sub/dec_sub.c +++ b/sub/dec_sub.c @@ -18,16 +18,17 @@ #include <stdlib.h> #include <stdbool.h> +#include <string.h> #include <assert.h> #include "config.h" -#include "demux/stheader.h" +#include "demux/demux.h" #include "sd.h" #include "sub.h" #include "dec_sub.h" -#include "subreader.h" #include "core/options.h" #include "core/mp_msg.h" +#include "core/charset_conv.h" extern const struct sd_functions sd_ass; extern const struct sd_functions sd_lavc; @@ -35,6 +36,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_lavf_srt; extern const struct sd_functions sd_lavc_conv; static const struct sd_functions *sd_list[] = { @@ -46,6 +48,7 @@ static const struct sd_functions *sd_list[] = { &sd_movtext, &sd_srt, &sd_microdvd, + &sd_lavf_srt, &sd_lavc_conv, NULL }; @@ -56,10 +59,18 @@ struct dec_sub { struct MPOpts *opts; struct sd init_sd; + double video_fps; + const char *charset; + struct sd *sd[MAX_NUM_SD]; int num_sd; }; +struct packet_list { + struct demux_packet **packets; + int num_packets; +}; + struct dec_sub *sub_create(struct MPOpts *opts) { struct dec_sub *sub = talloc_zero(NULL, struct dec_sub); @@ -102,6 +113,11 @@ void sub_set_video_res(struct dec_sub *sub, int w, int h) sub->init_sd.sub_video_h = h; } +void sub_set_video_fps(struct dec_sub *sub, double fps) +{ + sub->video_fps = fps; +} + void sub_set_extradata(struct dec_sub *sub, void *data, int data_len) { sub->init_sd.extradata = data_len ? talloc_memdup(sub, data, data_len) : NULL; @@ -120,74 +136,12 @@ static void print_chain(struct dec_sub *sub) mp_msg(MSGT_OSD, MSGL_V, "Subtitle filter chain: "); for (int n = 0; n < sub->num_sd; n++) { struct sd *sd = sub->sd[n]; - mp_msg(MSGT_OSD, MSGL_V, "%s%s (%s)", n > 0 ? " -> " : "", + mp_msg(MSGT_OSD, MSGL_V, "%s%s (%s)", n > 0 ? " -> " : "", sd->driver->name, sd->codec); } mp_msg(MSGT_OSD, MSGL_V, "\n"); } -// Subtitles read with subreader.c -static void read_sub_data(struct dec_sub *sub, struct sub_data *subdata) -{ - assert(sub_accept_packets_in_advance(sub)); - char *temp = NULL; - - struct sd *sd = sub_get_last_sd(sub); - - sd->no_remove_duplicates = true; - - for (int i = 0; i < subdata->sub_num; i++) { - subtitle *st = &subdata->subtitles[i]; - // subdata is in 10 ms ticks, pts is in seconds - double t = subdata->sub_uses_time ? 0.01 : (1 / subdata->fallback_fps); - - int len = 0; - for (int j = 0; j < st->lines; j++) - len += st->text[j] ? strlen(st->text[j]) : 0; - - len += 2 * st->lines; // '\N', including the one after the last line - len += 6; // {\anX} - len += 1; // '\0' - - if (talloc_get_size(temp) < len) { - talloc_free(temp); - temp = talloc_array(NULL, char, len); - } - - char *p = temp; - char *end = p + len; - - if (st->alignment) - p += snprintf(p, end - p, "{\\an%d}", st->alignment); - - for (int j = 0; j < st->lines; j++) - p += snprintf(p, end - p, "%s\\N", st->text[j]); - - if (st->lines > 0) - p -= 2; // remove last "\N" - *p = 0; - - struct demux_packet pkt = {0}; - pkt.pts = st->start * t; - pkt.duration = (st->end - st->start) * t; - pkt.buffer = temp; - pkt.len = strlen(temp); - - sub_decode(sub, &pkt); - } - - // 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); - - sd->no_remove_duplicates = false; - - talloc_free(temp); -} - static int sub_init_decoder(struct dec_sub *sub, struct sd *sd) { sd->driver = NULL; @@ -230,8 +184,6 @@ void sub_init_from_sh(struct dec_sub *sub, struct sh_sub *sh) // Try adding new converters until a decoder is reached if (sd->driver->get_bitmaps || sd->driver->get_text) { print_chain(sub); - if (sh->sub_data) - read_sub_data(sub, sh->sub_data); return; } init_sd = (struct sd) { @@ -249,32 +201,245 @@ void sub_init_from_sh(struct dec_sub *sub, struct sh_sub *sh) sh->gsh->codec ? sh->gsh->codec : "<unknown>"); } -bool sub_accept_packets_in_advance(struct dec_sub *sub) +static struct demux_packet *get_decoded_packet(struct sd *sd) { - // Converters are assumed to always accept packets in advance - struct sd *sd = sub_get_last_sd(sub); - return sd && sd->driver->accept_packets_in_advance; + return sd->driver->get_converted ? sd->driver->get_converted(sd) : NULL; } -static void decode_next(struct dec_sub *sub, int n, struct demux_packet *packet) +static void decode_chain(struct sd **sd, int num_sd, struct demux_packet *packet) { - struct sd *sd = sub->sd[n]; - sd->driver->decode(sd, packet); - if (n + 1 >= sub->num_sd || !sd->driver->get_converted) + if (num_sd == 0) return; - while (1) { - struct demux_packet *next = - sd->driver->get_converted ? sd->driver->get_converted(sd) : NULL; - if (!next) - break; - decode_next(sub, n + 1, next); + struct sd *dec = sd[0]; + dec->driver->decode(dec, packet); + if (num_sd > 1) { + while (1) { + struct demux_packet *next = get_decoded_packet(dec); + if (!next) + break; + decode_chain(sd + 1, num_sd - 1, next); + } + } +} + +static struct demux_packet *recode_packet(struct demux_packet *in, + const char *charset) +{ + struct demux_packet *pkt = NULL; + bstr in_buf = {in->buffer, in->len}; + bstr conv = mp_iconv_to_utf8(in_buf, charset, MP_ICONV_VERBOSE); + if (conv.start && conv.start != in_buf.start) { + pkt = talloc_ptrtype(NULL, pkt); + talloc_steal(pkt, conv.start); + *pkt = (struct demux_packet) { + .buffer = conv.start, + .len = conv.len, + .pts = in->pts, + .duration = in->duration, + .avpacket = in->avpacket, // questionable, but gives us sidedata + }; + } + return pkt; +} + +static void decode_chain_recode(struct dec_sub *sub, struct sd **sd, int num_sd, + struct demux_packet *packet) +{ + if (num_sd > 0) { + struct demux_packet *recoded = NULL; + if (sub->charset) + recoded = recode_packet(packet, sub->charset); + decode_chain(sd, num_sd, recoded ? recoded : packet); } } void sub_decode(struct dec_sub *sub, struct demux_packet *packet) { - if (sub->num_sd > 0) - decode_next(sub, 0, packet); + decode_chain_recode(sub, sub->sd, sub->num_sd, packet); +} + +static const char *guess_sub_cp(struct packet_list *subs, const char *usercp) +{ + if (!mp_charset_requires_guess(usercp)) + return usercp; + + // Concat all subs into a buffer. We can't probably do much better without + // having the original data (which we don't, not anymore). + int max_size = 2 * 1024 * 1024; + const char *sep = "\n\n"; // In utf-16: U+0A0A GURMUKHI LETTER UU + int sep_len = strlen(sep); + int num_pkt = 0; + int size = 0; + for (int n = 0; n < subs->num_packets; n++) { + struct demux_packet *pkt = subs->packets[n]; + if (size + pkt->len > max_size) + break; + size += pkt->len + sep_len; + num_pkt++; + } + bstr text = {talloc_size(NULL, size), 0}; + for (int n = 0; n < num_pkt; n++) { + struct demux_packet *pkt = subs->packets[n]; + memcpy(text.start + text.len, pkt->buffer, pkt->len); + memcpy(text.start + text.len + pkt->len, sep, sep_len); + text.len += pkt->len + sep_len; + } + const char *guess = mp_charset_guess(text, usercp); + talloc_free(text.start); + return guess; +} + +static void multiply_timings(struct packet_list *subs, double factor) +{ + for (int n = 0; n < subs->num_packets; n++) { + struct demux_packet *pkt = subs->packets[n]; + if (pkt->pts != MP_NOPTS_VALUE) + pkt->pts *= factor; + if (pkt->duration > 0) + pkt->duration *= factor; + } +} + +// Remove overlaps and fill gaps between adjacent subtitle packets. This is done +// by adjusting the duration of the earlier packet. If the gaps or overlap are +// larger than the threshold, or if the durations are close to the threshold, +// don't change the events. +// The algorithm is maximally naive and doesn't work if there are multiple +// overlapping lines. (It's not worth the trouble.) +static void fix_overlaps_and_gaps(struct packet_list *subs) +{ + double threshold = 0.2; // up to 200 ms overlaps or gaps are removed + double keep = threshold * 2;// don't change timings if durations are smaller + for (int i = 0; i < subs->num_packets - 1; i++) { + struct demux_packet *cur = subs->packets[i]; + struct demux_packet *next = subs->packets[i + 1]; + if (cur->pts != MP_NOPTS_VALUE && cur->duration > 0 && + next->pts != MP_NOPTS_VALUE && next->duration > 0) + { + double end = cur->pts + cur->duration; + if (fabs(next->pts - end) <= threshold && cur->duration >= keep && + next->duration >= keep) + { + cur->duration = next->pts - cur->pts; + } + } + } +} + +static void add_sub_list(struct dec_sub *sub, int at, struct packet_list *subs) +{ + struct sd *sd = sub_get_last_sd(sub); + assert(sd); + + sd->no_remove_duplicates = true; + + for (int n = 0; n < subs->num_packets; n++) + decode_chain_recode(sub, sub->sd + at, sub->num_sd - at, subs->packets[n]); + + // Hack for broken FFmpeg packet format: make sd_ass keep the subtitle + // events on reset(), even if 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->driver->fix_events) + sd->driver->fix_events(sd); + + sd->no_remove_duplicates = false; +} + +static void add_packet(struct packet_list *subs, struct demux_packet *pkt) +{ + pkt = demux_copy_packet(pkt); + talloc_steal(subs, pkt); + MP_TARRAY_APPEND(subs, subs->packets, subs->num_packets, pkt); +} + +// Read all packets from the demuxer and decode/add them. Returns false if +// there are circumstances which makes this not possible. +bool sub_read_all_packets(struct dec_sub *sub, struct sh_sub *sh) +{ + struct MPOpts *opts = sub->opts; + + if (!sub_accept_packets_in_advance(sub) || sh->track || sub->num_sd < 1) + return false; + + struct packet_list *subs = talloc_zero(NULL, struct packet_list); + + // In some cases, we want to put the packets through a decoder first. + // Preprocess until sub->sd[preprocess]. + int preprocess = 0; + + // movtext is currently the only subtitle format that has text output, + // but binary input. Do charset conversion after converting to text. + if (sub->sd[0]->driver == &sd_movtext) + preprocess = 1; + + // Broken Libav libavformat srt packet format (fix timestamps first). + if (sub->sd[0]->driver == &sd_lavf_srt) + preprocess = 1; + + for (;;) { + ds_get_next_pts(sh->ds); + struct demux_packet *pkt = ds_get_packet_sub(sh->ds); + if (!pkt) + break; + if (preprocess) { + decode_chain(sub->sd, preprocess, pkt); + while (1) { + pkt = get_decoded_packet(sub->sd[preprocess - 1]); + if (!pkt) + break; + add_packet(subs, pkt); + } + } else { + add_packet(subs, pkt); + } + } + + if (opts->sub_cp && !sh->is_utf8) + sub->charset = guess_sub_cp(subs, opts->sub_cp); + + if (sub->charset) + mp_msg(MSGT_OSD, MSGL_INFO, "Using subtitle charset: %s\n", sub->charset); + + double sub_speed = 1.0; + + // 23.976 FPS is used as default timebase for frame based formats + if (sub->video_fps && sh->frame_based) + sub_speed *= sub->video_fps / 23.976; + + if (opts->sub_fps && sub->video_fps) + sub_speed *= opts->sub_fps / sub->video_fps; + + sub_speed *= opts->sub_speed; + + if (sub_speed != 1.0) + multiply_timings(subs, sub_speed); + + if (!opts->suboverlap_enabled) + fix_overlaps_and_gaps(subs); + + if (sh->gsh->codec && strcmp(sh->gsh->codec, "microdvd") == 0) { + // The last subtitle event in MicroDVD subs can have duration unset, + // which means show the subtitle until end of video. + // See FFmpeg FATE MicroDVD_capability_tester.sub + if (subs->num_packets) { + struct demux_packet *last = subs->packets[subs->num_packets - 1]; + if (last->duration <= 0) + last->duration = 10; // arbitrary + } + } + + add_sub_list(sub, preprocess, subs); + + talloc_free(subs); + return true; +} + +bool sub_accept_packets_in_advance(struct dec_sub *sub) +{ + // Converters are assumed to always accept packets in advance + struct sd *sd = sub_get_last_sd(sub); + return sd && sd->driver->accept_packets_in_advance; } void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, double pts, |