add uploadVideo, encode video and upload to given url using Firefogg Chunk upload api
This commit is contained in:
parent
166268a30c
commit
dd86adb0c2
6 changed files with 183 additions and 42 deletions
111
OxFF/bin/oxd.py
111
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'
|
||||
|
|
|
@ -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) {
|
||||
|
|
Binary file not shown.
|
@ -1,5 +1,4 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// vi:si:et:sw=4:sts=4:ts=4
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
let EXPORTED_SYMBOLS = [ "oxff" ];
|
||||
|
||||
|
@ -51,5 +50,15 @@ let oxff = {
|
|||
q.executeStep();
|
||||
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);
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -28,5 +28,6 @@ interface nsIOxFF : nsISupports
|
|||
boolean files(in AString volume, 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 uploadVideo(in AString oshash, in AString url, in AString profile, in oxICallback callback, [optional] in oxICallback progress);
|
||||
};
|
||||
|
||||
|
|
|
@ -67,8 +67,6 @@ var _ids = new Array();
|
|||
|
||||
var ox = new OxFF();
|
||||
ox.access(true);
|
||||
ox.login('username');
|
||||
|
||||
//ox.get('b2c8f0aa3a447d09', 'stills', 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() {
|
||||
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) {
|
|||
</script>
|
||||
</head>
|
||||
<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="update()" value="Update">
|
||||
<div id="files">
|
||||
|
|
Loading…
Reference in a new issue