--- /dev/null
+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)
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() {
$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() {
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(