add some backend/selectfile ui and api calls
This commit is contained in:
parent
5de2461eb6
commit
74eed215b5
9 changed files with 623 additions and 10 deletions
|
@ -5,7 +5,6 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from glob import glob
|
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
|
|
||||||
root = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..')
|
root = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..')
|
||||||
|
@ -16,10 +15,10 @@ import pandoralocal
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = OptionParser()
|
parser = OptionParser()
|
||||||
parser.add_option('-c', '--config', dest='config', help='config file', default='config.json')
|
parser.add_option('-d', '--db', dest='db', help='settings db', default=pandoralocal.db_path())
|
||||||
(opts, args) = parser.parse_args()
|
(opts, args) = parser.parse_args()
|
||||||
|
|
||||||
if None in (opts.config, ):
|
if None in (opts.db, ):
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
pandoralocal.main(opts.config)
|
pandoralocal.main(opts.db)
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
# vi:si:et:sw=4:sts=4:ts=4
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import gobject
|
||||||
|
gobject.threads_init()
|
||||||
|
|
||||||
|
from twisted.internet import glib2reactor
|
||||||
|
glib2reactor.install()
|
||||||
|
|
||||||
from twisted.web.server import Site
|
from twisted.web.server import Site
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
|
@ -11,6 +18,15 @@ import api
|
||||||
|
|
||||||
from version import __version__
|
from version import __version__
|
||||||
|
|
||||||
|
def db_path():
|
||||||
|
#use something like http://pypi.python.org/pypi/appdirs/?
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
path = os.path.expanduser('~/Library/Application Support/pandora/local.db')
|
||||||
|
elif sys.platform == 'win32':
|
||||||
|
path = os.path.expanduser('~\\AppData\\pandora\\local.db')
|
||||||
|
else:
|
||||||
|
path = os.path.expanduser('~/.local/share/pandora/local.db')
|
||||||
|
return path
|
||||||
|
|
||||||
def main(config):
|
def main(config):
|
||||||
base = os.path.abspath(os.path.dirname(__file__))
|
base = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
# vi:si:et:sw=4:sts=4:ts=4
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
import os
|
||||||
|
|
||||||
from server import actions, json_response
|
from server import actions, json_response
|
||||||
|
import ui
|
||||||
|
|
||||||
def init(backend, site, data):
|
def init(backend, site, data):
|
||||||
response = {}
|
response = {}
|
||||||
|
@ -14,3 +17,83 @@ actions.register(echo, cache=False)
|
||||||
def site(backend, site, data):
|
def site(backend, site, data):
|
||||||
return json_response({'site': site})
|
return json_response({'site': site})
|
||||||
actions.register(site, cache=False)
|
actions.register(site, cache=False)
|
||||||
|
|
||||||
|
def addVolume(backend, site, data):
|
||||||
|
print data
|
||||||
|
path = ui.selectFolder(data)
|
||||||
|
if path:
|
||||||
|
name = os.path.basename(path)
|
||||||
|
volume = backend.add_volume(site, name, path)
|
||||||
|
return json_response(volume)
|
||||||
|
return json_response({})
|
||||||
|
actions.register(addVolume, cache=False)
|
||||||
|
|
||||||
|
def removeVolume(backend, site, data):
|
||||||
|
print data
|
||||||
|
return json_response({})
|
||||||
|
actions.register(removeVolume, cache=False)
|
||||||
|
|
||||||
|
def renameVolume(backend, site, data):
|
||||||
|
volume = backend.rename_volume(site, data['name'], data['newName'])
|
||||||
|
return json_response(volume)
|
||||||
|
actions.register(renameVolume, cache=False)
|
||||||
|
|
||||||
|
def findVolumes(backend, site, data):
|
||||||
|
print site, data
|
||||||
|
response = {}
|
||||||
|
response['items'] = backend.volumes(site)
|
||||||
|
return json_response(response)
|
||||||
|
actions.register(findVolumes, cache=False)
|
||||||
|
|
||||||
|
def findFiles(backend, site, data):
|
||||||
|
'''
|
||||||
|
implements Ox.List api for files
|
||||||
|
keys: path, id, size, volume, item, uploaded
|
||||||
|
'''
|
||||||
|
print site, data
|
||||||
|
response = {}
|
||||||
|
if not 'keys' in data:
|
||||||
|
response['items'] = backend.files(site, keys=['count(*)'])[0]['count(*)']
|
||||||
|
print response
|
||||||
|
else:
|
||||||
|
response['items'] = backend.files(site, keys=data['keys'], limit=data['range'])
|
||||||
|
|
||||||
|
return json_response(response)
|
||||||
|
actions.register(findFiles, cache=False)
|
||||||
|
|
||||||
|
def uploadFile(backend, site, data):
|
||||||
|
'''
|
||||||
|
uploadFile {
|
||||||
|
id:
|
||||||
|
}
|
||||||
|
upload file with provides id
|
||||||
|
'''
|
||||||
|
print data
|
||||||
|
return json_response({})
|
||||||
|
actions.register(uploadFile, cache=False)
|
||||||
|
|
||||||
|
def encodeFile(backend, site, data):
|
||||||
|
path = ui.selectFile(data)
|
||||||
|
oshash = backend.add_file(site, path)
|
||||||
|
url = '/%s/480p.webm' % oshash
|
||||||
|
return json_response({
|
||||||
|
'path': path,
|
||||||
|
'oshash': oshash,
|
||||||
|
'url': url
|
||||||
|
})
|
||||||
|
actions.register(encodeFile, cache=False)
|
||||||
|
|
||||||
|
def cacheVideo(backend, site, data):
|
||||||
|
print data
|
||||||
|
return json_response({})
|
||||||
|
actions.register(encodeFile, cache=False)
|
||||||
|
|
||||||
|
def selectFile(backend, site, data):
|
||||||
|
path = ui.selectFile(data)
|
||||||
|
oshash = backend.add_file(site, path)
|
||||||
|
return json_response({
|
||||||
|
'path': path,
|
||||||
|
'oshash': oshash
|
||||||
|
})
|
||||||
|
actions.register(selectFile, cache=False)
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,209 @@
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
# vi:si:et:sw=4:sts=4:ts=4
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
import json
|
||||||
|
import sqlite3
|
||||||
|
import os
|
||||||
|
|
||||||
|
import ox
|
||||||
|
|
||||||
|
import utils
|
||||||
|
|
||||||
|
|
||||||
class Backend:
|
class Backend:
|
||||||
def __init__(self, config):
|
def __init__(self, db):
|
||||||
self.config = config
|
self.db = db
|
||||||
|
|
||||||
def get_file(self, site, itemId, filename):
|
conn, c = self._conn()
|
||||||
|
|
||||||
|
c.execute('''CREATE TABLE IF NOT EXISTS setting (key varchar(1024) unique, value text)''')
|
||||||
|
|
||||||
|
if int(self.get('version', 0)) < 1:
|
||||||
|
self.set('version', 1)
|
||||||
|
db = [
|
||||||
|
'''CREATE TABLE IF NOT EXISTS file (
|
||||||
|
path varchar(1024) unique,
|
||||||
|
oshash varchar(16),
|
||||||
|
atime FLOAT,
|
||||||
|
ctime FLOAT,
|
||||||
|
mtime FLOAT,
|
||||||
|
size INT,
|
||||||
|
info TEXT,
|
||||||
|
created INT,
|
||||||
|
modified INT,
|
||||||
|
deleted INT)''',
|
||||||
|
'''CREATE INDEX IF NOT EXISTS path_idx ON file (path)''',
|
||||||
|
'''CREATE INDEX IF NOT EXISTS oshash_idx ON file (oshash)''',
|
||||||
|
]
|
||||||
|
for i in db:
|
||||||
|
c.execute(i)
|
||||||
|
conn.commit()
|
||||||
|
if int(self.get('version', 0)) < 2:
|
||||||
|
self.set('version', 2)
|
||||||
|
db = [
|
||||||
|
'''CREATE TABLE IF NOT EXISTS encode (
|
||||||
|
oshash varchar(16),
|
||||||
|
site varchar(255))''',
|
||||||
|
'''CREATE INDEX IF NOT EXISTS upload_site_idx ON encode (site)''',
|
||||||
|
]
|
||||||
|
for i in db:
|
||||||
|
c.execute(i)
|
||||||
|
conn.commit()
|
||||||
|
if int(self.get('version', 0)) < 3:
|
||||||
|
self.set('version', 3)
|
||||||
|
db = [
|
||||||
|
'''CREATE TABLE IF NOT EXISTS volume (
|
||||||
|
name varchar(1024) unique,
|
||||||
|
path text,
|
||||||
|
site varchar(255))''',
|
||||||
|
'''CREATE TABLE IF NOT EXISTS part (
|
||||||
|
id varchar(1024),
|
||||||
|
part int,
|
||||||
|
oshash varchar(16),
|
||||||
|
site varchar(255))''',
|
||||||
|
]
|
||||||
|
for i in db:
|
||||||
|
c.execute(i)
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
self.media_cache = self.get('media_cache')
|
||||||
|
if not self.media_cache:
|
||||||
|
self.media_cache = os.path.join(os.path.dirname(self.db), 'media')
|
||||||
|
self.set('media_cache', self.media_cache)
|
||||||
|
|
||||||
|
def _conn(self):
|
||||||
|
if not os.path.exists(os.path.dirname(self.db)):
|
||||||
|
os.makedirs(os.path.dirname(self.db))
|
||||||
|
conn = sqlite3.connect(self.db, timeout=10)
|
||||||
|
conn.text_factory = sqlite3.OptimizedUnicode
|
||||||
|
return conn, conn.cursor()
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
conn, c = self._conn()
|
||||||
|
c.execute('SELECT value FROM setting WHERE key = ?', (key, ))
|
||||||
|
for row in c:
|
||||||
|
return row[0]
|
||||||
|
return default
|
||||||
|
|
||||||
|
def set(self, key, value):
|
||||||
|
conn, c = self._conn()
|
||||||
|
c.execute(u'INSERT OR REPLACE INTO setting VALUES (?, ?)', (key, str(value)))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
def info(self, oshash):
|
||||||
|
conn, c = self._conn()
|
||||||
|
c.execute('SELECT info FROM file WHERE oshash = ?', (oshash, ))
|
||||||
|
for row in c:
|
||||||
|
return json.loads(row[0])
|
||||||
|
return None
|
||||||
|
|
||||||
|
def path(self, oshash):
|
||||||
|
conn, c = self._conn()
|
||||||
|
c.execute('SELECT path FROM file WHERE oshash = ?', (oshash, ))
|
||||||
|
paths = []
|
||||||
|
for row in c:
|
||||||
|
paths.append(row[0])
|
||||||
|
return paths
|
||||||
|
|
||||||
|
def cache_path(self, oshash, profile):
|
||||||
|
return os.path.join(self.media_cache, os.path.join(*utils.hash_prefix(oshash)), profile)
|
||||||
|
|
||||||
|
def volumes(self, site):
|
||||||
|
conn, c = self._conn()
|
||||||
|
c.execute('SELECT name, path FROM volume WHERE site= ?', (site, ))
|
||||||
|
volumes = []
|
||||||
|
for r in c:
|
||||||
|
volumes.append({'name': r[0], 'path': r[1]})
|
||||||
|
return volumes
|
||||||
|
|
||||||
|
def add_volume(self, site, name, path):
|
||||||
|
volumes = self.volumes(site)
|
||||||
|
exists = filter(lambda v: v['path'] == path, volumes)
|
||||||
|
if exists:
|
||||||
|
return exists[0]
|
||||||
|
_name = name
|
||||||
|
n = 2
|
||||||
|
while filter(lambda v: v['name'] == _name, volumes):
|
||||||
|
_name = "%s %d" % (name, n)
|
||||||
|
name = _name
|
||||||
|
conn, c = self._conn()
|
||||||
|
c.execute('INSERT INTO volume (site, name, path) VALUES (?, ?, ?)', (site, name, path))
|
||||||
|
conn.commit()
|
||||||
|
return {
|
||||||
|
'name': name, 'path': path
|
||||||
|
}
|
||||||
|
|
||||||
|
def rename_volume(self, site, name, new_name):
|
||||||
|
volumes = self.volumes(site)
|
||||||
|
_name = new_name
|
||||||
|
n = 2
|
||||||
|
while filter(lambda v: v['name'] == _name, volumes):
|
||||||
|
_name = "%s %d" % (new_name, n)
|
||||||
|
new_name = _name
|
||||||
|
conn, c = self._conn()
|
||||||
|
c.execute('UPDATE volume SET name = ? WHERE name = ? AND site = ?', (new_name, name, site))
|
||||||
|
conn.commit()
|
||||||
|
return {
|
||||||
|
'name': new_name,
|
||||||
|
}
|
||||||
|
|
||||||
|
def files(self, site, keys=[], order='path', limit=None):
|
||||||
|
conn, c = self._conn()
|
||||||
|
files = []
|
||||||
|
sql = 'SELECT %s FROM file ORDER BY %s '% (','.join(keys), order)
|
||||||
|
if limit:
|
||||||
|
sql += ' LIMIT %d, %d' %(limit[0], limit[1]-limit[0])
|
||||||
|
print sql
|
||||||
|
c.execute(sql)
|
||||||
|
for r in c:
|
||||||
|
f = {}
|
||||||
|
for i in range(len(r)):
|
||||||
|
f[keys[i]] = r[i]
|
||||||
|
files.append(f)
|
||||||
|
return files
|
||||||
|
|
||||||
|
def add_file(self, site, filename):
|
||||||
|
info = utils.avinfo(filename)
|
||||||
|
return info['oshash']
|
||||||
|
|
||||||
|
def cache_file(self, site, url, itemId, filename):
|
||||||
|
conn, c = self._conn()
|
||||||
filename, ext = filename.split('.')
|
filename, ext = filename.split('.')
|
||||||
resolution, part = filename.split('p')
|
resolution, part = filename.split('p')
|
||||||
print site, itemId, resolution, part, ext
|
print site, itemId, resolution, part, ext
|
||||||
|
c.execute('SELECT oshash FROM part WHERE site = ? AND id = ? AND part =?',
|
||||||
|
(site, itemId, part))
|
||||||
|
path = ''
|
||||||
|
for r in c:
|
||||||
|
oshash = r[0]
|
||||||
|
path = self.cache_path(oshash, '480p.webm')
|
||||||
|
break
|
||||||
|
|
||||||
|
if path and not os.path.exists(path):
|
||||||
|
path = ''
|
||||||
|
if not path:
|
||||||
|
#FIXME: oshash get oshash for part
|
||||||
|
path = self.cache_path(oshash, '480p.webm')
|
||||||
|
#FIXME: need to add cookies
|
||||||
|
ox.net.saveUrl(url, path)
|
||||||
|
t = (oshash, part, itemId, site)
|
||||||
|
c.execute('INSERT INTO part (oshash, part, id, site) VALUES (?, ?, ?, ?)', t)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def get_file(self, site, itemId, filename):
|
||||||
|
conn, c = self._conn()
|
||||||
|
filename, ext = filename.split('.')
|
||||||
|
resolution, part = filename.split('p')
|
||||||
|
print site, itemId, resolution, part, ext
|
||||||
|
if len(itemId) == 16 and itemId.islower() and itemId.isalnum():
|
||||||
|
path = self.cache_path(itemId, '480p.webm')
|
||||||
|
else:
|
||||||
|
c.execute('SELECT oshash FROM part WHERE site = ? AND id = ? AND part =?',
|
||||||
|
(site, itemId, part))
|
||||||
|
path = ''
|
||||||
|
for r in c:
|
||||||
|
path = self.cache_path(r[0], '480p.webm')
|
||||||
|
break
|
||||||
|
|
||||||
|
if path and not os.path.exists(path):
|
||||||
path = ''
|
path = ''
|
||||||
if resolution == '480' and ext == 'webm':
|
|
||||||
path = '/home/j/.ox/media/44/c4/b1/11a888e96a/480p.webm'
|
|
||||||
return path
|
return path
|
||||||
|
|
|
@ -110,7 +110,7 @@ class ApiActions(dict):
|
||||||
result = self[action](backend, site, data)
|
result = self[action](backend, site, data)
|
||||||
else:
|
else:
|
||||||
result = json_response(status=404, text='not found')
|
result = json_response(status=404, text='not found')
|
||||||
print result
|
#print result
|
||||||
return json.dumps(result)
|
return json.dumps(result)
|
||||||
|
|
||||||
actions = ApiActions()
|
actions = ApiActions()
|
||||||
|
|
12
pandoralocal/static/files.html
Normal file
12
pandoralocal/static/files.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>pandoralocal</title>
|
||||||
|
<link rel="shortcut icon" type="image/png" href="/png/icon16.png"/>
|
||||||
|
<script type="text/javascript" src="/oxjs/dev/Ox.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/files.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
189
pandoralocal/static/js/files.js
Executable file
189
pandoralocal/static/js/files.js
Executable file
|
@ -0,0 +1,189 @@
|
||||||
|
/***
|
||||||
|
PandoraLocal
|
||||||
|
***/
|
||||||
|
Ox.load('UI', {
|
||||||
|
hideScreen: false,
|
||||||
|
showScreen: true,
|
||||||
|
theme: 'classic'
|
||||||
|
}, function() {
|
||||||
|
|
||||||
|
window.pandora = new Ox.App({
|
||||||
|
apiURL: '/api/',
|
||||||
|
init: 'init',
|
||||||
|
}).bindEvent('load', function(data) {
|
||||||
|
pandora.site = {
|
||||||
|
};
|
||||||
|
Ox.UI.hideLoadingScreen();
|
||||||
|
|
||||||
|
pandora.$ui = {
|
||||||
|
body: $('body'),
|
||||||
|
document: $(document),
|
||||||
|
window: $(window)
|
||||||
|
.bind({
|
||||||
|
resize: function() {
|
||||||
|
//pandora.resizeWindow();
|
||||||
|
},
|
||||||
|
unload: function() {
|
||||||
|
//pandora.nloadWindow();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
pandora.$ui.volumes = constructList();
|
||||||
|
pandora.$ui.files = constructFiles();
|
||||||
|
|
||||||
|
var $left = new Ox.SplitPanel({
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
element: new Ox.Element().append(new Ox.Element()
|
||||||
|
.html('Pandoralocal').css({
|
||||||
|
'padding': '4px',
|
||||||
|
})).css({
|
||||||
|
'background-color': '#ddd',
|
||||||
|
'font-weight': 'bold',
|
||||||
|
}),
|
||||||
|
size: 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: pandora.$ui.volumes
|
||||||
|
}
|
||||||
|
],
|
||||||
|
orientation: 'vertical'
|
||||||
|
});
|
||||||
|
var $main = new Ox.SplitPanel({
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
element: $left,
|
||||||
|
size: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: pandora.$ui.files,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
orientation: 'horizontal'
|
||||||
|
});
|
||||||
|
|
||||||
|
$main.appendTo(pandora.$ui.body);
|
||||||
|
});
|
||||||
|
|
||||||
|
function constructFiles() {
|
||||||
|
return new Ox.TextList({
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
align: "left",
|
||||||
|
id: "path",
|
||||||
|
operator: "+",
|
||||||
|
title: "Name",
|
||||||
|
unique: true,
|
||||||
|
visible: true,
|
||||||
|
width: 860
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: "left",
|
||||||
|
id: "size",
|
||||||
|
operator: "-",
|
||||||
|
title: "Size",
|
||||||
|
visible: true,
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: "left",
|
||||||
|
id: "oshash",
|
||||||
|
operator: "-",
|
||||||
|
title: "ID",
|
||||||
|
visible: true,
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
],
|
||||||
|
columnsMovable: true,
|
||||||
|
columnsRemovable: true,
|
||||||
|
items: pandora.api.findFiles,
|
||||||
|
scrollbarVisible: true,
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
key: "path",
|
||||||
|
operator: "+"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).bindEvent({
|
||||||
|
select: function(data) {
|
||||||
|
var info = $('<div>').addClass('OxSelectable'),
|
||||||
|
hash = '#';
|
||||||
|
if(data.ids.length) {
|
||||||
|
data.ids.forEach(function(id) {
|
||||||
|
info.append($("<h2>").html(id));
|
||||||
|
var $doc =$('<pre>')
|
||||||
|
.html(pandora.actions[id].doc.replace('/\n/<br>\n/g'))
|
||||||
|
.appendTo(info);
|
||||||
|
var $code = $('<code class="python">')
|
||||||
|
.html(pandora.actions[id].code[1].replace('/\n/<br>\n/g'))
|
||||||
|
.hide();
|
||||||
|
var f = pandora.actions[id].code[0];
|
||||||
|
$('<span>')
|
||||||
|
.html(' View Source ('+f+')')
|
||||||
|
.click(function() { $code.toggle();})
|
||||||
|
.appendTo(info);
|
||||||
|
$('<pre>').append($code).appendTo(info);
|
||||||
|
hljs.highlightBlock($code[0], ' ');
|
||||||
|
|
||||||
|
hash += id + ',';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
info.html(pandora.site.default_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.location.hash = hash.substring(0, hash.length-1);
|
||||||
|
pandora.$ui.actionInfo.html(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function constructList() {
|
||||||
|
return new Ox.TextList({
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
align: "left",
|
||||||
|
id: "name",
|
||||||
|
operator: "+",
|
||||||
|
title: "Name",
|
||||||
|
unique: true,
|
||||||
|
visible: true,
|
||||||
|
width: 140
|
||||||
|
},
|
||||||
|
],
|
||||||
|
columnsMovable: false,
|
||||||
|
columnsRemovable: false,
|
||||||
|
id: 'actionList',
|
||||||
|
items: function(data, callback) {
|
||||||
|
function _sort(a, b) {
|
||||||
|
if(a.name > b.name)
|
||||||
|
return 1;
|
||||||
|
else if(a.name == b.name)
|
||||||
|
return 0;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
pandora.api.findVolumes(function(result) {
|
||||||
|
var items = result.data.items;
|
||||||
|
items.sort(_sort);
|
||||||
|
callback({'data': {
|
||||||
|
'items': data.keys ? items : items.length
|
||||||
|
}});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
scrollbarVisible: true,
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
key: "name",
|
||||||
|
operator: "+"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).bindEvent({
|
||||||
|
select: function(data) {
|
||||||
|
if(data.ids.length) {
|
||||||
|
var volume = data.ids[0];
|
||||||
|
Ox.print("FIXME", volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
43
pandoralocal/ui.py
Normal file
43
pandoralocal/ui.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
import pygtk
|
||||||
|
pygtk.require('2.0')
|
||||||
|
import gtk
|
||||||
|
|
||||||
|
def selectFolder(data):
|
||||||
|
dialog = gtk.FileChooserDialog("Select Folder..",
|
||||||
|
None,
|
||||||
|
gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
|
||||||
|
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
|
||||||
|
gtk.STOCK_OPEN, gtk.RESPONSE_OK))
|
||||||
|
dialog.set_default_response(gtk.RESPONSE_OK)
|
||||||
|
|
||||||
|
response = dialog.run()
|
||||||
|
if response == gtk.RESPONSE_OK:
|
||||||
|
filename = dialog.get_filename()
|
||||||
|
print filename, 'selected'
|
||||||
|
elif response == gtk.RESPONSE_CANCEL:
|
||||||
|
print 'Closed, no files selected'
|
||||||
|
filename = None
|
||||||
|
dialog.destroy()
|
||||||
|
print "done"
|
||||||
|
return filename
|
||||||
|
|
||||||
|
def selectFile(data):
|
||||||
|
dialog = gtk.FileChooserDialog("Select File..",
|
||||||
|
None,
|
||||||
|
gtk.FILE_CHOOSER_ACTION_OPEN,
|
||||||
|
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
|
||||||
|
gtk.STOCK_OPEN, gtk.RESPONSE_OK))
|
||||||
|
dialog.set_default_response(gtk.RESPONSE_OK)
|
||||||
|
|
||||||
|
response = dialog.run()
|
||||||
|
if response == gtk.RESPONSE_OK:
|
||||||
|
filename = dialog.get_filename()
|
||||||
|
print filename, 'selected'
|
||||||
|
elif response == gtk.RESPONSE_CANCEL:
|
||||||
|
print 'Closed, no files selected'
|
||||||
|
filename = None
|
||||||
|
dialog.destroy()
|
||||||
|
print "done"
|
||||||
|
return filename
|
77
pandoralocal/utils.py
Normal file
77
pandoralocal/utils.py
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vi:si:et:sw=4:sts=4:ts=4
|
||||||
|
# GPL 2010
|
||||||
|
from __future__ import division, with_statement
|
||||||
|
|
||||||
|
import fractions
|
||||||
|
from glob import glob
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sqlite3
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
|
||||||
|
import ox
|
||||||
|
|
||||||
|
|
||||||
|
class AspectRatio(fractions.Fraction):
|
||||||
|
def __new__(cls, numerator, denominator=None):
|
||||||
|
if not denominator:
|
||||||
|
ratio = map(int, numerator.split(':'))
|
||||||
|
if len(ratio) == 1: ratio.append(1)
|
||||||
|
numerator = ratio[0]
|
||||||
|
denominator = ratio[1]
|
||||||
|
#if its close enough to the common aspect ratios rather use that
|
||||||
|
if abs(numerator/denominator - 4/3) < 0.03:
|
||||||
|
numerator = 4
|
||||||
|
denominator = 3
|
||||||
|
elif abs(numerator/denominator - 16/9) < 0.02:
|
||||||
|
numerator = 16
|
||||||
|
denominator = 9
|
||||||
|
return super(AspectRatio, cls).__new__(cls, numerator, denominator)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ratio(self):
|
||||||
|
return "%d:%d" % (self.numerator, self.denominator)
|
||||||
|
|
||||||
|
def avinfo(filename):
|
||||||
|
if os.path.getsize(filename):
|
||||||
|
info = ox.avinfo(filename)
|
||||||
|
if 'video' in info and info['video'] and 'width' in info['video'][0]:
|
||||||
|
if not 'display_aspect_ratio' in info['video'][0]:
|
||||||
|
dar = AspectRatio(info['video'][0]['width'], info['video'][0]['height'])
|
||||||
|
info['video'][0]['display_aspect_ratio'] = dar.ratio
|
||||||
|
del info['path']
|
||||||
|
if os.path.splitext(filename)[-1] in ('.srt', '.sub', '.idx', '.rar') and 'error' in info:
|
||||||
|
del info['error']
|
||||||
|
if 'code' in info and info['code'] == 'badfile':
|
||||||
|
del info['code']
|
||||||
|
return info
|
||||||
|
return {'path': filename, 'size': 0}
|
||||||
|
|
||||||
|
def hash_prefix(h):
|
||||||
|
return [h[:2], h[2:4], h[4:6], h[6:]]
|
||||||
|
|
||||||
|
def run_command(cmd, timeout=25):
|
||||||
|
#print cmd
|
||||||
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
|
||||||
|
while timeout > 0:
|
||||||
|
time.sleep(0.2)
|
||||||
|
timeout -= 0.2
|
||||||
|
if p.poll() != None:
|
||||||
|
return p.returncode
|
||||||
|
if p.poll() == None:
|
||||||
|
os.kill(p.pid, 9)
|
||||||
|
killedpid, stat = os.waitpid(p.pid, os.WNOHANG)
|
||||||
|
return p.returncode
|
||||||
|
|
||||||
|
def video_frame_positions(duration):
|
||||||
|
pos = duration / 2
|
||||||
|
#return [pos/4, pos/2, pos/2+pos/4, pos, pos+pos/2, pos+pos/2+pos/4]
|
||||||
|
return map(int, [pos/2, pos, pos+pos/2])
|
||||||
|
|
Loading…
Reference in a new issue