From dd86adb0c2a25ddf8b7fceaa8bfb1f661c24f7d5 Mon Sep 17 00:00:00 2001 From: j <0x006A@0x2620.org> Date: Fri, 13 Aug 2010 19:15:59 +0200 Subject: [PATCH] add uploadVideo, encode video and upload to given url using Firefogg Chunk upload api --- OxFF/bin/oxd.py | 111 +++++++++++++++++++++++++----------- OxFF/components/OxFF.js | 57 ++++++++++++++++-- OxFF/components/nsIOxFF.xpt | Bin 481 -> 479 bytes OxFF/modules/oxff.jsm | 13 ++++- src/nsIOxFF.idl | 1 + test/import.html | 43 +++++++++++++- 6 files changed, 183 insertions(+), 42 deletions(-) diff --git a/OxFF/bin/oxd.py b/OxFF/bin/oxd.py index faee24a..cfd05f6 100755 --- a/OxFF/bin/oxd.py +++ b/OxFF/bin/oxd.py @@ -44,6 +44,8 @@ VIDEO_PROFILES = [ ] +enc_status = {} + class AspectRatio(fractions.Fraction): def __new__(cls, numerator, denominator=None): if not denominator: @@ -178,6 +180,9 @@ def extract_still(video, target, position): return r == 0 def extract_video(video, target, profile, info): + global enc_status + enc_status[info['oshash']] = '' + if not os.path.exists(target): fdir = os.path.dirname(target) if not os.path.exists(fdir): @@ -198,53 +203,82 @@ def extract_video(video, target, profile, info): ''' if profile == '720p': height = 720 - width = int(dar * height) - profile_cmd = ['-vb', '2M', '-g', '250'] - if info['audio']: - profile_cmd += ['-ar', '48000', '-aq', '5'] + + audiorate = 48000 + audioquality = 5 + audiobitrate = None + audiochannels = None if profile == '480p': height = 480 - width = int(dar * height) - profile_cmd = ['-vb', '1400k', '-g', '250'] - if info['audio']: - profile_cmd += ['-ar', '44100', '-aq', '2'] - if 'channels' in info['audio'][0] and info['audio'][0]['channels'] > 2: - profile_cmd += ['-ac', '2'] + + audiorate = 44100 + audioquality = 2 + audiobitrate = None + audiochannels = 2 elif profile == '360p': height = 360 - width = int(dar * height) - profile_cmd = ['-vb', '768k'] - if info['audio']: - profile_cmd += ['-ar', '44100', '-aq', '1'] - if 'channels' in info['audio'][0] and info['audio'][0]['channels'] > 2: - profile_cmd += ['-ac', '2'] + + audiorate = 44100 + audioquality = 1 + audiobitrate = None + audiochannels = 1 + elif profile == '270p': + height = 270 + + audiorate = 44100 + audioquality = 0 + audiobitrate = None + audiochannels = 1 else: height = 96 - 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'] + audiorate = 22050 + audioquality = -1 + audiobitrate = '22k' + audiochannels = 1 + bpp = 0.17 + fps = AspectRatio(info['video'][0]['framerate']) + + width = int(dar * height) + + bitrate = height*width*fps*bpp/1000 aspect = dar.ratio #use 1:1 pixel aspect ratio if dar is close to that if abs(width/height - dar) < 0.02: aspect = '%s:%s' % (width, height) - cmd = ['./ffmpeg', '-y', '-threads', '2', - '-i', video - ] + profile_cmd + [ + if info['audio']: + audio_settings = ['-ar', str(audiorate), '-aq', str(audioquality)] + 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), '-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 + #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() r = p.returncode print "done" @@ -349,7 +383,7 @@ class Database(object): c.execute(sql, (oshash, )) for row in c: f['path'] = row[0] - f['info'] = json.loads(row[2]) + f['info'] = json.loads(row[1]) break return f @@ -407,7 +441,7 @@ class Database(object): conn.commit() 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['location'] = '/'.join(['http://127.0.0.1:2620/media', ] + prefix + [name, ]) return d @@ -599,6 +633,7 @@ class OxControl(Resource): return self.render_POST(request) def render_POST(self, request): + global enc_status print request.path, request.args def required_args(*required_args): args = {} @@ -705,11 +740,18 @@ class OxControl(Resource): }.get(derivative['status'], 'extracting') if derivative['status'] == STATUS_NEW: 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': response['stills'] = filter(lambda f: f.endswith('.png'), files) 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) if request.path == '/get': @@ -744,6 +786,7 @@ if __name__ == '__main__': db = sys.argv[1] if os.path.exists(sys.argv[2]): FFMPEG2THEORA = sys.argv[2] + port = 2620 interface = '127.0.0.1' interface = '10.26.20.10' diff --git a/OxFF/components/OxFF.js b/OxFF/components/OxFF.js index 9c85171..dfc42cb 100644 --- a/OxFF/components/OxFF.js +++ b/OxFF/components/OxFF.js @@ -14,6 +14,7 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); Components.utils.import("resource://ox/utils.jsm"); Components.utils.import("resource://ox/oxff.jsm"); +Components.utils.import("resource://ox/firefogg.jsm"); var OxFFFactory = { @@ -204,8 +205,7 @@ OxFF.prototype = { var base = this.base; //later base can not be a public property var url = base + action; - var formData = Cc["@mozilla.org/files/formdata;1"] - .createInstance(Ci.nsIDOMFormData); + var formData = Cc["@mozilla.org/files/formdata;1"].createInstance(Ci.nsIDOMFormData); if (data) { for(key in data) { formData.append(key, data[key]); @@ -220,6 +220,51 @@ OxFF.prototype = { extract: function(oshash, 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) { return this.api('files', {'volume': volume}, callback.callback); }, @@ -236,9 +281,13 @@ OxFF.prototype = { logout: function(user) { var _this = this; this.api('stop', function() { - _this._user = null; - _this._daemon = null; + _this._user = null; + _this._daemon = null; }); + if (this.chunk_upload) { + this.chunk_upload.cancel(); + this.chunk_upload = null; + } return true; }, update: function(callback) { diff --git a/OxFF/components/nsIOxFF.xpt b/OxFF/components/nsIOxFF.xpt index aa269f6917076814ddb1bf8a402a86e77ef7c5c2..53ba4e082f5015ba03bbf0e2e2b4cb291c214c08 100644 GIT binary patch literal 479 zcmazDaQ64*3aKne^~p@)<&t7#Vqj)qV7$-3z@P-gr42v=3=TjjHz36bVKOin8#TOD zQ*e7bF?Z*oc`Y}00mXTta-s!{(GP)gO}lIwl%Jn=S_9PP3Xx+B%q#W`E-fg?FDfZ! z0J8ln+|@jenTsTA!UD1fS;PWE#DNiFRzq28QE_H|9z#xk zdS)I&QEF~}S!!5*PHAo`kdv2~3*!{0miXi+CzgQJX6Ax|fT5r$KfNfmxR@a^IT=Ki z7NjJWq%xF&br&').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() { + var username = $('#username').val(); + var password = $('#password').val(); + ox.login(username); + pandora.request('login', {'username': username, 'password': password}); var updating = true; ox.update(function(result) { updating = false; @@ -119,6 +148,13 @@ function update() { var post = {'info': {}}; function highlight_resulsts(result) { $.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).parent().css('background', 'orange'); }); @@ -200,6 +236,9 @@ function setLocation(name) { + Username: + Password: +