add some backend/selectfile ui and api calls

This commit is contained in:
j 2012-01-14 18:27:33 +05:30
parent 5de2461eb6
commit 74eed215b5
9 changed files with 623 additions and 10 deletions

View file

@ -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)

View file

@ -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__))

View 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)

View file

@ -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

View file

@ -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()

View 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
View 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
View 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
View 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])