forked from 0x2620/pandora
add importMediaDialog
This commit is contained in:
parent
383ad8a535
commit
893f2e5423
5 changed files with 320 additions and 0 deletions
122
pandora/archive/external.py
Normal file
122
pandora/archive/external.py
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
from __future__ import division, print_function
|
||||||
|
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
|
||||||
|
import ox
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from item.models import Item
|
||||||
|
from item.tasks import load_subtitles
|
||||||
|
|
||||||
|
import models
|
||||||
|
|
||||||
|
info_keys = [
|
||||||
|
'title',
|
||||||
|
'description',
|
||||||
|
'duration',
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
'webpage_url',
|
||||||
|
'thumbnail',
|
||||||
|
'ext',
|
||||||
|
'uploader',
|
||||||
|
'subtitles',
|
||||||
|
'tags'
|
||||||
|
]
|
||||||
|
|
||||||
|
info_key_map = {
|
||||||
|
'webpage_url': 'url',
|
||||||
|
'ext': 'extension',
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_info(url):
|
||||||
|
cmd = ['youtube-dl', '-j', '--all-subs', url]
|
||||||
|
p = subprocess.Popen(cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE, close_fds=True)
|
||||||
|
stdout, stderr = p.communicate()
|
||||||
|
stdout = stdout.decode().strip()
|
||||||
|
info = []
|
||||||
|
if stdout:
|
||||||
|
for line in stdout.split('\n'):
|
||||||
|
i = json.loads(line)
|
||||||
|
if not i.get('is_live'):
|
||||||
|
info.append({
|
||||||
|
info_key_map.get(k, k): i[k]
|
||||||
|
for k in info_keys
|
||||||
|
if k in i and i[k]
|
||||||
|
})
|
||||||
|
return info
|
||||||
|
|
||||||
|
def add_subtitles(item, media, tmp):
|
||||||
|
for language in media.get('subtitles', {}):
|
||||||
|
for subtitle in media['subtitles'][language]:
|
||||||
|
if subtitle['ext'] in ('vtt', 'srt'):
|
||||||
|
data = ox.cache.read_url(subtitle['url'])
|
||||||
|
srt = os.path.join(tmp, 'media.' + subtitle['ext'])
|
||||||
|
with open(srt, 'wb') as fd:
|
||||||
|
fd.write(data)
|
||||||
|
oshash = ox.oshash(srt)
|
||||||
|
sub, created = models.File.objects.get_or_create(oshash=oshash)
|
||||||
|
if created:
|
||||||
|
sub.item = item
|
||||||
|
sub.data.name = sub.get_path('data.' + subtitle['ext'])
|
||||||
|
ox.makedirs(os.path.dirname(sub.data.path))
|
||||||
|
shutil.move(srt, sub.data.path)
|
||||||
|
sub.path = '.'.join([media['title'], language, subtitle['ext']])
|
||||||
|
sub.info = ox.avinfo(sub.data.path)
|
||||||
|
if 'path' in sub.info:
|
||||||
|
del sub.info['path']
|
||||||
|
sub.info['extension'] = subtitle['ext']
|
||||||
|
sub.info['language'] = language
|
||||||
|
sub.parse_info()
|
||||||
|
sub.selected = True
|
||||||
|
sub.save()
|
||||||
|
|
||||||
|
def download(item_id, url):
|
||||||
|
item = Item.objects.get(public_id=item_id)
|
||||||
|
info = get_info(url)
|
||||||
|
if len(info) != 1:
|
||||||
|
return '%s contains %d videos' % (url, len(info))
|
||||||
|
media = info[0]
|
||||||
|
cdir = os.path.abspath(os.curdir)
|
||||||
|
tmp = tempfile.mkdtemp().decode('utf-8')
|
||||||
|
os.chdir(tmp)
|
||||||
|
cmd = ['youtube-dl', '-q', media['url']]
|
||||||
|
p = subprocess.Popen(cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE, close_fds=True)
|
||||||
|
stdout, stderr = p.communicate()
|
||||||
|
fname = list(os.listdir(tmp))
|
||||||
|
if fname:
|
||||||
|
fname = os.path.join(tmp, fname[0])
|
||||||
|
oshash = ox.oshash(fname)
|
||||||
|
f, created = models.File.objects.get_or_create(oshash=oshash)
|
||||||
|
if created:
|
||||||
|
f.data.name = f.get_path('data.' + fname.split('.')[-1])
|
||||||
|
ox.makedirs(os.path.dirname(f.data.path))
|
||||||
|
shutil.move(fname, f.data.path)
|
||||||
|
f.item = item
|
||||||
|
f.info = ox.avinfo(f.data.path)
|
||||||
|
f.info['extension'] = media['extension']
|
||||||
|
f.path = '%(title)s.%(extension)s' % media
|
||||||
|
f.parse_info()
|
||||||
|
f.selected = True
|
||||||
|
f.save()
|
||||||
|
f.item.save()
|
||||||
|
f.extract_stream()
|
||||||
|
status = True
|
||||||
|
else:
|
||||||
|
status = 'file exists'
|
||||||
|
add_subtitles(f.item, media, tmp)
|
||||||
|
else:
|
||||||
|
status = 'download failed'
|
||||||
|
os.chdir(cdir)
|
||||||
|
shutil.rmtree(tmp)
|
||||||
|
return status
|
|
@ -10,6 +10,7 @@ from django.db.models import Q
|
||||||
from item.models import Item
|
from item.models import Item
|
||||||
import models
|
import models
|
||||||
import extract
|
import extract
|
||||||
|
import external
|
||||||
|
|
||||||
_INSTANCE_KEYS = ('mtime', 'path')
|
_INSTANCE_KEYS = ('mtime', 'path')
|
||||||
|
|
||||||
|
@ -169,3 +170,7 @@ def update_stream(id):
|
||||||
for c in s.file.item.clips.all():
|
for c in s.file.item.clips.all():
|
||||||
c.update_calculated_values()
|
c.update_calculated_values()
|
||||||
c.save()
|
c.save()
|
||||||
|
|
||||||
|
@task(queue="encoding")
|
||||||
|
def download_media(item_id, url):
|
||||||
|
return external.download(item_id, url)
|
||||||
|
|
|
@ -24,6 +24,7 @@ from changelog.models import add_changelog
|
||||||
from . import models
|
from . import models
|
||||||
from . import queue
|
from . import queue
|
||||||
from . import tasks
|
from . import tasks
|
||||||
|
from . import external
|
||||||
from .chunk import process_chunk
|
from .chunk import process_chunk
|
||||||
|
|
||||||
|
|
||||||
|
@ -718,3 +719,48 @@ def getEncodingStatus(request, data):
|
||||||
response['data']['status'] = queue.status()
|
response['data']['status'] = queue.status()
|
||||||
return render_to_json_response(response)
|
return render_to_json_response(response)
|
||||||
actions.register(getEncodingStatus, cache=False)
|
actions.register(getEncodingStatus, cache=False)
|
||||||
|
|
||||||
|
@login_required_json
|
||||||
|
def getMediaUrlInfo(request, data):
|
||||||
|
'''
|
||||||
|
Get info (title, duration,...) about given media url,
|
||||||
|
if url is a playlist, result has info about each item.
|
||||||
|
|
||||||
|
takes {
|
||||||
|
url: string // url
|
||||||
|
}
|
||||||
|
returns {
|
||||||
|
items: [{title, url,...}] // info for each url found
|
||||||
|
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
if not request.user.profile.capability('canAddItems'):
|
||||||
|
response = json_response(status=403, text='permission denied')
|
||||||
|
else:
|
||||||
|
response = json_response()
|
||||||
|
response['data']['items'] = external.get_info(data['url'])
|
||||||
|
return render_to_json_response(response)
|
||||||
|
actions.register(getMediaUrlInfo, cache=False)
|
||||||
|
|
||||||
|
@login_required_json
|
||||||
|
def addMediaUrl(request, data):
|
||||||
|
'''
|
||||||
|
Import video from url and add to item
|
||||||
|
|
||||||
|
takes {
|
||||||
|
url: string, // url
|
||||||
|
item: string // item
|
||||||
|
}
|
||||||
|
returns {
|
||||||
|
taskId: string, // taskId
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
if not request.user.profile.capability('canAddItems'):
|
||||||
|
response = json_response(status=403, text='permission denied')
|
||||||
|
else:
|
||||||
|
response = json_response()
|
||||||
|
t = tasks.download_media.delay(data['item'], data['url'])
|
||||||
|
response['data']['taskId'] = t.task_id
|
||||||
|
add_changelog(request, data, data['item'])
|
||||||
|
return render_to_json_response(response)
|
||||||
|
actions.register(addMediaUrl, cache=False)
|
||||||
|
|
146
static/js/importMediaDialog.js
Normal file
146
static/js/importMediaDialog.js
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
pandora.ui.importMediaDialog = function(options) {
|
||||||
|
var
|
||||||
|
$content = Ox.Element().css({margin: '16px'}),
|
||||||
|
$url = Ox.Input({
|
||||||
|
label: Ox._('Url'),
|
||||||
|
labelWidth: 32,
|
||||||
|
width: 384
|
||||||
|
})
|
||||||
|
.css({
|
||||||
|
marginTop: '16px'
|
||||||
|
})
|
||||||
|
.bindEvent({
|
||||||
|
change: function(data) {
|
||||||
|
$info.empty();
|
||||||
|
that.disableButton('import');
|
||||||
|
if (data.value) {
|
||||||
|
$info.append(loadingIcon());
|
||||||
|
getInfo(data.value, function(items) {
|
||||||
|
$info.empty();
|
||||||
|
if (items.length) {
|
||||||
|
// FIXME: support playlists / multiple items
|
||||||
|
var info = items[0];
|
||||||
|
that.enableButton('import');
|
||||||
|
$info.append($('<img>').css({
|
||||||
|
'max-width': '128px',
|
||||||
|
margin: '4px',
|
||||||
|
float: 'left'
|
||||||
|
}).attr('src', info.thumbnail));
|
||||||
|
$info.append($('<div>').css({
|
||||||
|
'font-weight': 'bold'
|
||||||
|
}).html(info.title));
|
||||||
|
if (info.duration) {
|
||||||
|
$info.append($('<div>').html(Ox.formatDuration(info.duration)));
|
||||||
|
}
|
||||||
|
$info.append($('<div>').html(info.description));
|
||||||
|
} else {
|
||||||
|
$info.empty();
|
||||||
|
that.disableButton('import');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.appendTo($content),
|
||||||
|
$info = Ox.Element()
|
||||||
|
.css({margin: '8px'})
|
||||||
|
.html('Enter a url from another site (like YouTube) to create a new item.')
|
||||||
|
.appendTo($content),
|
||||||
|
that = Ox.Dialog({
|
||||||
|
buttons: [
|
||||||
|
Ox.Button({
|
||||||
|
id: 'cancel',
|
||||||
|
title: Ox._('Cancel')
|
||||||
|
})
|
||||||
|
.bindEvent({
|
||||||
|
click: function() {
|
||||||
|
that.close();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Ox.Button({
|
||||||
|
disabled: true,
|
||||||
|
id: 'import',
|
||||||
|
title: Ox._('Import')
|
||||||
|
}).bindEvent({
|
||||||
|
click: importMedia
|
||||||
|
})
|
||||||
|
],
|
||||||
|
closeButton: true,
|
||||||
|
content: $content,
|
||||||
|
fixedSize: true,
|
||||||
|
height: 176,
|
||||||
|
keys: {
|
||||||
|
escape: 'cancel'
|
||||||
|
},
|
||||||
|
removeOnClose: true,
|
||||||
|
title: Ox._('Import Media'),
|
||||||
|
width: 416
|
||||||
|
});
|
||||||
|
window.$info = $info;
|
||||||
|
function getInfo(url, callback) {
|
||||||
|
pandora.api.getMediaUrlInfo({url: url}, function(result) {
|
||||||
|
callback(result.data.items);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMedia(url, callback) {
|
||||||
|
pandora.api.getMediaUrlInfo({url: url}, function(result) {
|
||||||
|
result.data.items.forEach(function(info) {
|
||||||
|
pandora.api.add({title: info.title}, function(result) {
|
||||||
|
var edit = {
|
||||||
|
director: info.uploader ? [info.uploader] : [],
|
||||||
|
id: result.data.id,
|
||||||
|
notes: info.url,
|
||||||
|
summary: info.description,
|
||||||
|
topic: info.tags
|
||||||
|
};
|
||||||
|
pandora.api.edit(edit, function(result) {
|
||||||
|
pandora.api.addMediaUrl({
|
||||||
|
url: info.url,
|
||||||
|
item: edit.id
|
||||||
|
}, function(result) {
|
||||||
|
if (result.data.taskId) {
|
||||||
|
pandora.wait(result.data.taskId, function(result) {
|
||||||
|
Ox.print('status?', result);
|
||||||
|
callback(edit.id);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback(edit.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function importMedia() {
|
||||||
|
var url = $url.value();
|
||||||
|
$info.empty();
|
||||||
|
$info.append(loadingIcon());
|
||||||
|
that.disableButton('import');
|
||||||
|
that.disableButton('cancel');
|
||||||
|
addMedia(url, function(item) {
|
||||||
|
if (item) {
|
||||||
|
that.close();
|
||||||
|
Ox.Request.clearCache();
|
||||||
|
pandora.URL.push('/'+item+'/media');
|
||||||
|
} else {
|
||||||
|
$info.empty().html('Import failed');
|
||||||
|
that.enableButton('cancel');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadingIcon() {
|
||||||
|
return Ox.LoadingIcon().css({
|
||||||
|
margin: 'auto',
|
||||||
|
marginTop: '64px',
|
||||||
|
width: '100%'
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
return that;
|
||||||
|
};
|
|
@ -69,6 +69,7 @@ apt-get install -y \
|
||||||
gpac \
|
gpac \
|
||||||
imagemagick \
|
imagemagick \
|
||||||
poppler-utils \
|
poppler-utils \
|
||||||
|
youtube-dl \
|
||||||
ipython \
|
ipython \
|
||||||
postfix \
|
postfix \
|
||||||
postgresql \
|
postgresql \
|
||||||
|
|
Loading…
Reference in a new issue