]> git.0d.be Git - panikdb.git/commitdiff
regie: run youtube-dl from a management command
authorFrédéric Péters <fpeters@0d.be>
Wed, 5 Jan 2022 09:32:09 +0000 (10:32 +0100)
committerFrédéric Péters <fpeters@0d.be>
Wed, 5 Jan 2022 09:32:36 +0000 (10:32 +0100)
(to be launched by a cron job or a systemd path unit)

panikdb/regie/management/commands/ytdl.py [new file with mode: 0644]
panikdb/regie/templates/regie-home.html
panikdb/regie/views.py

diff --git a/panikdb/regie/management/commands/ytdl.py b/panikdb/regie/management/commands/ytdl.py
new file mode 100644 (file)
index 0000000..4e962f0
--- /dev/null
@@ -0,0 +1,56 @@
+import glob
+import json
+import os
+
+import youtube_dl
+from django.conf import settings
+from django.core.files.storage import default_storage
+from django.core.management.base import BaseCommand
+
+
+class Command(BaseCommand):
+    def handle(self, verbosity, **kwargs):
+        ytdl_path = default_storage.path('ytdl')
+        if not os.path.exists(ytdl_path):
+            os.mkdir(ytdl_path)
+        for filename in glob.glob(os.path.join(ytdl_path, '*.json')):
+            with open(filename) as fd:
+                data = json.load(fd)
+
+            url = data['_url']
+            if data.get('_status') == 'finished':
+                continue
+
+            def progress_hook(d):
+                data['_percent_str'] = d.get('_percent_str')
+                data['_status'] = d.get('status')
+                with open(filename, 'w') as fd:
+                    json.dump(data, fp=fd, indent=2)
+
+            ydl_opts = {
+                'quiet': True,
+                'noplaylist': True,
+                'format': 'bestaudio',
+                'outtmpl': os.path.join(ytdl_path, '%(id)s.%(ext)s'),
+                'postprocessors': [
+                    {
+                        'key': 'FFmpegExtractAudio',
+                        'preferredcodec': 'opus',
+                    }
+                ],
+                'progress_hooks': [progress_hook],
+            }
+            with youtube_dl.YoutubeDL(ydl_opts) as ydl:
+                ydl.download([url])
+
+            if data.get('_status') == 'finished':
+                base_filename = os.path.splitext(filename)[0]
+                real_filename = [
+                    x
+                    for x in glob.glob(base_filename + '*')
+                    if not x.endswith('.part') and not x.endswith('.json')
+                ][0]
+                url = os.path.join(settings.MEDIA_URL, 'ytdl', real_filename[len(ytdl_path) + 1 :])
+                data['_audio_url'] = url
+                with open(filename, 'w') as fd:
+                    json.dump(data, fp=fd, indent=2)
index d7dfbd39608faf38f5ebb0313e7d9e8587f328c4..e534341cfbcd17d9c854decc6e1dd11e1f2b56d1 100644 (file)
@@ -177,7 +177,7 @@ $(function() {
            return;
         }
         $('#add-track-form').parents('.ui-dialog').find('.ui-dialog-title').text('');
-        for (const track of data.data) {
+        for (var track of data.data) {
           var $line = $('<li></li>')
           var $add_button = $('<button>+</button>');
           $add_button.on('click', function() {
@@ -199,6 +199,29 @@ $(function() {
             $line.append(' ');
             $line.append($('<span></span>', {'class': 'type', 'text': '(' + track.duration + ')'}));
           }
+          if (track.url && ! track.ready) {
+            $line.append(' ');
+            $add_button.attr('disabled', 'disabled');
+            var $percent = $('<span></span>', {'class': 'percent'});
+            $line.append($percent);
+            // ytdl download in progress
+            function update_download() {
+              $.ajax(track.url).done(function(data) {
+                if (data._status == 'finished') {
+                  track.url = data._audio_url;
+                  $percent.hide();
+                  $add_button.attr('disabled', null);
+                  return;
+                }
+                percent_str = data._percent_str || '0%';
+                $percent.text(percent_str);
+                window.setTimeout(update_download, 1000);
+              }).fail(function(data) {
+                window.setTimeout(update_download, 500);
+              });
+            }
+            update_download();
+          }
           $('#add-track-form ul').append($line);
         }
       }).always(function() {
index 8645005f14a0a12c6bc69eb5f7eb2dd160786edb..759c07ce504eac9d27a7dc3a38c579be15df1671 100644 (file)
@@ -136,42 +136,61 @@ def regie_tracks_search(request):
         ytdl_path = default_storage.path('ytdl')
         if not os.path.exists(ytdl_path):
             os.mkdir(ytdl_path)
-        for param in ('--dump-json', '--print-json'):
-            # make a first run with just metadata, this is to obtain the filename
-            # and use a file that was previously downloaded.
-            proc = subprocess.run(
-                [
-                    'youtube-dl',
-                    '--extract-audio',
-                    '--no-playlist',
-                    '--no-mtime',
-                    param,
-                    '-o',
-                    ytdl_path + '/%(id)s.%(ext)s',
-                    q,
-                ],
-                capture_output=True,
-            )
-            output = json.loads(proc.stdout)
-            filename = output['_filename']
-            # audio conversion may be done by youtube-dl and filename would still
-            # contain the original filename, look for files with same basename,
-            # regardeless of the extension.
-            base_filename = os.path.splitext(filename)[0]
-            try:
-                real_filename = [x for x in glob.glob(base_filename + '*') if not x.endswith('.part')][0]
-            except IndexError:
-                real_filename = filename
-            else:
-                break
-        if not os.path.exists(real_filename):
-            return JsonResponse({'err': 1})
+        # make a first run with just metadata, this is to obtain the filename
+        # and use a file that was previously downloaded.
+        proc = subprocess.run(
+            [
+                'youtube-dl',
+                '--extract-audio',
+                '--no-playlist',
+                '--no-mtime',
+                '--dump-json',
+                '--audio-format',
+                'opus',
+                '-o',
+                ytdl_path + '/%(id)s.%(ext)s',
+                q,
+            ],
+            capture_output=True,
+        )
+        output = json.loads(proc.stdout)
+        filename = output['_filename']
+        # audio conversion may be done by youtube-dl and filename would still
+        # contain the original filename, look for files with same basename,
+        # regardless of the extension.
+        base_filename = os.path.splitext(filename)[0]
+        try:
+            real_filename = [
+                x
+                for x in glob.glob(base_filename + '*')
+                if not x.endswith('.part') and not x.endswith('.json')
+            ][0]
+        except IndexError:
+            # not yet downloaded, store json for async downloading with the ytdl
+            # management command
+            ready = False
+            real_filename = os.path.splitext(filename)[0] + '.json'
+            output['_url'] = q
+            with open(real_filename, 'w') as fd:
+                json.dump(output, fp=fd, indent=2)
+        else:
+            ready = True
+        url = os.path.join(settings.MEDIA_URL, 'ytdl', real_filename[len(ytdl_path) + 1 :])
         title = output['title']
         duration = '%d:%02d' % (int(output['duration']) / 60, int(output['duration']) % 60)
-        url = os.path.join(settings.MEDIA_URL, 'ytdl', real_filename[len(ytdl_path) + 1 :])
         good_filename = '%s%s' % (title, os.path.splitext(real_filename)[-1])
         return JsonResponse(
-            {'data': [{'title': title, 'duration': duration, 'url': url, 'filename': good_filename}]}
+            {
+                'data': [
+                    {
+                        'title': title,
+                        'duration': duration,
+                        'url': url,
+                        'filename': good_filename,
+                        'ready': ready,
+                    }
+                ]
+            }
         )
 
     tracks = Track.objects.filter(duration__isnull=False).filter(