From 5f664d78e6e9bd5809dc7d0f12c4099e76582cb3 Mon Sep 17 00:00:00 2001 From: wm4 Date: Thu, 27 Jun 2013 18:21:07 +0200 Subject: 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. --- DOCS/man/en/input.rst | 1 + DOCS/man/en/options.rst | 15 ++++- Makefile | 1 + configure | 25 ++++++++ core/command.c | 64 ++++++++++++++++++++- core/mplayer.c | 48 ++++++++++++++-- core/playlist.c | 12 +++- core/resolve.h | 21 +++++++ core/resolve_quvi9.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++++ etc/input.conf | 3 + 10 files changed, 330 insertions(+), 10 deletions(-) create mode 100644 core/resolve_quvi9.c 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= These options set various parameters of the radio capture module. For listening to radio with mpv use ``radio://`` (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 #include +#include +#include + #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 . + */ + +#include +#include + +#include + +#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 -- cgit v1.2.3