1
0
mirror of https://github.com/mpv-player/mpv synced 2025-04-19 21:56:40 +00:00

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.
This commit is contained in:
wm4 2013-06-27 18:21:07 +02:00
parent ac79eb7337
commit 5f664d78e6
10 changed files with 330 additions and 10 deletions

View File

@ -399,6 +399,7 @@ tv-hue x
track-list list of audio/video/sub tracks, cur. entr. marked track-list list of audio/video/sub tracks, cur. entr. marked
chapter-list list of chapters, current entry marked chapter-list list of chapters, current entry marked
playlist playlist, current entry marked playlist playlist, current entry marked
quvi-format x see ``--quvi-format``
=========================== = ================================================== =========================== = ==================================================
.. _property_expansion: .. _property_expansion:

View File

@ -1567,12 +1567,25 @@
Video format/quality that is directly passed to libquvi (default: ``best``). Video format/quality that is directly passed to libquvi (default: ``best``).
This is used when opening links to streaming sites like YouTube. The This is used when opening links to streaming sites like YouTube. The
interpretation of this value is highly specific to the streaming site and 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). (best quality/highest bandwidth, default), and ``default`` (lowest quality).
The quvi command line tool can be used to find out which formats are The quvi command line tool can be used to find out which formats are
supported for a given URL: ``quvi --query-formats URL``. 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:...> --radio=<option1:option2:...>
These options set various parameters of the radio capture module. For These options set various parameters of the radio capture module. For
listening to radio with mpv use ``radio://<frequency>`` (if channels listening to radio with mpv use ``radio://<frequency>`` (if channels

View File

@ -108,6 +108,7 @@ SOURCES-$(GL_WAYLAND) += video/out/wayland_common.c \
SOURCES-$(JACK) += audio/out/ao_jack.c SOURCES-$(JACK) += audio/out/ao_jack.c
SOURCES-$(JOYSTICK) += core/input/joystick.c SOURCES-$(JOYSTICK) += core/input/joystick.c
SOURCES-$(LIBQUVI) += core/resolve_quvi.c SOURCES-$(LIBQUVI) += core/resolve_quvi.c
SOURCES-$(LIBQUVI9) += core/resolve_quvi9.c
SOURCES-$(LIRC) += core/input/lirc.c SOURCES-$(LIRC) += core/input/lirc.c
SOURCES-$(OPENAL) += audio/out/ao_openal.c SOURCES-$(OPENAL) += audio/out/ao_openal.c
SOURCES-$(OSS) += audio/out/ao_oss.c SOURCES-$(OSS) += audio/out/ao_oss.c

25
configure vendored
View File

@ -311,6 +311,7 @@ Optional features:
--enable-winsock2_h enable winsock2_h [autodetect] --enable-winsock2_h enable winsock2_h [autodetect]
--enable-smb enable Samba (SMB) input [autodetect] --enable-smb enable Samba (SMB) input [autodetect]
--enable-libquvi enable libquvi [autodetect] --enable-libquvi enable libquvi [autodetect]
--disable-libquvi9 disable libquvi 0.9.x [autodetect]
--enable-lcms2 enable LCMS2 support [autodetect] --enable-lcms2 enable LCMS2 support [autodetect]
--disable-vcd disable VCD support [autodetect] --disable-vcd disable VCD support [autodetect]
--disable-bluray disable Blu-ray support [autodetect] --disable-bluray disable Blu-ray support [autodetect]
@ -466,6 +467,7 @@ networking=yes
_winsock2_h=auto _winsock2_h=auto
_smb=auto _smb=auto
_libquvi=auto _libquvi=auto
_libquvi9=auto
_libguess=auto _libguess=auto
_joystick=no _joystick=no
_lirc=auto _lirc=auto
@ -668,6 +670,8 @@ for ac_option do
--disable-smb) _smb=no ;; --disable-smb) _smb=no ;;
--enable-libquvi) _libquvi=yes ;; --enable-libquvi) _libquvi=yes ;;
--disable-libquvi) _libquvi=no ;; --disable-libquvi) _libquvi=no ;;
--enable-libquvi9) _libquvi9=yes ;;
--disable-libquvi9) _libquvi9=no ;;
--enable-libguess) _libguess=yes ;; --enable-libguess) _libguess=yes ;;
--disable-libguess) _libguess=no ;; --disable-libguess) _libguess=no ;;
--enable-joystick) _joystick=yes ;; --enable-joystick) _joystick=yes ;;
@ -1727,7 +1731,26 @@ else
fi fi
echores "$_smb" 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" echocheck "libquvi support"
if test "$_libquvi9" = yes ; then
_libquvi=no
res_comment="using libquvi 0.9.x"
fi
if test "$_libquvi" = auto ; then if test "$_libquvi" = auto ; then
_libquvi=no _libquvi=no
if pkg_config_add 'libquvi >= 0.4.1' ; then if pkg_config_add 'libquvi >= 0.4.1' ; then
@ -3190,6 +3213,7 @@ VF_LAVFI = $vf_lavfi
AF_LAVFI = $af_lavfi AF_LAVFI = $af_lavfi
LIBSMBCLIENT = $_smb LIBSMBCLIENT = $_smb
LIBQUVI = $_libquvi LIBQUVI = $_libquvi
LIBQUVI9 = $_libquvi9
LIBGUESS = $_libguess LIBGUESS = $_libguess
LIBTHEORA = $_theora LIBTHEORA = $_theora
LIRC = $_lirc LIRC = $_lirc
@ -3389,6 +3413,7 @@ $def_inet_pton
$def_networking $def_networking
$def_smb $def_smb
$def_libquvi $def_libquvi
$def_libquvi9
$def_libguess $def_libguess
$def_socklen_t $def_socklen_t
$def_vstream $def_vstream

View File

@ -24,6 +24,9 @@
#include <assert.h> #include <assert.h>
#include <time.h> #include <time.h>
#include <libavutil/avstring.h>
#include <libavutil/common.h>
#include "config.h" #include "config.h"
#include "talloc.h" #include "talloc.h"
#include "command.h" #include "command.h"
@ -67,7 +70,6 @@
#include "core/mp_core.h" #include "core/mp_core.h"
#include "mp_fifo.h" #include "mp_fifo.h"
#include "libavutil/avstring.h"
static void change_video_filters(MPContext *mpctx, const char *cmd, static void change_video_filters(MPContext *mpctx, const char *cmd,
const char *arg); 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; 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 /// Number of titles in file
static int mp_property_titles(m_option_t *prop, int action, void *arg, static int mp_property_titles(m_option_t *prop, int action, void *arg,
MPContext *mpctx) MPContext *mpctx)
@ -1523,6 +1584,7 @@ static const m_option_t mp_properties[] = {
{ "chapter", mp_property_chapter, CONF_TYPE_INT, { "chapter", mp_property_chapter, CONF_TYPE_INT,
M_OPT_MIN, 0, 0, NULL }, M_OPT_MIN, 0, 0, NULL },
M_OPTION_PROPERTY_CUSTOM("edition", mp_property_edition), 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, { "titles", mp_property_titles, CONF_TYPE_INT,
0, 0, 0, NULL }, 0, 0, 0, NULL },
{ "chapters", mp_property_chapters, CONF_TYPE_INT, { "chapters", mp_property_chapters, CONF_TYPE_INT,

View File

@ -3890,6 +3890,9 @@ static struct track *open_external_file(struct MPContext *mpctx, char *filename,
if (!filename) if (!filename)
return NULL; return NULL;
int format = 0; 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); struct stream *stream = open_stream(filename, &mpctx->opts, &format);
if (!stream) if (!stream)
goto err_out; goto err_out;
@ -3920,7 +3923,7 @@ static struct track *open_external_file(struct MPContext *mpctx, char *filename,
if (stream->type == filter) { if (stream->type == filter) {
struct track *t = add_stream_track(mpctx, stream, false); struct track *t = add_stream_track(mpctx, stream, false);
t->is_external = true; t->is_external = true;
t->title = talloc_strdup(t, filename); t->title = talloc_strdup(t, disp_filename);
t->external_filename = talloc_strdup(t, filename); t->external_filename = talloc_strdup(t, filename);
first = t; first = t;
} }
@ -3928,7 +3931,7 @@ static struct track *open_external_file(struct MPContext *mpctx, char *filename,
if (!first) { if (!first) {
free_demuxer(demuxer); free_demuxer(demuxer);
mp_msg(MSGT_CPLAYER, MSGL_WARN, "No streams added from file %s.\n", mp_msg(MSGT_CPLAYER, MSGL_WARN, "No streams added from file %s.\n",
filename); disp_filename);
goto err_out; goto err_out;
} }
MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources, demuxer); 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: err_out:
mp_msg(MSGT_CPLAYER, MSGL_ERR, "Can not open external file %s.\n", mp_msg(MSGT_CPLAYER, MSGL_ERR, "Can not open external file %s.\n",
filename); disp_filename);
return false; return false;
} }
@ -3954,6 +3957,25 @@ struct track *mp_add_subtitles(struct MPContext *mpctx, char *filename, int noer
STREAM_SUB); 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) static void print_timeline(struct MPContext *mpctx)
{ {
if (mpctx->timeline) { 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, static struct mp_resolve_result *resolve_url(const char *filename,
struct MPOpts *opts) struct MPOpts *opts)
{ {
#ifdef CONFIG_LIBQUVI #if defined(CONFIG_LIBQUVI) || defined(CONFIG_LIBQUVI9)
return mp_resolve_quvi(filename, opts); return mp_resolve_quvi(filename, opts);
#else #else
return NULL; return NULL;
@ -4138,8 +4160,17 @@ static void play_current_file(struct MPContext *mpctx)
char *stream_filename = mpctx->filename; char *stream_filename = mpctx->filename;
mpctx->resolve_result = resolve_url(stream_filename, opts); 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; stream_filename = mpctx->resolve_result->url;
}
int file_format = DEMUXER_TYPE_UNKNOWN; int file_format = DEMUXER_TYPE_UNKNOWN;
mpctx->stream = open_stream(stream_filename, opts, &file_format); mpctx->stream = open_stream(stream_filename, opts, &file_format);
if (!mpctx->stream) { // error... if (!mpctx->stream) { // error...
@ -4253,6 +4284,7 @@ goto_reopen_demuxer: ;
add_subtitle_fonts_from_sources(mpctx); add_subtitle_fonts_from_sources(mpctx);
open_subtitles_from_options(mpctx); open_subtitles_from_options(mpctx);
open_subtitles_from_resolve(mpctx);
open_audiofiles_from_options(mpctx); open_audiofiles_from_options(mpctx);
check_previous_track_selection(mpctx); check_previous_track_selection(mpctx);
@ -4366,6 +4398,12 @@ goto_reopen_demuxer: ;
queue_seek(mpctx, MPSEEK_ABSOLUTE, startpos, 0); queue_seek(mpctx, MPSEEK_ABSOLUTE, startpos, 0);
execute_queued_seek(mpctx); 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 (opts->chapterrange[0] > 0) {
if (mp_seek_chapter(mpctx, opts->chapterrange[0] - 1)) if (mp_seek_chapter(mpctx, opts->chapterrange[0] - 1))
execute_queued_seek(mpctx); execute_queued_seek(mpctx);

View File

@ -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. // Move all entries from source_pl to pl, appending them after the current entry
// source_pl will be empty, and all entries have changed ownership to pl. // 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) 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) { while (source_pl->first) {
struct playlist_entry *e = source_pl->first; struct playlist_entry *e = source_pl->first;
playlist_unlink(source_pl, e); playlist_unlink(source_pl, e);
playlist_add(pl, e); playlist_insert(pl, add_after, e);
} }
} }

View File

@ -25,6 +25,27 @@ struct MPOpts;
struct mp_resolve_result { struct mp_resolve_result {
char *url; char *url;
char *title; 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); struct mp_resolve_result *mp_resolve_quvi(const char *url, struct MPOpts *opts);

150
core/resolve_quvi9.c Normal file
View File

@ -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;
}

View File

@ -130,6 +130,9 @@ E cycle edition # next edition
A cycle angle A cycle angle
U stop U stop
l cycle quvi-format 1
L cycle quvi-format -1
# TV # TV
h tv_step_channel 1 h tv_step_channel 1
k tv_step_channel -1 k tv_step_channel -1