1
mirror of https://github.com/mpv-player/mpv synced 2024-09-12 23:45:53 +02:00

Merge branch 'edl'

* edl:
  core: support timeline with audio-only files
  core: wake up a bit less often for audio-only files
  core: audio: cut audio writes at end of timeline part
  EDL: add support for new EDL file format
  stream.[ch], ass_mp: new stream function for whole-file reads
  tl_matroska.c: move the find_files() function here
  bstr.[ch], path.[ch]: add string and path handling functions
  core: ordered chapters: move timeline creation to timeline/
  options: drop support for numeric -demuxer values
  cleanup: demuxer.[ch]: remove unused code, make functions static
  cleanup: reindent demuxer.h, use struct names for types
This commit is contained in:
Uoti Urpala 2011-04-08 22:50:06 +03:00
commit 9ef15ac4fc
19 changed files with 1341 additions and 660 deletions

View File

@ -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 <filename>
@ -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 <mode>

View File

@ -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)

91
bstr.c
View File

@ -18,6 +18,10 @@
#include <string.h>
#include <libavutil/avutil.h>
#include <assert.h>
#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;
}

36
bstr.h
View File

@ -22,18 +22,50 @@
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <stdbool.h>
#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 */

58
libmpdemux/demux_edl.c Normal file
View File

@ -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 <stdlib.h>
#include <stdbool.h>
#include <string.h>
#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,
};

View File

@ -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;
}

View File

@ -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_pos<ds->buffer_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_pos<ds->buffer_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_pos<ds->buffer_size)) ? ds->buffer[ds->buffer_pos] \
: ((unlikely(!ds_fill_buffer(ds))) ? (-1) : ds->buffer[ds->buffer_pos]))
#define demux_getc(ds) ( \
(likely(ds->buffer_pos<ds->buffer_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

View File

@ -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 */

View File

@ -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 */

370
mplayer.c
View File

@ -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);
}
}

View File

@ -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 <dirent.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#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;
}

View File

@ -1,2 +0,0 @@
char **find_files(const char *original_file, const char *suffix,
int *num_results_ptr);

43
path.c
View File

@ -26,6 +26,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "config.h"
#include "mp_msg.h"
#include "path.h"
@ -42,6 +43,8 @@
#include <sys/cygwin.h>
#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));
}

18
path.h
View File

@ -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 */

View File

@ -29,6 +29,8 @@
#include <fcntl.h>
#include <strings.h>
#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};
}

View File

@ -28,6 +28,8 @@
#include <sys/types.h>
#include <fcntl.h>
#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;

View File

@ -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;
}

398
timeline/tl_edl.c Normal file
View File

@ -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 <stdlib.h>
#include <stdbool.h>
#include <inttypes.h>
#include <ctype.h>
#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);
}

271
timeline/tl_matroska.c Normal file
View File

@ -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 <stdlib.h>
#include <stdbool.h>
#include <inttypes.h>
#include <assert.h>
#include <dirent.h>
#include <libavutil/common.h>
#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;
}