add uploadVideo, encode video and upload to given url using Firefogg Chunk upload api

This commit is contained in:
j 2010-08-13 19:15:59 +02:00
parent 166268a30c
commit dd86adb0c2
6 changed files with 183 additions and 42 deletions

View file

@ -44,6 +44,8 @@ VIDEO_PROFILES = [
] ]
enc_status = {}
class AspectRatio(fractions.Fraction): class AspectRatio(fractions.Fraction):
def __new__(cls, numerator, denominator=None): def __new__(cls, numerator, denominator=None):
if not denominator: if not denominator:
@ -178,6 +180,9 @@ def extract_still(video, target, position):
return r == 0 return r == 0
def extract_video(video, target, profile, info): def extract_video(video, target, profile, info):
global enc_status
enc_status[info['oshash']] = ''
if not os.path.exists(target): if not os.path.exists(target):
fdir = os.path.dirname(target) fdir = os.path.dirname(target)
if not os.path.exists(fdir): if not os.path.exists(fdir):
@ -198,53 +203,82 @@ def extract_video(video, target, profile, info):
''' '''
if profile == '720p': if profile == '720p':
height = 720 height = 720
width = int(dar * height)
profile_cmd = ['-vb', '2M', '-g', '250'] audiorate = 48000
if info['audio']: audioquality = 5
profile_cmd += ['-ar', '48000', '-aq', '5'] audiobitrate = None
audiochannels = None
if profile == '480p': if profile == '480p':
height = 480 height = 480
width = int(dar * height)
profile_cmd = ['-vb', '1400k', '-g', '250'] audiorate = 44100
if info['audio']: audioquality = 2
profile_cmd += ['-ar', '44100', '-aq', '2'] audiobitrate = None
if 'channels' in info['audio'][0] and info['audio'][0]['channels'] > 2: audiochannels = 2
profile_cmd += ['-ac', '2']
elif profile == '360p': elif profile == '360p':
height = 360 height = 360
width = int(dar * height)
profile_cmd = ['-vb', '768k'] audiorate = 44100
if info['audio']: audioquality = 1
profile_cmd += ['-ar', '44100', '-aq', '1'] audiobitrate = None
if 'channels' in info['audio'][0] and info['audio'][0]['channels'] > 2: audiochannels = 1
profile_cmd += ['-ac', '2'] elif profile == '270p':
height = 270
audiorate = 44100
audioquality = 0
audiobitrate = None
audiochannels = 1
else: else:
height = 96 height = 96
audiorate = 22050
audioquality = -1
audiobitrate = '22k'
audiochannels = 1
bpp = 0.17
fps = AspectRatio(info['video'][0]['framerate'])
width = int(dar * height) width = int(dar * height)
profile_cmd = ['-vb', '96k', '-g', '50']
if info['audio']:
profile_cmd += ['-ar', '22050', '-ac', '1', '-aq', '-1']
if 'channels' in info['audio'][0] and info['audio'][0]['channels'] > 1:
profile_cmd += ['-ac', '1']
if info['audio']:
profile_cmd +=['-acodec', 'libvorbis']
bitrate = height*width*fps*bpp/1000
aspect = dar.ratio aspect = dar.ratio
#use 1:1 pixel aspect ratio if dar is close to that #use 1:1 pixel aspect ratio if dar is close to that
if abs(width/height - dar) < 0.02: if abs(width/height - dar) < 0.02:
aspect = '%s:%s' % (width, height) aspect = '%s:%s' % (width, height)
cmd = ['./ffmpeg', '-y', '-threads', '2', if info['audio']:
'-i', video audio_settings = ['-ar', str(audiorate), '-aq', str(audioquality)]
] + profile_cmd + [ if audiochannels and 'channels' in info['audio'][0] and info['audio'][0]['channels'] > audiochannels:
audio_settings += ['-ac', str(audiochannels)]
if audiobitrate:
audio_settings += ['-ab', audiobitrate]
audio_settings +=['-acodec', 'libvorbis']
else:
audio_settings = ['-an']
video_settings = [
'-vb', '%dk'%bitrate, '-g', '%d' % int(fps*2),
'-s', '%dx%d'%(width, height), '-s', '%dx%d'%(width, height),
'-aspect', aspect, '-aspect', aspect,
'-f','webm', ]
target] ffmpeg = FFMPEG2THEORA.replace('2theora', '')
cmd = [ffmpeg, '-y', '-threads', '2', '-i', video] \
+ audio_settings \
+ video_settings \
+ ['-f','webm', target]
print cmd print cmd
#r = run_command(cmd, -1) #r = run_command(cmd, -1)
p = subprocess.Popen(cmd, stdin=subprocess.PIPE) p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
line = p.stderr.readline()
while line:
if line.startswith('frame='):
frames = line.split('=')[1].strip().split(' ')[0]
enc_status[info['oshash']] = (float(frames) / fps) / info['duration']
line = p.stderr.readline()
p.wait() p.wait()
r = p.returncode r = p.returncode
print "done" print "done"
@ -349,7 +383,7 @@ class Database(object):
c.execute(sql, (oshash, )) c.execute(sql, (oshash, ))
for row in c: for row in c:
f['path'] = row[0] f['path'] = row[0]
f['info'] = json.loads(row[2]) f['info'] = json.loads(row[1])
break break
return f return f
@ -407,7 +441,7 @@ class Database(object):
conn.commit() conn.commit()
prefix = hash_prefix(oshash) prefix = hash_prefix(oshash)
path_prefix = os.path.join(self.get('media_cache', 'media'), *prefix) path_prefix = os.path.join(self.get('media_cache', '/tmp/media'), *prefix)
d['path'] = os.path.join(path_prefix, name) d['path'] = os.path.join(path_prefix, name)
d['location'] = '/'.join(['http://127.0.0.1:2620/media', ] + prefix + [name, ]) d['location'] = '/'.join(['http://127.0.0.1:2620/media', ] + prefix + [name, ])
return d return d
@ -599,6 +633,7 @@ class OxControl(Resource):
return self.render_POST(request) return self.render_POST(request)
def render_POST(self, request): def render_POST(self, request):
global enc_status
print request.path, request.args print request.path, request.args
def required_args(*required_args): def required_args(*required_args):
args = {} args = {}
@ -705,11 +740,18 @@ class OxControl(Resource):
}.get(derivative['status'], 'extracting') }.get(derivative['status'], 'extracting')
if derivative['status'] == STATUS_NEW: if derivative['status'] == STATUS_NEW:
self.db.extract.put((oshash, name)) self.db.extract.put((oshash, name))
files = [f['location'] for f in self.db.derivatives(oshash)]
if derivative['status'] == STATUS_EXTRACTING:
response['progress'] = enc_status.get(oshash, 0)
files = [f['path'] for f in self.db.derivatives(oshash)]
if media == 'stills': if media == 'stills':
response['stills'] = filter(lambda f: f.endswith('.png'), files) response['stills'] = filter(lambda f: f.endswith('.png'), files)
else: else:
response['video'] = filter(lambda f: f.endswith('.webm'), files) response['video'] = filter(lambda f: f.endswith(media), files)
if response['video']: response['video'] = response['video'][0]
else:
del response['video']
return json_response(request, response) return json_response(request, response)
if request.path == '/get': if request.path == '/get':
@ -744,6 +786,7 @@ if __name__ == '__main__':
db = sys.argv[1] db = sys.argv[1]
if os.path.exists(sys.argv[2]): if os.path.exists(sys.argv[2]):
FFMPEG2THEORA = sys.argv[2] FFMPEG2THEORA = sys.argv[2]
port = 2620 port = 2620
interface = '127.0.0.1' interface = '127.0.0.1'
interface = '10.26.20.10' interface = '10.26.20.10'

View file

@ -14,6 +14,7 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.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 =
{ {
@ -204,8 +205,7 @@ OxFF.prototype = {
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;
var formData = Cc["@mozilla.org/files/formdata;1"] var formData = Cc["@mozilla.org/files/formdata;1"].createInstance(Ci.nsIDOMFormData);
.createInstance(Ci.nsIDOMFormData);
if (data) { if (data) {
for(key in data) { for(key in data) {
formData.append(key, data[key]); formData.append(key, data[key]);
@ -220,6 +220,51 @@ OxFF.prototype = {
extract: function(oshash, media, callback) { extract: function(oshash, media, callback) {
return this.api('extract', {'oshash': oshash, 'media': media}, callback); return this.api('extract', {'oshash': oshash, 'media': media}, callback);
}, },
upload: function(url, file, callback, progress) {
if(!this.chunk_upload) {
var _this = this;
this.chunk_upload = new FirefoggUploader(this, file, url, {});
this.chunk_upload.onProgress(function(upload_progress) {
var info = {'status': 'uploading', 'progress': upload_progress};
progress(JSON.stringify(info));
});
this.chunk_upload.ready = true;
this.chunk_upload.start();
this.chunk_upload.done(function() {
_this.chunk_upload = null;
var info = {'status': 'done', 'progress': 1};
callback(JSON.stringify(info));
});
return true;
}
return false;
},
uploadVideo: function(oshash, url, profile, callback, progress) {
var _this = this;
if(progress)
progress = progress.callback;
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(function() {
return _this.api('extract', {'oshash': oshash, 'media': profile}, function(result) {
var data = JSON.parse(result);
if (data.status == 'extracting') {
if(progress) {
var info = {'status': data.status, 'progress': data.progress};
progress(JSON.stringify(info));
}
} else {
timer.cancel();
if(data.status == 'available') {
_this.upload(url, data.video, callback.callback, progress);
} else if(data.status == 'failed') {
_this.debug('failed');
}
}
});
} ,2000, Ci.nsITimer.TYPE_REPEATING_SLACK);
return true;
},
files: function(volume, callback) { files: function(volume, callback) {
return this.api('files', {'volume': volume}, callback.callback); return this.api('files', {'volume': volume}, callback.callback);
}, },
@ -239,6 +284,10 @@ OxFF.prototype = {
_this._user = null; _this._user = null;
_this._daemon = null; _this._daemon = null;
}); });
if (this.chunk_upload) {
this.chunk_upload.cancel();
this.chunk_upload = null;
}
return true; return true;
}, },
update: function(callback) { update: function(callback) {

Binary file not shown.

View file

@ -1,5 +1,4 @@
// -*- coding: utf-8 -*- /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
// vi:si:et:sw=4:sts=4:ts=4
let EXPORTED_SYMBOLS = [ "oxff" ]; let EXPORTED_SYMBOLS = [ "oxff" ];
@ -51,5 +50,15 @@ let oxff = {
q.executeStep(); q.executeStep();
q.finalize(); q.finalize();
}, },
extractVideo: function(oshash, url, profile, callback, progress) {
var conn = this.getDB();
var q = conn.createStatement("SELECT path FROM file WHERE oshash = :oshash");
q.params.oshash = oshash;
if (q.executeStep())
var path = q.row.path;
q.finalize();
ox.subprocess(command, options, callback);
},
}; };

View file

@ -28,5 +28,6 @@ interface nsIOxFF : nsISupports
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 extract(in AString oshash, in AString media, in oxICallback callback);
boolean uploadVideo(in AString oshash, in AString url, in AString profile, in oxICallback callback, [optional] in oxICallback progress);
}; };

View file

@ -67,8 +67,6 @@ var _ids = new Array();
var ox = new OxFF(); var ox = new OxFF();
ox.access(true); ox.access(true);
ox.login('username');
//ox.get('b2c8f0aa3a447d09', 'stills', function(result) { console.log(result);}); //ox.get('b2c8f0aa3a447d09', 'stills', function(result) { console.log(result);});
//ox.files(function(result) { console.log(result);}); //ox.files(function(result) { console.log(result);});
@ -104,7 +102,38 @@ function for_each_sorted(elements, callback) {
}); });
} }
function extract(oshash) {
console.log(oshash);
/*
ox.extract(oshash, 'stills', function(result) {
console.log(result);
});
*/
profile = '96p.webm';
var url = 'http://127.0.0.1:8000/api/upload/?profile='+profile+'&oshash='+oshash;
$('<div>').attr('id', 'progress_'+oshash)
.appendTo('#' + oshash);
ox.uploadVideo(oshash, url, profile, 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() {
var username = $('#username').val();
var password = $('#password').val();
ox.login(username);
pandora.request('login', {'username': username, 'password': password});
var updating = true; var updating = true;
ox.update(function(result) { ox.update(function(result) {
updating = false; updating = false;
@ -119,6 +148,13 @@ function update() {
var post = {'info': {}}; var post = {'info': {}};
function highlight_resulsts(result) { function highlight_resulsts(result) {
$.each(result.data.data, function(i, oshash) { $.each(result.data.data, function(i, oshash) {
if($('#' + oshash).css('background') != 'red') {
$('#' + oshash).unbind('click').click(function() {
$(this).unbind('click');
extract(this.id);
return true;
});
}
$('#' + oshash).css('background', 'red'); $('#' + oshash).css('background', 'red');
$('#' + oshash).parent().css('background', 'orange'); $('#' + oshash).parent().css('background', 'orange');
}); });
@ -200,6 +236,9 @@ function setLocation(name) {
</script> </script>
</head> </head>
<body> <body>
Username: <input type="text" id="username" value="">
Password: <input type="password" id="password" value="">
<br>
<input type="button" onClick="setLocation('test')" value="Set Location"> <input type="button" onClick="setLocation('test')" value="Set Location">
<input type="button" onClick="update()" value="Update"> <input type="button" onClick="update()" value="Update">
<div id="files"> <div id="files">