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):
|
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'
|
||||||
|
|
|
@ -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.
|
@ -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);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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">
|
||||||
|
|
Loading…
Reference in a new issue