[vlc-devel] [PATCH 4/4] ytdl: avoid intermediate playlist for single media

RĂ©mi Denis-Courmont remi at remlab.net
Wed Sep 23 17:43:09 CEST 2020


Rather than exposing a playlist with a single item, this wraps the
media at the found URL and expose it as a regular byte stream. This
allows the actual media URL to be resolved and possibly refreshed
everytime.
---
 modules/stream_filter/ytdl.c | 114 ++++++++++++++++++++++++++++++-----
 share/ytdl-extract.py        |  22 +++----
 2 files changed, 109 insertions(+), 27 deletions(-)

diff --git a/modules/stream_filter/ytdl.c b/modules/stream_filter/ytdl.c
index 10af6c23f7..f13b5d13fd 100644
--- a/modules/stream_filter/ytdl.c
+++ b/modules/stream_filter/ytdl.c
@@ -37,9 +37,10 @@
 
 
 struct ytdl_playlist {
+    void (*close)(stream_t *s);
     pid_t pid;
     int fd;
-    int first_byte;
+    bool first_byte;
 };
 
 static size_t readall(int fd, void *buf, size_t len)
@@ -70,11 +71,11 @@ static ssize_t Read(stream_t *s, void *buf, size_t len)
 {
     struct ytdl_playlist *sys = s->p_sys;
 
-    if (unlikely(sys->first_byte != EOF)) {
+    if (unlikely(sys->first_byte)) {
         /* Relay the first byte that was dequeued while probing. */
         if (likely(len > 0)) {
-            *(unsigned char *)buf = sys->first_byte;
-            sys->first_byte = EOF;
+            *(unsigned char *)buf = '#';
+            sys->first_byte = false;
         }
 
         return len > 0;
@@ -121,9 +122,8 @@ static int Control(stream_t *s, int query, va_list args)
     return VLC_SUCCESS;
 }
 
-static void Close(vlc_object_t *obj)
+static void ClosePlaylist(stream_t *s)
 {
-    stream_t *s = (stream_t *)obj;
     struct ytdl_playlist *sys = s->p_sys;
 
     msg_Dbg(s, "terminating PID %u", (unsigned)sys->pid);
@@ -132,12 +132,63 @@ static void Close(vlc_object_t *obj)
     vlc_waitpid(sys->pid);
 }
 
+struct ytdl_stream {
+    void (*close)(stream_t *s);
+    stream_t *source;
+};
+
+static ssize_t ReadNested(stream_t *s, void *buf, size_t len)
+{
+    struct ytdl_stream *sys = s->p_sys;
+
+    return vlc_stream_ReadPartial(sys->source, buf, len);
+}
+
+static int SeekNested(stream_t *s, uint64_t offset)
+{
+    struct ytdl_stream *sys = s->p_sys;
+
+    return vlc_stream_Seek(sys->source, offset);
+}
+
+static int ControlNested(stream_t *s, int query, va_list args)
+{
+    struct ytdl_stream *sys = s->p_sys;
+
+    return vlc_stream_vaControl(sys->source, query, args);
+}
+
+static void CloseNested(stream_t *s)
+{
+    struct ytdl_stream *sys = s->p_sys;
+
+    vlc_stream_Delete(sys->source);
+}
+
+/* Must be struct to ensure equal pointer representation. */
+struct ytdl_common {
+    union {
+        void (*close)(stream_t *s);
+        struct ytdl_playlist playlist;
+        struct ytdl_stream stream;
+    };
+};
+
+/* TODO: remove this and union whence vlc_stream_operations comes to exist. */
+static void Close(vlc_object_t *obj)
+{
+    stream_t *s = (stream_t *)obj;
+    struct ytdl_common *sys = s->p_sys;
+
+    return sys->close(s);
+}
+
 static int OpenCommon(vlc_object_t *obj)
 {
     stream_t *s = (stream_t *)obj;
     ssize_t val;
 
-    struct ytdl_playlist *sys = vlc_obj_malloc(obj, sizeof (*sys));
+    struct ytdl_common *sys = vlc_obj_malloc(obj, sizeof (*sys));
     if (unlikely(sys == NULL))
         return VLC_EGENERIC;
 
@@ -153,12 +204,12 @@ static int OpenCommon(vlc_object_t *obj)
     }
 
     s->p_sys = sys;
-    sys->fd = fds[0];
+    sys->playlist.fd = fds[0];
 
     int fdv[] = { -1, fds[1], 2, -1 };
     const char *argv[] = { path, s->psz_url, NULL };
 
-    val = vlc_spawn(&sys->pid, path, fdv, argv);
+    val = vlc_spawn(&sys->playlist.pid, path, fdv, argv);
     vlc_close(fds[1]);
 
     if (val) {
@@ -170,17 +221,48 @@ static int OpenCommon(vlc_object_t *obj)
 
     free(path);
 
-    unsigned char first_byte;
-    if (readall(sys->fd, &first_byte, 1) <= 0) {
+    char first_byte;
+
+    if (readall(sys->playlist.fd, &first_byte, 1) <= 0) {
         /* Location not handled */
         msg_Dbg(s, "cannot extract infos");
-        Close(obj);
+        ClosePlaylist(s);
+        return VLC_EGENERIC;
+    }
+
+    if (first_byte == '#') {
+        /* Playlist */
+        msg_Dbg(s, "extracting playlist");
+        s->pf_read = Read;
+        s->pf_control = Control;
+        sys->close = ClosePlaylist;
+        sys->playlist.first_byte = true;
+        return VLC_SUCCESS;
+    }
+
+    /* Redirect if there is a single URL, so that we can refresh it every
+     * time it is opened.
+     */
+    char url[65536];
+
+    url[0] = first_byte;
+    val = readall(sys->playlist.fd, url + 1, sizeof (url) - 2);
+    url[val] = '\0';
+
+    ClosePlaylist(s);
+    var_Create(obj, "ytdl", VLC_VAR_BOOL);
+    sys->stream.source = vlc_stream_NewURL(obj, url);
+
+    if (sys->stream.source == NULL) {
+        msg_Err(s, "cannot open URL: %s", url);
         return VLC_EGENERIC;
     }
 
-    sys->first_byte = first_byte;
-    s->pf_read = Read;
-    s->pf_control = Control;
+    msg_Dbg(s, "redirecting to: %s", url);
+    s->pf_read = ReadNested;
+    s->pf_seek = SeekNested;
+    s->pf_control = ControlNested;
+    sys->close = CloseNested;
     return VLC_SUCCESS;
 }
 
@@ -193,7 +275,7 @@ static int OpenFilter(vlc_object_t *obj)
     if (strncasecmp(s->psz_url, "http:", 5)
      && strncasecmp(s->psz_url, "https:", 6))
         return VLC_EGENERIC;
-    if (!var_InheritBool(obj, "ytdl"))
+    if (!var_InheritBool(s->s, "ytdl"))
         return VLC_EGENERIC;
 
     return OpenCommon(obj);
diff --git a/share/ytdl-extract.py b/share/ytdl-extract.py
index f166384b60..ce1e9ba355 100755
--- a/share/ytdl-extract.py
+++ b/share/ytdl-extract.py
@@ -59,14 +59,14 @@ def formats_choose_best(fmts):
 
     return best_format
 
-def entry_extract(entry):
+def entry_extract(entry, meta=True):
     # Process a given entry of a playlist
     if 'formats' in entry:
         fmt = formats_choose_best(entry['formats'])
     else:
         fmt = entry
 
-    if 'title' in entry:
+    if meta and 'title' in entry:
         print('#EXTINF:,,' + entry['title'].splitlines()[0])
 
     if 'ie_key' in fmt and fmt['ie_key']:
@@ -75,7 +75,8 @@ def entry_extract(entry):
         return
 
     if 'url' in fmt:
-        print('#EXTVLCOPT:no-ytdl') # don't parse recursively
+        if meta:
+            print('#EXTVLCOPT:no-ytdl') # don't parse recursively
         print(fmt['url'])
     else:
         print('vlc://nop')
@@ -92,18 +93,18 @@ def url_extract(url):
     # Process a given URL
     infos = dl.extract_info(url, download=False)
 
-    print('#EXTM3U')
-
-    if 'title' in infos:
-        print('#PLAYLIST:' + infos['title'].splitlines()[0])
-
     if 'entries' in infos:
         # URL is a playlist: iterate over entries
+        print('#EXTM3U')
+
+        if 'title' in infos:
+            print('#PLAYLIST:' + infos['title'].splitlines()[0])
+
         for entry in infos['entries']:
             entry_extract(entry)
     else:
         # URL is a single media
-        entry_extract(infos)
+        entry_extract(infos, meta=False)
 
 def url_process(ie_url):
     opts = {
@@ -120,8 +121,7 @@ def url_process(ie_url):
         entry[p[0]] = p[1]
 
     entry = dl.process_ie_result(entry, download=False)
-    print('#EXTM3U')
-    entry_extract(entry)
+    entry_extract(entry, meta=False)
 
 url = sys.argv[1]
 
-- 
2.28.0



More information about the vlc-devel mailing list