summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--libmpdemux/demux_edl.c58
-rw-r--r--libmpdemux/demuxer.c2
-rw-r--r--libmpdemux/demuxer.h3
-rw-r--r--mp_core.h2
-rw-r--r--mplayer.c26
-rw-r--r--timeline/tl_edl.c398
-rw-r--r--timeline/tl_matroska.c18
8 files changed, 491 insertions, 18 deletions
diff --git a/Makefile b/Makefile
index 90802b0ae2..43d1937c25 100644
--- a/Makefile
+++ b/Makefile
@@ -389,6 +389,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 \
@@ -441,6 +442,7 @@ SRCS_COMMON = asxparser.c \
sub/subassconvert.c \
sub/subreader.c \
sub/vobsub.c \
+ timeline/tl_edl.c \
timeline/tl_matroska.c \
$(SRCS_COMMON-yes)
diff --git a/libmpdemux/demux_edl.c b/libmpdemux/demux_edl.c
new file mode 100644
index 0000000000..4c864cfe42
--- /dev/null
+++ b/libmpdemux/demux_edl.c
@@ -0,0 +1,58 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <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,
+};
diff --git a/libmpdemux/demuxer.c b/libmpdemux/demuxer.c
index bbcce939be..07b929b588 100644
--- a/libmpdemux/demuxer.c
+++ b/libmpdemux/demuxer.c
@@ -53,6 +53,7 @@
static void clear_parser(sh_common_t *sh);
// Demuxer list
+extern const struct demuxer_desc demuxer_desc_edl;
extern const demuxer_desc_t demuxer_desc_rawaudio;
extern const demuxer_desc_t demuxer_desc_rawvideo;
extern const demuxer_desc_t demuxer_desc_tv;
@@ -101,6 +102,7 @@ extern const demuxer_desc_t demuxer_desc_mng;
* libraries and demuxers requiring binary support. */
const demuxer_desc_t *const demuxer_list[] = {
+ &demuxer_desc_edl,
&demuxer_desc_rawaudio,
&demuxer_desc_rawvideo,
#ifdef CONFIG_TV
diff --git a/libmpdemux/demuxer.h b/libmpdemux/demuxer.h
index a4f62ec822..4a8a7545d1 100644
--- a/libmpdemux/demuxer.h
+++ b/libmpdemux/demuxer.h
@@ -88,6 +88,7 @@ enum demuxer_type {
DEMUXER_TYPE_LAVF_PREFERRED,
DEMUXER_TYPE_RTP_NEMESI,
DEMUXER_TYPE_MNG,
+ DEMUXER_TYPE_EDL,
/* Values after this are for internal use and can not be selected
* as demuxer type by the user (-demuxer option). */
@@ -282,6 +283,8 @@ typedef struct demuxer {
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; // demuxer-specific internal data
char **info; // metadata
diff --git a/mp_core.h b/mp_core.h
index 6f4d1d8557..bbb93bce48 100644
--- a/mp_core.h
+++ b/mp_core.h
@@ -254,5 +254,7 @@ void update_subtitles(struct MPContext *mpctx, double refpts,
// timeline/tl_matroska.c
void build_ordered_chapter_timeline(struct MPContext *mpctx);
+// timeline/tl_edl.c
+void build_edl_timeline(struct MPContext *mpctx);
#endif /* MPLAYER_MP_CORE_H */
diff --git a/mplayer.c b/mplayer.c
index 161f1c9a2c..2afbbbb821 100644
--- a/mplayer.c
+++ b/mplayer.c
@@ -4349,6 +4349,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,
diff --git a/timeline/tl_edl.c b/timeline/tl_edl.c
new file mode 100644
index 0000000000..99b9ddad82
--- /dev/null
+++ b/timeline/tl_edl.c
@@ -0,0 +1,398 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <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);
+}
diff --git a/timeline/tl_matroska.c b/timeline/tl_matroska.c
index 46d0e33a45..03b6cc3dc4 100644
--- a/timeline/tl_matroska.c
+++ b/timeline/tl_matroska.c
@@ -259,31 +259,13 @@ void build_ordered_chapter_timeline(struct MPContext *mpctx)
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;
}