1
mirror of https://code.videolan.org/videolan/vlc synced 2024-07-25 09:41:30 +02:00

ytdl: stream filter module for YoutubeDL

This passes every HTTP(S) URL through YoutubeDL to extract playlists
or media.
This commit is contained in:
Rémi Denis-Courmont 2020-09-20 21:46:41 +03:00
parent 3f5d4031d9
commit 4fa60bf98d
4 changed files with 281 additions and 1 deletions

1
NEWS
View File

@ -65,6 +65,7 @@ Access:
* Audio CD data tracks are now correctly detected and skipped
* Deprecates Audio CD CDDB lookups in favor of more accurate Musicbrainz
* Improved CD-TEXT and added Shift-JIS encoding support
* Support for YoutubeDL (where available).
Access output:
* Added support for the RIST (Reliable Internet Stream Transport) Protocol

View File

@ -505,6 +505,14 @@ libadaptive_plugin_la_LIBADD += $(GCRYPT_LIBS)
endif
demux_LTLIBRARIES += libadaptive_plugin.la
libytdl_plugin_la_SOURCES = demux/ytdl.c
libytdl_plugin_la_LIBADD = libvlc_json.la
if !HAVE_WIN32
if !HAVE_ANDROID
demux_LTLIBRARIES += libytdl_plugin.la
endif
endif
libnoseek_plugin_la_SOURCES = demux/filter/noseek.c
demux_LTLIBRARIES += libnoseek_plugin.la
@ -519,6 +527,6 @@ libvlc_json_la_SOURCES = \
demux/json/grammar.y \
demux/json/json.c demux/json/json.h
libvlc_json_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/demux/json
libvlc_json_la_LIBADD = $(LTLIBVLCCORE) ../compat/libcompat.la
libvlc_json_la_LIBADD = $(LTLIBVLCCORE) ../compat/libcompat.la $(LIBM)
libvlc_json_la_LDFLAGS = -static
noinst_LTLIBRARIES += libvlc_json.la

270
modules/demux/ytdl.c Normal file
View File

@ -0,0 +1,270 @@
/*****************************************************************************
* ytdl.c:
*****************************************************************************
* Copyright (C) 2019-2020 Rémi Denis-Courmont
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <errno.h>
#include <math.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include "json/json.h"
#include <vlc_common.h>
#include <vlc_stream.h>
#include <vlc_fs.h>
#include <vlc_input_item.h>
#include <vlc_plugin.h>
#include <vlc_spawn.h>
void json_parse_error(void *data, const char *msg)
{
struct vlc_logger *log = data;
vlc_error(log, "%s", msg);
}
static
FILE *vlc_popen(pid_t *restrict pid, const char *argv[])
{
int fds[2];
if (vlc_pipe(fds))
return NULL;
FILE *input = fdopen(fds[0], "rt");
if (input == NULL) {
vlc_close(fds[1]);
vlc_close(fds[0]);
return NULL;
}
int fdv[] = { -1, fds[1], 2, -1 };
int val = vlc_spawn(pid, argv[0], fdv, argv);
vlc_close(fds[1]);
if (val) {
fclose(input);
input = NULL;
errno = val;
}
return input;
}
struct ytdl_playlist {
struct json_object json;
};
static const struct json_object *PickFormat(stream_t *s,
const struct json_object *entry)
{
const struct json_value *fmts = json_get(entry, "formats");
if (fmts == NULL)
return entry; /* only one format */
if (fmts->type != JSON_ARRAY)
return NULL;
const struct json_object *best_fmt = NULL;
double pref_height = var_InheritInteger(s, "preferred-resolution");
double best_height = -1.;
double best_abr = -1.;
for (size_t i = 0; i < fmts->array.size; i++) {
const struct json_value *v = &fmts->array.entries[i];
if (v->type != JSON_OBJECT)
continue;
const struct json_object *fmt = &v->object;
double height = json_get_num(fmt, "height");
double abr = json_get_num(fmt, "abr");
if (!isgreaterequal(height, best_height)
|| (best_height < pref_height && pref_height < height))
continue;
if (!isgreaterequal(abr, best_abr))
continue;
best_fmt = fmt;
best_height = height;
best_abr = abr;
}
return best_fmt;
}
static int ReadItem(stream_t *s, input_item_node_t *node,
const struct json_object *json)
{
const struct json_object *fmt = PickFormat(s, json);
if (fmt == NULL)
return VLC_EGENERIC;
const char *url = json_get_str(fmt, "url");
if (url == NULL)
return VLC_EGENERIC;
const char *title = json_get_str(json, "title");
double duration = json_get_num(json, "duration");
vlc_tick_t ticks = isnan(duration) ? INPUT_DURATION_UNSET
: lround(duration * CLOCK_FREQ);
if (title == NULL)
title = url;
input_item_t *item = input_item_NewStream(url, title, ticks);
if (likely(item != NULL)) {
input_item_AddOption(item, "no-ytdl", 0);
input_item_node_AppendItem(node, item);
input_item_Release(item);
}
return VLC_SUCCESS;
}
static int ReadDir(stream_t *s, input_item_node_t *node)
{
struct ytdl_playlist *sys = s->p_sys;
const struct json_value *v = json_get(&sys->json, "entries");
if (v == NULL) /* Single item */
return ReadItem(s, node, &sys->json);
/* Playlist: parse each entry */
if (v->type != JSON_ARRAY)
return VLC_EGENERIC;
for (size_t i = 0; i < v->array.size; i++) {
const struct json_value *e = &v->array.entries[i];
if (e->type == JSON_OBJECT)
ReadItem(s, node, &e->object);
}
return VLC_SUCCESS;
}
static int Control(stream_t *s, int query, va_list args)
{
switch (query)
{
case STREAM_CAN_SEEK:
case STREAM_CAN_FASTSEEK:
case STREAM_CAN_PAUSE:
case STREAM_CAN_CONTROL_PACE:
*va_arg(args, bool *) = false;
break;
case STREAM_GET_TYPE:
*va_arg(args, int *) = ITEM_TYPE_PLAYLIST;
break;
case STREAM_GET_PTS_DELAY:
*va_arg(args, vlc_tick_t *) =
VLC_TICK_FROM_MS(var_InheritInteger(s, "network-caching"));
break;
default:
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
static void Close(vlc_object_t *obj)
{
stream_t *s = (stream_t *)obj;
struct ytdl_playlist *sys = s->p_sys;
json_free(&sys->json);
}
static int OpenFilter(vlc_object_t *obj)
{
stream_t *s = (stream_t *)obj;
if (s->psz_url == NULL)
return VLC_EGENERIC;
if (strncasecmp(s->psz_url, "http:", 5)
&& strncasecmp(s->psz_url, "https:", 6))
return VLC_EGENERIC;
if (!var_InheritBool(s, "ytdl"))
return VLC_EGENERIC;
struct ytdl_playlist *sys = vlc_obj_malloc(obj, sizeof (*sys));
if (unlikely(sys == NULL))
return VLC_EGENERIC;
char *path = config_GetSysPath(VLC_PKG_DATA_DIR, "ytdl-extract.py");
if (unlikely(path == NULL))
return VLC_EGENERIC;
pid_t pid;
const char *argv[] = { path, s->psz_url, NULL };
FILE *input = vlc_popen(&pid, argv);
if (input == NULL) {
msg_Dbg(obj, "cannot start %s: %s", path, vlc_strerror_c(errno));
free(path);
return VLC_EGENERIC;
}
free(path);
int val = json_parse(obj->logger, input, &sys->json);
kill(pid, SIGTERM);
fclose(input);
vlc_waitpid(pid);
if (val) {
/* Location not handled */
msg_Dbg(s, "cannot extract infos");
return VLC_EGENERIC;
}
s->p_sys = sys;
s->pf_readdir = ReadDir;
s->pf_control = Control;
return VLC_SUCCESS;
}
vlc_module_begin()
set_shortname("YT-DL")
set_description("YoutubeDL")
set_category(CAT_INPUT)
set_subcategory(SUBCAT_INPUT_STREAM_FILTER)
set_capability("stream_filter", 305)
set_callbacks(OpenFilter, Close)
/* TODO: convert to demux and enable by default */
add_bool("ytdl", false, N_("Enable YT-DL"), N_("Enable YT-DL"), true)
change_safe()
vlc_module_end()

View File

@ -435,6 +435,7 @@ modules/demux/xa.c
modules/demux/xiph.h
modules/demux/xiph_metadata.c
modules/demux/xiph_metadata.h
modules/demux/ytdl.c
modules/gui/macosx/coreinteraction/VLCHotkeysController.h
modules/gui/macosx/coreinteraction/VLCHotkeysController.m
modules/gui/macosx/coreinteraction/VLCVideoFilterHelper.h