summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2013-06-27 18:21:07 +0200
committerwm4 <wm4@nowhere>2013-06-28 15:47:35 +0200
commit5f664d78e6e9bd5809dc7d0f12c4099e76582cb3 (patch)
tree618ad84b255a8f99afbb196a71d0d29eec90faac /core
parentac79eb733741d8d22bbd550be39fe63c28a575f9 (diff)
downloadmpv-5f664d78e6e9bd5809dc7d0f12c4099e76582cb3.tar.bz2
mpv-5f664d78e6e9bd5809dc7d0f12c4099e76582cb3.tar.xz
core: add libquvi 0.9 support
This adds support for libquvi 0.9.x, and these features: - start time (part of youtube URL) - youtube subtitles - alternative source switching ('l' and 'L' keys) - youtube playlists Note that libquvi 0.9 is still in development. Although this seems to be API stable now, it looks like there will be a 1.0 release, which is supposed to be the next stable release and the actual successor of libquvi 0.4.x.
Diffstat (limited to 'core')
-rw-r--r--core/command.c64
-rw-r--r--core/mplayer.c48
-rw-r--r--core/playlist.c12
-rw-r--r--core/resolve.h21
-rw-r--r--core/resolve_quvi9.c150
5 files changed, 286 insertions, 9 deletions
diff --git a/core/command.c b/core/command.c
index 11ee73ec57..0418e7a82c 100644
--- a/core/command.c
+++ b/core/command.c
@@ -24,6 +24,9 @@
#include <assert.h>
#include <time.h>
+#include <libavutil/avstring.h>
+#include <libavutil/common.h>
+
#include "config.h"
#include "talloc.h"
#include "command.h"
@@ -67,7 +70,6 @@
#include "core/mp_core.h"
#include "mp_fifo.h"
-#include "libavutil/avstring.h"
static void change_video_filters(MPContext *mpctx, const char *cmd,
const char *arg);
@@ -464,6 +466,65 @@ static int mp_property_edition(m_option_t *prop, int action, void *arg,
return M_PROPERTY_NOT_IMPLEMENTED;
}
+static struct mp_resolve_src *find_source(struct mp_resolve_result *res,
+ char *url)
+{
+ if (res->num_srcs == 0)
+ return NULL;
+
+ int src = 0;
+ for (int n = 0; n < res->num_srcs; n++) {
+ if (strcmp(res->srcs[n]->url, res->url) == 0) {
+ src = n;
+ break;
+ }
+ }
+ return res->srcs[src];
+}
+
+static int mp_property_quvi_format(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct mp_resolve_result *res = mpctx->resolve_result;
+ if (!res || !res->num_srcs)
+ return M_PROPERTY_UNAVAILABLE;
+
+ struct mp_resolve_src *cur = find_source(res, res->url);
+ if (!cur)
+ return M_PROPERTY_UNAVAILABLE;
+
+ switch (action) {
+ case M_PROPERTY_GET:
+ *(char **)arg = talloc_strdup(NULL, cur->encid);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_SET: {
+ mpctx->stop_play = PT_RESTART;
+ break;
+ }
+ case M_PROPERTY_SWITCH: {
+ struct m_property_switch_arg *sarg = arg;
+ int pos = 0;
+ for (int n = 0; n < res->num_srcs; n++) {
+ if (res->srcs[n] == cur) {
+ pos = n;
+ break;
+ }
+ }
+ pos += sarg->inc;
+ if (pos < 0 || pos >= res->num_srcs) {
+ if (sarg->wrap) {
+ pos = (res->num_srcs + pos) % res->num_srcs;
+ } else {
+ pos = av_clip(pos, 0, res->num_srcs);
+ }
+ }
+ char *arg = res->srcs[pos]->encid;
+ return mp_property_quvi_format(prop, M_PROPERTY_SET, &arg, mpctx);
+ }
+ }
+ return mp_property_generic_option(prop, action, arg, mpctx);
+}
+
/// Number of titles in file
static int mp_property_titles(m_option_t *prop, int action, void *arg,
MPContext *mpctx)
@@ -1523,6 +1584,7 @@ static const m_option_t mp_properties[] = {
{ "chapter", mp_property_chapter, CONF_TYPE_INT,
M_OPT_MIN, 0, 0, NULL },
M_OPTION_PROPERTY_CUSTOM("edition", mp_property_edition),
+ M_OPTION_PROPERTY_CUSTOM("quvi-format", mp_property_quvi_format),
{ "titles", mp_property_titles, CONF_TYPE_INT,
0, 0, 0, NULL },
{ "chapters", mp_property_chapters, CONF_TYPE_INT,
diff --git a/core/mplayer.c b/core/mplayer.c
index fa0723f022..4377f9f9bc 100644
--- a/core/mplayer.c
+++ b/core/mplayer.c
@@ -3890,6 +3890,9 @@ static struct track *open_external_file(struct MPContext *mpctx, char *filename,
if (!filename)
return NULL;
int format = 0;
+ char *disp_filename = filename;
+ if (strncmp(disp_filename, "memory://", 9) == 0)
+ disp_filename = "memory://"; // avoid noise
struct stream *stream = open_stream(filename, &mpctx->opts, &format);
if (!stream)
goto err_out;
@@ -3920,7 +3923,7 @@ static struct track *open_external_file(struct MPContext *mpctx, char *filename,
if (stream->type == filter) {
struct track *t = add_stream_track(mpctx, stream, false);
t->is_external = true;
- t->title = talloc_strdup(t, filename);
+ t->title = talloc_strdup(t, disp_filename);
t->external_filename = talloc_strdup(t, filename);
first = t;
}
@@ -3928,7 +3931,7 @@ static struct track *open_external_file(struct MPContext *mpctx, char *filename,
if (!first) {
free_demuxer(demuxer);
mp_msg(MSGT_CPLAYER, MSGL_WARN, "No streams added from file %s.\n",
- filename);
+ disp_filename);
goto err_out;
}
MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources, demuxer);
@@ -3936,7 +3939,7 @@ static struct track *open_external_file(struct MPContext *mpctx, char *filename,
err_out:
mp_msg(MSGT_CPLAYER, MSGL_ERR, "Can not open external file %s.\n",
- filename);
+ disp_filename);
return false;
}
@@ -3954,6 +3957,25 @@ struct track *mp_add_subtitles(struct MPContext *mpctx, char *filename, int noer
STREAM_SUB);
}
+static void open_subtitles_from_resolve(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = &mpctx->opts;
+ struct mp_resolve_result *res = mpctx->resolve_result;
+ if (!res)
+ return;
+ for (int n = 0; n < res->num_subs; n++) {
+ struct mp_resolve_sub *sub = res->subs[n];
+ char *s = talloc_strdup(NULL, sub->url);
+ if (!s)
+ s = talloc_asprintf(NULL, "memory://%s", sub->data);
+ struct track *t =
+ open_external_file(mpctx, s, opts->sub_demuxer_name, 0, STREAM_SUB);
+ talloc_free(s);
+ if (t)
+ t->lang = talloc_strdup(t, sub->lang);
+ }
+}
+
static void print_timeline(struct MPContext *mpctx)
{
if (mpctx->timeline) {
@@ -4005,7 +4027,7 @@ static void add_subtitle_fonts_from_sources(struct MPContext *mpctx)
static struct mp_resolve_result *resolve_url(const char *filename,
struct MPOpts *opts)
{
-#ifdef CONFIG_LIBQUVI
+#if defined(CONFIG_LIBQUVI) || defined(CONFIG_LIBQUVI9)
return mp_resolve_quvi(filename, opts);
#else
return NULL;
@@ -4138,8 +4160,17 @@ static void play_current_file(struct MPContext *mpctx)
char *stream_filename = mpctx->filename;
mpctx->resolve_result = resolve_url(stream_filename, opts);
- if (mpctx->resolve_result)
+ if (mpctx->resolve_result) {
+ if (mpctx->resolve_result->playlist) {
+ // Replace entry with playlist contents
+ playlist_transfer_entries(mpctx->playlist,
+ mpctx->resolve_result->playlist);
+ if (mpctx->playlist->current)
+ playlist_remove(mpctx->playlist, mpctx->playlist->current);
+ goto terminate_playback;
+ }
stream_filename = mpctx->resolve_result->url;
+ }
int file_format = DEMUXER_TYPE_UNKNOWN;
mpctx->stream = open_stream(stream_filename, opts, &file_format);
if (!mpctx->stream) { // error...
@@ -4253,6 +4284,7 @@ goto_reopen_demuxer: ;
add_subtitle_fonts_from_sources(mpctx);
open_subtitles_from_options(mpctx);
+ open_subtitles_from_resolve(mpctx);
open_audiofiles_from_options(mpctx);
check_previous_track_selection(mpctx);
@@ -4366,6 +4398,12 @@ goto_reopen_demuxer: ;
queue_seek(mpctx, MPSEEK_ABSOLUTE, startpos, 0);
execute_queued_seek(mpctx);
}
+ if (startpos == -1 && mpctx->resolve_result &&
+ mpctx->resolve_result->start_time > 0)
+ {
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, mpctx->resolve_result->start_time, 0);
+ execute_queued_seek(mpctx);
+ }
if (opts->chapterrange[0] > 0) {
if (mp_seek_chapter(mpctx, opts->chapterrange[0] - 1))
execute_queued_seek(mpctx);
diff --git a/core/playlist.c b/core/playlist.c
index 5456931afa..7188392bc6 100644
--- a/core/playlist.c
+++ b/core/playlist.c
@@ -180,13 +180,19 @@ void playlist_add_base_path(struct playlist *pl, bstr base_path)
}
}
-// Move all entries from source_pl to pl, appending them at the end of pl.
-// source_pl will be empty, and all entries have changed ownership to pl.
+// Move all entries from source_pl to pl, appending them after the current entry
+// of pl. source_pl will be empty, and all entries have changed ownership to pl.
void playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl)
{
+ struct playlist_entry *add_after = pl->current;
+ if (pl->current && pl->current_was_replaced)
+ add_after = pl->current->next;
+ if (!add_after)
+ add_after = pl->last;
+
while (source_pl->first) {
struct playlist_entry *e = source_pl->first;
playlist_unlink(source_pl, e);
- playlist_add(pl, e);
+ playlist_insert(pl, add_after, e);
}
}
diff --git a/core/resolve.h b/core/resolve.h
index d991bf1a39..91684df250 100644
--- a/core/resolve.h
+++ b/core/resolve.h
@@ -25,6 +25,27 @@ struct MPOpts;
struct mp_resolve_result {
char *url;
char *title;
+
+ struct mp_resolve_src **srcs;
+ int num_srcs;
+
+ double start_time;
+
+ struct mp_resolve_sub **subs;
+ int num_subs;
+
+ struct playlist *playlist;
+};
+
+struct mp_resolve_src {
+ char *url;
+ char *encid; // indicates quality level, contents are libquvi specific
+};
+
+struct mp_resolve_sub {
+ char *url;
+ char *data;
+ char *lang;
};
struct mp_resolve_result *mp_resolve_quvi(const char *url, struct MPOpts *opts);
diff --git a/core/resolve_quvi9.c b/core/resolve_quvi9.c
new file mode 100644
index 0000000000..f6e6e8b94f
--- /dev/null
+++ b/core/resolve_quvi9.c
@@ -0,0 +1,150 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdbool.h>
+#include <assert.h>
+
+#include <quvi.h>
+
+#include "talloc.h"
+#include "core/mp_msg.h"
+#include "core/options.h"
+#include "core/playlist.h"
+#include "resolve.h"
+
+static bool mp_quvi_ok(quvi_t q)
+{
+ if (!quvi_ok(q)) {
+ mp_msg(MSGT_OPEN, MSGL_ERR, "[quvi] %s\n", quvi_errmsg(q));
+ return false;
+ }
+ return true;
+}
+
+struct mp_resolve_result *mp_resolve_quvi(const char *url, struct MPOpts *opts)
+{
+ int mode = QUVI_SUPPORTS_MODE_OFFLINE;
+
+ quvi_t q = quvi_new();
+ if (!quvi_ok(q)) {
+ mp_msg(MSGT_OPEN, MSGL_ERR, "[quvi] %s\n", quvi_errmsg(q));
+
+ quvi_free(q);
+ return NULL;
+ }
+
+ struct mp_resolve_result *res = talloc_zero(NULL, struct mp_resolve_result);
+
+ if (quvi_supports(q, url, mode, QUVI_SUPPORTS_TYPE_PLAYLIST)) {
+ mp_msg(MSGT_OPEN, MSGL_INFO, "[quvi] Checking playlist...\n");
+ quvi_playlist_t qp = quvi_playlist_new(q, url);
+ if (mp_quvi_ok(q)) {
+ res->playlist = talloc_zero(res, struct playlist);
+ while (quvi_playlist_media_next(qp)) {
+ char *entry = NULL;
+ quvi_playlist_get(qp, QUVI_PLAYLIST_MEDIA_PROPERTY_URL, &entry);
+ if (entry)
+ playlist_add_file(res->playlist, entry);
+ }
+ }
+ quvi_playlist_free(qp);
+ }
+
+ if (quvi_supports(q, url, mode, QUVI_SUPPORTS_TYPE_MEDIA)) {
+ mp_msg(MSGT_OPEN, MSGL_INFO, "[quvi] Checking URL...\n");
+ quvi_media_t media = quvi_media_new(q, url);
+ if (mp_quvi_ok(q)) {
+ char *format = opts->quvi_format ? opts->quvi_format : "best";
+ bool use_default = strcmp(format, "default") == 0;
+ if (!use_default)
+ quvi_media_stream_select(media, format);
+
+ char *val = NULL;
+ quvi_media_get(media, QUVI_MEDIA_STREAM_PROPERTY_URL, &val);
+ res->url = talloc_strdup(res, val);
+
+ val = NULL;
+ quvi_media_get(media, QUVI_MEDIA_PROPERTY_TITLE, &val);
+ res->title = talloc_strdup(res, val);
+
+ double start = 0;
+ quvi_media_get(media, QUVI_MEDIA_PROPERTY_START_TIME_MS, &start);
+ res->start_time = start / 1000.0;
+
+ quvi_media_stream_reset(media);
+ while (quvi_media_stream_next(media)) {
+ char *entry = NULL, *id = NULL;
+ quvi_media_get(media, QUVI_MEDIA_STREAM_PROPERTY_URL, &entry);
+ quvi_media_get(media, QUVI_MEDIA_STREAM_PROPERTY_ID, &id);
+ if (entry) {
+ struct mp_resolve_src *src = talloc_ptrtype(res, src);
+ *src = (struct mp_resolve_src) {
+ .url = talloc_strdup(src, entry),
+ .encid = talloc_strdup(src, id),
+ };
+ MP_TARRAY_APPEND(res, res->srcs, res->num_srcs, src);
+ talloc_steal(res->srcs, src);
+ }
+ }
+
+ }
+ quvi_media_free(media);
+ }
+
+ if (quvi_supports(q, url, mode, QUVI_SUPPORTS_TYPE_SUBTITLE)) {
+ mp_msg(MSGT_OPEN, MSGL_INFO, "[quvi] Getting subtitles...\n");
+ quvi_subtitle_t qsub = quvi_subtitle_new(q, url);
+ if (mp_quvi_ok(q)) {
+ while (1) {
+ quvi_subtitle_type_t qst = quvi_subtitle_type_next(qsub);
+ if (!qst)
+ break;
+ while (1) {
+ quvi_subtitle_lang_t qsl = quvi_subtitle_lang_next(qst);
+ if (!qsl)
+ break;
+ char *lang;
+ quvi_subtitle_lang_get(qsl, QUVI_SUBTITLE_LANG_PROPERTY_ID,
+ &lang);
+ // Let quvi convert the subtitle to SRT.
+ quvi_subtitle_export_t qse =
+ quvi_subtitle_export_new(qsl, "srt");
+ if (mp_quvi_ok(q)) {
+ const char *subdata = quvi_subtitle_export_data(qse);
+ struct mp_resolve_sub *sub = talloc_ptrtype(res, sub);
+ *sub = (struct mp_resolve_sub) {
+ .lang = talloc_strdup(sub, lang),
+ .data = talloc_strdup(sub, subdata),
+ };
+ MP_TARRAY_APPEND(res, res->subs, res->num_subs, sub);
+ talloc_steal(res->subs, sub);
+ }
+ quvi_subtitle_export_free(qse);
+ }
+ }
+ }
+ quvi_subtitle_free(qsub);
+ }
+
+ quvi_free(q);
+
+ if (!res->url && (!res->playlist || !res->playlist->first)) {
+ talloc_free(res);
+ res = NULL;
+ }
+ return res;
+}