mirror of
https://github.com/mpv-player/mpv
synced 2025-03-19 18:05:21 +00: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:
commit
9ef15ac4fc
@ -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>
|
||||
|
5
Makefile
5
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)
|
||||
|
91
bstr.c
91
bstr.c
@ -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
36
bstr.h
@ -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
58
libmpdemux/demux_edl.c
Normal 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,
|
||||
};
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 */
|
||||
|
@ -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
370
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
char **find_files(const char *original_file, const char *suffix,
|
||||
int *num_results_ptr);
|
43
path.c
43
path.c
@ -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
18
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 */
|
||||
|
@ -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};
|
||||
}
|
||||
|
@ -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;
|
||||
|
46
sub/ass_mp.c
46
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;
|
||||
}
|
||||
|
||||
|
398
timeline/tl_edl.c
Normal file
398
timeline/tl_edl.c
Normal 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
271
timeline/tl_matroska.c
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user