From 4f5e12136de717896bf322e75d42de1af09e1c3e Mon Sep 17 00:00:00 2001 From: wm4 Date: Tue, 11 Jun 2013 12:16:42 +0200 Subject: stream: remove padding parameter from stream_read_complete() Seems like a completely unnecessary complication. Instead, always add a 1 byte padding (could be extended if a caller needs it), and clear it. Also add some documentation. There was some, but it was outdated and incomplete. --- sub/ass_mp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sub') diff --git a/sub/ass_mp.c b/sub/ass_mp.c index 258dd57688..0efbdd9c90 100644 --- a/sub/ass_mp.c +++ b/sub/ass_mp.c @@ -122,7 +122,7 @@ ASS_Track *mp_ass_read_stream(ASS_Library *library, const char *fname, if (!s) // Stream code should have printed an error already return NULL; - struct bstr content = stream_read_complete(s, NULL, 100000000, 1); + struct bstr content = stream_read_complete(s, NULL, 100000000); if (content.start == NULL) mp_tmsg(MSGT_ASS, MSGL_ERR, "Refusing to load subtitle file " "larger than 100 MB: %s\n", fname); -- cgit v1.2.3 From a70d575291d48289669ee8989e0597a94189dd8d Mon Sep 17 00:00:00 2001 From: wm4 Date: Tue, 11 Jun 2013 21:39:54 +0200 Subject: sub: preload external text subtitles If a subtitle is external, read it completely and add all subtitle events in advance when the subtitle track is selected. This is done for text subtitles only. (Note that subreader.c and subtitles loaded with libass are different and don't have anything to do with this commit.) --- sub/dec_sub.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- sub/dec_sub.h | 2 ++ 2 files changed, 61 insertions(+), 1 deletion(-) (limited to 'sub') diff --git a/sub/dec_sub.c b/sub/dec_sub.c index b72630470c..187ae5f22c 100644 --- a/sub/dec_sub.c +++ b/sub/dec_sub.c @@ -21,7 +21,7 @@ #include #include "config.h" -#include "demux/stheader.h" +#include "demux/demux.h" #include "sd.h" #include "sub.h" #include "dec_sub.h" @@ -56,10 +56,17 @@ struct dec_sub { struct MPOpts *opts; struct sd init_sd; + double video_fps; + 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 +109,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; @@ -249,6 +261,52 @@ void sub_init_from_sh(struct dec_sub *sub, struct sh_sub *sh) sh->gsh->codec ? sh->gsh->codec : ""); } +static void add_sub_list(struct dec_sub *sub, 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++) + sub_decode(sub, 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; +} + +// 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) +{ + if (!sub_accept_packets_in_advance(sub) || sh->track) + return false; + + void *tmp = talloc_new(NULL); + struct packet_list subs = {0}; + + for (;;) { + ds_get_next_pts(sh->ds); + struct demux_packet *pkt = ds_get_packet_sub(sh->ds); + if (!pkt) + break; + pkt = demux_copy_packet(pkt); + talloc_steal(tmp, pkt); + MP_TARRAY_APPEND(tmp, subs.packets, subs.num_packets, pkt); + } + + add_sub_list(sub, &subs); + + talloc_free(tmp); + return true; +} + bool sub_accept_packets_in_advance(struct dec_sub *sub) { // Converters are assumed to always accept packets in advance diff --git a/sub/dec_sub.h b/sub/dec_sub.h index 805a87ef5c..c285449f94 100644 --- a/sub/dec_sub.h +++ b/sub/dec_sub.h @@ -20,6 +20,7 @@ struct dec_sub *sub_create(struct MPOpts *opts); void sub_destroy(struct dec_sub *sub); void sub_set_video_res(struct dec_sub *sub, int w, int h); +void sub_set_video_fps(struct dec_sub *sub, double fps); void sub_set_extradata(struct dec_sub *sub, void *data, int data_len); void sub_set_ass_renderer(struct dec_sub *sub, struct ass_library *ass_library, struct ass_renderer *ass_renderer); @@ -27,6 +28,7 @@ void sub_init_from_sh(struct dec_sub *sub, struct sh_sub *sh); bool sub_is_initialized(struct dec_sub *sub); +bool sub_read_all_packets(struct dec_sub *sub, struct sh_sub *sh); bool sub_accept_packets_in_advance(struct dec_sub *sub); void sub_decode(struct dec_sub *sub, struct demux_packet *packet); void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, double pts, -- cgit v1.2.3 From 64b1374a4456435cc4486a8153703fa89af58e31 Mon Sep 17 00:00:00 2001 From: wm4 Date: Tue, 11 Jun 2013 21:41:50 +0200 Subject: sub: do some timing postprocessing on preloaded subs This fixes the -subfps option (which unfortunately is still useful), and fixes minor annoying timing errors (which unfortunately still happen). Note that none of these affect ASS or image subtitles. ASS is specially handled: libass loads subtitles as ASS_Track. There are no actual packets passed around, and sd_ass just uses the ASS_Track. Disable the --sub-no-text-pp option. It's misleading now and always was completely useless. --- sub/dec_sub.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) (limited to 'sub') diff --git a/sub/dec_sub.c b/sub/dec_sub.c index 187ae5f22c..230dfa2f55 100644 --- a/sub/dec_sub.c +++ b/sub/dec_sub.c @@ -261,6 +261,43 @@ void sub_init_from_sh(struct dec_sub *sub, struct sh_sub *sh) sh->gsh->codec ? sh->gsh->codec : ""); } +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, struct packet_list *subs) { struct sd *sd = sub_get_last_sd(sub); @@ -285,6 +322,8 @@ static void add_sub_list(struct dec_sub *sub, struct packet_list *subs) // 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) return false; @@ -301,6 +340,12 @@ bool sub_read_all_packets(struct dec_sub *sub, struct sh_sub *sh) MP_TARRAY_APPEND(tmp, subs.packets, subs.num_packets, pkt); } + if (opts->sub_fps && sub->video_fps) + multiply_timings(&subs, opts->sub_fps / sub->video_fps); + + if (!opts->suboverlap_enabled) + fix_overlaps_and_gaps(&subs); + add_sub_list(sub, &subs); talloc_free(tmp); -- cgit v1.2.3 From bd45eb468ced227f335bbdebc59ab36eb9a6fb06 Mon Sep 17 00:00:00 2001 From: wm4 Date: Tue, 11 Jun 2013 22:18:59 +0200 Subject: subreader: remove overlap handling code Some of this (fixing timing) is now done in dec_sub.c (although it's not active for subreader.c code yet - this will be fixed when subreader.c subs are read through a demuxer wrapper). Another reason to remove this is that this code doesn't do much good anymore. libass does handle overlap, and trying to fold overlapping lines into single subtitle events will prevent libass from handling this properly. --- sub/subreader.c | 246 +------------------------------------------------------- 1 file changed, 3 insertions(+), 243 deletions(-) (limited to 'sub') diff --git a/sub/subreader.c b/sub/subreader.c index f3821ba5ab..befac689a4 100644 --- a/sub/subreader.c +++ b/sub/subreader.c @@ -1098,7 +1098,6 @@ static void adjust_subs_time(subtitle* sub, float subtime, float fps, subtitle* nextsub; int i = sub_num; unsigned long subfms = (sub_uses_time ? 100 : fps) * subtime; - unsigned long overlap = (sub_uses_time ? 100 : fps) / 5; // 0.2s n=m=0; if (i) for (;;){ @@ -1110,15 +1109,6 @@ static void adjust_subs_time(subtitle* sub, float subtime, float fps, if (!--i) break; nextsub = sub + 1; if(block){ - if ((sub->end > nextsub->start) && (sub->end <= nextsub->start + overlap)) { - // these subtitles overlap for less than 0.2 seconds - // and would result in very short overlapping subtitle - // so let's fix the problem here, before overlapping code - // get its hands on them - unsigned delta = sub->end - nextsub->start, half = delta / 2; - sub->end -= half + 1; - nextsub->start += delta - half; - } if (sub->end >= nextsub->start){ sub->end = nextsub->start - 1; if (sub->end - sub->start > subfms) @@ -1128,23 +1118,6 @@ static void adjust_subs_time(subtitle* sub, float subtime, float fps, } } - /* Theory: - * Movies are often converted from FILM (24 fps) - * to PAL (25) by simply speeding it up, so we - * to multiply the original timestmaps by - * (Movie's FPS / Subtitle's (guessed) FPS) - * so eg. for 23.98 fps movie and PAL time based - * subtitles we say -subfps 25 and we're fine! - */ - - /* timed sub fps correction ::atmos */ - /* the frame-based case is handled in mpcommon.c - * where find_sub is called */ - if(sub_uses_time && sub_fps) { - sub->start *= sub_fps/fps; - sub->end *= sub_fps/fps; - } - sub = nextsub; m = 0; } @@ -1225,8 +1198,8 @@ sub_data* sub_read_file(char *filename, float fps, struct MPOpts *opts) { int utf16; stream_t* fd; - int n_max, n_first, i, j, sub_first, sub_orig; - subtitle *first, *second, *sub, *return_sub, *alloced_sub = NULL; + int n_max, i, j; + subtitle *first, *sub, *return_sub, *alloced_sub = NULL; sub_data *subt_data; int uses_time = 0, sub_num = 0, sub_errs = 0; static const struct subreader sr[]= @@ -1376,222 +1349,9 @@ sub_data* sub_read_file(char *filename, float fps, struct MPOpts *opts) return NULL; } - // we do overlap if the user forced it (suboverlap_enable == 2) or - // the user didn't forced no-overlapsub and the format is Jacosub or Ssa. - // this is because usually overlapping subtitles are found in these formats, - // while in others they are probably result of bad timing -if ((opts->suboverlap_enabled == 2) || - ((opts->suboverlap_enabled) && ((sub_format == SUB_JACOSUB) || (sub_format == SUB_SSA)))) { - adjust_subs_time(first, 6.0, fps, opts->sub_fps, 0, sub_num, uses_time);/*~6 secs AST*/ -// here we manage overlapping subtitles - sub_orig = sub_num; - n_first = sub_num; - sub_num = 0; - second = NULL; - // for each subtitle in first[] we deal with its 'block' of - // bonded subtitles - for (sub_first = 0; sub_first < n_first; ++sub_first) { - unsigned long global_start = first[sub_first].start, - global_end = first[sub_first].end, local_start, local_end; - int lines_to_add = first[sub_first].lines, sub_to_add = 0, - **placeholder = NULL, higher_line = 0, counter, start_block_sub = sub_num; - char real_block = 1; - - // here we find the number of subtitles inside the 'block' - // and its span interval. this works well only with sorted - // subtitles - while ((sub_first + sub_to_add + 1 < n_first) && (first[sub_first + sub_to_add + 1].start < global_end)) { - ++sub_to_add; - lines_to_add += first[sub_first + sub_to_add].lines; - if (first[sub_first + sub_to_add].start < global_start) { - global_start = first[sub_first + sub_to_add].start; - } - if (first[sub_first + sub_to_add].end > global_end) { - global_end = first[sub_first + sub_to_add].end; - } - } - - /* Avoid n^2 memory use for the "placeholder" data structure - * below with subtitles that have a huge number of - * consecutive overlapping lines. */ - lines_to_add = FFMIN(lines_to_add, SUB_MAX_TEXT); - - // we need a structure to keep trace of the screen lines - // used by the subs, a 'placeholder' - counter = 2 * sub_to_add + 1; // the maximum number of subs derived - // from a block of sub_to_add+1 subs - placeholder = malloc(sizeof(int *) * counter); - for (i = 0; i < counter; ++i) { - placeholder[i] = malloc(sizeof(int) * lines_to_add + 1); - for (j = 0; j < lines_to_add; ++j) { - placeholder[i][j] = -1; - } - } - - counter = 0; - local_end = global_start - 1; - do { - int ls; - - // here we find the beginning and the end of a new - // subtitle in the block - local_start = local_end + 1; - local_end = global_end; - for (j = 0; j <= sub_to_add; ++j) { - if ((first[sub_first + j].start - 1 > local_start) && (first[sub_first + j].start - 1 < local_end)) { - local_end = first[sub_first + j].start - 1; - } else if ((first[sub_first + j].end > local_start) && (first[sub_first + j].end < local_end)) { - local_end = first[sub_first + j].end; - } - } - // here we allocate the screen lines to subs we must - // display in current local_start-local_end interval. - // if the subs were yet presents in the previous interval - // they keep the same lines, otherside they get unused lines - for (j = 0; j <= sub_to_add; ++j) { - if ((first[sub_first + j].start <= local_end) && (first[sub_first + j].end > local_start)) { - unsigned long sub_lines = first[sub_first + j].lines, fragment_length = lines_to_add + 1, - tmp = 0; - char boolean = 0; - int fragment_position = -1; - - // if this is not the first new sub of the block - // we find if this sub was present in the previous - // new sub - if (counter) - for (i = 0; i < lines_to_add; ++i) { - if (placeholder[counter - 1][i] == sub_first + j) { - placeholder[counter][i] = sub_first + j; - boolean = 1; - } - } - if (boolean) - continue; - - // we are looking for the shortest among all groups of - // sequential blank lines whose length is greater than or - // equal to sub_lines. we store in fragment_position the - // position of the shortest group, in fragment_length its - // length, and in tmp the length of the group currently - // examinated - for (i = 0; i < lines_to_add; ++i) { - if (placeholder[counter][i] == -1) { - // placeholder[counter][i] is part of the current group - // of blank lines - ++tmp; - } else { - if (tmp == sub_lines) { - // current group's size fits exactly the one we - // need, so we stop looking - fragment_position = i - tmp; - tmp = 0; - break; - } - if ((tmp) && (tmp > sub_lines) && (tmp < fragment_length)) { - // current group is the best we found till here, - // but is still bigger than the one we are looking - // for, so we keep on looking - fragment_length = tmp; - fragment_position = i - tmp; - tmp = 0; - } else { - // current group doesn't fit at all, so we forget it - tmp = 0; - } - } - } - if (tmp) { - // last screen line is blank, a group ends with it - if ((tmp >= sub_lines) && (tmp < fragment_length)) { - fragment_position = i - tmp; - } - } - if (fragment_position == -1) { - // it was not possible to find free screen line(s) for a subtitle, - // usually this means a bug in the code; however we do not overlap - mp_msg(MSGT_SUBREADER, MSGL_WARN, "SUB: we could not find a suitable position for an overlapping subtitle\n"); - higher_line = SUB_MAX_TEXT + 1; - break; - } else { - for (tmp = 0; tmp < sub_lines; ++tmp) { - placeholder[counter][fragment_position + tmp] = sub_first + j; - } - } - } - } - for (j = higher_line + 1; j < lines_to_add; ++j) { - if (placeholder[counter][j] != -1) - higher_line = j; - else - break; - } - if (higher_line >= SUB_MAX_TEXT) { - // the 'block' has too much lines, so we don't overlap the - // subtitles - second = realloc(second, (sub_num + sub_to_add + 1) * sizeof(subtitle)); - for (j = 0; j <= sub_to_add; ++j) { - int ls; - memset(&second[sub_num + j], '\0', sizeof(subtitle)); - second[sub_num + j].start = first[sub_first + j].start; - second[sub_num + j].end = first[sub_first + j].end; - second[sub_num + j].lines = first[sub_first + j].lines; - second[sub_num + j].alignment = first[sub_first + j].alignment; - for (ls = 0; ls < second[sub_num + j].lines; ls++) { - second[sub_num + j].text[ls] = strdup(first[sub_first + j].text[ls]); - } - } - sub_num += sub_to_add + 1; - sub_first += sub_to_add; - real_block = 0; - break; - } - - // we read the placeholder structure and create the new - // subs. - second = realloc(second, (sub_num + 1) * sizeof(subtitle)); - memset(&second[sub_num], '\0', sizeof(subtitle)); - second[sub_num].start = local_start; - second[sub_num].end = local_end; - second[sub_num].alignment = first[sub_first].alignment; - n_max = (lines_to_add < SUB_MAX_TEXT) ? lines_to_add : SUB_MAX_TEXT; - for (i = 0, j = 0; j < n_max; ++j) { - if (placeholder[counter][j] != -1) { - int lines = first[placeholder[counter][j]].lines; - for (ls = 0; ls < lines; ++ls) { - second[sub_num].text[i++] = strdup(first[placeholder[counter][j]].text[ls]); - } - j += lines - 1; - } else { - second[sub_num].text[i++] = strdup(" "); - } - } - ++sub_num; - ++counter; - } while (local_end < global_end); - if (real_block) - for (i = 0; i < counter; ++i) - second[start_block_sub + i].lines = higher_line + 1; - - counter = 2 * sub_to_add + 1; - for (i = 0; i < counter; ++i) { - free(placeholder[i]); - } - free(placeholder); - sub_first += sub_to_add; - } - - for (j = sub_orig - 1; j >= 0; --j) { - for (i = first[j].lines - 1; i >= 0; --i) { - free(first[j].text[i]); - } - } - free(first); - - return_sub = second; -} else { //if(suboverlap_enabled) adjust_subs_time(first, 6.0, fps, opts->sub_fps, 1, sub_num, uses_time);/*~6 secs AST*/ return_sub = first; -} + if (return_sub == NULL) return NULL; subt_data = talloc_zero(NULL, sub_data); talloc_set_destructor(subt_data, sub_destroy); -- cgit v1.2.3 From 1058d80a5ecafeb4eebcc558be548ac6877955ef Mon Sep 17 00:00:00 2001 From: wm4 Date: Sun, 23 Jun 2013 22:09:04 +0200 Subject: sd_ass: handle libavformat ASS comment packets as well Currently, we are filtering libavformat style ASS packets by checking whether they are prefixed "Dialogue: ". Unfortunately, comment packets are demuxed too. These start with "Comment: ", so they are not caught. Change the filtering, and use the codec ID instead. libavformat uses "ssa" as codec ID for ASS subtitles, while mpv uses "ass". Also, at least FFmpeg will change the ASS packet format to the same format mpv and Matroska use, and identify these with "ass" as codec ID, so this is works out nicely. --- sub/sd_ass.c | 30 +++++++++++++----------------- sub/sd_lavc_conv.c | 2 +- 2 files changed, 14 insertions(+), 18 deletions(-) (limited to 'sub') diff --git a/sub/sd_ass.c b/sub/sd_ass.c index c46c55c1ab..22cd33b113 100644 --- a/sub/sd_ass.c +++ b/sub/sd_ass.c @@ -41,17 +41,14 @@ struct sd_ass_priv { char last_text[500]; }; -static bool is_native_ass(const char *t) -{ - return strcmp(t, "ass") == 0 || strcmp(t, "ssa") == 0; -} - static bool supports_format(const char *format) { // ass-text is produced by converters and the subreader.c ssa parser; this // format has ASS tags, but doesn't start with any prelude, nor does it // have extradata. - return format && (is_native_ass(format) || strcmp(format, "ass-text") == 0); + return format && (strcmp(format, "ass") == 0 || + strcmp(format, "ssa") == 0 || + strcmp(format, "ass-text") == 0); } static void free_last_event(ASS_Track *track) @@ -64,7 +61,7 @@ static void free_last_event(ASS_Track *track) static int init(struct sd *sd) { struct MPOpts *opts = sd->opts; - if (!sd->ass_library || !sd->ass_renderer) + if (!sd->ass_library || !sd->ass_renderer || !sd->codec) return -1; bool is_converted = sd->converted_from != NULL; @@ -99,16 +96,15 @@ static void decode(struct sd *sd, struct demux_packet *packet) unsigned char *text = data; struct sd_ass_priv *ctx = sd->priv; ASS_Track *track = ctx->ass_track; - if (is_native_ass(sd->codec)) { - if (bstr_startswith0((bstr){data, data_len}, "Dialogue: ")) { - // broken ffmpeg ASS packet format - ctx->flush_on_seek = true; - ass_process_data(track, data, data_len); - } else { - ass_process_chunk(track, data, data_len, - (long long)(pts*1000 + 0.5), - (long long)(duration*1000 + 0.5)); - } + if (strcmp(sd->codec, "ass") == 0) { + ass_process_chunk(track, data, data_len, + (long long)(pts*1000 + 0.5), + (long long)(duration*1000 + 0.5)); + return; + } else if (strcmp(sd->codec, "ssa") == 0) { + // broken ffmpeg ASS packet format + ctx->flush_on_seek = true; + ass_process_data(track, data, data_len); return; } // plaintext subs diff --git a/sub/sd_lavc_conv.c b/sub/sd_lavc_conv.c index 1fc0262f96..4f24e20709 100644 --- a/sub/sd_lavc_conv.c +++ b/sub/sd_lavc_conv.c @@ -87,7 +87,7 @@ static int init(struct sd *sd) avctx->time_base = (AVRational) {1, 1000}; priv->avctx = avctx; sd->priv = priv; - sd->output_codec = "ass"; + sd->output_codec = "ssa"; sd->output_extradata = avctx->subtitle_header; sd->output_extradata_len = avctx->subtitle_header_size; if (sd->output_extradata) { -- cgit v1.2.3 From 7e033da8923801da5f8ef08f442dd50f172bac7c Mon Sep 17 00:00:00 2001 From: wm4 Date: Sun, 23 Jun 2013 22:10:10 +0200 Subject: sd_ass: disable special handling of subtitles with duration 0 sd_ass contains some code that treats subtitle events with duration 0 specially, and adjust their duration so that they will disappear with the next event. This is most likely not needed anymore. Some subtitle formats allow omitting the duration so that the event is visible until the next one, but both subreader.c as well as libavformat subtitle demuxers already handle this. Subtitles embedded in mp4 files (movtext) used to trigger this code. But these files appear to export subtitle duration correctly (at least libavcodec's movtext decoder is using this assumption). Since commit 6dbedd2 changed demux_lavf to actually copy the packet duration field, the code removed with this commit isn't needed anymore for correct display of movtext subtitles. (The change in sd_movtext is for dropping empty subtitle events, which would now be "displayed" - libavcodec does the same.) On the other hand, this code incorrectly displayed hidden events in .srt subtitles. See for example the first event in SubRip_capability_tester.srt (part of FFmpeg's FATE). These intentionally have a duration of 0, and should not be displayed. (As of with this commit, they are still displayed in external .srt subs because of subreader.c hacks.) However, we can't be 100% sure that this code is really unneeded, so just comment the code. Hopefully it can be removed if there are no regressions after some weeks or months. --- sub/sd_ass.c | 20 ++++++++++++++++++++ sub/sd_movtext.c | 3 ++- 2 files changed, 22 insertions(+), 1 deletion(-) (limited to 'sub') diff --git a/sub/sd_ass.c b/sub/sd_ass.c index 22cd33b113..fad323a735 100644 --- a/sub/sd_ass.c +++ b/sub/sd_ass.c @@ -32,6 +32,10 @@ #include "ass_mp.h" #include "sd.h" +// Enable code that treats subtitle events with duration 0 specially, and +// adjust their duration so that they will disappear with the next event. +#define INCOMPLETE_EVENTS 0 + struct sd_ass_priv { struct ass_track *ass_track; bool vsfilter_aspect; @@ -114,6 +118,7 @@ static void decode(struct sd *sd, struct demux_packet *packet) } long long ipts = pts * 1000 + 0.5; long long iduration = duration * 1000 + 0.5; +#if INCOMPLETE_EVENTS if (ctx->incomplete_event) { ctx->incomplete_event = false; ASS_Event *event = track->events + track->n_events - 1; @@ -144,6 +149,21 @@ static void decode(struct sd *sd, struct demux_packet *packet) iduration = 10000; ctx->incomplete_event = true; } +#else + if (duration <= 0) { + mp_msg(MSGT_SUBREADER, MSGL_WARN, "Subtitle without duration or " + "duration set to 0 at pts %f, ignored\n", pts); + return; + } + if (!sd->no_remove_duplicates) { + for (int i = 0; i < track->n_events; i++) { + if (track->events[i].Start == ipts + && (track->events[i].Duration == iduration) + && strcmp(track->events[i].Text, text) == 0) + return; // We've already added this subtitle + } + } +#endif int eid = ass_alloc_event(track); ASS_Event *event = track->events + eid; event->Start = ipts; diff --git a/sub/sd_movtext.c b/sub/sd_movtext.c index a6ef120ec7..3038a4c132 100644 --- a/sub/sd_movtext.c +++ b/sub/sd_movtext.c @@ -42,7 +42,8 @@ static void decode(struct sd *sd, struct demux_packet *packet) return; len = FFMIN(len - 2, AV_RB16(data)); data += 2; - sd_conv_add_packet(sd, data, len, packet->pts, packet->duration); + if (len > 0) + sd_conv_add_packet(sd, data, len, packet->pts, packet->duration); } const struct sd_functions sd_movtext = { -- cgit v1.2.3 From 2cd53d449abacde3e461c0daa15617aca4450eea Mon Sep 17 00:00:00 2001 From: wm4 Date: Sun, 23 Jun 2013 22:10:38 +0200 Subject: sd_ass: fix nonsense Actually check the newly added text for whitespace, and not the uninitialized buffer after it. Also, if an even is only whitespace, don't add it at all. --- sub/sd_ass.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'sub') diff --git a/sub/sd_ass.c b/sub/sd_ass.c index fad323a735..9c51398f33 100644 --- a/sub/sd_ass.c +++ b/sub/sd_ass.c @@ -275,8 +275,11 @@ static char *get_text(struct sd *sd, double pts) if (event->Text) { int start = b.len; ass_to_plaintext(&b, event->Text); - if (!is_whitespace_only(&b.start[b.len], b.len - start)) + if (is_whitespace_only(&b.start[start], b.len - start)) { + b.len = start; + } else { append(&b, '\n'); + } } } } -- cgit v1.2.3 From db2e1ef4d210f5a8a4a2555d0a78b0a4dea103ec Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 21 Jun 2013 00:26:05 +0200 Subject: Move/rename subreader.c --- sub/ass_mp.c | 1 - sub/ass_mp.h | 1 - sub/dec_sub.c | 2 +- sub/sub.c | 1 - sub/subreader.c | 1378 ------------------------------------------------------- sub/subreader.h | 79 ---- 6 files changed, 1 insertion(+), 1461 deletions(-) delete mode 100644 sub/subreader.c delete mode 100644 sub/subreader.h (limited to 'sub') diff --git a/sub/ass_mp.c b/sub/ass_mp.c index 0efbdd9c90..48a713f6ee 100644 --- a/sub/ass_mp.c +++ b/sub/ass_mp.c @@ -33,7 +33,6 @@ #include "core/mp_msg.h" #include "core/path.h" #include "ass_mp.h" -#include "subreader.h" #include "sub/sub.h" #include "stream/stream.h" #include "core/options.h" diff --git a/sub/ass_mp.h b/sub/ass_mp.h index 9f40b34166..dbdc95f031 100644 --- a/sub/ass_mp.h +++ b/sub/ass_mp.h @@ -25,7 +25,6 @@ #include #include "config.h" -#include "subreader.h" // This is probably arbitrary. // sd_lavc_conv might indirectly still assume this PlayResY, though. diff --git a/sub/dec_sub.c b/sub/dec_sub.c index 230dfa2f55..bfd6d90d03 100644 --- a/sub/dec_sub.c +++ b/sub/dec_sub.c @@ -25,7 +25,7 @@ #include "sd.h" #include "sub.h" #include "dec_sub.h" -#include "subreader.h" +#include "demux/subreader.h" #include "core/options.h" #include "core/mp_msg.h" diff --git a/sub/sub.c b/sub/sub.c index a0965dc1ec..7eb32d5885 100644 --- a/sub/sub.c +++ b/sub/sub.c @@ -37,7 +37,6 @@ #include "dec_sub.h" #include "img_convert.h" #include "draw_bmp.h" -#include "subreader.h" #include "video/mp_image.h" #include "video/mp_image_pool.h" diff --git a/sub/subreader.c b/sub/subreader.c deleted file mode 100644 index befac689a4..0000000000 --- a/sub/subreader.c +++ /dev/null @@ -1,1378 +0,0 @@ -/* - * Subtitle reader with format autodetection - * - * Copyright (c) 2001 laaz - * Some code cleanup & realloc() by A'rpi/ESP-team - * - * This file is part of MPlayer. - * - * MPlayer 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. - * - * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "core/mp_msg.h" -#include "subreader.h" -#include "core/mp_common.h" -#include "core/options.h" -#include "stream/stream.h" -#include "libavutil/common.h" -#include "libavutil/avstring.h" - -#ifdef CONFIG_ENCA -#include -#endif - -#define ERR ((void *) -1) - -#ifdef CONFIG_ICONV -#include -#endif - -// Parameter struct for the format-specific readline functions -struct readline_args { - int utf16; - struct MPOpts *opts; - - // subtitle reader state used by some formats - - float mpsub_multiplier; - float mpsub_position; - int sub_slacktime; - - /* - Some subtitling formats, namely AQT and Subrip09, define the end of a - subtitle as the beginning of the following. Since currently we read one - subtitle at time, for these format we keep two global *subtitle, - previous_aqt_sub and previous_subrip09_sub, pointing to previous subtitle, - so we can change its end when we read current subtitle starting time. - We use a single global unsigned long, - previous_sub_end, for both (and even future) formats, to store the end of - the previous sub: it is initialized to 0 in sub_read_file and eventually - modified by sub_read_aqt_line or sub_read_subrip09_line. - */ - unsigned long previous_sub_end; -}; - -/* Maximal length of line of a subtitle */ -#define LINE_LEN 1000 - -static int eol(char p) { - return p=='\r' || p=='\n' || p=='\0'; -} - -/* Remove leading and trailing space */ -static void trail_space(char *s) { - int i = 0; - while (isspace(s[i])) ++i; - if (i) strcpy(s, s + i); - i = strlen(s) - 1; - while (i > 0 && isspace(s[i])) s[i--] = '\0'; -} - -static char *stristr(const char *haystack, const char *needle) { - int len = 0; - const char *p = haystack; - - if (!(haystack && needle)) return NULL; - - len=strlen(needle); - while (*p != '\0') { - if (strncasecmp(p, needle, len) == 0) return (char*)p; - p++; - } - - return NULL; -} - -static void sami_add_line(subtitle *current, char *buffer, char **pos) { - char *p = *pos; - *p = 0; - trail_space(buffer); - if (*buffer && current->lines < SUB_MAX_TEXT) - current->text[current->lines++] = strdup(buffer); - *pos = buffer; -} - -static subtitle *sub_read_line_sami(stream_t* st, subtitle *current, - struct readline_args *args) -{ - int utf16 = args->utf16; - static char line[LINE_LEN+1]; - static char *s = NULL, *slacktime_s; - char text[LINE_LEN+1], *p=NULL, *q; - int state; - - current->lines = current->start = current->end = 0; - current->alignment = SUB_ALIGNMENT_BOTTOMCENTER; - state = 0; - - /* read the first line */ - if (!s) - if (!(s = stream_read_line(st, line, LINE_LEN, utf16))) return 0; - - do { - switch (state) { - - case 0: /* find "START=" or "Slacktime:" */ - slacktime_s = stristr (s, "Slacktime:"); - if (slacktime_s) - args->sub_slacktime = strtol (slacktime_s+10, NULL, 0) / 10; - - s = stristr (s, "Start="); - if (s) { - current->start = strtol (s + 6, &s, 0) / 10; - /* eat '>' */ - for (; *s != '>' && *s != '\0'; s++); - s++; - state = 1; continue; - } - break; - - case 1: /* find (optional) " TAG */ - if (*s == '\0') - break; - s++; - continue; - - case 2: /* find ">" */ - if ((s = strchr (s, '>'))) { s++; state = 3; p = text; continue; } - break; - - case 3: /* get all text until '<' appears */ - if (p - text >= LINE_LEN) - sami_add_line(current, text, &p); - if (*s == '\0') break; - else if (!strncasecmp (s, "
", 4)) { - sami_add_line(current, text, &p); - s += 4; - } - else if ((*s == '{') && !args->opts->sub_no_text_pp) { state = 5; ++s; continue; } - else if (*s == '<') { state = 4; } - else if (!strncasecmp (s, " ", 6)) { *p++ = ' '; s += 6; } - else if (*s == '\t') { *p++ = ' '; s++; } - else if (*s == '\r' || *s == '\n') { s++; } - else *p++ = *s++; - - /* skip duplicated space */ - if (p > text + 2) if (*(p-1) == ' ' && *(p-2) == ' ') p--; - - continue; - - case 4: /* get current->end or skip */ - q = stristr (s, "Start="); - if (q) { - current->end = strtol (q + 6, &q, 0) / 10 - 1; - *p = '\0'; trail_space (text); - if (text[0] != '\0') - current->text[current->lines++] = strdup (text); - if (current->lines > 0) { state = 99; break; } - state = 0; continue; - } - s = strchr (s, '>'); - if (s) { s++; state = 3; continue; } - break; - case 5: /* get rid of {...} text, but read the alignment code */ - if ((*s == '\\') && (*(s + 1) == 'a') && !args->opts->sub_no_text_pp) { - if (stristr(s, "\\a1") != NULL) { - current->alignment = SUB_ALIGNMENT_BOTTOMLEFT; - s = s + 3; - } - if (stristr(s, "\\a2") != NULL) { - current->alignment = SUB_ALIGNMENT_BOTTOMCENTER; - s = s + 3; - } else if (stristr(s, "\\a3") != NULL) { - current->alignment = SUB_ALIGNMENT_BOTTOMRIGHT; - s = s + 3; - } else if ((stristr(s, "\\a4") != NULL) || (stristr(s, "\\a5") != NULL) || (stristr(s, "\\a8") != NULL)) { - current->alignment = SUB_ALIGNMENT_TOPLEFT; - s = s + 3; - } else if (stristr(s, "\\a6") != NULL) { - current->alignment = SUB_ALIGNMENT_TOPCENTER; - s = s + 3; - } else if (stristr(s, "\\a7") != NULL) { - current->alignment = SUB_ALIGNMENT_TOPRIGHT; - s = s + 3; - } else if (stristr(s, "\\a9") != NULL) { - current->alignment = SUB_ALIGNMENT_MIDDLELEFT; - s = s + 3; - } else if (stristr(s, "\\a10") != NULL) { - current->alignment = SUB_ALIGNMENT_MIDDLECENTER; - s = s + 4; - } else if (stristr(s, "\\a11") != NULL) { - current->alignment = SUB_ALIGNMENT_MIDDLERIGHT; - s = s + 4; - } - } - if (*s == '}') state = 3; - ++s; - continue; - } - - /* read next line */ - if (state != 99 && !(s = stream_read_line (st, line, LINE_LEN, utf16))) { - if (current->start > 0) { - break; // if it is the last subtitle - } else { - return 0; - } - } - - } while (state != 99); - - // For the last subtitle - if (current->end <= 0) { - current->end = current->start + args->sub_slacktime; - sami_add_line(current, text, &p); - } - - return current; -} - - -static const char *sub_readtext(const char *source, char **dest) { - int len=0; - const char *p=source; - -// printf("src=%p dest=%p \n",source,dest); - - while ( !eol(*p) && *p!= '|' ) { - p++,len++; - } - - *dest= malloc (len+1); - if (!*dest) {return ERR;} - - strncpy(*dest, source, len); - (*dest)[len]=0; - - while (*p=='\r' || *p=='\n' || *p=='|') p++; - - if (*p) return p; // not-last text field - else return NULL; // last text field -} - -static subtitle *set_multiline_text(subtitle *current, const char *text, int start) -{ - int i = start; - while ((text = sub_readtext(text, current->text + i))) { - if (current->text[i] == ERR) return ERR; - i++; - if (i >= SUB_MAX_TEXT) { - mp_msg(MSGT_SUBREADER, MSGL_WARN, "Too many lines in a subtitle\n"); - current->lines = i; - return current; - } - } - current->lines = i + 1; - return current; -} - -static subtitle *sub_read_line_microdvd(stream_t *st,subtitle *current, - struct readline_args *args) -{ - int utf16 = args->utf16; - char line[LINE_LEN+1]; - char line2[LINE_LEN+1]; - - do { - if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL; - } while ((sscanf (line, - "{%ld}{}%[^\r\n]", - &(current->start), line2) < 2) && - (sscanf (line, - "{%ld}{%ld}%[^\r\n]", - &(current->start), &(current->end), line2) < 3)); - - return set_multiline_text(current, line2, 0); -} - -static subtitle *sub_read_line_mpl2(stream_t *st,subtitle *current, - struct readline_args *args) -{ - int utf16 = args->utf16; - char line[LINE_LEN+1]; - char line2[LINE_LEN+1]; - - do { - if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL; - } while ((sscanf (line, - "[%ld][%ld]%[^\r\n]", - &(current->start), &(current->end), line2) < 3)); - current->start *= 10; - current->end *= 10; - - return set_multiline_text(current, line2, 0); -} - -static subtitle *sub_read_line_subrip(stream_t* st, subtitle *current, - struct readline_args *args) -{ - int utf16 = args->utf16; - char line[LINE_LEN+1]; - int a1,a2,a3,a4,b1,b2,b3,b4; - char *p=NULL, *q=NULL; - int len; - - while (1) { - if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL; - if (sscanf (line, "%d:%d:%d.%d,%d:%d:%d.%d",&a1,&a2,&a3,&a4,&b1,&b2,&b3,&b4) < 8) continue; - current->start = a1*360000+a2*6000+a3*100+a4; - current->end = b1*360000+b2*6000+b3*100+b4; - - if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL; - - p=q=line; - for (current->lines=1; current->lines < SUB_MAX_TEXT; current->lines++) { - for (q=p,len=0; *p && *p!='\r' && *p!='\n' && *p!='|' && strncmp(p,"[br]",4); p++,len++); - current->text[current->lines-1]=malloc (len+1); - if (!current->text[current->lines-1]) return ERR; - strncpy (current->text[current->lines-1], q, len); - current->text[current->lines-1][len]='\0'; - if (!*p || *p=='\r' || *p=='\n') break; - if (*p=='|') p++; - else while (*p++!=']'); - } - break; - } - return current; -} - -static subtitle *sub_read_line_subviewer(stream_t *st, subtitle *current, - struct readline_args *args) -{ - int utf16 = args->utf16; - int a1, a2, a3, a4, b1, b2, b3, b4, j = 0; - - while (!current->text[0]) { - char line[LINE_LEN + 1], full_line[LINE_LEN + 1]; - int i; - - /* Parse SubRip header */ - if (!stream_read_line(st, line, LINE_LEN, utf16)) - return NULL; - if (sscanf(line, "%d:%d:%d%*1[,.:]%d --> %d:%d:%d%*1[,.:]%d", - &a1, &a2, &a3, &a4, &b1, &b2, &b3, &b4) < 8) - continue; - - current->start = a1 * 360000 + a2 * 6000 + a3 * 100 + a4 / 10; - current->end = b1 * 360000 + b2 * 6000 + b3 * 100 + b4 / 10; - - /* Concat lines */ - full_line[0] = 0; - for (i = 0; i < SUB_MAX_TEXT; i++) { - int blank = 1, len = 0; - char *p; - - if (!stream_read_line(st, line, LINE_LEN, utf16)) - break; - - for (p = line; *p != '\n' && *p != '\r' && *p; p++, len++) - if (*p != ' ' && *p != '\t') - blank = 0; - - if (blank) - break; - - *p = 0; - - if (!(j + 1 + len < sizeof(full_line) - 1)) - break; - - if (j != 0) - full_line[j++] = '\n'; - strcpy(&full_line[j], line); - j += len; - } - - if (full_line[0]) { - current->text[0] = strdup(full_line); - current->lines = 1; - } - } - return current; -} - -static subtitle *sub_read_line_subviewer2(stream_t *st,subtitle *current, - struct readline_args *args) -{ - int utf16 = args->utf16; - char line[LINE_LEN+1]; - int a1,a2,a3,a4; - char *p=NULL; - int i,len; - - while (!current->text[0]) { - if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL; - if (line[0]!='{') - continue; - if ((len=sscanf (line, "{T %d:%d:%d:%d",&a1,&a2,&a3,&a4)) < 4) - continue; - current->start = a1*360000+a2*6000+a3*100+a4/10; - for (i=0; itext[i]=malloc (len+1); - if (!current->text[i]) return ERR; - strncpy (current->text[i], line, len); current->text[i][len]='\0'; - ++i; - } else { - break; - } - } - current->lines=i; - } - return current; -} - - -static subtitle *sub_read_line_vplayer(stream_t *st,subtitle *current, - struct readline_args *args) -{ - int utf16 = args->utf16; - char line[LINE_LEN+1]; - int a1,a2,a3; - char *p=NULL, separator; - int len,plen; - - while (!current->text[0]) { - if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL; - if ((len=sscanf (line, "%d:%d:%d%c%n",&a1,&a2,&a3,&separator,&plen)) < 4) - continue; - - if (!(current->start = a1*360000+a2*6000+a3*100)) - continue; - /* removed by wodzu - p=line; - // finds the body of the subtitle - for (i=0; i<3; i++){ - p=strchr(p,':'); - if (p==NULL) break; - ++p; - } - if (p==NULL) { - printf("SUB: Skipping incorrect subtitle line!\n"); - continue; - } - */ - // by wodzu: hey! this time we know what length it has! what is - // that magic for? it can't deal with space instead of third - // colon! look, what simple it can be: - p = &line[ plen ]; - - if (*p!='|') { - // - return set_multiline_text(current, p, 0); - } - } - return current; -} - -static subtitle *sub_read_line_rt(stream_t *st,subtitle *current, - struct readline_args *args) -{ - int utf16 = args->utf16; - - //TODO: This format uses quite rich (sub/super)set of xhtml - // I couldn't check it since DTD is not included. - // WARNING: full XML parses can be required for proper parsing - char line[LINE_LEN+1]; - int a1,a2,a3,a4,b1,b2,b3,b4; - char *p=NULL,*next=NULL; - int len,plen; - - while (!current->text[0]) { - if (!stream_read_line (st, line, LINE_LEN, utf16)) return NULL; - //TODO: it seems that format of time is not easily determined, it may be 1:12, 1:12.0 or 0:1:12.0 - //to describe the same moment in time. Maybe there are even more formats in use. - //if ((len=sscanf (line, "