[vlc-commits] ytdl: stream filter module for YoutubeDL
Rémi Denis-Courmont
git at videolan.org
Sun Sep 27 15:16:40 CEST 2020
vlc | branch: master | Rémi Denis-Courmont <remi at remlab.net> | Sun Sep 20 21:46:41 2020 +0300| [4fa60bf98d9697c09a412c35a38a4a460044fa2a] | committer: Rémi Denis-Courmont
ytdl: stream filter module for YoutubeDL
This passes every HTTP(S) URL through YoutubeDL to extract playlists
or media.
> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=4fa60bf98d9697c09a412c35a38a4a460044fa2a
---
NEWS | 1 +
modules/demux/Makefile.am | 10 +-
modules/demux/ytdl.c | 270 ++++++++++++++++++++++++++++++++++++++++++++++
po/POTFILES.in | 1 +
4 files changed, 281 insertions(+), 1 deletion(-)
diff --git a/NEWS b/NEWS
index 9af6a77b8f..7eb1a72567 100644
--- a/NEWS
+++ b/NEWS
@@ -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
diff --git a/modules/demux/Makefile.am b/modules/demux/Makefile.am
index d7b8df6ece..88075b06a0 100644
--- a/modules/demux/Makefile.am
+++ b/modules/demux/Makefile.am
@@ -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
diff --git a/modules/demux/ytdl.c b/modules/demux/ytdl.c
new file mode 100644
index 0000000000..d0066eda24
--- /dev/null
+++ b/modules/demux/ytdl.c
@@ -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()
diff --git a/po/POTFILES.in b/po/POTFILES.in
index d5ce933408..0069a2b6db 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -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
More information about the vlc-commits
mailing list