summaryrefslogtreecommitdiffstats
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
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.
-rw-r--r--DOCS/man/en/input.rst1
-rw-r--r--DOCS/man/en/options.rst15
-rw-r--r--Makefile1
-rwxr-xr-xconfigure25
-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
-rw-r--r--etc/input.conf3
10 files changed, 330 insertions, 10 deletions
diff --git a/DOCS/man/en/input.rst b/DOCS/man/en/input.rst
index bf6ec4437a..bc20a5c42e 100644
--- a/DOCS/man/en/input.rst
+++ b/DOCS/man/en/input.rst
@@ -399,6 +399,7 @@ tv-hue x
track-list list of audio/video/sub tracks, cur. entr. marked
chapter-list list of chapters, current entry marked
playlist playlist, current entry marked
+quvi-format x see ``--quvi-format``
=========================== = ==================================================
.. _property_expansion:
diff --git a/DOCS/man/en/options.rst b/DOCS/man/en/options.rst
index 690073e18b..8a5a9baa44 100644
--- a/DOCS/man/en/options.rst
+++ b/DOCS/man/en/options.rst
@@ -1567,12 +1567,25 @@
Video format/quality that is directly passed to libquvi (default: ``best``).
This is used when opening links to streaming sites like YouTube. The
interpretation of this value is highly specific to the streaming site and
- the video. The only well defined values that work on all sites are ``best``
+ the video.
+
+ libquvi 0.4.x:
+
+ The only well defined values that work on all sites are ``best``
(best quality/highest bandwidth, default), and ``default`` (lowest quality).
The quvi command line tool can be used to find out which formats are
supported for a given URL: ``quvi --query-formats URL``.
+ libquvi 0.9.x:
+
+ The following explanations are relevant:
+ ``http://quvi.sourceforge.net/doc/0.9/glossary_termino.html#m_stream_id``
+
+ With 0.9.x, the ``quvi-format`` property can be used at runtime to cycle
+ through the list of formats. Unfortunately, this resets the playback
+ position and is slow too.
+
--radio=<option1:option2:...>
These options set various parameters of the radio capture module. For
listening to radio with mpv use ``radio://<frequency>`` (if channels
diff --git a/Makefile b/Makefile
index 151ddf9226..2a912182d7 100644
--- a/Makefile
+++ b/Makefile
@@ -108,6 +108,7 @@ SOURCES-$(GL_WAYLAND) += video/out/wayland_common.c \
SOURCES-$(JACK) += audio/out/ao_jack.c
SOURCES-$(JOYSTICK) += core/input/joystick.c
SOURCES-$(LIBQUVI) += core/resolve_quvi.c
+SOURCES-$(LIBQUVI9) += core/resolve_quvi9.c
SOURCES-$(LIRC) += core/input/lirc.c
SOURCES-$(OPENAL) += audio/out/ao_openal.c
SOURCES-$(OSS) += audio/out/ao_oss.c
diff --git a/configure b/configure
index 0fcaf5821d..f325af284c 100755
--- a/configure
+++ b/configure
@@ -311,6 +311,7 @@ Optional features:
--enable-winsock2_h enable winsock2_h [autodetect]
--enable-smb enable Samba (SMB) input [autodetect]
--enable-libquvi enable libquvi [autodetect]
+ --disable-libquvi9 disable libquvi 0.9.x [autodetect]
--enable-lcms2 enable LCMS2 support [autodetect]
--disable-vcd disable VCD support [autodetect]
--disable-bluray disable Blu-ray support [autodetect]
@@ -466,6 +467,7 @@ networking=yes
_winsock2_h=auto
_smb=auto
_libquvi=auto
+_libquvi9=auto
_libguess=auto
_joystick=no
_lirc=auto
@@ -668,6 +670,8 @@ for ac_option do
--disable-smb) _smb=no ;;
--enable-libquvi) _libquvi=yes ;;
--disable-libquvi) _libquvi=no ;;
+ --enable-libquvi9) _libquvi9=yes ;;
+ --disable-libquvi9) _libquvi9=no ;;
--enable-libguess) _libguess=yes ;;
--disable-libguess) _libguess=no ;;
--enable-joystick) _joystick=yes ;;
@@ -1727,7 +1731,26 @@ else
fi
echores "$_smb"
+
+echocheck "libquvi 0.9.0 support"
+if test "$_libquvi9" = auto ; then
+ _libquvi9=no
+ if pkg_config_add 'libquvi-0.9 >= 0.9.0' ; then
+ _libquvi9=yes
+ fi
+fi
+if test "$_libquvi9" = yes; then
+ def_libquvi9="#define CONFIG_LIBQUVI9 1"
+else
+ def_libquvi9="#undef CONFIG_LIBQUVI9"
+fi
+echores "$_libquvi9"
+
echocheck "libquvi support"
+if test "$_libquvi9" = yes ; then
+ _libquvi=no
+ res_comment="using libquvi 0.9.x"
+fi
if test "$_libquvi" = auto ; then
_libquvi=no
if pkg_config_add 'libquvi >= 0.4.1' ; then
@@ -3190,6 +3213,7 @@ VF_LAVFI = $vf_lavfi
AF_LAVFI = $af_lavfi
LIBSMBCLIENT = $_smb
LIBQUVI = $_libquvi
+LIBQUVI9 = $_libquvi9
LIBGUESS = $_libguess
LIBTHEORA = $_theora
LIRC = $_lirc
@@ -3389,6 +3413,7 @@ $def_inet_pton
$def_networking
$def_smb
$def_libquvi
+$def_libquvi9
$def_libguess
$def_socklen_t
$def_vstream
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;
+}
diff --git a/etc/input.conf b/etc/input.conf
index 3f9cea3c44..f6a5b2a078 100644
--- a/etc/input.conf
+++ b/etc/input.conf
@@ -130,6 +130,9 @@ E cycle edition # next edition
A cycle angle
U stop
+l cycle quvi-format 1
+L cycle quvi-format -1
+
# TV
h tv_step_channel 1
k tv_step_channel -1