diff --git a/DOCS/man/en/mplayer.1 b/DOCS/man/en/mplayer.1 index 91ae431846..82d53b5d6d 100644 --- a/DOCS/man/en/mplayer.1 +++ b/DOCS/man/en/mplayer.1 @@ -1177,9 +1177,7 @@ Plays a Matroska file in Japanese. Force audio demuxer type for \-audiofile. Use a '+' before the name to force it, this will skip some checks! Give the demuxer name as printed by \-audio\-demuxer help. -For backward compatibility it also accepts the demuxer ID as defined in -libmpdemux/\:demuxer.h. -\-audio\-demuxer audio or \-audio\-demuxer 17 forces MP3. +\-audio\-demuxer audio forces MP3. . .TP .B \-audiofile @@ -1356,8 +1354,6 @@ This nullifies stream delays. Force demuxer type. Use a '+' before the name to force it, this will skip some checks! Give the demuxer name as printed by \-demuxer help. -For backward compatibility it also accepts the demuxer ID as defined in -libmpdemux/\:demuxer.h. . .TP .B \-dumpaudio @@ -2416,8 +2412,6 @@ intensity of the color. Force subtitle demuxer type for \-subfile. Use a '+' before the name to force it, this will skip some checks! Give the demuxer name as printed by \-sub\-demuxer help. -For backward compatibility it also accepts the demuxer ID as defined in -subreader.h. . .TP .B \-sub\-fuzziness diff --git a/Makefile b/Makefile index f362a814ee..2f4e668877 100644 --- a/Makefile +++ b/Makefile @@ -374,6 +374,7 @@ SRCS_COMMON = asxparser.c \ libmpdemux/demux_audio.c \ libmpdemux/demux_avi.c \ libmpdemux/demux_demuxers.c \ + libmpdemux/demux_edl.c \ libmpdemux/demux_film.c \ libmpdemux/demux_fli.c \ libmpdemux/demux_lmlm4.c \ @@ -407,7 +408,6 @@ SRCS_COMMON = asxparser.c \ libmpdemux/yuv4mpeg.c \ libmpdemux/yuv4mpeg_ratio.c \ libvo/osd.c \ - osdep/findfiles.c \ osdep/numcores.c \ osdep/$(GETCH) \ osdep/$(TIMER) \ @@ -426,6 +426,8 @@ SRCS_COMMON = asxparser.c \ sub/subassconvert.c \ sub/subreader.c \ sub/vobsub.c \ + timeline/tl_edl.c \ + timeline/tl_matroska.c \ $(SRCS_COMMON-yes) @@ -569,6 +571,7 @@ DIRS = . \ stream/librtsp \ stream/realrtsp \ sub \ + timeline \ TOOLS \ MOFILES := $(MSG_LANGS:%=locale/%/LC_MESSAGES/mplayer.mo) diff --git a/bstr.c b/bstr.c index 3cc3928086..d86b488912 100644 --- a/bstr.c +++ b/bstr.c @@ -18,6 +18,10 @@ #include #include +#include + +#include "talloc.h" + #include "bstr.h" int bstrcmp(struct bstr str1, struct bstr str2) @@ -49,3 +53,90 @@ int bstrcasecmp(struct bstr str1, struct bstr str2) } return ret; } + +int bstrchr(struct bstr str, int c) +{ + for (int i = 0; i < str.len; i++) + if (str.start[i] == c) + return i; + return -1; +} + +struct bstr bstr_strip(struct bstr str) +{ + while (str.len && isspace(*str.start)) { + str.start++; + str.len--; + } + while (str.len && isspace(str.start[str.len - 1])) + str.len--; + return str; +} + +struct bstr bstr_split(struct bstr str, char *sep, struct bstr *rest) +{ + int start, end; + for (start = 0; start < str.len; start++) + if (!strchr(sep, str.start[start])) + break; + for (end = start; end < str.len; end++) + if (strchr(sep, str.start[end])) + break; + if (rest) { + *rest = bstr_cut(str, end); + } + str.start += start; + str.len = end - start; + return str; +} + + +struct bstr bstr_splice(struct bstr str, int start, int end) +{ + if (start < 0) + start += str.len; + if (end < 0) + end += str.len; + end = FFMIN(end, str.len); + start = FFMAX(start, 0); + if (start >= end) + return (struct bstr){NULL, 0}; + str.start += start; + str.len = end - start; + return str; +} + +long long bstrtoll(struct bstr str, struct bstr *rest, int base) +{ + char buf[51]; + int len = FFMIN(str.len, 50); + memcpy(buf, str.start, len); + buf[len] = 0; + char *endptr; + long long r = strtoll(buf, &endptr, base); + if (rest) + *rest = bstr_cut(str, endptr - buf); + return r; +} + +struct bstr *bstr_splitlines(void *talloc_ctx, struct bstr str) +{ + if (str.len == 0) + return NULL; + int count = 0; + for (int i = 0; i < str.len; i++) + if (str.start[i] == '\n') + count++; + if (str.start[str.len - 1] != '\n') + count++; + struct bstr *r = talloc_array_ptrtype(talloc_ctx, r, count); + unsigned char *p = str.start; + for (int i = 0; i < count - 1; i++) { + r[i].start = p; + while (*p++ != '\n'); + r[i].len = p - r[i].start; + } + r[count - 1].start = p; + r[count - 1].len = str.start + str.len - p; + return r; +} diff --git a/bstr.h b/bstr.h index 459bf70bef..33a47c0abc 100644 --- a/bstr.h +++ b/bstr.h @@ -22,18 +22,50 @@ #include #include #include +#include +#include "talloc.h" + +/* NOTE: 'len' is size_t, but most string-handling functions below assume + * that input size has been sanity checked and len fits in an int. + */ struct bstr { - const uint8_t *start; + unsigned char *start; size_t len; }; int bstrcmp(struct bstr str1, struct bstr str2); int bstrcasecmp(struct bstr str1, struct bstr str2); +int bstrchr(struct bstr str, int c); +struct bstr *bstr_splitlines(void *talloc_ctx, struct bstr str); +struct bstr bstr_strip(struct bstr str); +struct bstr bstr_split(struct bstr str, char *sep, struct bstr *rest); +struct bstr bstr_splice(struct bstr str, int start, int end); +long long bstrtoll(struct bstr str, struct bstr *rest, int base); + +static inline struct bstr bstr_cut(struct bstr str, int n) +{ + return (struct bstr){str.start + n, str.len - n}; +} + +static inline bool bstr_startswith(struct bstr str, struct bstr prefix) +{ + if (str.len < prefix.len) + return false; + return !memcmp(str.start, prefix.start, prefix.len); +} + +static inline char *bstrdup0(void *talloc_ctx, struct bstr str) +{ + // cast is live555 C++ compilation workaround + return talloc_strndup(talloc_ctx, (char *)str.start, str.len); +} // Create bstr compound literal from null-terminated string -#define BSTR(s) (struct bstr){(s), (s) ? strlen(s) : 0} +#define BSTR(s) (struct bstr){(char *)(s), (s) ? strlen(s) : 0} // create a pair (not single value!) for "%.*s" printf syntax #define BSTR_P(bstr) (int)((bstr).len), (bstr).start +#define WHITESPACE " \f\n\r\t\v" + #endif /* MPLAYER_BSTR_H */ diff --git a/libmpdemux/demux_edl.c b/libmpdemux/demux_edl.c new file mode 100644 index 0000000000..4c864cfe42 --- /dev/null +++ b/libmpdemux/demux_edl.c @@ -0,0 +1,58 @@ +/* + * 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 "demuxer.h" +#include "stream/stream.h" + +static int try_open_file(struct demuxer *demuxer) +{ + struct stream *s = demuxer->stream; + const char header[] = "mplayer EDL file"; + const int len = sizeof(header) - 1; + char buf[len]; + if (stream_read(s, buf, len) < len) + return 0; + if (strncmp(buf, header, len)) + return 0; + stream_seek(s, 0); + demuxer->file_contents = stream_read_complete(s, demuxer, 1000000, 0); + if (demuxer->file_contents.start == NULL) + return 0; + return DEMUXER_TYPE_EDL; +} + +static int dummy_fill_buffer(struct demuxer *demuxer, struct demux_stream *ds) +{ + return 0; +} + +const struct demuxer_desc demuxer_desc_edl = { + .info = "EDL file demuxer", + .name = "edl", + .shortdesc = "EDL", + .author = "Uoti Urpala", + .comment = "", + .type = DEMUXER_TYPE_EDL, + .safe_check = true, + .check_file = try_open_file, // no separate .open + .fill_buffer = dummy_fill_buffer, +}; diff --git a/libmpdemux/demuxer.c b/libmpdemux/demuxer.c index 9b38af9452..9efcf91862 100644 --- a/libmpdemux/demuxer.c +++ b/libmpdemux/demuxer.c @@ -53,6 +53,7 @@ static void clear_parser(sh_common_t *sh); // Demuxer list +extern const struct demuxer_desc demuxer_desc_edl; extern const demuxer_desc_t demuxer_desc_rawaudio; extern const demuxer_desc_t demuxer_desc_rawvideo; extern const demuxer_desc_t demuxer_desc_tv; @@ -101,6 +102,7 @@ extern const demuxer_desc_t demuxer_desc_mng; * libraries and demuxers requiring binary support. */ const demuxer_desc_t *const demuxer_list[] = { + &demuxer_desc_edl, &demuxer_desc_rawaudio, &demuxer_desc_rawvideo, #ifdef CONFIG_TV @@ -253,13 +255,13 @@ void free_demux_packet(struct demux_packet *dp) free(dp); } -void free_demuxer_stream(demux_stream_t *ds) +static void free_demuxer_stream(struct demux_stream *ds) { ds_free_packs(ds); free(ds); } -demux_stream_t *new_demuxer_stream(struct demuxer *demuxer, int id) +static struct demux_stream *new_demuxer_stream(struct demuxer *demuxer, int id) { demux_stream_t *ds = malloc(sizeof(demux_stream_t)); *ds = (demux_stream_t){ @@ -881,19 +883,18 @@ void demuxer_help(void) int i; mp_msg(MSGT_DEMUXER, MSGL_INFO, "Available demuxers:\n"); - mp_msg(MSGT_DEMUXER, MSGL_INFO, " demuxer: type info: (comment)\n"); + mp_msg(MSGT_DEMUXER, MSGL_INFO, " demuxer: info: (comment)\n"); mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_DEMUXERS\n"); for (i = 0; demuxer_list[i]; i++) { - if (demuxer_list[i]->type > DEMUXER_TYPE_MAX) // Don't display special demuxers + if (demuxer_list[i]->type >= DEMUXER_TYPE_END) // internal type continue; if (demuxer_list[i]->comment && strlen(demuxer_list[i]->comment)) - mp_msg(MSGT_DEMUXER, MSGL_INFO, "%10s %2d %s (%s)\n", - demuxer_list[i]->name, demuxer_list[i]->type, - demuxer_list[i]->info, demuxer_list[i]->comment); + mp_msg(MSGT_DEMUXER, MSGL_INFO, "%10s %s (%s)\n", + demuxer_list[i]->name, demuxer_list[i]->info, + demuxer_list[i]->comment); else - mp_msg(MSGT_DEMUXER, MSGL_INFO, "%10s %2d %s\n", - demuxer_list[i]->name, demuxer_list[i]->type, - demuxer_list[i]->info); + mp_msg(MSGT_DEMUXER, MSGL_INFO, "%10s %s\n", + demuxer_list[i]->name, demuxer_list[i]->info); } } @@ -906,32 +907,22 @@ void demuxer_help(void) * May be NULL. * @return DEMUXER_TYPE_xxx, -1 if error or not found */ -int get_demuxer_type_from_name(char *demuxer_name, int *force) +static int get_demuxer_type_from_name(char *demuxer_name, int *force) { - int i; - long type_int; - char *endptr; - if (!demuxer_name || !demuxer_name[0]) return DEMUXER_TYPE_UNKNOWN; if (force) *force = demuxer_name[0] == '+'; if (demuxer_name[0] == '+') demuxer_name = &demuxer_name[1]; - for (i = 0; demuxer_list[i]; i++) { - if (demuxer_list[i]->type > DEMUXER_TYPE_MAX) // Can't select special demuxers from commandline + for (int i = 0; demuxer_list[i]; i++) { + if (demuxer_list[i]->type >= DEMUXER_TYPE_END) + // Can't select special demuxers from commandline continue; if (strcmp(demuxer_name, demuxer_list[i]->name) == 0) return demuxer_list[i]->type; } - // No match found, try to parse name as an integer (demuxer number) - type_int = strtol(demuxer_name, &endptr, 0); - if (*endptr) // Conversion failed - return -1; - if ((type_int > 0) && (type_int <= DEMUXER_TYPE_MAX)) - return (int) type_int; - return -1; } diff --git a/libmpdemux/demuxer.h b/libmpdemux/demuxer.h index e66b8f8a12..4a8a7545d1 100644 --- a/libmpdemux/demuxer.h +++ b/libmpdemux/demuxer.h @@ -32,8 +32,8 @@ struct MPOpts; #ifdef HAVE_BUILTIN_EXPECT -#define likely(x) __builtin_expect ((x) != 0, 1) -#define unlikely(x) __builtin_expect ((x) != 0, 0) +#define likely(x) __builtin_expect((x) != 0, 1) +#define unlikely(x) __builtin_expect((x) != 0, 0) #else #define likely(x) (x) #define unlikely(x) (x) @@ -42,60 +42,61 @@ struct MPOpts; #define MAX_PACKS 4096 #define MAX_PACK_BYTES 0x8000000 // 128 MiB -#define DEMUXER_TYPE_UNKNOWN 0 -#define DEMUXER_TYPE_MPEG_ES 1 -#define DEMUXER_TYPE_MPEG_PS 2 -#define DEMUXER_TYPE_AVI 3 -#define DEMUXER_TYPE_AVI_NI 4 -#define DEMUXER_TYPE_AVI_NINI 5 -#define DEMUXER_TYPE_ASF 6 -#define DEMUXER_TYPE_MOV 7 -#define DEMUXER_TYPE_VIVO 8 -#define DEMUXER_TYPE_TV 9 -#define DEMUXER_TYPE_FLI 10 -#define DEMUXER_TYPE_REAL 11 -#define DEMUXER_TYPE_Y4M 12 -#define DEMUXER_TYPE_FILM 14 -#define DEMUXER_TYPE_ROQ 15 -#define DEMUXER_TYPE_MF 16 -#define DEMUXER_TYPE_AUDIO 17 -#define DEMUXER_TYPE_OGG 18 -#define DEMUXER_TYPE_RAWAUDIO 20 -#define DEMUXER_TYPE_RTP 21 -#define DEMUXER_TYPE_RAWDV 22 -#define DEMUXER_TYPE_PVA 23 -#define DEMUXER_TYPE_SMJPEG 24 -#define DEMUXER_TYPE_XMMS 25 -#define DEMUXER_TYPE_RAWVIDEO 26 -#define DEMUXER_TYPE_MPEG4_ES 27 -#define DEMUXER_TYPE_GIF 28 -#define DEMUXER_TYPE_MPEG_TS 29 -#define DEMUXER_TYPE_H264_ES 30 -#define DEMUXER_TYPE_MATROSKA 31 -#define DEMUXER_TYPE_REALAUDIO 32 -#define DEMUXER_TYPE_MPEG_TY 33 -#define DEMUXER_TYPE_LMLM4 34 -#define DEMUXER_TYPE_LAVF 35 -#define DEMUXER_TYPE_NSV 36 -#define DEMUXER_TYPE_VQF 37 -#define DEMUXER_TYPE_AVS 38 -#define DEMUXER_TYPE_AAC 39 -#define DEMUXER_TYPE_MPC 40 -#define DEMUXER_TYPE_MPEG_PES 41 -#define DEMUXER_TYPE_MPEG_GXF 42 -#define DEMUXER_TYPE_NUT 43 -#define DEMUXER_TYPE_LAVF_PREFERRED 44 -#define DEMUXER_TYPE_RTP_NEMESI 45 -#define DEMUXER_TYPE_MNG 46 +enum demuxer_type { + DEMUXER_TYPE_UNKNOWN = 0, + DEMUXER_TYPE_MPEG_ES, + DEMUXER_TYPE_MPEG_PS, + DEMUXER_TYPE_AVI, + DEMUXER_TYPE_AVI_NI, + DEMUXER_TYPE_AVI_NINI, + DEMUXER_TYPE_ASF, + DEMUXER_TYPE_MOV, + DEMUXER_TYPE_VIVO, + DEMUXER_TYPE_TV, + DEMUXER_TYPE_FLI, + DEMUXER_TYPE_REAL, + DEMUXER_TYPE_Y4M, + DEMUXER_TYPE_FILM, + DEMUXER_TYPE_ROQ, + DEMUXER_TYPE_MF, + DEMUXER_TYPE_AUDIO, + DEMUXER_TYPE_OGG, + DEMUXER_TYPE_RAWAUDIO, + DEMUXER_TYPE_RTP, + DEMUXER_TYPE_RAWDV, + DEMUXER_TYPE_PVA, + DEMUXER_TYPE_SMJPEG, + DEMUXER_TYPE_XMMS, + DEMUXER_TYPE_RAWVIDEO, + DEMUXER_TYPE_MPEG4_ES, + DEMUXER_TYPE_GIF, + DEMUXER_TYPE_MPEG_TS, + DEMUXER_TYPE_H264_ES, + DEMUXER_TYPE_MATROSKA, + DEMUXER_TYPE_REALAUDIO, + DEMUXER_TYPE_MPEG_TY, + DEMUXER_TYPE_LMLM4, + DEMUXER_TYPE_LAVF, + DEMUXER_TYPE_NSV, + DEMUXER_TYPE_VQF, + DEMUXER_TYPE_AVS, + DEMUXER_TYPE_AAC, + DEMUXER_TYPE_MPC, + DEMUXER_TYPE_MPEG_PES, + DEMUXER_TYPE_MPEG_GXF, + DEMUXER_TYPE_NUT, + DEMUXER_TYPE_LAVF_PREFERRED, + DEMUXER_TYPE_RTP_NEMESI, + DEMUXER_TYPE_MNG, + DEMUXER_TYPE_EDL, -// This should always match the higest demuxer type number. -// Unless you want to disallow users to force the demuxer to some types -#define DEMUXER_TYPE_MIN 0 -#define DEMUXER_TYPE_MAX 46 + /* Values after this are for internal use and can not be selected + * as demuxer type by the user (-demuxer option). */ + DEMUXER_TYPE_END, -#define DEMUXER_TYPE_DEMUXERS (1<<16) -// A virtual demuxer type for the network code -#define DEMUXER_TYPE_PLAYLIST (2<<16) + DEMUXER_TYPE_DEMUXERS, + DEMUXER_TYPE_PLAYLIST, +}; enum timestamp_type { TIMESTAMP_TYPE_PTS, @@ -125,54 +126,54 @@ enum timestamp_type { // Holds one packet/frame/whatever typedef struct demux_packet { - int len; - double pts; - double duration; - double stream_pts; - off_t pos; // position in index (AVI) or file (MPG) - unsigned char* buffer; - int flags; // keyframe, etc - int refcount; //refcounter for the master packet, if 0, buffer can be free()d - struct demux_packet *master; //pointer to the master packet if this one is a cloned one - struct demux_packet *next; + int len; + double pts; + double duration; + double stream_pts; + off_t pos; // position in index (AVI) or file (MPG) + unsigned char *buffer; + int flags; // keyframe, etc + int refcount; // counter for the master packet, if 0, buffer can be free()d + struct demux_packet *master; //in clones, pointer to the master packet + struct demux_packet *next; } demux_packet_t; typedef struct demux_stream { - int buffer_pos; // current buffer position - int buffer_size; // current buffer size - unsigned char* buffer; // current buffer, never free() it, always use free_demux_packet(buffer_ref); - double pts; // current buffer's pts - int pts_bytes; // number of bytes read after last pts stamp - int eof; // end of demuxed stream? (true if all buffer empty) - off_t pos; // position in the input stream (file) - off_t dpos; // position in the demuxed stream - int pack_no; // serial number of packet - int flags; // flags of current packet (keyframe etc) - int non_interleaved; // 1 if this stream is not properly interleaved, + int buffer_pos; // current buffer position + int buffer_size; // current buffer size + unsigned char *buffer; // current buffer, never free() it, always use free_demux_packet(buffer_ref); + double pts; // current buffer's pts + int pts_bytes; // number of bytes read after last pts stamp + int eof; // end of demuxed stream? (true if all buffer empty) + off_t pos; // position in the input stream (file) + off_t dpos; // position in the demuxed stream + int pack_no; // serial number of packet + int flags; // flags of current packet (keyframe etc) + int non_interleaved; // 1 if this stream is not properly interleaved, // so e.g. subtitle handling must do explicit reads. //--------------- - int packs; // number of packets in buffer - int bytes; // total bytes of packets in buffer - demux_packet_t *first; // read to current buffer from here - demux_packet_t *last; // append new packets from input stream to here - demux_packet_t *current;// needed for refcounting of the buffer - int id; // stream ID (for multiple audio/video streams) - struct demuxer *demuxer; // parent demuxer structure (stream handler) + int packs; // number of packets in buffer + int bytes; // total bytes of packets in buffer + demux_packet_t *first; // read to current buffer from here + demux_packet_t *last; // append new packets from input stream to here + demux_packet_t *current; // needed for refcounting of the buffer + int id; // stream ID (for multiple audio/video streams) + struct demuxer *demuxer; // parent demuxer structure (stream handler) // ---- asf ----- - demux_packet_t *asf_packet; // read asf fragments here - int asf_seq; + struct demux_packet *asf_packet; // read asf fragments here + int asf_seq; // ---- mov ----- - unsigned int ss_mul,ss_div; + unsigned int ss_mul, ss_div; // ---- stream header ---- - void* sh; + void *sh; } demux_stream_t; typedef struct demuxer_info { - char *name; - char *author; - char *encoder; - char *comments; - char *copyright; + char *name; + char *author; + char *encoder; + char *comments; + char *copyright; } demuxer_info_t; #define MAX_A_STREAMS 256 @@ -185,33 +186,36 @@ struct demuxer; * Demuxer description structure */ typedef struct demuxer_desc { - const char *info; ///< What is it (long name and/or description) - const char *name; ///< Demuxer name, used with -demuxer switch - const char *shortdesc; ///< Description printed at demuxer detection - const char *author; ///< Demuxer author(s) - const char *comment; ///< Comment, printed with -demuxer help + const char *info; // What is it (long name and/or description) + const char *name; // Demuxer name, used with -demuxer switch + const char *shortdesc; // Description printed at demuxer detection + const char *author; // Demuxer author(s) + const char *comment; // Comment, printed with -demuxer help - int type; ///< DEMUXER_TYPE_xxx - int safe_check; ///< If 1 detection is safe and fast, do it before file extension check + enum demuxer_type type; + // If 1 detection is safe and fast, do it before file extension check + int safe_check; - /// Check if can demux the file, return DEMUXER_TYPE_xxx on success - int (*check_file)(struct demuxer *demuxer); ///< Mandatory if safe_check == 1, else optional - /// Get packets from file, return 0 on eof - int (*fill_buffer)(struct demuxer *demuxer, demux_stream_t *ds); ///< Mandatory - /// Open the demuxer, return demuxer on success, NULL on failure - struct demuxer* (*open)(struct demuxer *demuxer); ///< Optional - /// Close the demuxer - void (*close)(struct demuxer *demuxer); ///< Optional - // Seek - void (*seek)(struct demuxer *demuxer, float rel_seek_secs, float audio_delay, int flags); ///< Optional - // Control - int (*control)(struct demuxer *demuxer, int cmd, void *arg); ///< Optional + // Check if can demux the file, return DEMUXER_TYPE_xxx on success + // Mandatory if safe_check == 1, else optional + int (*check_file)(struct demuxer *demuxer); + /// Get packets from file, return 0 on eof. Mandatory + int (*fill_buffer)(struct demuxer *demuxer, struct demux_stream *ds); + /// Open the demuxer, return demuxer on success, NULL on failure + struct demuxer *(*open)(struct demuxer *demuxer); // Optional + /// Close the demuxer + void (*close)(struct demuxer *demuxer); // Optional + // Seek. Optional + void (*seek)(struct demuxer *demuxer, float rel_seek_secs, + float audio_delay, int flags); + // Various control functions. Optional + int (*control)(struct demuxer *demuxer, int cmd, void *arg); } demuxer_desc_t; typedef struct demux_chapter { - uint64_t start, end; - char* name; + uint64_t start, end; + char *name; } demux_chapter_t; struct matroska_data { @@ -229,59 +233,67 @@ struct matroska_data { typedef struct demux_attachment { - char* name; - char* type; - void* data; - unsigned int data_size; + char *name; + char *type; + void *data; + unsigned int data_size; } demux_attachment_t; typedef struct demuxer { - const demuxer_desc_t *desc; ///< Demuxer description structure - char *filetype; // format name when not identified by demuxer (libavformat) - off_t filepos; // input stream current pos. - off_t movi_start; - off_t movi_end; - stream_t *stream; - double stream_pts; // current stream pts, if applicable (e.g. dvd) - double reference_clock; - char *filename; ///< Needed by avs_check_file - int synced; // stream synced (used by mpeg) - int type; // demuxer type: mpeg PS, mpeg ES, avi, avi-ni, avi-nini, asf - int file_format; // file format: mpeg/avi/asf - int seekable; // flag + const demuxer_desc_t *desc; ///< Demuxer description structure + char *filetype; // format name when not identified by demuxer (libavformat) + off_t filepos; // input stream current pos. + off_t movi_start; + off_t movi_end; + struct stream *stream; + double stream_pts; // current stream pts, if applicable (e.g. dvd) + double reference_clock; + char *filename; // Needed by avs_check_file + int synced; // stream synced (used by mpeg) + enum demuxer_type type; + /* Normally the file_format field is just a copy of the type field above. + * There are 2 exceptions I noticed. Internal demux_avi may force + * ->type to DEMUXER_TYPE_AVI_[NI|NINI] while leaving ->file_format at + * DEMUXER_TYPE_AVI. Internal demux_mov may set ->type to + * DEMUXER_TYPE_PLAYLIST and also return that from the check function + * or not (looks potentially buggy). */ + enum demuxer_type file_format; + int seekable; // flag /* Set if using absolute seeks for small movements is OK (no pts resets * that would make pts ambigious, preferably supports back/forward flags */ bool accurate_seek; enum timestamp_type timestamp_type; - // - demux_stream_t *audio; // audio buffer/demuxer - demux_stream_t *video; // video buffer/demuxer - demux_stream_t *sub; // dvd subtitle buffer/demuxer - // stream headers: - struct sh_audio *a_streams[MAX_A_STREAMS]; - struct sh_video *v_streams[MAX_V_STREAMS]; - struct sh_sub *s_streams[MAX_S_STREAMS]; + struct demux_stream *audio; // audio buffer/demuxer + struct demux_stream *video; // video buffer/demuxer + struct demux_stream *sub; // dvd subtitle buffer/demuxer - // pointer to teletext decoder private data, if demuxer stream contains teletext - void *teletext; + // stream headers: + struct sh_audio *a_streams[MAX_A_STREAMS]; + struct sh_video *v_streams[MAX_V_STREAMS]; + struct sh_sub *s_streams[MAX_S_STREAMS]; - demux_chapter_t* chapters; - int num_chapters; + // teletext decoder private data, if demuxer stream contains teletext + void *teletext; - demux_attachment_t* attachments; - int num_attachments; + struct demux_chapter *chapters; + int num_chapters; + + struct demux_attachment *attachments; + int num_attachments; struct matroska_data matroska_data; + // for trivial demuxers which just read the whole file for codec to use + struct bstr file_contents; - void* priv; // fileformat-dependent data - char** info; - struct MPOpts *opts; + void *priv; // demuxer-specific internal data + char **info; // metadata + struct MPOpts *opts; } demuxer_t; typedef struct { - int progid; //program id - int aid, vid, sid; //audio, video and subtitle id + int progid; //program id + int aid, vid, sid; //audio, video and subtitle id } demux_program_t; struct demux_packet *new_demux_packet(size_t len); @@ -293,80 +305,76 @@ void free_demux_packet(struct demux_packet *dp); #define SIZE_MAX ((size_t)-1) #endif -static inline void *realloc_struct(void *ptr, size_t nmemb, size_t size) { - if (nmemb > SIZE_MAX / size) { - free(ptr); - return NULL; - } - return realloc(ptr, nmemb * size); +static inline void *realloc_struct(void *ptr, size_t nmemb, size_t size) +{ + if (nmemb > SIZE_MAX / size) { + free(ptr); + return NULL; + } + return realloc(ptr, nmemb * size); } -demux_stream_t* new_demuxer_stream(struct demuxer *demuxer,int id); -demuxer_t* new_demuxer(struct MPOpts *opts, stream_t *stream,int type,int a_id,int v_id,int s_id,char *filename); -void free_demuxer_stream(demux_stream_t *ds); -void free_demuxer(demuxer_t *demuxer); +struct demuxer *new_demuxer(struct MPOpts *opts, struct stream *stream, + int type, int a_id, int v_id, int s_id, + char *filename); +void free_demuxer(struct demuxer *demuxer); -void ds_add_packet(demux_stream_t *ds,demux_packet_t* dp); -void ds_read_packet(demux_stream_t *ds, stream_t *stream, int len, double pts, off_t pos, int flags); +void ds_add_packet(struct demux_stream *ds, struct demux_packet *dp); +void ds_read_packet(struct demux_stream *ds, struct stream *stream, int len, + double pts, off_t pos, int flags); -int demux_fill_buffer(demuxer_t *demux,demux_stream_t *ds); -int ds_fill_buffer(demux_stream_t *ds); +int demux_fill_buffer(struct demuxer *demux, struct demux_stream *ds); +int ds_fill_buffer(struct demux_stream *ds); -static inline off_t ds_tell(demux_stream_t *ds){ - return (ds->dpos-ds->buffer_size)+ds->buffer_pos; +static inline off_t ds_tell(struct demux_stream *ds) +{ + return (ds->dpos - ds->buffer_size) + ds->buffer_pos; } -static inline int ds_tell_pts(demux_stream_t *ds){ - return (ds->pts_bytes-ds->buffer_size)+ds->buffer_pos; +static inline int ds_tell_pts(struct demux_stream *ds) +{ + return (ds->pts_bytes - ds->buffer_size) + ds->buffer_pos; } -int demux_read_data(demux_stream_t *ds,unsigned char* mem,int len); -int demux_pattern_3(demux_stream_t *ds, unsigned char *mem, int maxlen, +int demux_read_data(struct demux_stream *ds, unsigned char *mem, int len); +int demux_pattern_3(struct demux_stream *ds, unsigned char *mem, int maxlen, int *read, uint32_t pattern); -#define demux_peekc(ds) (\ - (likely(ds->buffer_posbuffer_size)) ? ds->buffer[ds->buffer_pos] \ - :((unlikely(!ds_fill_buffer(ds)))? (-1) : ds->buffer[ds->buffer_pos] ) ) -#if 1 -#define demux_getc(ds) (\ - (likely(ds->buffer_posbuffer_size)) ? ds->buffer[ds->buffer_pos++] \ - :((unlikely(!ds_fill_buffer(ds)))? (-1) : ds->buffer[ds->buffer_pos++] ) ) -#else -static inline int demux_getc(demux_stream_t *ds){ - if(ds->buffer_pos>=ds->buffer_size){ - if(!ds_fill_buffer(ds)){ -// printf("DEMUX_GETC: EOF reached!\n"); - return -1; // EOF - } - } -// printf("[%02X]",ds->buffer[ds->buffer_pos]); - return ds->buffer[ds->buffer_pos++]; -} -#endif +#define demux_peekc(ds) ( \ + (likely(ds->buffer_posbuffer_size)) ? ds->buffer[ds->buffer_pos] \ + : ((unlikely(!ds_fill_buffer(ds))) ? (-1) : ds->buffer[ds->buffer_pos])) +#define demux_getc(ds) ( \ + (likely(ds->buffer_posbuffer_size)) ? ds->buffer[ds->buffer_pos++] \ + : ((unlikely(!ds_fill_buffer(ds))) ? (-1) : ds->buffer[ds->buffer_pos++])) -void ds_free_packs(demux_stream_t *ds); -int ds_get_packet(demux_stream_t *ds,unsigned char **start); -int ds_get_packet_pts(demux_stream_t *ds, unsigned char **start, double *pts); -int ds_get_packet_sub(demux_stream_t *ds,unsigned char **start); -double ds_get_next_pts(demux_stream_t *ds); -int ds_parse(demux_stream_t *sh, uint8_t **buffer, int *len, double pts, off_t pos); -void ds_clear_parser(demux_stream_t *sh); +void ds_free_packs(struct demux_stream *ds); +int ds_get_packet(struct demux_stream *ds, unsigned char **start); +int ds_get_packet_pts(struct demux_stream *ds, unsigned char **start, + double *pts); +int ds_get_packet_sub(struct demux_stream *ds, unsigned char **start); +double ds_get_next_pts(struct demux_stream *ds); +int ds_parse(struct demux_stream *sh, uint8_t **buffer, int *len, double pts, + off_t pos); +void ds_clear_parser(struct demux_stream *sh); -// This is defined here because demux_stream_t ins't defined in stream.h -stream_t* new_ds_stream(demux_stream_t *ds); - -static inline int avi_stream_id(unsigned int id){ - unsigned char a,b; - a = id - '0'; - b = (id >> 8) - '0'; - if(a>9 || b>9) return 100; // invalid ID - return a*10+b; +static inline int avi_stream_id(unsigned int id) +{ + unsigned char a, b; + a = id - '0'; + b = (id >> 8) - '0'; + if (a>9 || b>9) + return 100; // invalid ID + return a * 10 + b; } -demuxer_t* demux_open(struct MPOpts *opts, stream_t *stream,int file_format,int aid,int vid,int sid,char* filename); -void demux_flush(demuxer_t *demuxer); -int demux_seek(demuxer_t *demuxer,float rel_seek_secs,float audio_delay,int flags); -demuxer_t* new_demuxers_demuxer(demuxer_t* vd, demuxer_t* ad, demuxer_t* sd); +struct demuxer *demux_open(struct MPOpts *opts, struct stream *stream, + int file_format, int aid, int vid, int sid, + char *filename); +void demux_flush(struct demuxer *demuxer); +int demux_seek(struct demuxer *demuxer, float rel_seek_secs, float audio_delay, + int flags); +struct demuxer *new_demuxers_demuxer(struct demuxer *vd, struct demuxer *ad, + struct demuxer *sd); // AVI demuxer params: extern int index_mode; // -1=untouched 0=don't use index 1=use (geneate) index @@ -374,45 +382,43 @@ extern char *index_file_save, *index_file_load; extern int force_ni; extern int pts_from_bps; -extern int extension_parsing; +int demux_info_add(struct demuxer *demuxer, const char *opt, const char *param); +int demux_info_add_bstr(struct demuxer *demuxer, struct bstr opt, + struct bstr param); +char *demux_info_get(struct demuxer *demuxer, const char *opt); +int demux_info_print(struct demuxer *demuxer); +int demux_control(struct demuxer *demuxer, int cmd, void *arg); -int demux_info_add(demuxer_t *demuxer, const char *opt, const char *param); -int demux_info_add_bstr(demuxer_t *demuxer, struct bstr opt, struct bstr param); -char* demux_info_get(demuxer_t *demuxer, const char *opt); -int demux_info_print(demuxer_t *demuxer); -int demux_control(demuxer_t *demuxer, int cmd, void *arg); +int demuxer_switch_audio(struct demuxer *demuxer, int index); +int demuxer_switch_video(struct demuxer *demuxer, int index); -int demuxer_switch_audio(demuxer_t *demuxer, int index); -int demuxer_switch_video(demuxer_t *demuxer, int index); - -int demuxer_type_by_filename(char* filename); +int demuxer_type_by_filename(char *filename); void demuxer_help(void); -int get_demuxer_type_from_name(char *demuxer_name, int *force); -int demuxer_add_attachment(demuxer_t *demuxer, struct bstr name, +int demuxer_add_attachment(struct demuxer *demuxer, struct bstr name, struct bstr type, struct bstr data); -int demuxer_add_chapter(demuxer_t *demuxer, struct bstr name, +int demuxer_add_chapter(struct demuxer *demuxer, struct bstr name, uint64_t start, uint64_t end); -int demuxer_seek_chapter(demuxer_t *demuxer, int chapter, double *seek_pts, +int demuxer_seek_chapter(struct demuxer *demuxer, int chapter, double *seek_pts, char **chapter_name); /// Get current chapter index if available. -int demuxer_get_current_chapter(demuxer_t *demuxer, double time_now); +int demuxer_get_current_chapter(struct demuxer *demuxer, double time_now); /// Get chapter name by index if available. -char *demuxer_chapter_name(demuxer_t *demuxer, int chapter); +char *demuxer_chapter_name(struct demuxer *demuxer, int chapter); /// Get chapter display name by index. -char *demuxer_chapter_display_name(demuxer_t *demuxer, int chapter); +char *demuxer_chapter_display_name(struct demuxer *demuxer, int chapter); /// Get chapter start time and end time by index if available. -float demuxer_chapter_time(demuxer_t *demuxer, int chapter, float *end); +float demuxer_chapter_time(struct demuxer *demuxer, int chapter, float *end); /// Get total chapter number. -int demuxer_chapter_count(demuxer_t *demuxer); +int demuxer_chapter_count(struct demuxer *demuxer); /// Get current angle index. -int demuxer_get_current_angle(demuxer_t *demuxer); +int demuxer_get_current_angle(struct demuxer *demuxer); /// Set angle. -int demuxer_set_angle(demuxer_t *demuxer, int angle); +int demuxer_set_angle(struct demuxer *demuxer, int angle); /// Get number of angles. -int demuxer_angles_count(demuxer_t *demuxer); +int demuxer_angles_count(struct demuxer *demuxer); /* Get the index of a track. * lang is a comma-separated list, NULL is same as empty list diff --git a/mp_core.h b/mp_core.h index 825718f161..bbb93bce48 100644 --- a/mp_core.h +++ b/mp_core.h @@ -251,4 +251,10 @@ char *chapter_display_name(struct MPContext *mpctx, int chapter); void update_subtitles(struct MPContext *mpctx, double refpts, double sub_offset, bool reset); + +// timeline/tl_matroska.c +void build_ordered_chapter_timeline(struct MPContext *mpctx); +// timeline/tl_edl.c +void build_edl_timeline(struct MPContext *mpctx); + #endif /* MPLAYER_MP_CORE_H */ diff --git a/mpcommon.h b/mpcommon.h index 891331293f..bc35934e41 100644 --- a/mpcommon.h +++ b/mpcommon.h @@ -24,6 +24,8 @@ #define ROUND(x) ((int)((x) < 0 ? (x) - 0.5 : (x) + 0.5)) +#define MP_TALLOC_ELEMS(p) (talloc_get_size(p) / sizeof((p)[0])) + extern const char *mplayer_version; #endif /* MPLAYER_MPCOMMON_H */ diff --git a/mplayer.c b/mplayer.c index 826634f635..803bb33bd1 100644 --- a/mplayer.c +++ b/mplayer.c @@ -100,7 +100,6 @@ #include "osdep/getch2.h" #include "osdep/timer.h" -#include "osdep/findfiles.h" #include "input/input.h" @@ -2379,35 +2378,26 @@ static int fill_audio_out_buffers(struct MPContext *mpctx) double tt; int playsize; int playflags=0; - int audio_eof=0; + bool audio_eof = false; + bool partial_fill = false; bool format_change = false; sh_audio_t * const sh_audio = mpctx->sh_audio; + bool modifiable_audio_format = !(ao_data.format & AF_FORMAT_SPECIAL_MASK); + int unitsize = ao_data.channels * af_fmt2bits(ao_data.format) / 8; current_module="play_audio"; - while (1) { - int sleep_time; - // all the current uses of ao_data.pts seem to be in aos that handle - // sync completely wrong; there should be no need to use ao_data.pts - // in get_space() - ao_data.pts = ((mpctx->sh_video?mpctx->sh_video->timer:0)+mpctx->delay)*90000.0; - playsize = mpctx->audio_out->get_space(); - if (mpctx->sh_video || playsize >= ao_data.outburst) - break; - - // handle audio-only case: - // this is where mplayer sleeps during audio-only playback - // to avoid 100% CPU use - sleep_time = (ao_data.outburst - playsize) * 1000 / ao_data.bps; - if (sleep_time < 10) sleep_time = 10; // limit to 100 wakeups per second - usec_sleep(sleep_time * 1000); - } + // all the current uses of ao_data.pts seem to be in aos that handle + // sync completely wrong; there should be no need to use ao_data.pts + // in get_space() + ao_data.pts = ((mpctx->sh_video?mpctx->sh_video->timer:0)+mpctx->delay)*90000.0; + playsize = mpctx->audio_out->get_space(); // Fill buffer if needed: current_module="decode_audio"; t = GetTimer(); - if (!opts->initial_audio_sync || (ao_data.format & AF_FORMAT_SPECIAL_MASK)) + if (!opts->initial_audio_sync || !modifiable_audio_format) mpctx->syncing_audio = false; int res; @@ -2419,23 +2409,33 @@ static int fill_audio_out_buffers(struct MPContext *mpctx) if (res == -2) format_change = true; else if (res == ASYNC_PLAY_DONE) - return 1; - else if (mpctx->d_audio->eof) { - audio_eof = 1; - int unitsize = ao_data.channels * af_fmt2bits(ao_data.format) / 8; - if (sh_audio->a_out_buffer_len < unitsize) - return 0; - } + return 0; + else if (mpctx->d_audio->eof) + audio_eof = true; } t = GetTimer() - t; tt = t*0.000001f; audio_time_usage+=tt; + if (mpctx->timeline && modifiable_audio_format) { + double endpts = mpctx->timeline[mpctx->timeline_part + 1].start; + double bytes = (endpts - written_audio_pts(mpctx) + audio_delay) + * ao_data.bps / opts->playback_speed; + if (playsize > bytes) { + playsize = FFMAX(bytes, 0); + playflags |= AOPLAY_FINAL_CHUNK; + audio_eof = true; + partial_fill = true; + } + } + if (playsize > sh_audio->a_out_buffer_len) { + partial_fill = true; playsize = sh_audio->a_out_buffer_len; if (audio_eof) playflags |= AOPLAY_FINAL_CHUNK; } + playsize -= playsize % unitsize; if (!playsize) - return 1; + return partial_fill && audio_eof ? -2 : -partial_fill; // play audio: current_module="play_audio"; @@ -2465,13 +2465,14 @@ static int fill_audio_out_buffers(struct MPContext *mpctx) if (format_change) { uninit_player(mpctx, INITIALIZED_AO); reinit_audio_chain(mpctx); + return -1; } - return 1; + return -partial_fill; } static int sleep_until_near_frame(struct MPContext *mpctx, float *time_frame, - float *aq_sleep_time) + bool sync_to_audio, float *aq_sleep_time) { struct MPOpts *opts = &mpctx->opts; double audio_limit = 2; @@ -2482,7 +2483,7 @@ static int sleep_until_near_frame(struct MPContext *mpctx, float *time_frame, *time_frame -= get_relative_time(mpctx); // reset timer - if (mpctx->sh_audio && !mpctx->d_audio->eof) { + if (sync_to_audio) { float delay = mpctx->audio_out->get_delay(); mp_dbg(MSGT_AVSYNC, MSGL_DBG2, "delay=%f\n", delay); @@ -2533,6 +2534,8 @@ int reinit_video_chain(struct MPContext *mpctx) { struct MPOpts *opts = &mpctx->opts; sh_video_t * const sh_video = mpctx->sh_video; + if (!sh_video) + return 0; double ar=-1.0; //================== Init VIDEO (codec & libvo) ========================== if (!opts->fixed_vo || !(mpctx->initialized_flags & INITIALIZED_VO)) { @@ -2984,7 +2987,7 @@ static void reinit_decoders(struct MPContext *mpctx) mp_property_do("sub", M_PROPERTY_SET, &(int){mpctx->global_sub_pos}, mpctx); } -static void seek_reset(struct MPContext *mpctx) +static void seek_reset(struct MPContext *mpctx, bool reset_ao) { if (mpctx->sh_video) { current_module = "seek_video_reset"; @@ -3010,7 +3013,9 @@ static void seek_reset(struct MPContext *mpctx) if (mpctx->sh_audio) { current_module = "seek_audio_reset"; resync_audio_stream(mpctx->sh_audio); - mpctx->audio_out->reset(); // stop audio, throwing away buffered data + if (reset_ao) + // stop audio, throwing away buffered data + mpctx->audio_out->reset(); mpctx->sh_audio->a_buffer_len = 0; mpctx->sh_audio->a_out_buffer_len = 0; if (!mpctx->sh_video) @@ -3042,7 +3047,11 @@ static bool timeline_set_part(struct MPContext *mpctx, int i) mpctx->video_offset = n->start - n->source_start; if (n->source == p->source) return false; - uninit_player(mpctx, INITIALIZED_VCODEC | (mpctx->opts.fixed_vo && mpctx->opts.video_id != -2 ? 0 : INITIALIZED_VO) | INITIALIZED_AO | INITIALIZED_ACODEC | INITIALIZED_SUB); + enum stop_play_reason orig_stop_play = mpctx->stop_play; + if (!mpctx->sh_video && mpctx->stop_play == KEEP_PLAYING) + mpctx->stop_play = AT_END_OF_FILE; // let audio uninit drain data + uninit_player(mpctx, INITIALIZED_VCODEC | (mpctx->opts.fixed_vo ? 0 : INITIALIZED_VO) | (mpctx->opts.gapless_audio ? 0 : INITIALIZED_AO) | INITIALIZED_ACODEC | INITIALIZED_SUB); + mpctx->stop_play = orig_stop_play; mpctx->demuxer = n->source->demuxer; mpctx->d_video = mpctx->demuxer->video; mpctx->d_audio = mpctx->demuxer->audio; @@ -3071,7 +3080,8 @@ static double timeline_set_from_time(struct MPContext *mpctx, double pts, // return -1 if seek failed (non-seekable stream?), 0 otherwise -static int seek(MPContext *mpctx, struct seek_params seek) +static int seek(MPContext *mpctx, struct seek_params seek, + bool timeline_fallthrough) { struct MPOpts *opts = &mpctx->opts; @@ -3136,7 +3146,7 @@ static int seek(MPContext *mpctx, struct seek_params seek) if (seekresult == 0) return -1; - seek_reset(mpctx); + seek_reset(mpctx, !timeline_fallthrough); /* Use the target time as "current position" for further relative * seeks etc until a new video frame has been decoded */ @@ -3287,7 +3297,7 @@ int seek_chapter(struct MPContext *mpctx, int chapter, double *seek_pts, chapter_name); if (res >= 0) { if (*seek_pts == -1) - seek_reset(mpctx); + seek_reset(mpctx, true); else { mpctx->last_chapter_seek = res; mpctx->last_chapter_pts = *seek_pts; @@ -3313,6 +3323,7 @@ static void run_playloop(struct MPContext *mpctx) { struct MPOpts *opts = &mpctx->opts; float aq_sleep_time = 0; + bool full_audio_buffers = false; if (opts->chapterrange[1] > 0) { int cur_chapter = get_current_chapter(mpctx); @@ -3329,11 +3340,14 @@ static void run_playloop(struct MPContext *mpctx) /*========================== PLAY AUDIO ============================*/ if (mpctx->sh_audio && !mpctx->paused - && (!mpctx->restart_playback || !mpctx->sh_video)) - if (!fill_audio_out_buffers(mpctx)) + && (!mpctx->restart_playback || !mpctx->sh_video)) { + int status = fill_audio_out_buffers(mpctx); + full_audio_buffers = status >= 0; + if (status == -2) // at eof, all audio at least written to ao if (!mpctx->sh_video) mpctx->stop_play = AT_END_OF_FILE; + } if (!mpctx->sh_video) { @@ -3351,10 +3365,28 @@ static void run_playloop(struct MPContext *mpctx) print_status(mpctx, a_pos, false); - if (end_at.type == END_AT_TIME && end_at.pos < a_pos) - mpctx->stop_play = PT_NEXT_ENTRY; update_subtitles(mpctx, a_pos, mpctx->video_offset, false); update_osd_msg(mpctx); + if (end_at.type == END_AT_TIME && end_at.pos < a_pos) { + mpctx->stop_play = AT_END_OF_FILE; + } else if (mpctx->timeline && mpctx->stop_play == AT_END_OF_FILE + && mpctx->timeline_part + 1 < mpctx->num_timeline_parts + && mpctx->sh_audio) { + struct timeline_part *p = mpctx->timeline + mpctx->timeline_part; + double delay = mpctx->audio_out->get_delay(); + if (!opts->gapless_audio && p->source != (p+1)->source + && delay > 0.05) { + mpctx->stop_play = KEEP_PLAYING; + mp_input_get_cmd(mpctx->input, (delay-.05) * 1000, true); + } else { + seek(mpctx, (struct seek_params){ .type = MPSEEK_ABSOLUTE, + .amount = (p+1)->start }, + true); + } + } else if (!mpctx->stop_play) { + int sleep_time = full_audio_buffers || !mpctx->sh_audio ? 100 : 20; + mp_input_get_cmd(mpctx->input, sleep_time, true); + } } else { /*========================== PLAY VIDEO ============================*/ @@ -3388,7 +3420,8 @@ static void run_playloop(struct MPContext *mpctx) || mpctx->stop_play == AT_END_OF_FILE && mpctx->timeline_part + 1 < mpctx->num_timeline_parts) { seek(mpctx, (struct seek_params){ .type = MPSEEK_ABSOLUTE, - .amount = next->start }); + .amount = next->start }, + true); return; } } @@ -3415,6 +3448,7 @@ static void run_playloop(struct MPContext *mpctx) bool frame_time_remaining = sleep_until_near_frame(mpctx, &mpctx->time_frame, + full_audio_buffers, &aq_sleep_time); //====================== FLIP PAGE (VIDEO BLT): ========================= @@ -3603,218 +3637,12 @@ static void run_playloop(struct MPContext *mpctx) } if (mpctx->seek.type) { - seek(mpctx, mpctx->seek); + seek(mpctx, mpctx->seek, false); mpctx->seek = (struct seek_params){0}; } } -static int find_ordered_chapter_sources(struct MPContext *mpctx, - struct content_source *sources, - int num_sources, - unsigned char uid_map[][16]) -{ - int num_filenames = 0; - char **filenames = NULL; - if (num_sources > 1) { - mp_msg(MSGT_CPLAYER, MSGL_INFO, "This file references data from " - "other sources.\n"); - if (mpctx->stream->type != STREAMTYPE_FILE) { - mp_msg(MSGT_CPLAYER, MSGL_WARN, "Playback source is not a " - "normal disk file. Will not search for related files.\n"); - } else { - mp_msg(MSGT_CPLAYER, MSGL_INFO, "Will scan other files in the " - "same directory to find referenced sources.\n"); - filenames = find_files(mpctx->demuxer->filename, ".mkv", - &num_filenames); - } - } - - int num_left = num_sources - 1; - for (int i = 0; i < num_filenames && num_left > 0; i++) { - mp_msg(MSGT_CPLAYER, MSGL_INFO, "Checking file %s\n", - filename_recode(filenames[i])); - int format; - struct stream *s = open_stream(filenames[i], &mpctx->opts, &format); - if (!s) - continue; - struct demuxer *d = demux_open(&mpctx->opts, s, DEMUXER_TYPE_MATROSKA, - mpctx->opts.audio_id, - mpctx->opts.video_id, - mpctx->opts.sub_id, filenames[i]); - if (!d) { - free_stream(s); - continue; - } - if (d->file_format == DEMUXER_TYPE_MATROSKA) { - for (int i = 1; i < num_sources; i++) { - if (sources[i].demuxer) - continue; - if (!memcmp(uid_map[i], d->matroska_data.segment_uid, 16)) { - mp_msg(MSGT_CPLAYER, MSGL_INFO,"Match for source %d: %s\n", - i, filename_recode(d->filename)); - sources[i].stream = s; - sources[i].demuxer = d; - num_left--; - goto match; - } - } - } - free_demuxer(d); - free_stream(s); - continue; - match: - ; - } - talloc_free(filenames); - if (num_left) { - mp_msg(MSGT_CPLAYER, MSGL_ERR, "Failed to find ordered chapter part!\n" - "There will be parts MISSING from the video!\n"); - for (int i = 1, j = 1; i < num_sources; i++) - if (sources[i].demuxer) { - sources[j] = sources[i]; - memcpy(uid_map[j], uid_map[i], 16); - j++; - } - } - return num_sources - num_left; -} - -static void build_ordered_chapter_timeline(struct MPContext *mpctx) -{ - struct MPOpts *opts = &mpctx->opts; - - if (!opts->ordered_chapters) { - mp_msg(MSGT_CPLAYER, MSGL_INFO, "File uses ordered chapters, but " - "you have disabled support for them. Ignoring.\n"); - return; - } - - mp_msg(MSGT_CPLAYER, MSGL_INFO, "File uses ordered chapters, will build " - "edit timeline.\n"); - - struct demuxer *demuxer = mpctx->demuxer; - struct matroska_data *m = &demuxer->matroska_data; - - // +1 because sources/uid_map[0] is original file even if all chapters - // actually use other sources and need separate entries - struct content_source *sources = talloc_array_ptrtype(NULL, sources, - m->num_ordered_chapters+1); - sources[0].stream = mpctx->stream; - sources[0].demuxer = mpctx->demuxer; - unsigned char uid_map[m->num_ordered_chapters+1][16]; - int num_sources = 1; - memcpy(uid_map[0], m->segment_uid, 16); - - for (int i = 0; i < m->num_ordered_chapters; i++) { - struct matroska_chapter *c = m->ordered_chapters + i; - if (!c->has_segment_uid) - memcpy(c->segment_uid, m->segment_uid, 16); - - for (int j = 0; j < num_sources; j++) - if (!memcmp(c->segment_uid, uid_map[j], 16)) - goto found1; - memcpy(uid_map[num_sources], c->segment_uid, 16); - sources[num_sources] = (struct content_source){}; - num_sources++; - found1: - ; - } - - num_sources = find_ordered_chapter_sources(mpctx, sources, num_sources, - uid_map); - - - // +1 for terminating chapter with start time marking end of last real one - struct timeline_part *timeline = talloc_array_ptrtype(NULL, timeline, - m->num_ordered_chapters + 1); - struct chapter *chapters = talloc_array_ptrtype(NULL, chapters, - m->num_ordered_chapters); - uint64_t starttime = 0; - uint64_t missing_time = 0; - int part_count = 0; - int num_chapters = 0; - uint64_t prev_part_offset = 0; - for (int i = 0; i < m->num_ordered_chapters; i++) { - struct matroska_chapter *c = m->ordered_chapters + i; - - int j; - for (j = 0; j < num_sources; j++) { - if (!memcmp(c->segment_uid, uid_map[j], 16)) - goto found2; - } - missing_time += c->end - c->start; - continue; - found2:; - /* Only add a separate part if the time or file actually changes. - * Matroska files have chapter divisions that are redundant from - * timeline point of view because the same chapter structure is used - * both to specify the timeline and for normal chapter information. - * Removing a missing inserted external chapter can also cause this. - * We allow for a configurable fudge factor because of files which - * specify chapter end times that are one frame too early; - * we don't want to try seeking over a one frame gap. */ - int64_t join_diff = c->start - starttime - prev_part_offset; - if (part_count == 0 - || FFABS(join_diff) > opts->chapter_merge_threshold * 1000000 - || sources + j != timeline[part_count - 1].source) { - timeline[part_count].source = sources + j; - timeline[part_count].start = starttime / 1e9; - timeline[part_count].source_start = c->start / 1e9; - prev_part_offset = c->start - starttime; - part_count++; - } else if (part_count > 0 && join_diff) { - /* Chapter was merged at an inexact boundary; - * adjust timestamps to match. */ - mp_msg(MSGT_CPLAYER, MSGL_V, "Merging timeline part %d with " - "offset %d ms.\n", i, (int) join_diff); - starttime += join_diff; - } - chapters[num_chapters].start = starttime / 1e9; - chapters[num_chapters].name = talloc_strdup(chapters, c->name); - starttime += c->end - c->start; - num_chapters++; - } - timeline[part_count].start = starttime / 1e9; - - if (!part_count) { - // None of the parts come from the file itself??? - talloc_free(sources); - talloc_free(timeline); - talloc_free(chapters); - return; - } - - mp_msg(MSGT_CPLAYER, MSGL_V, "Timeline contains %d parts from %d " - "sources. Total length %.3f seconds.\n", part_count, num_sources, - timeline[part_count].start); - if (missing_time) - mp_msg(MSGT_CPLAYER, MSGL_ERR, "There are %.3f seconds missing " - "from the timeline!\n", missing_time / 1e9); - mp_msg(MSGT_CPLAYER, MSGL_V, "Source files:\n"); - for (int i = 0; i < num_sources; i++) - mp_msg(MSGT_CPLAYER, MSGL_V, "%d: %s\n", i, - filename_recode(sources[i].demuxer->filename)); - mp_msg(MSGT_CPLAYER, MSGL_V, "Timeline parts: (number, start, " - "source_start, source):\n"); - for (int i = 0; i < part_count; i++) { - struct timeline_part *p = timeline + i; - mp_msg(MSGT_CPLAYER, MSGL_V, "%3d %9.3f %9.3f %3td\n", i, p->start, - p->source_start, p->source - sources); - } - mp_msg(MSGT_CPLAYER, MSGL_V, "END %9.3f\n", timeline[part_count].start); - mpctx->sources = sources; - mpctx->num_sources = num_sources; - mpctx->timeline = timeline; - mpctx->num_timeline_parts = part_count; - mpctx->num_chapters = num_chapters; - mpctx->chapters = chapters; - - mpctx->timeline_part = 0; - mpctx->demuxer = timeline[0].source->demuxer; -} - - static int read_keys(void *ctx, int fd) { getch2(ctx); @@ -4385,14 +4213,9 @@ if (edl_output_filename) { mpctx->stream=NULL; mpctx->demuxer=NULL; - if (mpctx->d_audio) { - //free_demuxer_stream(mpctx->d_audio); - mpctx->d_audio=NULL; - } - if (mpctx->d_video) { - //free_demuxer_stream(d_video); - mpctx->d_video=NULL; - } + mpctx->d_audio=NULL; + mpctx->d_video=NULL; + mpctx->d_sub = NULL; mpctx->sh_audio=NULL; mpctx->sh_video=NULL; @@ -4575,6 +4398,32 @@ if (mpctx->demuxer && mpctx->demuxer->type==DEMUXER_TYPE_PLAYLIST) if (mpctx->demuxer->matroska_data.ordered_chapters) build_ordered_chapter_timeline(mpctx); + if (mpctx->demuxer->type == DEMUXER_TYPE_EDL) + build_edl_timeline(mpctx); + + if (mpctx->timeline) { + mpctx->timeline_part = 0; + mpctx->demuxer = mpctx->timeline[0].source->demuxer; + + int part_count = mpctx->num_timeline_parts; + mp_msg(MSGT_CPLAYER, MSGL_V, "Timeline contains %d parts from %d " + "sources. Total length %.3f seconds.\n", part_count, + mpctx->num_sources, mpctx->timeline[part_count].start); + mp_msg(MSGT_CPLAYER, MSGL_V, "Source files:\n"); + for (int i = 0; i < mpctx->num_sources; i++) + mp_msg(MSGT_CPLAYER, MSGL_V, "%d: %s\n", i, + filename_recode(mpctx->sources[i].demuxer->filename)); + mp_msg(MSGT_CPLAYER, MSGL_V, "Timeline parts: (number, start, " + "source_start, source):\n"); + for (int i = 0; i < part_count; i++) { + struct timeline_part *p = mpctx->timeline + i; + mp_msg(MSGT_CPLAYER, MSGL_V, "%3d %9.3f %9.3f %3td\n", i, p->start, + p->source_start, p->source - mpctx->sources); + } + mp_msg(MSGT_CPLAYER, MSGL_V, "END %9.3f\n", + mpctx->timeline[part_count].start); + } + if (!mpctx->sources) { mpctx->sources = talloc_ptrtype(NULL, mpctx->sources); *mpctx->sources = (struct content_source){.stream = mpctx->stream, @@ -4751,8 +4600,7 @@ if (select_subtitle(mpctx)) { print_file_properties(mpctx, mpctx->filename); - if (mpctx->sh_video) - reinit_video_chain(mpctx); + reinit_video_chain(mpctx); if (mpctx->sh_video) { if(mpctx->sh_video->output_flags & VFCAP_SPU && vo_spudec) spudec_set_hw_spu(vo_spudec,mpctx->video_out); @@ -4865,7 +4713,7 @@ if(play_n_frames==0){ // If there's a timeline force an absolute seek to initialize state if (seek_to_sec || mpctx->timeline) { queue_seek(mpctx, MPSEEK_ABSOLUTE, seek_to_sec, 0); - seek(mpctx, mpctx->seek); + seek(mpctx, mpctx->seek, false); end_at.pos += seek_to_sec; } if (opts->chapterrange[0] > 0) { @@ -4873,7 +4721,7 @@ if (opts->chapterrange[0] > 0) { if (seek_chapter(mpctx, opts->chapterrange[0]-1, &pts, NULL) >= 0 && pts > -1.0) { queue_seek(mpctx, MPSEEK_ABSOLUTE, pts, 0); - seek(mpctx, mpctx->seek); + seek(mpctx, mpctx->seek, false); } } diff --git a/osdep/findfiles.c b/osdep/findfiles.c deleted file mode 100644 index 879f6d5c98..0000000000 --- a/osdep/findfiles.c +++ /dev/null @@ -1,97 +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. - */ - -#include -#include -#include -#include - -#include "talloc.h" - -#if defined(__MINGW32__) || defined(__CYGWIN__) -static const char dir_separators[] = "/\\:"; -#else -static const char dir_separators[] = "/"; -#endif - -char **find_files(const char *original_file, const char *suffix, - int *num_results_ptr) -{ - void *tmpmem = talloc_new(NULL); - char *fname = talloc_strdup(tmpmem, original_file); - char *basename = NULL; - char *next = fname; - while (1) { - next = strpbrk(next, dir_separators); - if (!next) - break; - basename = next++; - } - char *directory; - if (basename) { - directory = fname; - *basename++ = 0; - } else { - directory = "."; - basename = fname; - } - - - char **results = talloc_size(NULL, 0); - DIR *dp = opendir(directory); - struct dirent *ep; - char ***names_by_matchlen = talloc_array(tmpmem, char **, - strlen(basename) + 1); - memset(names_by_matchlen, 0, talloc_get_size(names_by_matchlen)); - int num_results = 0; - while ((ep = readdir(dp))) { - int suffix_offset = strlen(ep->d_name) - strlen(suffix); - // name must end with suffix - if (suffix_offset < 0 || strcmp(ep->d_name + suffix_offset, suffix)) - continue; - // don't list the original name - if (!strcmp(ep->d_name, basename)) - continue; - - char *name = talloc_asprintf(results, "%s/%s", directory, ep->d_name); - char *s1 = ep->d_name; - char *s2 = basename; - int matchlen = 0; - while (*s1 && *s1++ == *s2++) - matchlen++; - int oldcount = talloc_get_size(names_by_matchlen[matchlen]) / - sizeof(char **); - names_by_matchlen[matchlen] = talloc_realloc(names_by_matchlen, - names_by_matchlen[matchlen], - char *, oldcount + 1); - names_by_matchlen[matchlen][oldcount] = name; - num_results++; - } - closedir(dp); - results = talloc_realloc(NULL, results, char *, num_results); - char **resptr = results; - for (int i = strlen(basename); i >= 0; i--) { - char **p = names_by_matchlen[i]; - for (int j = 0; j < talloc_get_size(p) / sizeof(char *); j++) - *resptr++ = p[j]; - } - assert(resptr == results + num_results); - talloc_free(tmpmem); - *num_results_ptr = num_results; - return results; -} diff --git a/osdep/findfiles.h b/osdep/findfiles.h deleted file mode 100644 index 97443e7319..0000000000 --- a/osdep/findfiles.h +++ /dev/null @@ -1,2 +0,0 @@ -char **find_files(const char *original_file, const char *suffix, - int *num_results_ptr); diff --git a/path.c b/path.c index 7f4ce88f17..f97e8a6c3a 100644 --- a/path.c +++ b/path.c @@ -26,6 +26,7 @@ #include #include #include +#include #include "config.h" #include "mp_msg.h" #include "path.h" @@ -42,6 +43,8 @@ #include #endif +#include "talloc.h" + #include "osdep/osdep.h" char *get_path(const char *filename){ @@ -194,7 +197,7 @@ void set_codec_path(const char *path) needs_free = 1; } -const char *mp_basename(const char *path) +char *mp_basename(const char *path) { char *s; @@ -207,5 +210,41 @@ const char *mp_basename(const char *path) path = s + 1; #endif s = strrchr(path, '/'); - return s ? s + 1 : path; + return s ? s + 1 : (char *)path; +} + +struct bstr mp_dirname(const char *path) +{ + struct bstr ret = {(uint8_t *)path, mp_basename(path) - path}; + if (ret.len == 0) + return BSTR("."); + return ret; +} + +char *mp_path_join(void *talloc_ctx, struct bstr p1, struct bstr p2) +{ + if (p1.len == 0) + return bstrdup0(talloc_ctx, p2); + if (p2.len == 0) + return bstrdup0(talloc_ctx, p1); + +#if HAVE_DOS_PATHS + if (p2.len >= 2 && p2.start[1] == ':' + || p2.start[0] == '\\' || p2.start[0] == '/') +#else + if (p2.start[0] == '/') +#endif + return bstrdup0(talloc_ctx, p2); // absolute path + + bool have_separator; + int endchar1 = p1.start[p1.len - 1]; +#if HAVE_DOS_PATHS + have_separator = endchar1 == '/' || endchar1 == '\\' + || p1.len == 2 && endchar1 == ':'; // "X:" only +#else + have_separator = endchar1 == '/'; +#endif + + return talloc_asprintf(talloc_ctx, "%.*s%s%.*s", BSTR_P(p1), + have_separator ? "" : "/", BSTR_P(p2)); } diff --git a/path.h b/path.h index 349f55e3cc..ffa052d680 100644 --- a/path.h +++ b/path.h @@ -21,11 +21,27 @@ #ifndef MPLAYER_PATH_H #define MPLAYER_PATH_H +#include "bstr.h" + extern char *codec_path; char *get_path(const char *filename); void set_path_env(void); void set_codec_path(const char *path); -const char *mp_basename(const char *path); + +// Return pointer to filename part of path + +char *mp_basename(const char *path); + +/* Return struct bstr referencing directory part of path, or if that + * would be empty, ".". + */ +struct bstr mp_dirname(const char *path); + +/* Join two path components and return a newly allocated string + * for the result. '/' is inserted between the components if needed. + * If p2 is an absolute path then the value of p1 is ignored. + */ +char *mp_path_join(void *talloc_ctx, struct bstr p1, struct bstr p2); #endif /* MPLAYER_PATH_H */ diff --git a/stream/stream.c b/stream/stream.c index 6f35252e3d..86dd61389e 100644 --- a/stream/stream.c +++ b/stream/stream.c @@ -29,6 +29,8 @@ #include #include +#include "talloc.h" + #include "config.h" #if HAVE_WINSOCK2_H @@ -658,3 +660,35 @@ unsigned char* stream_read_line(stream_t *s,unsigned char* mem, int max, int utf if(s->eof && ptr == mem) return NULL; return mem; } + +struct bstr stream_read_complete(struct stream *s, void *talloc_ctx, + int max_size, int padding_bytes) +{ + if (max_size > 1000000000) + abort(); + + int bufsize; + int total_read = 0; + int padding = FFMAX(padding_bytes, 1); + char *buf = NULL; + if (s->end_pos > max_size) + return (struct bstr){NULL, 0}; + if (s->end_pos > 0) + bufsize = s->end_pos + padding; + else + bufsize = 1000; + while (1) { + buf = talloc_realloc_size(talloc_ctx, buf, bufsize); + int readsize = stream_read(s, buf + total_read, bufsize - total_read); + total_read += readsize; + if (total_read < bufsize) + break; + if (bufsize > max_size) { + talloc_free(buf); + return (struct bstr){NULL, 0}; + } + bufsize = FFMIN(bufsize + (bufsize >> 1), max_size + padding); + } + buf = talloc_realloc_size(talloc_ctx, buf, total_read + padding); + return (struct bstr){buf, total_read}; +} diff --git a/stream/stream.h b/stream/stream.h index cad61d3256..94bfb0e343 100644 --- a/stream/stream.h +++ b/stream/stream.h @@ -28,6 +28,8 @@ #include #include +#include "bstr.h" + #ifndef O_BINARY #define O_BINARY 0 #endif @@ -331,6 +333,14 @@ inline static int stream_skip(stream_t *s,off_t len){ } struct MPOpts; +/* + * Return allocated buffer for all data until EOF. + * If amount of data would be more than max_size return NULL as data ptr. + * Make the allocated buffer padding_bytes larger than the data read. + * Write number of bytes read at *amount_read. + */ +struct bstr stream_read_complete(struct stream *s, void *talloc_ctx, + int max_size, int padding_bytes); void stream_reset(stream_t *s); int stream_control(stream_t *s, int cmd, void *arg); stream_t* new_stream(int fd,int type); @@ -341,6 +351,9 @@ stream_t *open_stream(const char *filename, struct MPOpts *options, stream_t *open_stream_full(const char *filename,int mode, struct MPOpts *options, int *file_format); stream_t *open_output_stream(const char *filename, struct MPOpts *options); +struct demux_stream; +struct stream *new_ds_stream(struct demux_stream *ds); + /// Set the callback to be used by libstream to check for user /// interruption during long blocking operations (cache filling, etc). struct input_ctx; diff --git a/sub/ass_mp.c b/sub/ass_mp.c index 98602ace03..78f607bc7a 100644 --- a/sub/ass_mp.c +++ b/sub/ass_mp.c @@ -233,50 +233,28 @@ ASS_Track *mp_ass_read_subdata(ASS_Library *library, sub_data *subdata, ASS_Track *mp_ass_read_stream(ASS_Library *library, const char *fname, char *charset) { - int i; - char *buf = NULL; ASS_Track *track; - size_t sz = 0; - size_t buf_alloc = 0; - stream_t *fd; - fd = open_stream(fname, NULL, NULL); - if (!fd) + struct stream *s = open_stream(fname, NULL, NULL); + if (!s) // Stream code should have printed an error already return NULL; - if (fd->end_pos > STREAM_BUFFER_SIZE) - /* read entire file if size is known */ - buf_alloc = fd->end_pos; - else - buf_alloc = 1000; - for (;;) { - if (sz > 100000000) { - mp_tmsg(MSGT_ASS, MSGL_ERR, "Refusing to load subtitle file " - "larger than 100 MB: %s\n", fname); - sz = 0; - break; - } - buf_alloc = FFMAX(buf_alloc, sz + (sz >> 1)); - buf_alloc = FFMIN(buf_alloc, 100000001); - buf = realloc(buf, buf_alloc + 1); - i = stream_read(fd, buf + sz, buf_alloc - sz); - if (i <= 0) - break; - sz += i; - } - free_stream(fd); - if (!sz) { - free(buf); + struct bstr content = stream_read_complete(s, NULL, 100000000, 1); + if (content.start == NULL) + mp_tmsg(MSGT_ASS, MSGL_ERR, "Refusing to load subtitle file " + "larger than 100 MB: %s\n", fname); + free_stream(s); + if (content.len == 0) { + talloc_free(content.start); return NULL; } - buf[sz] = 0; - buf = realloc(buf, sz + 1); - track = ass_read_memory(library, buf, sz, charset); + content.start[content.len] = 0; + track = ass_read_memory(library, content.start, content.len, charset); if (track) { free(track->name); track->name = strdup(fname); } - free(buf); + talloc_free(content.start); return track; } diff --git a/timeline/tl_edl.c b/timeline/tl_edl.c new file mode 100644 index 0000000000..99b9ddad82 --- /dev/null +++ b/timeline/tl_edl.c @@ -0,0 +1,398 @@ +/* + * 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 "talloc.h" + +#include "mp_core.h" +#include "mp_msg.h" +#include "libmpdemux/demuxer.h" +#include "path.h" +#include "bstr.h" +#include "mpcommon.h" + + +struct edl_source { + struct bstr id; + char *filename; + int lineno; +}; + +struct edl_time { + int64_t start; + int64_t end; + bool implied_start; + bool implied_end; +}; + +struct edl_part { + struct edl_time tl; + struct edl_time src; + int64_t duration; + int id; + int lineno; +}; + +static int find_edl_source(struct edl_source *sources, int num_sources, + struct bstr name) +{ + for (int i = 0; i < num_sources; i++) + if (!bstrcmp(sources[i].id, name)) + return i; + return -1; +} + +void build_edl_timeline(struct MPContext *mpctx) +{ + const struct bstr file_prefix = BSTR("<"); + void *tmpmem = talloc_new(NULL); + + struct bstr *lines = bstr_splitlines(tmpmem, mpctx->demuxer->file_contents); + int linec = MP_TALLOC_ELEMS(lines); + struct bstr header = BSTR("mplayer EDL file, version "); + if (!linec || !bstr_startswith(lines[0], header)) { + mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Bad EDL header!\n"); + goto out; + } + struct bstr version = bstr_strip(bstr_cut(lines[0], header.len)); + if (bstrcmp(BSTR("2"), version)) { + mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Unsupported EDL file version!\n"); + goto out; + } + int num_sources = 0; + int num_parts = 0; + for (int i = 1; i < linec; i++) { + if (bstr_startswith(lines[i], file_prefix)) { + num_sources++; + } else { + int comment = bstrchr(lines[i], '#'); + if (comment >= 0) + lines[i] = bstr_splice(lines[i], 0, comment); + if (bstr_strip(lines[i]).len) + num_parts++; + } + } + if (!num_parts) { + mp_msg(MSGT_CPLAYER, MSGL_ERR, "No parts in timeline!\n"); + goto out; + } + + // Parse source filename definitions + + struct edl_source *edl_ids = talloc_array_ptrtype(tmpmem, edl_ids, + num_sources); + num_sources = 0; + for (int i = 1; i < linec; i++) { + struct bstr line = lines[i]; + if (!bstr_startswith(line, file_prefix)) + continue; + line = bstr_cut(line, file_prefix.len); + struct bstr id = bstr_split(line, WHITESPACE, &line); + if (find_edl_source(edl_ids, num_sources, id) >= 0) { + mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Repeated ID on line %d!\n", + i+1); + goto out; + } + if (!isalpha(*id.start)) { + mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Invalid ID on line %d!\n", + i+1); + goto out; + } + char *filename = mp_basename(bstrdup0(tmpmem, bstr_strip(line))); + if (!strlen(filename)) { + mp_msg(MSGT_CPLAYER, MSGL_ERR, + "EDL: Invalid filename on line %d!\n", i+1); + goto out; + } + struct bstr dirname = mp_dirname(mpctx->demuxer->filename); + char *fullname = mp_path_join(tmpmem, dirname, BSTR(filename)); + edl_ids[num_sources++] = (struct edl_source){id, fullname, i+1}; + } + + // Parse timeline part definitions + + struct edl_part *parts = talloc_array_ptrtype(tmpmem, parts, num_parts); + int total_parts = num_parts; + num_parts = 0; + for (int i = 1; i < linec; i++) { + struct bstr line = bstr_strip(lines[i]); + if (!line.len || bstr_startswith(line, file_prefix)) + continue; + parts[num_parts] = (struct edl_part){{-1, -1}, {-1, -1}, 0, -1}; + parts[num_parts].lineno = i + 1; + for (int s = 0; s < 2; s++) { + struct edl_time *p = !s ? &parts[num_parts].tl : + &parts[num_parts].src; + while (1) { + struct bstr t = bstr_split(line, WHITESPACE, &line); + if (!t.len) { + if (!s && num_parts < total_parts - 1) { + mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: missing source " + "identifier on line %d (not last)!\n", i+1); + goto out; + } + break; + } + if (isalpha(*t.start)) { + if (s) + goto bad; + parts[num_parts].id = find_edl_source(edl_ids, num_sources, + t); + if (parts[num_parts].id < 0) { + mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Undefined source " + "identifier on line %d!\n", i+1); + goto out; + } + break; + } + while (t.len) { + struct bstr next; + struct bstr arg = bstr_split(t, "+-", &next); + if (!arg.len) { + next = bstr_split(line, WHITESPACE, &line); + arg = bstr_split(next, "+-", &next); + } + if (!arg.len) + goto bad; + int64_t val; + if (!bstrcmp(arg, BSTR("*"))) + val = -1; + else if (isdigit(*arg.start)) { + val = bstrtoll(arg, &arg, 10) * 1000000000; + if (arg.len && *arg.start == '.') { + int len = arg.len - 1; + arg = bstr_splice(arg, 1, 10); + int64_t val2 = bstrtoll(arg, &arg, 10); + if (arg.len) + goto bad; + for (; len < 9; len++) + val2 *= 10; + val += val2; + } + } else + goto bad; + int c = *t.start; + if (isdigit(c) || c == '*') { + if (val < 0) + p->implied_start = true; + else + p->start = val; + } else if (c == '-') { + if (val < 0) + p->implied_end = true; + else + p->end = val; + } else if (c == '+') { + if (val < 0) + goto bad; + if (val == 0) { + mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: zero duration " + "on line %d!\n", i+1); + goto out; + } + parts[num_parts].duration = val; + } else + goto bad; + t = next; + } + } + } + num_parts++; + continue; + bad: + mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Malformed line %d!\n", i+1); + goto out; + } + + // Fill in implied start/stop/duration values + + int64_t *times = talloc_zero_array(tmpmem, int64_t, num_sources); + while (1) { + int64_t time = 0; + for (int i = 0; i < num_parts; i++) { + for (int s = 0; s < 2; s++) { + struct edl_time *p = s ? &parts[i].tl : &parts[i].src; + if (!s && parts[i].id == -1) + continue; + int64_t *t = s ? &time : times + parts[i].id; + p->implied_start |= s && *t >= 0; + if (p->implied_start && p->start >= 0 && *t >= 0 + && p->start != *t) { + mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Inconsistent line " + "%d!\n", parts[i].lineno); + goto out; + } + if (p->start >= 0) + *t = p->start; + if (p->implied_start) + p->start = *t; + if (*t >= 0 && parts[i].duration) + *t += parts[i].duration; + else + *t = -1; + if (p->end >= 0) + *t = p->end; + } + } + for (int i = 0; i < num_sources; i++) + times[i] = -1; + time = -1; + for (int i = num_parts - 1; i >= 0; i--) { + for (int s = 0; s < 2; s++) { + struct edl_time *p = s ? &parts[i].tl : &parts[i].src; + if (!s && parts[i].id == -1) + continue; + int64_t *t = s ? &time : times + parts[i].id; + p->implied_end |= s && *t >= 0; + if (p->implied_end && p->end >= 0 && *t >=0 && p->end != *t) { + mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Inconsistent line " + "%d!\n", parts[i].lineno); + goto out; + } + if (p->end >= 0) + *t = p->end; + if (p->implied_end) + p->end = *t; + if (*t >= 0 && parts[i].duration) { + *t -= parts[i].duration; + if (t < 0) { + mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Negative time " + "on line %d!\n", parts[i].lineno); + goto out; + } + } else + *t = -1; + if (p->start >= 0) + *t = p->start; + } + } + int missing_duration = -1; + int missing_srcstart = -1; + bool anything_done = false; + for (int i = 0; i < num_parts; i++) { + int64_t duration = parts[i].duration; + if (parts[i].tl.start >= 0 && parts[i].tl.end >= 0) { + int64_t duration2 = parts[i].tl.end - parts[i].tl.start; + if (duration && duration != duration2) + goto incons; + duration = duration2; + if (duration <= 0) + goto neg; + } + if (parts[i].src.start >= 0 && parts[i].src.end >= 0) { + int64_t duration2 = parts[i].src.end - parts[i].src.start; + if (duration && duration != duration2) { + incons: + mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Inconsistent line " + "%d!\n", i+1); + goto out; + } + duration = duration2; + if (duration <= 0) { + neg: + mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: duration <= 0 on " + "line %d!\n", parts[i].lineno); + goto out; + } + } + if (parts[i].id == -1) + continue; + if (!duration) + missing_duration = i; + else if (!parts[i].duration) + anything_done = true; + parts[i].duration = duration; + if (duration && parts[i].src.start < 0) + if (parts[i].src.end < 0) + missing_srcstart = i; + else + parts[i].src.start = parts[i].src.end - duration; + } + if (!anything_done) { + if (missing_duration >= 0) { + mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Could not determine " + "duration for line %d!\n", + parts[missing_duration].lineno); + goto out; + } + if (missing_srcstart >= 0) { + mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: no source start time for " + "line %d!\n", parts[missing_srcstart].lineno); + goto out; + } + break; + } + } + + // Open source files + + struct content_source *sources = talloc_array_ptrtype(NULL, sources, + num_sources + 1); + mpctx->sources = sources; + sources[0].stream = mpctx->stream; + sources[0].demuxer = mpctx->demuxer; + mpctx->num_sources = 1; + + for (int i = 0; i < num_sources; i++) { + int format = 0; + struct stream *s = open_stream(edl_ids[i].filename, &mpctx->opts, + &format); + if (!s) + goto openfail; + struct demuxer *d = demux_open(&mpctx->opts, s, format, + mpctx->opts.audio_id, + mpctx->opts.video_id, + mpctx->opts.sub_id, + edl_ids[i].filename); + if (!d) { + free_stream(s); + openfail: + mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Could not open source " + "file on line %d!\n", edl_ids[i].lineno); + goto out; + } + sources[mpctx->num_sources].stream = s; + sources[mpctx->num_sources].demuxer = d; + mpctx->num_sources++; + } + + // Write final timeline structure + + struct timeline_part *timeline = talloc_array_ptrtype(NULL, timeline, + num_parts + 1); + int64_t starttime = 0; + for (int i = 0; i < num_parts; i++) { + timeline[i].start = starttime / 1e9; + starttime += parts[i].duration; + timeline[i].source_start = parts[i].src.start / 1e9; + timeline[i].source = sources + parts[i].id + 1; + } + if (parts[num_parts - 1].id != -1) { + timeline[num_parts].start = starttime / 1e9; + num_parts++; + } + mpctx->timeline = timeline; + mpctx->num_timeline_parts = num_parts - 1; + + out: + talloc_free(tmpmem); +} diff --git a/timeline/tl_matroska.c b/timeline/tl_matroska.c new file mode 100644 index 0000000000..03b6cc3dc4 --- /dev/null +++ b/timeline/tl_matroska.c @@ -0,0 +1,271 @@ +/* + * 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 "talloc.h" + +#include "mp_core.h" +#include "mp_msg.h" +#include "libmpdemux/demuxer.h" +#include "path.h" +#include "bstr.h" +#include "mpcommon.h" + +static char **find_files(const char *original_file, const char *suffix) +{ + void *tmpmem = talloc_new(NULL); + char *basename = mp_basename(original_file); + struct bstr directory = mp_dirname(original_file); + char **results = talloc_size(NULL, 0); + char *dir_zero = bstrdup0(tmpmem, directory); + DIR *dp = opendir(dir_zero); + if (!dp) { + talloc_free(tmpmem); + return results; + } + struct dirent *ep; + char ***names_by_matchlen = talloc_zero_array(tmpmem, char **, + strlen(basename) + 1); + int num_results = 0; + while ((ep = readdir(dp))) { + int suffix_offset = strlen(ep->d_name) - strlen(suffix); + // name must end with suffix + if (suffix_offset < 0 || strcmp(ep->d_name + suffix_offset, suffix)) + continue; + // don't list the original name + if (!strcmp(ep->d_name, basename)) + continue; + + char *name = mp_path_join(results, directory, BSTR(ep->d_name)); + char *s1 = ep->d_name; + char *s2 = basename; + int matchlen = 0; + while (*s1 && *s1++ == *s2++) + matchlen++; + int oldcount = MP_TALLOC_ELEMS(names_by_matchlen[matchlen]); + names_by_matchlen[matchlen] = talloc_realloc(names_by_matchlen, + names_by_matchlen[matchlen], + char *, oldcount + 1); + names_by_matchlen[matchlen][oldcount] = name; + num_results++; + } + closedir(dp); + results = talloc_realloc(NULL, results, char *, num_results); + char **resptr = results; + for (int i = strlen(basename); i >= 0; i--) { + char **p = names_by_matchlen[i]; + for (int j = 0; j < talloc_get_size(p) / sizeof(char *); j++) + *resptr++ = p[j]; + } + assert(resptr == results + num_results); + talloc_free(tmpmem); + return results; +} + +static int find_ordered_chapter_sources(struct MPContext *mpctx, + struct content_source *sources, + int num_sources, + unsigned char uid_map[][16]) +{ + int num_filenames = 0; + char **filenames = NULL; + if (num_sources > 1) { + mp_msg(MSGT_CPLAYER, MSGL_INFO, "This file references data from " + "other sources.\n"); + if (mpctx->stream->type != STREAMTYPE_FILE) { + mp_msg(MSGT_CPLAYER, MSGL_WARN, "Playback source is not a " + "normal disk file. Will not search for related files.\n"); + } else { + mp_msg(MSGT_CPLAYER, MSGL_INFO, "Will scan other files in the " + "same directory to find referenced sources.\n"); + filenames = find_files(mpctx->demuxer->filename, ".mkv"); + num_filenames = MP_TALLOC_ELEMS(filenames); + } + } + + int num_left = num_sources - 1; + for (int i = 0; i < num_filenames && num_left > 0; i++) { + mp_msg(MSGT_CPLAYER, MSGL_INFO, "Checking file %s\n", + filename_recode(filenames[i])); + int format = 0; + struct stream *s = open_stream(filenames[i], &mpctx->opts, &format); + if (!s) + continue; + struct demuxer *d = demux_open(&mpctx->opts, s, DEMUXER_TYPE_MATROSKA, + mpctx->opts.audio_id, + mpctx->opts.video_id, + mpctx->opts.sub_id, filenames[i]); + if (!d) { + free_stream(s); + continue; + } + if (d->file_format == DEMUXER_TYPE_MATROSKA) { + for (int i = 1; i < num_sources; i++) { + if (sources[i].demuxer) + continue; + if (!memcmp(uid_map[i], d->matroska_data.segment_uid, 16)) { + mp_msg(MSGT_CPLAYER, MSGL_INFO,"Match for source %d: %s\n", + i, filename_recode(d->filename)); + sources[i].stream = s; + sources[i].demuxer = d; + num_left--; + goto match; + } + } + } + free_demuxer(d); + free_stream(s); + continue; + match: + ; + } + talloc_free(filenames); + if (num_left) { + mp_msg(MSGT_CPLAYER, MSGL_ERR, "Failed to find ordered chapter part!\n" + "There will be parts MISSING from the video!\n"); + for (int i = 1, j = 1; i < num_sources; i++) + if (sources[i].demuxer) { + sources[j] = sources[i]; + memcpy(uid_map[j], uid_map[i], 16); + j++; + } + } + return num_sources - num_left; +} + +void build_ordered_chapter_timeline(struct MPContext *mpctx) +{ + struct MPOpts *opts = &mpctx->opts; + + if (!opts->ordered_chapters) { + mp_msg(MSGT_CPLAYER, MSGL_INFO, "File uses ordered chapters, but " + "you have disabled support for them. Ignoring.\n"); + return; + } + + mp_msg(MSGT_CPLAYER, MSGL_INFO, "File uses ordered chapters, will build " + "edit timeline.\n"); + + struct demuxer *demuxer = mpctx->demuxer; + struct matroska_data *m = &demuxer->matroska_data; + + // +1 because sources/uid_map[0] is original file even if all chapters + // actually use other sources and need separate entries + struct content_source *sources = talloc_array_ptrtype(NULL, sources, + m->num_ordered_chapters+1); + sources[0].stream = mpctx->stream; + sources[0].demuxer = mpctx->demuxer; + unsigned char uid_map[m->num_ordered_chapters+1][16]; + int num_sources = 1; + memcpy(uid_map[0], m->segment_uid, 16); + + for (int i = 0; i < m->num_ordered_chapters; i++) { + struct matroska_chapter *c = m->ordered_chapters + i; + if (!c->has_segment_uid) + memcpy(c->segment_uid, m->segment_uid, 16); + + for (int j = 0; j < num_sources; j++) + if (!memcmp(c->segment_uid, uid_map[j], 16)) + goto found1; + memcpy(uid_map[num_sources], c->segment_uid, 16); + sources[num_sources] = (struct content_source){}; + num_sources++; + found1: + ; + } + + num_sources = find_ordered_chapter_sources(mpctx, sources, num_sources, + uid_map); + + + // +1 for terminating chapter with start time marking end of last real one + struct timeline_part *timeline = talloc_array_ptrtype(NULL, timeline, + m->num_ordered_chapters + 1); + struct chapter *chapters = talloc_array_ptrtype(NULL, chapters, + m->num_ordered_chapters); + uint64_t starttime = 0; + uint64_t missing_time = 0; + int part_count = 0; + int num_chapters = 0; + uint64_t prev_part_offset = 0; + for (int i = 0; i < m->num_ordered_chapters; i++) { + struct matroska_chapter *c = m->ordered_chapters + i; + + int j; + for (j = 0; j < num_sources; j++) { + if (!memcmp(c->segment_uid, uid_map[j], 16)) + goto found2; + } + missing_time += c->end - c->start; + continue; + found2:; + /* Only add a separate part if the time or file actually changes. + * Matroska files have chapter divisions that are redundant from + * timeline point of view because the same chapter structure is used + * both to specify the timeline and for normal chapter information. + * Removing a missing inserted external chapter can also cause this. + * We allow for a configurable fudge factor because of files which + * specify chapter end times that are one frame too early; + * we don't want to try seeking over a one frame gap. */ + int64_t join_diff = c->start - starttime - prev_part_offset; + if (part_count == 0 + || FFABS(join_diff) > opts->chapter_merge_threshold * 1000000 + || sources + j != timeline[part_count - 1].source) { + timeline[part_count].source = sources + j; + timeline[part_count].start = starttime / 1e9; + timeline[part_count].source_start = c->start / 1e9; + prev_part_offset = c->start - starttime; + part_count++; + } else if (part_count > 0 && join_diff) { + /* Chapter was merged at an inexact boundary; + * adjust timestamps to match. */ + mp_msg(MSGT_CPLAYER, MSGL_V, "Merging timeline part %d with " + "offset %d ms.\n", i, (int) join_diff); + starttime += join_diff; + } + chapters[num_chapters].start = starttime / 1e9; + chapters[num_chapters].name = talloc_strdup(chapters, c->name); + starttime += c->end - c->start; + num_chapters++; + } + timeline[part_count].start = starttime / 1e9; + + if (!part_count) { + // None of the parts come from the file itself??? + talloc_free(sources); + talloc_free(timeline); + talloc_free(chapters); + return; + } + + if (missing_time) + mp_msg(MSGT_CPLAYER, MSGL_ERR, "There are %.3f seconds missing " + "from the timeline!\n", missing_time / 1e9); + mpctx->sources = sources; + mpctx->num_sources = num_sources; + mpctx->timeline = timeline; + mpctx->num_timeline_parts = part_count; + mpctx->num_chapters = num_chapters; + mpctx->chapters = chapters; +}