cleanup, use Firefogg module

This commit is contained in:
j 2010-09-03 21:48:41 +02:00
parent 5be87d8adb
commit a3e3de8c99
8 changed files with 325 additions and 480 deletions

View file

@ -18,16 +18,12 @@ import tempfile
import time import time
from threading import Thread from threading import Thread
from twisted.cred.portal import IRealm, Portal
from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
from twisted.internet import task, reactor, threads from twisted.internet import task, reactor, threads
from twisted.web import server from twisted.web import server
from twisted.web.guard import HTTPAuthSessionWrapper, DigestCredentialFactory from twisted.web.resource import Resource
from twisted.web.resource import Resource, IResource
from twisted.web.static import File from twisted.web.static import File
from twisted.web.server import NOT_DONE_YET from twisted.web.server import NOT_DONE_YET
from zope.interface import implements
FFMPEG2THEORA = 'ffmpeg2theora' FFMPEG2THEORA = 'ffmpeg2theora'
@ -93,21 +89,6 @@ def avinfo(filename):
def hash_prefix(h): def hash_prefix(h):
return [h[:2], h[2:4], h[4:6], h[6:]] return [h[:2], h[2:4], h[4:6], h[6:]]
def extract_all_stills():
db = Database('dev.sqlite')
conn = db.conn()
c = conn.cursor()
sql = 'SELECT path, oshash, info FROM file'
c.execute(sql)
for row in c:
video = row[0]
oshash = row[1]
info = json.loads(row[2])
if not 'Extras/' in video and 'video' in info and info['video']:
prefix = os.path.join('media', os.path.join(*hash_prefix(oshash)))
print video
extract_stills(video, prefix, info)
def run_command(cmd, timeout=25): def run_command(cmd, timeout=25):
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
while timeout > 0: while timeout > 0:
@ -120,7 +101,7 @@ def run_command(cmd, timeout=25):
killedpid, stat = os.waitpid(p.pid, os.WNOHANG) killedpid, stat = os.waitpid(p.pid, os.WNOHANG)
return p.returncode return p.returncode
def extract_still(video, target, position): def extract_frame(video, target, position):
fdir = os.path.dirname(target) fdir = os.path.dirname(target)
if fdir and not os.path.exists(fdir): if fdir and not os.path.exists(fdir):
os.makedirs(fdir) os.makedirs(fdir)
@ -153,13 +134,13 @@ def extract_still(video, target, position):
cmd = [ cmd = [
vlc_path, '--vout=dummy', video, '--start-time=%s'%position, '--stop-time=%s'%out, vlc_path, '--vout=dummy', video, '--start-time=%s'%position, '--stop-time=%s'%out,
'-I', 'dummy', '--video-filter=scene', '--scene-path=%s'%framedir, '-I', 'dummy', '--video-filter=scene', '--scene-path=%s'%framedir,
'--scene-format=png', '--scene-ratio=25', '--scene-prefix=still', '--swscale-mode=2', '--scene-format=png', '--scene-ratio=25', '--scene-prefix=frame', '--swscale-mode=2',
'--sout-transcode-vcodec=avcodec', '--noaudio', 'vlc://quit', '--sout-transcode-vcodec=avcodec', '--noaudio', 'vlc://quit',
] ]
#print cmd #print cmd
run_command(cmd) run_command(cmd)
images = glob('%s/still*.png' % framedir) images = glob('%s/frame*.png' % framedir)
if images: if images:
shutil.move(images[0], target) shutil.move(images[0], target)
shutil.rmtree(framedir) shutil.rmtree(framedir)
@ -175,6 +156,9 @@ def extract_still(video, target, position):
images = glob('%s/*.png' % framedir) images = glob('%s/*.png' % framedir)
if images: if images:
shutil.move(images[-1], target) shutil.move(images[-1], target)
r = 0
else:
r = 1
os.chdir(cwd) os.chdir(cwd)
shutil.rmtree(framedir) shutil.rmtree(framedir)
return r == 0 return r == 0
@ -241,6 +225,7 @@ def extract_video(video, target, profile, info):
fps = AspectRatio(info['video'][0]['framerate']) fps = AspectRatio(info['video'][0]['framerate'])
width = int(dar * height) width = int(dar * height)
width += width % 2
bitrate = height*width*fps*bpp/1000 bitrate = height*width*fps*bpp/1000
aspect = dar.ratio aspect = dar.ratio
@ -276,7 +261,10 @@ def extract_video(video, target, profile, info):
while line: while line:
if line.startswith('frame='): if line.startswith('frame='):
frames = line.split('=')[1].strip().split(' ')[0] frames = line.split('=')[1].strip().split(' ')[0]
enc_status[info['oshash']] = (float(frames) / fps) / info['duration'] if enc_status[info['oshash']] == 'cancel':
p.kill()
else:
enc_status[info['oshash']] = (float(frames) / fps) / info['duration']
line = p.stderr.readline() line = p.stderr.readline()
p.wait() p.wait()
@ -461,14 +449,14 @@ class Database(object):
if derivative['status'] == STATUS_NEW: if derivative['status'] == STATUS_NEW:
if name.endswith('.png'): if name.endswith('.png'):
for pos in video_frame_positions(f['info']['duration']): for pos in video_frame_positions(f['info']['duration']):
still_name = '%s.png' % pos frame_name = '%s.png' % pos
still_d = self.derivative(oshash, still_name) frame_d = self.derivative(oshash, frame_name)
if still_d['status'] == STATUS_NEW: if frame_d['status'] == STATUS_NEW:
self.derivative(oshash, still_name, STATUS_EXTRACTING) self.derivative(oshash, frame_name, STATUS_EXTRACTING)
if extract_still(f['path'], still_d['path'], pos): if extract_frame(f['path'], frame_d['path'], pos):
self.derivative(oshash, still_name, STATUS_AVAILABLE) self.derivative(oshash, frame_name, STATUS_AVAILABLE)
else: else:
self.derivative(oshash, still_name, STATUS_FAILED) self.derivative(oshash, frame_name, STATUS_FAILED)
elif name.endswith('.webm'): elif name.endswith('.webm'):
profile = name[:-5] profile = name[:-5]
print 'now lets go, are we having fun?' print 'now lets go, are we having fun?'
@ -476,7 +464,10 @@ class Database(object):
if extract_video(f['path'], derivative['path'], profile, f['info']): if extract_video(f['path'], derivative['path'], profile, f['info']):
self.derivative(oshash, name, STATUS_AVAILABLE) self.derivative(oshash, name, STATUS_AVAILABLE)
else: else:
self.derivative(oshash, name, STATUS_FAILED) if enc_status[oshash] == 'cancel':
self.derivative(oshash, name, STATUS_NEW)
else:
self.derivative(oshash, name, STATUS_FAILED)
#volumes #volumes
def update(self, path): def update(self, path):
@ -553,7 +544,7 @@ class Database(object):
conn, c = self.conn() conn, c = self.conn()
c.execute('DELETE FROM volume WHERE site=? AND user=? AND name=?', [site, user, name]) c.execute('DELETE FROM volume WHERE site=? AND user=? AND name=?', [site, user, name])
conn.commit() conn.commit()
#fixme, files could be still used by sub volumes #fixme, files could be frame used by sub volumes
#c.execute('DELETE FROM file WHERE path LIKE ?', ["%s%%"%path]) #c.execute('DELETE FROM file WHERE path LIKE ?', ["%s%%"%path])
def rename_volume(self, site, user, name, new_name): def rename_volume(self, site, user, name, new_name):
@ -637,7 +628,7 @@ class OxControl(Resource):
args[arg] = request.args.get(arg)[0] args[arg] = request.args.get(arg)[0]
return args return args
if request.args.get('key', '') != self.authkey: if request.args.get('key', [''])[0] != self.authkey:
response = {'status': '403', 'text': 'wrong authentication key provided'} response = {'status': '403', 'text': 'wrong authentication key provided'}
return json_response(request, response) return json_response(request, response)
@ -703,6 +694,15 @@ class OxControl(Resource):
threads.deferToThread(render, request) threads.deferToThread(render, request)
return NOT_DONE_YET return NOT_DONE_YET
if request.path == '/cancel':
"""
extract derivatives from videos
"""
global enc_status
oshash = request.args.get("oshash", [None])[0]
if oshash and oshash in enc_status:
enc_status[oshash] = 'cancel'
if request.path == '/extract': if request.path == '/extract':
""" """
extract derivatives from videos extract derivatives from videos
@ -719,7 +719,7 @@ class OxControl(Resource):
elif not 'duration' in f['info']: elif not 'duration' in f['info']:
response = {'status': 'unkown format, can not extract data'} response = {'status': 'unkown format, can not extract data'}
else: else:
if media == 'stills': if media == 'frames':
name = '%s.png'%video_frame_positions(f['info']['duration'])[0] name = '%s.png'%video_frame_positions(f['info']['duration'])[0]
elif media.endswith('.webm'): elif media.endswith('.webm'):
profile = media[:-5] profile = media[:-5]
@ -745,8 +745,8 @@ class OxControl(Resource):
response['progress'] = enc_status.get(oshash, 0) response['progress'] = enc_status.get(oshash, 0)
files = [f['path'] for f in self.db.derivatives(oshash)] files = [f['path'] for f in self.db.derivatives(oshash)]
if media == 'stills': if media == 'frames':
response['stills'] = filter(lambda f: f.endswith('.png'), files) response['frames'] = filter(lambda f: f.endswith('.png'), files)
else: else:
response['video'] = filter(lambda f: f.endswith(media), files) response['video'] = filter(lambda f: f.endswith(media), files)
if response['video']: response['video'] = response['video'][0] if response['video']: response['video'] = response['video'][0]
@ -767,7 +767,7 @@ class OxControl(Resource):
response['info'] = f['info'] response['info'] = f['info']
files = [f['location'] for f in self.db.derivatives(oshash)] files = [f['location'] for f in self.db.derivatives(oshash)]
response['video'] = filter(lambda f: f.endswith('.webm'), files) response['video'] = filter(lambda f: f.endswith('.webm'), files)
response['stills'] = filter(lambda f: f.endswith('.png'), files) response['frames'] = filter(lambda f: f.endswith('.png'), files)
return json_response(request, response) return json_response(request, response)
if request.path == '/stop': if request.path == '/stop':
@ -778,7 +778,7 @@ class OxControl(Resource):
response = {'status': 'ok'} response = {'status': 'ok'}
return json_response(request, response) return json_response(request, response)
return "<!DOCTYPE html><html>this is not for humans</html>" return "<!DOCTYPE html><html><head></head><body>this is not for humans</body></html>"
if __name__ == '__main__': if __name__ == '__main__':
db = 'dev.sqlite' db = 'dev.sqlite'
@ -789,35 +789,11 @@ if __name__ == '__main__':
port = 2620 port = 2620
interface = '127.0.0.1' interface = '127.0.0.1'
interface = '10.26.20.10'
interface = '0.0.0.0'
print 'http://%s:%d/' % (interface, port) print 'http://%s:%d/' % (interface, port)
root = OxControl(db) root = OxControl(db)
"""
username = root.db.get('username', 'fix')
password = root.db.get('password', 'me')
checker = InMemoryUsernamePasswordDatabaseDontUse()
checker.addUser(username, password)
class PublicHTMLRealm(object):
implements(IRealm)
def requestAvatar(self, avatarId, mind, *interfaces):
if IResource in interfaces:
return (IResource, root, lambda: None)
raise NotImplementedError()
portal = Portal(PublicHTMLRealm(), [checker])
credentialFactory = DigestCredentialFactory("md5", "oxbackend")
resource = HTTPAuthSessionWrapper(portal, [credentialFactory])
site = server.Site(resource)
"""
site = server.Site(root) site = server.Site(root)
reactor.listenTCP(port, site, interface=interface) reactor.listenTCP(port, site, interface=interface)
reactor.run() reactor.run()

View file

@ -12,9 +12,10 @@ const Ci = Components.interfaces;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://firefogg/uploader.jsm");
Components.utils.import("resource://ox/utils.jsm"); Components.utils.import("resource://ox/utils.jsm");
Components.utils.import("resource://ox/oxff.jsm"); Components.utils.import("resource://ox/oxff.jsm");
Components.utils.import("resource://ox/firefogg.jsm");
var OxFFFactory = var OxFFFactory =
{ {
@ -37,12 +38,12 @@ function OxFF() {
this.app = Cc["@mozilla.org/fuel/application;1"].getService(Ci.fuelIApplication); this.app = Cc["@mozilla.org/fuel/application;1"].getService(Ci.fuelIApplication);
this._site = this._window.document.location.hostname; this._site = this._window.document.location.hostname;
if(!this._site) { if (!this._site) {
this._site = 'localhost'; this._site = 'localhost';
} }
this.access(); this.access();
if(!oxff.get('username')) { if (!oxff.get('username')) {
oxff.set('username', ox.makeRandomString(8)); oxff.set('username', ox.makeRandomString(8));
oxff.set('password', ox.makeRandomString(8)); oxff.set('password', ox.makeRandomString(8));
} }
@ -133,7 +134,7 @@ OxFF.prototype = {
var msg = this.extensionID + ": "; var msg = this.extensionID + ": ";
for(var i=0;i<arguments.length;i++) { for(var i=0;i<arguments.length;i++) {
msg += arguments[i]; msg += arguments[i];
if(i+1<arguments.length) if (i+1<arguments.length)
msg += ', '; msg += ', ';
} }
this.app.console.log(msg); this.app.console.log(msg);
@ -153,13 +154,13 @@ OxFF.prototype = {
q.params.site = this._site; q.params.site = this._site;
this._access = false; this._access = false;
while(q.executeStep()) { while(q.executeStep()) {
if(q.row.access == 1) { if (q.row.access == 1) {
this._access = true; this._access = true;
} }
} }
q.finalize(); q.finalize();
if(request && !this._access) { if (request && !this._access) {
var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator); var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
var nsWindow = windowMediator.getMostRecentWindow("navigator:browser"); var nsWindow = windowMediator.getMostRecentWindow("navigator:browser");
var box = nsWindow.gBrowser.getNotificationBox(); var box = nsWindow.gBrowser.getNotificationBox();
@ -178,86 +179,50 @@ OxFF.prototype = {
} }
return this._access; return this._access;
}, },
api: function(action, data, callback) { api: function(action, data, callback) {
var _this = this; var _this = this;
if (typeof(data) == 'function') { if (typeof(data) == 'function') {
callback = data; callback = data;
data = {}; data = {};
} }
if(!this.canAccess()) if (!this.canAccess())
return false; return false;
data["key"] = this.authkey; data["key"] = this.authkey;
data["site"] = this._site; data["site"] = this._site;
data["user"] = this._user; data["user"] = this._user;
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
req.addEventListener("error", function(e) {
_this.startDaemon();
ox.setTimeout(function() { _this.api(action, data, callback); }, 1000);
}, false);
req.addEventListener("load", function(e) {
//links should have base prefixed or whatever proxy is used to access them, i.e. some api call
//var data = JSON.parse(e.target.responseText);
//callback(data);
callback(e.target.responseText);
}, false);
var base = this.base; //later base can not be a public property var base = this.base; //later base can not be a public property
var url = base + action; var url = base + action;
//req.open("POST", url, true, this.authkey, this.password); ox.request({
req.open("POST", url, true); url: url,
data: data,
try { load: function(e) {
var formData = Cc["@mozilla.org/files/formdata;1"].createInstance(Ci.nsIDOMFormData); //links should have base prefixed or whatever proxy is used to access them, i.e. some api call
if (data) { //var data = JSON.parse(e.target.responseText);
for(key in data) { //callback(data);
formData.append(key, data[key]); callback(e.target.responseText);
} },
} error: function(e) {
} catch(e) { _this.startDaemon();
//FF3.6 fallback ox.setTimeout(function() { _this.api(action, data, callback); }, 1000);
var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
var boundary = "--------XX" + Math.random();
var formData='';
if (data) {
for(key in data) {
if (data[key]) {
formData +="--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\""+key+"\"\r\n\r\n" +
data[key] + "\r\n";
}
}
} }
formData += "--" + boundary + "--\r\n"; });
req.setRequestHeader("Content-type", "multipart/form-data; boundary=" + boundary);
req.setRequestHeader("Content-length", formData.length);
var formData = converter.convertToInputStream(formData);
}
req.send(formData);
return true; return true;
}, },
extract: function(oshash, media, callback) { chunk_upload: function(url, data, file, callback, progress) {
return this.api('extract', {'oshash': oshash, 'media': media}, callback); if (!this._chunk_upload) {
},
upload: function(url, file, callback, progress) {
if(!this.chunk_upload) {
var _this = this; var _this = this;
this.chunk_upload = new FirefoggUploader(this, file, url, {}); this._chunk_upload = new FirefoggUploader(this, file, url, data);
this.chunk_upload.onProgress(function(upload_progress) { this._chunk_upload.onProgress(function(upload_progress) {
var info = {'status': 'uploading', 'progress': upload_progress}; var info = {'status': 'uploading', 'progress': upload_progress};
progress(JSON.stringify(info)); progress(JSON.stringify(info));
}); });
this.chunk_upload.ready = true; this._chunk_upload.ready = true;
this.chunk_upload.start(); this._chunk_upload.start();
this.chunk_upload.done(function() { this._chunk_upload.done(function() {
_this.chunk_upload = null; _this._chunk_upload = null;
var info = {'status': 'done', 'progress': 1}; var info = {'status': 'done', 'progress': 1};
callback(JSON.stringify(info)); callback(JSON.stringify(info));
}); });
@ -265,33 +230,106 @@ OxFF.prototype = {
} }
return false; return false;
}, },
uploadVideo: function(oshash, url, profile, callback, progress) { upload: function(options, callback, progress) {
var _this = this; var _this = this;
if(!this.canAccess()) if (!this.canAccess())
return false; return false;
if(progress) var options = JSON.parse(options);
if (options.data)
var formData = options.data;
else
var formData = {};
var url = options.url;
if (callback)
callback = callback.callback;
if (progress)
progress = progress.callback; progress = progress.callback;
var timer = ox.setInterval(function() { if (options.action == 'frames') {
_this.api('extract', {'oshash': oshash, 'media': profile}, function(result) { var timer = ox.setInterval(function() {
var data = JSON.parse(result); _this.api('extract', {oshash: options.oshash, media: 'frames'}, function(result) {
if (data.status == 'extracting') { var data = JSON.parse(result);
if(progress) { if (data.status == 'extracting') {
var info = {'status': data.status, 'progress': data.progress}; if (progress && data.progress >= 0) {
progress(JSON.stringify(info)); var info = {'status': data.status, 'progress': data.progress};
} progress(JSON.stringify(info));
} else { }
timer.cancel(); } else {
if(data.status == 'available') { timer.cancel();
_this.upload(url, data.video, callback.callback, progress); if (data.status == 'available') {
} else if(data.status == 'failed') { formData.frame = new Array();
_this.debug('failed'); for(i in data.frames) {
var f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
f.initWithPath(data.frames[i]);
formData.frame.push(f);
}
ox.request({
url: url,
data: formData,
load: function(e) {
if (callback)
callback(e.target.responseText);
},
error: function(e) {
_this.debug('uploading frames failed');
}
});
} else if (data.status == 'failed') {
_this.debug('uploading frames failed');
}
} }
});
}, 2000);
} else if (options.action == 'file') { //FIXME: should this use Firefogg style upload for lager files?
var conn = oxff.getDB();
var q = conn.createStatement("SELECT path FROM file WHERE oshash = :oshash LIMIT 1");
q.params.oshash = options.oshash;
if (q.executeStep())
var path = q.row.path;
q.finalize();
if (path) {
formData.file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile)
formData.file.initWithPath(path);
if (formData.file.exists()) { //FIXME: should check if more than one file with oshash is known
ox.request({
url: url,
data: formData,
load: function(e) {
if (callback)
callback(e.target.responseText);
},
error: function(e) {
_this.debug('uploading file failed');
}
});
return true;
} }
}); }
}, 2000); return false;
} else if (options.action == 'video') {
var timer = ox.setInterval(function() {
_this.api('extract', {oshash: options.oshash, media: options.profile}, function(result) {
var data = JSON.parse(result);
if (data.status == 'extracting') {
if (progress && data.progress >= 0) {
var info = {'status': data.status, 'progress': data.progress};
progress(JSON.stringify(info));
}
} else {
timer.cancel();
if (data.status == 'available') {
_this.chunk_upload(url, formData, data.video, callback, progress);
} else if (data.status == 'failed') {
_this.debug('uploading video failed');
}
}
});
}, 2000);
}
return true; return true;
}, },
files: function(volume, callback) { files: function(volume, callback) {
@ -301,7 +339,7 @@ OxFF.prototype = {
return this.api('get', {'oshash': oshash, 'media': media}, callback.callback); return this.api('get', {'oshash': oshash, 'media': media}, callback.callback);
}, },
login: function(user) { login: function(user) {
if(this._user==null) { if (this._user==null) {
this._user = user; this._user = user;
return true; return true;
} }
@ -313,9 +351,9 @@ OxFF.prototype = {
_this._user = null; _this._user = null;
_this._daemon = null; _this._daemon = null;
}); });
if (this.chunk_upload) { if (this._chunk_upload) {
this.chunk_upload.cancel(); this._chunk_upload.cancel();
this.chunk_upload = null; this._chunk_upload = null;
} }
return true; return true;
}, },
@ -324,7 +362,7 @@ OxFF.prototype = {
return true; return true;
}, },
setLocation: function(name) { setLocation: function(name) {
if(!this._access) { if (!this._access) {
return false; return false;
} }
const nsIFilePicker = Ci.nsIFilePicker; const nsIFilePicker = Ci.nsIFilePicker;
@ -358,11 +396,11 @@ OxFF.prototype = {
oxff.access(this._site, this._access); oxff.access(this._site, this._access);
}, },
canAccess: function() { canAccess: function() {
if(!this._access) { if (!this._access) {
this.debug('permission deinied'); this.debug('permission deinied');
return false; return false;
} }
if(!this._user) { if (!this._user) {
this.debug('login first'); this.debug('login first');
return false; return false;
} }
@ -371,7 +409,7 @@ OxFF.prototype = {
startDaemon: function() { startDaemon: function() {
var _this = this; var _this = this;
//if daemon is already running to not start it again //if daemon is already running to not start it again
if(this._daemon && this._daemon.isRunning) { if (this._daemon && this._daemon.isRunning) {
this.debug('daemon is still starting up, not starting again'); this.debug('daemon is still starting up, not starting again');
return false; return false;
} }

Binary file not shown.

View file

@ -10,16 +10,16 @@
<em:targetApplication> <em:targetApplication>
<Description> <Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>3.6</em:minVersion> <em:minVersion>4.0b1</em:minVersion>
<em:maxVersion>4.0b9</em:maxVersion> <em:maxVersion>4.0</em:maxVersion>
</Description> </Description>
</em:targetApplication> </em:targetApplication>
<em:requires> <em:requires>
<Description> <Description>
<em:id>firefogg@firefogg.org</em:id> <em:id>firefogg@firefogg.org</em:id>
<em:minVersion>1.2.10</em:minVersion> <em:minVersion>1.9.00</em:minVersion>
<em:maxVersion>1.2.*</em:maxVersion> <em:maxVersion>2.0.*</em:maxVersion>
</Description> </Description>
</em:requires> </em:requires>

View file

@ -1,299 +0,0 @@
// -*- coding: utf-8 -*-
// vi:si:et:sw=2:sts=2:ts=2:ft=js
let EXPORTED_SYMBOLS = [ "FirefoggUploader" ];
const Cc = Components.classes;
const Ci = Components.interfaces;
function FirefoggUploader(parent, path, url, data) {
this._parent = parent;
this._path = path;
this._url = url;
this._chunkUrl = false;
this._data = data;
this._status = 'initiated';
this.encodingStatus = 'encoding';
this.progress = 0.0;
this.chunkSize = 1024*1024; //1MB, is there a need to make this variable?
this.chunksUploaded = 0;
this.chunkStatus = {};
this._done_cb = function(upload) {};
this._onProgress = function(progress) {};
this.filename = 'video.ogv';
this.canceled = false;
this._retries = 0;
this._maxRetry = -1;
this.ready = false;
}
/*
Internal helper object dealing with chunked upload as defined at
http://firefogg.org/dev/chunk_post.html
*/
FirefoggUploader.prototype = {
start: function() {
var boundary = "--------XX" + Math.random();
this._status = 'requesting chunk upload';
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
//XMLHttpRequest status callbacks
var upload = this;
function transferComplete(evt) {
var response = {};
upload.responseText = evt.target.responseText;
try {
response = JSON.parse(evt.target.responseText);
//dump(evt.target.responseText);
} catch(e) {
dump('FAILED to parse response:\n\n');
dump(evt.target.responseText);
response = {};
}
if (response.maxRetry) {
upload._maxRetry = response.maxRetry;
}
upload._chunkUrl = response.uploadUrl;
if (upload._chunkUrl) {
//dump('now tracking chunk uploads to ' + upload._chunkUrl + '\n');
upload._status = 'uploading';
upload._progress = 0.0;
upload._uploadChunkId = 0;
upload.uploadChunk(0, false);
} else {
upload._status = 'upload failed, no upload url provided';
upload._progress = -1;
upload._parent.cancel();
return;
}
}
function transferFailed(evt) {
upload._status = 'uplaod failed';
upload._progress = -1;
upload.responseText = evt.target.responseText;
//dump(evt.target.responseText);
}
req.addEventListener("load", transferComplete, false);
req.addEventListener("error", transferFailed, false);
req.open("POST", this._url);
var formData = "";
for (key in this._data) {
formData += "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\""+key+"\"\r\n\r\n" + this._data[key] + "\r\n";
}
formData += "--" + boundary + "--\r\n";
var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
var formStream = converter.convertToInputStream(formData);
req.setRequestHeader("Content-type", "multipart/form-data; boundary=" + boundary);
req.setRequestHeader("Content-length", formStream.available());
req.send(formStream);
},
onProgress: function(cb) {
this._onProgress = cb;
},
done: function(done_cb) {
//used by Firefogg to indicate that encoding is done
//provies callback that is called once upload is done too
this.encodingStatus = 'done';
this._done_cb = done_cb;
},
uploadChunk: function(chunkId, last) {
/*
upload chunk with given chunkId, last indicated if this is the last chunk.
once upload is done next chunk is queued
*/
var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
file.initWithPath(this._path);
this.filename = file.leafName;
this.filename = this.filename.replace(/\(\.\d+\).ogv/, '.ogv');
this.filename = this.filename.replace(/\(\.\d+\).webm/, '.webm');
var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
fileStream.init(file, -1, -1, false);
fileStream.QueryInterface(Ci.nsISeekableStream);
var f = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
f.setInputStream(fileStream);
var bytesAvailable = fileStream.available();
var chunk = false;
var chunkOffset = chunkId * this.chunkSize;
this.progress = parseFloat(chunkOffset)/bytesAvailable;
if (last) {
//last chunk, read add remaining data
fileStream.seek(fileStream.NS_SEEK_SET, chunkOffset);
chunk = f.readBytes(bytesAvailable-chunkOffset);
}
else if (this.ready && bytesAvailable >= chunkOffset + this.chunkSize) {
//read next chunk
fileStream.seek(fileStream.NS_SEEK_SET, chunkOffset);
chunk = f.readBytes(this.chunkSize);
} else {
if (this.encodingStatus == 'done' && this._status == 'uploading') {
//uploading is done and last chunk is < chunkSize, upload remaining data
this._status = 'success';
this.uploadChunk(chunkId, true);
} else {
//encoding not ready. wait for 2 seconds and try again
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(
function(obj, chunkId, last) {
return function() { obj.uploadChunk(chunkId, last) }
}(this, chunkId, last),
2000, Ci.nsITimer.TYPE_ONE_SHOT);
}
f.close();
fileStream.close();
return;
}
f.close();
fileStream.close();
//FIXME: should this be checked so it does not get called again?
this.chunkStatus[chunkId] = {};
//dump('POST ' + this._chunkUrl + ' uploading chunk ' + chunkId +' ('+chunk.length+' bytes)\n');
var boundary = "--------XX" + Math.random();
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
var upload = this;
function transferComplete(evt) {
//if server resonds with valid {result=1} and status was is still 'uploading' go on to next chunk
upload.responseText = evt.target.responseText;
try {
var response = JSON.parse(evt.target.responseText);
} catch(e) {
dump('FAILED to parse response:\n\n');
dump(evt.target.responseText);
var response = {};
}
/*
if (upload.canceled) {
//FIXME: should we let the server know that upload was canceled?
}
else */
/*
dump('\n\n');
dump(evt.target.responseText);
dump('\n************\n\n');
*/
if (response.done == 1) {
//upload finished, update state and expose result
upload.resultUrl = response.resultUrl;
upload.progress = 1;
if (upload._done_cb)
upload._done_cb(upload);
//reset retry counter
upload._retries = 0;
}
else if (response.result == 1) {
//start uploading next chunk
upload._uploadChunkId = chunkId + 1;
upload.uploadChunk(upload._uploadChunkId, false);
//upload status
upload.chunkStatus[chunkId].progress = 1;
upload.chunkStatus[chunkId].loaded = evt.loaded;
upload.chunksUploaded++;
//reset retry counter
upload._retries = 0;
} else {
//failed to upload, try again in 3 second
//dump('could not parse response, failed to upload, will try again in 3 second\n');
//dump(evt.target.responseText);
if (upload.max_retry > 0 && upload._retries > upload.max_retry) {
upload.cancel();
upload._status = 'uplaod failed';
upload._progress = -1;
} else {
upload._retries++;
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(
function(obj, chunkId, last) {
return function() { obj.uploadChunk(chunkId, last); };
}(upload, chunkId, last), 3000, Ci.nsITimer.TYPE_ONE_SHOT);
}
}
}
function transferFailed(evt) {
//failed to upload, try again in 3 second
//dump('transferFailed, will try again in 3 second\n');
if (upload.max_retry > 0 && upload._retries > upload.max_retry) {
upload.cancel();
upload._status = 'uplaod failed';
upload._progress = -1;
} else {
upload._retries++;
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(
function(obj, chunkId, last) {
return function() { obj.uploadChunk(chunkId, last); };
}(upload, chunkId, last), 3000, Ci.nsITimer.TYPE_ONE_SHOT);
}
}
function updateProgress(evt) {
if (evt.lengthComputable) {
upload.progress = parseFloat(chunkOffset + evt.loaded)/bytesAvailable;
upload.chunkStatus[chunkId].loaded = evt.loaded;
upload.chunkStatus[chunkId].loaded = evt.total;
//dump('progress: chunk ' + chunkId + ' uploaded ' + evt.loaded + '\n');
upload._onProgress(upload.progress);
}
}
req.upload.addEventListener("progress", updateProgress, false);
req.addEventListener("load", transferComplete, false);
req.addEventListener("error", transferFailed, false);
req.open("POST", this._chunkUrl);
var chunk = "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"chunk\"; filename=\"" + this.filename + "\"\r\n" +
"Content-type: video/ogg\r\n\r\n" + chunk;
var chunkStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
chunkStream.setData(chunk, chunk.length);
// write done flag into stream
var formData = "\r\n";
var _data = {
chunkId: chunkId
};
if (last) {
_data['done'] = 1;
}
for (key in _data) {
formData += "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\""+key+"\"\r\n\r\n" + _data[key] + "\r\n";
}
formData += "--" + boundary + "--\r\n";
var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
var formStream = converter.convertToInputStream(formData);
var multiStream = Cc["@mozilla.org/io/multiplex-input-stream;1"].createInstance(Ci.nsIMultiplexInputStream);
multiStream.appendStream(chunkStream);
multiStream.appendStream(formStream);
//send mupltiplrex stream
req.setRequestHeader("Content-type", "multipart/form-data; boundary=" + boundary);
req.setRequestHeader("Content-length", multiStream.available());
//this._current_req = req;
req.send(multiStream);
},
cancel: function() {
this.canceled = true;
if (this._current_req) {
this._current_req.abort();
this._current_req = null;
}
},
}

View file

@ -51,6 +51,85 @@ let ox = {
} }
return s; return s;
}, },
request: function(options) {
var url = options.url,
data = options.data,
boundary = "--------XX" + Math.random();
var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
var multiStream = Cc["@mozilla.org/io/multiplex-input-stream;1"]
.createInstance(Ci.nsIMultiplexInputStream);
if(options.progress)
req.upload.addEventListener("progress", options.progress, false);
if(options.load)
req.addEventListener("load", options.load, false);
if(options.error)
req.addEventListener("error", options.error, false);
if(options.abort)
req.addEventListener("abort", options.abort, false);
function appendData(key, value) {
if(value.leafName) {
try {
var mimeService = Cc["@mozilla.org/mime;1"].createInstance(Ci.nsIMIMEService);
var mimeType = mimeService.getTypeFromFile(value);
}
catch(e) {
var mimeType = "application/octet-stream";
}
var filename = value.leafName;
var formData = "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"" + key +
"\"; filename=\"" + filename + "\"\r\n" +
"Content-type: " + mimeType + "\r\n\r\n";
var formData = converter.convertToInputStream(formData);
multiStream.appendStream(formData);
var fileStream = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
fileStream.init(value, 0x01, 0644, 0x04); // file is an nsIFile instance
multiStream.appendStream(fileStream);
formData = "\r\n";
formData = converter.convertToInputStream(formData);
multiStream.appendStream(formData);
} else {
var formData = "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\""+key+"\"\r\n\r\n" +
value + "\r\n";
formData = converter.convertToInputStream(formData);
multiStream.appendStream(formData);
}
}
if (data) {
for(key in data) {
if (typeof(data[key]) == 'object' && data[key].length>0) {
for(i in data[key])
appendData(key, data[key][i]);
} else if (data[key]) {
appendData(key, data[key]);
}
}
}
var formData = "--" + boundary + "--\r\n";
formData = converter.convertToInputStream(formData);
multiStream.appendStream(formData);
req.open("POST", url);
req.setRequestHeader("Content-type", "multipart/form-data; boundary=" + boundary);
req.setRequestHeader("Content-length", multiStream.available());
req.send(multiStream);
return req;
},
subprocess: function(command, options, callback) { subprocess: function(command, options, callback) {
if(!this.ipcService) { if(!this.ipcService) {
this.ipcService = Cc["@mozilla.org/process/ipc-service;1"] this.ipcService = Cc["@mozilla.org/process/ipc-service;1"]

View file

@ -27,7 +27,6 @@ interface nsIOxFF : nsISupports
boolean update(in oxICallback callback); boolean update(in oxICallback callback);
boolean files(in AString volume, in oxICallback callback); boolean files(in AString volume, in oxICallback callback);
boolean get(in AString oshash, in AString media, in oxICallback callback); boolean get(in AString oshash, in AString media, in oxICallback callback);
boolean extract(in AString oshash, in AString media, in oxICallback callback); boolean upload(in AString options, [optional] in oxICallback callback, [optional] in oxICallback progress);
boolean uploadVideo(in AString oshash, in AString url, in AString profile, in oxICallback callback, [optional] in oxICallback progress);
}; };

View file

@ -4,6 +4,20 @@
<script src="http://oxjs.org/js/jquery-1.4.2.min.js"></script> <script src="http://oxjs.org/js/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="/static/oxjs/build/js/ox.js"></script> <script type="text/javascript" src="/static/oxjs/build/js/ox.js"></script>
<script> <script>
function absolute_url(url) {
var base = document.location.href;
if (url.substring(0, 1) == '/') {
url = document.location.href.substring(0, document.location.href.length-document.location.pathname.length) + url;
}
else {
if(base.substring(base.length-1) == '/')
url = base + url;
else
url = base + '/' + url;
}
return url;
}
pandora = {}; pandora = {};
pandora.request_url = '/api/'; pandora.request_url = '/api/';
@ -102,31 +116,63 @@ function for_each_sorted(elements, callback) {
}); });
} }
function uploadFile(oshash) {
var url = absolute_url('/api/');
ox.upload(JSON.stringify({
url: url,
data: {action: 'upload', oshash: oshash},
oshash: oshash,
action: 'file'
}),
function(result) {
$('#' + oshash).css('background', '#fff');
console.log(result);
}
);
}
function extract(oshash) { function extract(oshash) {
console.log(oshash); /*
/*
ox.extract(oshash, 'stills', function(result) { ox.extract(oshash, 'stills', function(result) {
console.log(result); console.log(result);
}); });
*/ */
profile = '96p.webm'; var url = absolute_url('/api/');
var url = 'http://127.0.0.1:8000/api/upload/?profile='+profile+'&oshash='+oshash; ox.upload(JSON.stringify({
url: url,
$('<div>').attr('id', 'progress_'+oshash) data: {action: 'upload', oshash: oshash},
.appendTo('#' + oshash); oshash: oshash,
ox.uploadVideo(oshash, url, profile, function(result) { action: 'frames'
console.log(result); }),
$('#' + oshash).css('background', '#fff'); function(result) {
$('#' + oshash).parent().css('background', '#fff'); //FIXME: check result before posting video
$('#progress_'+oshash).remove(); profile = '96p.webm';
}, function(result) { var url = absolute_url('/api/upload/') + '?profile=' + profile + '&oshash=' + oshash;
try {
var data = JSON.parse(result); $('<div>').attr('id', 'progress_'+oshash)
$('#progress_'+oshash).html(data.status +': '+ data.progress); .appendTo('#' + oshash);
} catch(e) {
console.log('progress failed', e); ox.upload(JSON.stringify({
oshash: oshash,
action: 'video',
profile: profile,
url: url
}),
function(result) {
console.log(result);
$('#' + oshash).css('background', '#fff');
$('#' + oshash).parent().css('background', '#fff');
$('#progress_'+oshash).remove();
}, function(result) {
try {
var data = JSON.parse(result);
$('#progress_'+oshash).html(data.status +': '+ data.progress);
} catch(e) {
console.log('progress failed', e);
}
});
} }
}); );
} }
function update() { function update() {
@ -160,6 +206,12 @@ function update() {
}); });
$.each(result.data.file, function(i, oshash) { $.each(result.data.file, function(i, oshash) {
$('#' + oshash).css('background', 'blue'); $('#' + oshash).css('background', 'blue');
$('#' + oshash).unbind('click').click(function() {
$(this).unbind('click');
uploadFile(this.id);
return true;
});
}); });
} }
if (result.data.info.length>0) { if (result.data.info.length>0) {