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
chapter-list list of chapters, current entry marked
playlist playlist, current entry marked
quvi-format x see ``--quvi-format``
=========================== = ==================================================
.. _property_expansion:

View File

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

View File

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

25
configure vendored
View File

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

View File

@ -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,

View File

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

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.
// 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);
}
}

View File

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

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
U stop
l cycle quvi-format 1
L cycle quvi-format -1
# TV
h tv_step_channel 1
k tv_step_channel -1