diff --git a/core/mplayer.c b/core/mplayer.c index e0049a8bd0..f766246f63 100644 --- a/core/mplayer.c +++ b/core/mplayer.c @@ -71,7 +71,6 @@ #include "core/mplayer.h" #include "core/m_property.h" -#include "demux/subreader.h" #include "sub/find_subfiles.h" #include "sub/dec_sub.h" #include "sub/sd.h" @@ -1044,7 +1043,6 @@ struct track *mp_add_subtitles(struct MPContext *mpctx, char *filename, { struct MPOpts *opts = &mpctx->opts; struct ass_track *asst = NULL; - sub_data *subd = NULL; if (filename == NULL) return NULL; @@ -1055,17 +1053,14 @@ struct track *mp_add_subtitles(struct MPContext *mpctx, char *filename, // the weird special-cases. #ifdef CONFIG_ASS asst = mp_ass_read_stream(mpctx->ass_library, filename, opts->sub_cp); - if (!asst) - subd = sub_read_file(filename, fps, &mpctx->opts); - if (asst || subd) { + if (asst) { struct demuxer *d = new_sub_pseudo_demuxer(opts); assert(d->num_streams == 1); struct sh_stream *s = d->streams[0]; assert(s->type == STREAM_SUB); - s->codec = asst ? "ass" : subd->codec; + s->codec = "ass"; s->sub->track = asst; - s->sub->sub_data = subd; struct sh_sub **pptr = talloc(d, struct sh_sub*); *pptr = s->sub; diff --git a/core/options.h b/core/options.h index f925990a6c..c31d2063c9 100644 --- a/core/options.h +++ b/core/options.h @@ -147,7 +147,6 @@ typedef struct MPOpts { // subreader.c int suboverlap_enabled; char *sub_cp; - int sub_no_text_pp; char *audio_stream; int audio_stream_cache; diff --git a/demux/demux.c b/demux/demux.c index be00f38bd7..379a74f654 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -68,6 +68,7 @@ extern const demuxer_desc_t demuxer_desc_mpeg4_es; extern const demuxer_desc_t demuxer_desc_h264_es; extern const demuxer_desc_t demuxer_desc_mpeg_ts; extern const demuxer_desc_t demuxer_desc_sub; +extern const demuxer_desc_t demuxer_desc_subreader; /* Please do not add any new demuxers here. If you want to implement a new * demuxer, add it to libavformat, except for wrappers around external @@ -83,6 +84,7 @@ const demuxer_desc_t *const demuxer_list[] = { #endif &demuxer_desc_matroska, &demuxer_desc_lavf, + &demuxer_desc_subreader, &demuxer_desc_avi, &demuxer_desc_asf, #ifdef CONFIG_MNG diff --git a/demux/demux.h b/demux/demux.h index bf16001c0f..9c10ca0201 100644 --- a/demux/demux.h +++ b/demux/demux.h @@ -71,6 +71,7 @@ enum demuxer_type { DEMUXER_TYPE_MNG, DEMUXER_TYPE_EDL, DEMUXER_TYPE_CUE, + DEMUXER_TYPE_SUBREADER, /* Values after this are for internal use and can not be selected * as demuxer type by the user (-demuxer option). */ diff --git a/demux/demux_subreader.c b/demux/demux_subreader.c index befac689a4..e95ffda976 100644 --- a/demux/demux_subreader.c +++ b/demux/demux_subreader.c @@ -28,14 +28,15 @@ #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" +#include "demux/demux.h" #ifdef CONFIG_ENCA #include @@ -47,6 +48,54 @@ #include #endif +// subtitle formats +#define SUB_INVALID -1 +#define SUB_MICRODVD 0 +#define SUB_SUBRIP 1 +#define SUB_SUBVIEWER 2 +#define SUB_SAMI 3 +#define SUB_VPLAYER 4 +#define SUB_RT 5 +#define SUB_SSA 6 +#define SUB_PJS 7 +#define SUB_MPSUB 8 +#define SUB_AQTITLE 9 +#define SUB_SUBVIEWER2 10 +#define SUB_SUBRIP09 11 +#define SUB_JACOSUB 12 +#define SUB_MPL2 13 + +#define SUB_MAX_TEXT 12 +#define SUB_ALIGNMENT_BOTTOMLEFT 1 +#define SUB_ALIGNMENT_BOTTOMCENTER 2 +#define SUB_ALIGNMENT_BOTTOMRIGHT 3 +#define SUB_ALIGNMENT_MIDDLELEFT 4 +#define SUB_ALIGNMENT_MIDDLECENTER 5 +#define SUB_ALIGNMENT_MIDDLERIGHT 6 +#define SUB_ALIGNMENT_TOPLEFT 7 +#define SUB_ALIGNMENT_TOPCENTER 8 +#define SUB_ALIGNMENT_TOPRIGHT 9 + +typedef struct subtitle { + + int lines; + + unsigned long start; + unsigned long end; + + char *text[SUB_MAX_TEXT]; + unsigned char alignment; +} subtitle; + +typedef struct sub_data { + const char *codec; + subtitle *subtitles; + int sub_uses_time; + int sub_num; // number of subtitle structs + int sub_errs; + double fallback_fps; +} sub_data; + // Parameter struct for the format-specific readline functions struct readline_args { int utf16; @@ -58,6 +107,8 @@ struct readline_args { float mpsub_position; int sub_slacktime; + int uses_time; + /* Some subtitling formats, namely AQT and Subrip09, define the end of a subtitle as the beginning of the following. Since currently we read one @@ -171,7 +222,7 @@ static subtitle *sub_read_line_sami(stream_t* st, subtitle *current, 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 = 5; ++s; continue; } else if (*s == '<') { state = 4; } else if (!strncasecmp (s, " ", 6)) { *p++ = ' '; s += 6; } else if (*s == '\t') { *p++ = ' '; s++; } @@ -197,7 +248,7 @@ static subtitle *sub_read_line_sami(stream_t* st, subtitle *current, 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 ((*s == '\\') && (*(s + 1) == 'a')) { if (stristr(s, "\\a1") != NULL) { current->alignment = SUB_ALIGNMENT_BOTTOMLEFT; s = s + 3; @@ -1130,6 +1181,7 @@ struct subreader { void (*post)(subtitle *dest); const char *name; const char *codec_name; + struct readline_args args; }; #ifdef CONFIG_ENCA @@ -1192,16 +1244,9 @@ static const char* guess_cp(stream_t *st, const char *preferred_language, const #undef MAX_GUESS_BUFFER_SIZE #endif -static int sub_destroy(void *ptr); - -sub_data* sub_read_file(char *filename, float fps, struct MPOpts *opts) +static bool subreader_autodetect(stream_t *fd, struct MPOpts *opts, + struct subreader *out) { - int utf16; - stream_t* fd; - 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[]= { { sub_read_line_microdvd, NULL, "microdvd", "microdvd" }, @@ -1221,42 +1266,46 @@ sub_data* sub_read_file(char *filename, float fps, struct MPOpts *opts) }; const struct subreader *srp; - if(filename==NULL) return NULL; //qnx segfault - fd=open_stream (filename, NULL, NULL); if (!fd) return NULL; - int sub_format = SUB_INVALID; + int utf16; + int uses_time = 0; for (utf16 = 0; sub_format == SUB_INVALID && utf16 < 3; utf16++) { sub_format=sub_autodetect (fd, &uses_time, utf16); stream_seek(fd,0); } utf16--; - struct readline_args args = {utf16, opts}; - args.sub_slacktime = 20000; //20 sec - args.mpsub_multiplier = (uses_time ? 100.0 : 1.0); - if (sub_format==SUB_INVALID) { - mp_msg(MSGT_SUBREADER,MSGL_WARN,"SUB: Could not determine file format\n"); - free_stream(fd); - return NULL; + mp_msg(MSGT_SUBREADER,MSGL_V,"SUB: Could not determine file format\n"); + return false; } srp=sr+sub_format; mp_msg(MSGT_SUBREADER, MSGL_V, "SUB: Detected subtitle file format: %s\n", srp->name); + *out = *srp; + out->args = (struct readline_args) { + .utf16 = utf16, + .opts = opts, + .sub_slacktime = 20000, //20 sec + .mpsub_multiplier = (uses_time ? 100.0 : 1.0), + .uses_time = uses_time, + }; + + return true; +} + +static sub_data* sub_read_file(stream_t *fd, struct subreader *srp) +{ + struct MPOpts *opts = fd->opts; + float fps = 23.976; + int n_max, i, j; + subtitle *first, *sub, *return_sub, *alloced_sub = NULL; + sub_data *subt_data; + int sub_num = 0, sub_errs = 0; + struct readline_args args = srp->args; + #ifdef CONFIG_ICONV - iconv_t icdsc = (iconv_t)(-1); - { - int l,k; - k = -1; - if ((l=strlen(filename))>4){ - char *exts[] = {".utf", ".utf8", ".utf-8" }; - for (k=3;--k>=0;) - if (l >= strlen(exts[k]) && !strcasecmp(filename+(l - strlen(exts[k])), exts[k])){ - break; - } - } - if (k<0) icdsc = subcp_open(fd, opts->sub_cp); - } + iconv_t icdsc = subcp_open(fd, opts->sub_cp); #endif sub_num=0;n_max=32; @@ -1287,11 +1336,10 @@ sub_data* sub_read_file(char *filename, float fps, struct MPOpts *opts) #endif free(first); free(alloced_sub); - free_stream(fd); return NULL; } // Apply any post processing that needs recoding first - if ((sub!=ERR) && !args.opts->sub_no_text_pp && srp->post) srp->post(sub); + if ((sub!=ERR) && srp->post) srp->post(sub); if(!sub_num || (first[sub_num - 1].start <= sub->start)){ first[sub_num].start = sub->start; first[sub_num].end = sub->end; @@ -1333,8 +1381,6 @@ sub_data* sub_read_file(char *filename, float fps, struct MPOpts *opts) if(sub==ERR) ++sub_errs; else ++sub_num; // Error vs. Valid } - free_stream(fd); - #ifdef CONFIG_ICONV subcp_close(icdsc); #endif @@ -1349,15 +1395,13 @@ sub_data* sub_read_file(char *filename, float fps, struct MPOpts *opts) return NULL; } - adjust_subs_time(first, 6.0, fps, opts->sub_fps, 1, sub_num, uses_time);/*~6 secs AST*/ + adjust_subs_time(first, 6.0, fps, opts->sub_fps, 1, sub_num, args.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); subt_data->codec = srp->codec_name ? srp->codec_name : "text"; - subt_data->filename = strdup(filename); - subt_data->sub_uses_time = uses_time; + subt_data->sub_uses_time = args.uses_time; subt_data->sub_num = sub_num; subt_data->sub_errs = sub_errs; subt_data->subtitles = return_sub; @@ -1365,14 +1409,145 @@ sub_data* sub_read_file(char *filename, float fps, struct MPOpts *opts) return subt_data; } -static int sub_destroy(void *ptr) +static void subdata_free(sub_data *subd) { - sub_data *subd = ptr; int i, j; for (i = 0; i < subd->sub_num; i++) for (j = 0; j < subd->subtitles[i].lines; j++) free( subd->subtitles[i].text[j] ); free( subd->subtitles ); - free( subd->filename ); - return 0; + talloc_free(subd); } + +struct priv { + struct demux_packet **pkts; + int num_pkts; + int current; + struct sh_stream *sh; +}; + +static void add_sub_data(struct demuxer *demuxer, struct sub_data *subdata) +{ + struct priv *priv = demuxer->priv; + + 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' + + char *data = talloc_array(NULL, char, len); + + char *p = data; + 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 = talloc_ptrtype(priv, pkt); + *pkt = (struct demux_packet) { + .pts = st->start * t, + .duration = (st->end - st->start) * t, + .buffer = talloc_steal(pkt, data), + .len = strlen(data), + }; + + MP_TARRAY_APPEND(priv, priv->pkts, priv->num_pkts, pkt); + } +} + +static struct stream *read_probe_stream(struct stream *s, int max) +{ + // Very roundabout, but only needed for initial probing. + bstr probe = stream_peek(s, max); + return open_memory_stream(probe.start, probe.len); +} + +#define PROBE_SIZE FFMIN(32 * 1024, STREAM_MAX_BUFFER_SIZE) + +static int d_check_file(struct demuxer *demuxer) +{ + struct stream *ps = read_probe_stream(demuxer->stream, PROBE_SIZE); + + struct subreader sr; + bool res = subreader_autodetect(ps, demuxer->opts, &sr); + + free_stream(ps); + + if (!res) + return 0; + + sub_data *sd = sub_read_file(demuxer->stream, &sr); + if (!sd) + return 0; + + struct priv *p = talloc_zero(demuxer, struct priv); + demuxer->priv = p; + + p->sh = new_sh_stream(demuxer, STREAM_SUB); + p->sh->codec = sd->codec; + + add_sub_data(demuxer, sd); + subdata_free(sd); + + demuxer->accurate_seek = true; + + return DEMUXER_TYPE_SUBREADER; +} + +static int d_fill_buffer(struct demuxer *demuxer, struct demux_stream *ds) +{ + struct priv *p = demuxer->priv; + struct demux_packet *dp = demux_packet_list_fill(p->pkts, p->num_pkts, + &p->current); + return demuxer_add_packet(demuxer, p->sh, dp); +} + +static void d_seek(struct demuxer *demuxer, float secs, float audio_delay, + int flags) +{ + struct priv *p = demuxer->priv; + demux_packet_list_seek(p->pkts, p->num_pkts, &p->current, secs, flags); +} + +static int d_control(struct demuxer *demuxer, int cmd, void *arg) +{ + struct priv *p = demuxer->priv; + switch (cmd) { + case DEMUXER_CTRL_CORRECT_PTS: + return DEMUXER_CTRL_OK; + case DEMUXER_CTRL_GET_TIME_LENGTH: + *((double *) arg) = demux_packet_list_duration(p->pkts, p->num_pkts); + return DEMUXER_CTRL_OK; + default: + return DEMUXER_CTRL_NOTIMPL; + } +} + +const struct demuxer_desc demuxer_desc_subreader = { + .info = "Deprecated MPlayer subtitle reader", + .name = "subreader", + .shortdesc = "Deprecated Subreader", + .author = "", + .comment = "", + .type = DEMUXER_TYPE_SUBREADER, + .safe_check = 1, + .check_file = d_check_file, + .fill_buffer = d_fill_buffer, + .seek = d_seek, + .control = d_control, +}; diff --git a/demux/stheader.h b/demux/stheader.h index 421dfaf857..14418accb0 100644 --- a/demux/stheader.h +++ b/demux/stheader.h @@ -164,7 +164,6 @@ typedef struct sh_sub { unsigned char *extradata; // extra header data passed from demuxer int extradata_len; struct ass_track *track; // loaded by libass - struct sub_data *sub_data; // loaded by subreader.c struct dec_sub *dec_sub; // decoder context } sh_sub_t; diff --git a/demux/subreader.h b/demux/subreader.h deleted file mode 100644 index 3b2e53efd8..0000000000 --- a/demux/subreader.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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. - */ - -#ifndef MPLAYER_SUBREADER_H -#define MPLAYER_SUBREADER_H - -#include -#include - -#include "config.h" - -// subtitle formats -#define SUB_INVALID -1 -#define SUB_MICRODVD 0 -#define SUB_SUBRIP 1 -#define SUB_SUBVIEWER 2 -#define SUB_SAMI 3 -#define SUB_VPLAYER 4 -#define SUB_RT 5 -#define SUB_SSA 6 -#define SUB_PJS 7 -#define SUB_MPSUB 8 -#define SUB_AQTITLE 9 -#define SUB_SUBVIEWER2 10 -#define SUB_SUBRIP09 11 -#define SUB_JACOSUB 12 -#define SUB_MPL2 13 - -#define SUB_MAX_TEXT 12 -#define SUB_ALIGNMENT_BOTTOMLEFT 1 -#define SUB_ALIGNMENT_BOTTOMCENTER 2 -#define SUB_ALIGNMENT_BOTTOMRIGHT 3 -#define SUB_ALIGNMENT_MIDDLELEFT 4 -#define SUB_ALIGNMENT_MIDDLECENTER 5 -#define SUB_ALIGNMENT_MIDDLERIGHT 6 -#define SUB_ALIGNMENT_TOPLEFT 7 -#define SUB_ALIGNMENT_TOPCENTER 8 -#define SUB_ALIGNMENT_TOPRIGHT 9 - -typedef struct subtitle { - - int lines; - - unsigned long start; - unsigned long end; - - char *text[SUB_MAX_TEXT]; - unsigned char alignment; -} subtitle; - -typedef struct sub_data { - const char *codec; - subtitle *subtitles; - char *filename; - int sub_uses_time; - int sub_num; // number of subtitle structs - int sub_errs; - double fallback_fps; -} sub_data; - -struct MPOpts; -sub_data* sub_read_file (char *filename, float pts, struct MPOpts *opts); - -#endif /* MPLAYER_SUBREADER_H */ diff --git a/sub/dec_sub.c b/sub/dec_sub.c index bfd6d90d03..dd1168d0ad 100644 --- a/sub/dec_sub.c +++ b/sub/dec_sub.c @@ -25,7 +25,6 @@ #include "sd.h" #include "sub.h" #include "dec_sub.h" -#include "demux/subreader.h" #include "core/options.h" #include "core/mp_msg.h" @@ -138,68 +137,6 @@ static void print_chain(struct dec_sub *sub) 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; @@ -242,8 +179,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) {