forked from 0x2620/pandora
Add VP9/Opus support, use VP8 by default
- support vp9 and opus - switch to 2 pass encoding - use ffmpeg -movflags +faststart instead of qtfaststart
This commit is contained in:
parent
aaacc48259
commit
4785f314cb
3 changed files with 94 additions and 53 deletions
|
@ -142,6 +142,7 @@ def load_config(init=False):
|
||||||
formats = config.get('video', {}).get('formats')
|
formats = config.get('video', {}).get('formats')
|
||||||
if set(old_formats) != set(formats):
|
if set(old_formats) != set(formats):
|
||||||
sformats = supported_formats()
|
sformats = supported_formats()
|
||||||
|
settings.FFMPEG_SUPPORTS_VP9 = 'vp9' in sformats
|
||||||
if sformats:
|
if sformats:
|
||||||
for f in formats:
|
for f in formats:
|
||||||
if f not in sformats or not sformats[f]:
|
if f not in sformats or not sformats[f]:
|
||||||
|
|
|
@ -57,7 +57,10 @@ def supported_formats():
|
||||||
return {
|
return {
|
||||||
'ogg': 'libtheora' in stdout and 'libvorbis' in stdout,
|
'ogg': 'libtheora' in stdout and 'libvorbis' in stdout,
|
||||||
'webm': 'libvpx' in stdout and 'libvorbis' in stdout,
|
'webm': 'libvpx' in stdout and 'libvorbis' in stdout,
|
||||||
|
'vp8': 'libvpx' in stdout and 'libvorbis' in stdout,
|
||||||
|
'vp9': 'libvpx-vp9' in stdout and 'libopus' in stdout,
|
||||||
'mp4': 'libx264' in stdout and 'DEA.L. aac' in stdout,
|
'mp4': 'libx264' in stdout and 'DEA.L. aac' in stdout,
|
||||||
|
'h264': 'libx264' in stdout and 'DEA.L. aac' in stdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -78,6 +81,8 @@ def stream(video, target, profile, info, audio_track=0, flags={}):
|
||||||
'''
|
'''
|
||||||
profile, format = profile.split('.')
|
profile, format = profile.split('.')
|
||||||
bpp = 0.17
|
bpp = 0.17
|
||||||
|
video_codec = 'libvpx'
|
||||||
|
audio_codec = 'libvorbis'
|
||||||
|
|
||||||
if 'error' in info:
|
if 'error' in info:
|
||||||
return False, "Unsupported Format"
|
return False, "Unsupported Format"
|
||||||
|
@ -140,11 +145,25 @@ def stream(video, target, profile, info, audio_track=0, flags={}):
|
||||||
else:
|
else:
|
||||||
height = 96
|
height = 96
|
||||||
|
|
||||||
|
if settings.FFMPEG_SUPPORTS_VP9:
|
||||||
|
audio_codec = 'libopus'
|
||||||
|
video_codec = 'libvpx-vp9'
|
||||||
|
|
||||||
audiorate = 22050
|
audiorate = 22050
|
||||||
audioquality = -1
|
audioquality = -1
|
||||||
audiobitrate = '22k'
|
audiobitrate = '22k'
|
||||||
audiochannels = 1
|
audiochannels = 1
|
||||||
|
|
||||||
|
if format == 'webm' and audio_codec == 'libopus':
|
||||||
|
audiorate = 48000
|
||||||
|
if not audiobitrate:
|
||||||
|
audiobitrate = '%sk' % {
|
||||||
|
-1: 32, 0: 48, 1: 64, 2: 96, 3: 112, 4: 128,
|
||||||
|
5: 144, 6: 160, 7: 192, 8: 256, 9: 320, 10: 512,
|
||||||
|
}[audioquality]
|
||||||
|
if format == 'webm' and video_codec == 'libvpx-vp9':
|
||||||
|
bpp = 0.15
|
||||||
|
|
||||||
if info['video'] and 'display_aspect_ratio' in info['video'][0]:
|
if info['video'] and 'display_aspect_ratio' in info['video'][0]:
|
||||||
# dont make video bigger
|
# dont make video bigger
|
||||||
height = min(height, info['video'][0]['height'])
|
height = min(height, info['video'][0]['height'])
|
||||||
|
@ -197,11 +216,17 @@ def stream(video, target, profile, info, audio_track=0, flags={}):
|
||||||
]
|
]
|
||||||
if format == 'webm':
|
if format == 'webm':
|
||||||
video_settings += [
|
video_settings += [
|
||||||
|
'-c:v', video_codec,
|
||||||
'-deadline', 'good',
|
'-deadline', 'good',
|
||||||
'-cpu-used', '0',
|
'-cpu-used', '1' if video_codec == 'libvpx-vp9' else '0',
|
||||||
'-lag-in-frames', '16',
|
'-lag-in-frames', '25',
|
||||||
'-auto-alt-ref', '1',
|
'-auto-alt-ref', '1',
|
||||||
]
|
]
|
||||||
|
if video_codec == 'libvpx-vp9':
|
||||||
|
video_settings += [
|
||||||
|
'-tile-columns', '6',
|
||||||
|
'-frame-parallel', '1',
|
||||||
|
]
|
||||||
if format == 'mp4':
|
if format == 'mp4':
|
||||||
video_settings += [
|
video_settings += [
|
||||||
'-c:v', 'libx264',
|
'-c:v', 'libx264',
|
||||||
|
@ -232,7 +257,9 @@ def stream(video, target, profile, info, audio_track=0, flags={}):
|
||||||
else:
|
else:
|
||||||
video_settings += ['-map', '0:%s,0:%s' % (info['audio'][audio_track]['id'], n)]
|
video_settings += ['-map', '0:%s,0:%s' % (info['audio'][audio_track]['id'], n)]
|
||||||
mono_mix = False
|
mono_mix = False
|
||||||
audio_settings = ['-ar', str(audiorate), '-aq', str(audioquality)]
|
audio_settings = ['-ar', str(audiorate)]
|
||||||
|
if audio_codec != 'libopus':
|
||||||
|
audio_settings += ['-aq', str(audioquality)]
|
||||||
if mono_mix:
|
if mono_mix:
|
||||||
ac = 2
|
ac = 2
|
||||||
else:
|
else:
|
||||||
|
@ -246,31 +273,49 @@ def stream(video, target, profile, info, audio_track=0, flags={}):
|
||||||
audio_settings += ['-ab', audiobitrate]
|
audio_settings += ['-ab', audiobitrate]
|
||||||
if format == 'mp4':
|
if format == 'mp4':
|
||||||
audio_settings += ['-c:a', 'aac', '-strict', '-2']
|
audio_settings += ['-c:a', 'aac', '-strict', '-2']
|
||||||
|
elif audio_codec == 'libopus':
|
||||||
|
audio_settings += ['-c:a', 'libopus', '-frame_duration', '60']
|
||||||
else:
|
else:
|
||||||
audio_settings += ['-c:a', 'libvorbis']
|
audio_settings += ['-c:a', audio_codec]
|
||||||
else:
|
else:
|
||||||
audio_settings = ['-an']
|
audio_settings = ['-an']
|
||||||
|
|
||||||
cmd = [settings.FFMPEG,
|
cmds = []
|
||||||
|
|
||||||
|
base = [settings.FFMPEG,
|
||||||
'-nostats', '-loglevel', 'error',
|
'-nostats', '-loglevel', 'error',
|
||||||
'-y', '-i', video, '-threads', '4', '-map_metadata', '-1', '-sn'] \
|
'-y', '-i', video, '-threads', '4', '-map_metadata', '-1', '-sn']
|
||||||
+ audio_settings \
|
|
||||||
+ video_settings
|
|
||||||
|
|
||||||
if format == 'webm':
|
if format == 'webm':
|
||||||
enc_target = target + '.tmp.webm'
|
enc_target = target + '.tmp.webm'
|
||||||
|
elif format == 'mp4':
|
||||||
|
enc_target = target + '.tmp.mp4'
|
||||||
else:
|
else:
|
||||||
enc_target = target
|
enc_target = target
|
||||||
|
|
||||||
if format == 'webm':
|
if format == 'webm':
|
||||||
cmd += ['-f', 'webm', enc_target]
|
post = ['-f', 'webm', enc_target]
|
||||||
elif format == 'mp4':
|
elif format == 'mp4':
|
||||||
# mp4 needs postprocessing(qt-faststart), write to temp file
|
post = ['-movflags', '+faststart', '-f', 'mp4', enc_target]
|
||||||
cmd += ["%s.mp4" % enc_target]
|
|
||||||
else:
|
else:
|
||||||
cmd += [enc_target]
|
post = [target]
|
||||||
|
if video_settings != ['-vn']:
|
||||||
|
pass1_post = post[:]
|
||||||
|
pass1_post[-1] = '/dev/null'
|
||||||
|
if format == 'webm':
|
||||||
|
pass1_post = ['-speed', '4'] + pass1_post
|
||||||
|
post = ['-speed', '1'] + post
|
||||||
|
cmds.append(base + ['-an', '-pass', '1', '-passlogfile', '%s.log' % target]
|
||||||
|
+ video_settings + pass1_post)
|
||||||
|
cmds.append(base + ['-pass', '2', '-passlogfile', '%s.log' % target]
|
||||||
|
+ audio_settings + video_settings + post)
|
||||||
|
else:
|
||||||
|
cmds.append(base + audio_settings + video_settings + post)
|
||||||
|
|
||||||
# print(cmd)
|
# print(cmds)
|
||||||
|
n = 0
|
||||||
|
for cmd in cmds:
|
||||||
|
n += 1
|
||||||
p = subprocess.Popen(cmd, stdin=subprocess.PIPE,
|
p = subprocess.Popen(cmd, stdin=subprocess.PIPE,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
|
@ -278,23 +323,13 @@ def stream(video, target, profile, info, audio_track=0, flags={}):
|
||||||
stdout, stderr = p.communicate()
|
stdout, stderr = p.communicate()
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
t = "%s.mp4" % enc_target if format == 'mp4' else enc_target
|
if os.path.exists(enc_target):
|
||||||
if os.path.exists(t):
|
os.unlink(enc_target)
|
||||||
os.unlink(t)
|
|
||||||
if os.path.exists(target):
|
if os.path.exists(target):
|
||||||
os.unlink(target)
|
os.unlink(target)
|
||||||
stdout = stdout.replace('\r\n', '\n').replace('\r', '\n')
|
stdout = stdout.replace('\r\n', '\n').replace('\r', '\n')
|
||||||
return False, stdout
|
return False, stdout
|
||||||
if format == 'mp4':
|
if format == 'webm' and audio_only:
|
||||||
cmd = ['qt-faststart', "%s.mp4" % enc_target, enc_target]
|
|
||||||
# print(cmd)
|
|
||||||
p = subprocess.Popen(cmd, stdin=subprocess.PIPE,
|
|
||||||
stdout=open('/dev/null', 'w'),
|
|
||||||
stderr=subprocess.STDOUT,
|
|
||||||
close_fds=True)
|
|
||||||
p.communicate()
|
|
||||||
os.unlink("%s.mp4" % enc_target)
|
|
||||||
elif format == 'webm' and audio_only:
|
|
||||||
cmd = ['mkvmerge', '-w', '-o', target, '--cues', '-1:all', enc_target]
|
cmd = ['mkvmerge', '-w', '-o', target, '--cues', '-1:all', enc_target]
|
||||||
p = subprocess.Popen(cmd, stdin=subprocess.PIPE,
|
p = subprocess.Popen(cmd, stdin=subprocess.PIPE,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
|
@ -305,6 +340,8 @@ def stream(video, target, profile, info, audio_track=0, flags={}):
|
||||||
enc_target = target
|
enc_target = target
|
||||||
if p.returncode == 0 and enc_target != target:
|
if p.returncode == 0 and enc_target != target:
|
||||||
shutil.move(enc_target, target)
|
shutil.move(enc_target, target)
|
||||||
|
for f in glob('%s.log*' % target):
|
||||||
|
os.unlink(f)
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
|
@ -358,7 +395,8 @@ def ffmpeg_frame_cmd(video, frame, position, height=128):
|
||||||
|
|
||||||
def ffmpeg_version():
|
def ffmpeg_version():
|
||||||
p = subprocess.Popen([settings.FFMPEG],
|
p = subprocess.Popen([settings.FFMPEG],
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE, close_fds=True)
|
||||||
stdout, stderr = p.communicate()
|
stdout, stderr = p.communicate()
|
||||||
version = stderr.split(' ')[2].split('-')[0]
|
version = stderr.split(' ')[2].split('-')[0]
|
||||||
try:
|
try:
|
||||||
|
@ -414,16 +452,17 @@ def timeline(video, prefix, modes=None, size=None):
|
||||||
size = [64, 16]
|
size = [64, 16]
|
||||||
if isinstance(video, basestring):
|
if isinstance(video, basestring):
|
||||||
video = [video]
|
video = [video]
|
||||||
cmd = [
|
cmd = ['../bin/oxtimelines',
|
||||||
os.path.join(settings.PROJECT_ROOT, '../bin/oxtimelines'),
|
|
||||||
'-s', ','.join(map(str, reversed(sorted(size)))),
|
'-s', ','.join(map(str, reversed(sorted(size)))),
|
||||||
'-m', ','.join(modes),
|
'-m', ','.join(modes),
|
||||||
'-o', prefix,
|
'-o', prefix,
|
||||||
'-c', os.path.join(prefix, 'cuts.json'),
|
'-c', os.path.join(prefix, 'cuts.json'),
|
||||||
] + video
|
] + video
|
||||||
# print(cmd)
|
|
||||||
p = subprocess.Popen(cmd, stdin=subprocess.PIPE,
|
p = subprocess.Popen(cmd, stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||||
close_fds=True)
|
close_fds=True)
|
||||||
|
# print(cmd)
|
||||||
|
# p = subprocess.Popen(cmd)
|
||||||
p.wait()
|
p.wait()
|
||||||
|
|
||||||
|
|
||||||
|
@ -437,8 +476,8 @@ def average_color(prefix, start=0, end=0, mode='antialias'):
|
||||||
start = int(start * 25)
|
start = int(start * 25)
|
||||||
end = int(end * 25)
|
end = int(end * 25)
|
||||||
mode = 'timeline' + mode
|
mode = 'timeline' + mode
|
||||||
timelines = ox.sorted_strings(filter(lambda t: t!= '%s%s%sp.jpg'%(prefix, mode, height),
|
timelines = ox.sorted_strings(filter(lambda t: t != '%s%s%sp.jpg' % (prefix, mode, height),
|
||||||
glob("%s%s%sp*.jpg"%(prefix, mode, height))))
|
glob("%s%s%sp*.jpg" % (prefix, mode, height))))
|
||||||
for image in timelines:
|
for image in timelines:
|
||||||
start_offset = 0
|
start_offset = 0
|
||||||
if start and frames + 1500 <= start:
|
if start and frames + 1500 <= start:
|
||||||
|
@ -526,7 +565,7 @@ def timeline_strip(item, cuts, info, prefix):
|
||||||
timeline_image = Image.new('RGB', (timeline_width, timeline_height))
|
timeline_image = Image.new('RGB', (timeline_width, timeline_height))
|
||||||
if frame in cuts:
|
if frame in cuts:
|
||||||
c = cuts.index(frame)
|
c = cuts.index(frame)
|
||||||
if c +1 < len(cuts):
|
if c + 1 < len(cuts):
|
||||||
duration = cuts[c + 1] - cuts[c]
|
duration = cuts[c + 1] - cuts[c]
|
||||||
frames = math.ceil(duration / (video_width * timeline_height / video_height))
|
frames = math.ceil(duration / (video_width * timeline_height / video_height))
|
||||||
widths = divide(duration, frames)
|
widths = divide(duration, frames)
|
||||||
|
@ -545,7 +584,7 @@ def timeline_strip(item, cuts, info, prefix):
|
||||||
box = (0, top, video_width, top + height)
|
box = (0, top, video_width, top + height)
|
||||||
if _debug:
|
if _debug:
|
||||||
print(frame, 'cut', c, 'frame', s, frame, 'width', widths[s], box)
|
print(frame, 'cut', c, 'frame', s, frame, 'width', widths[s], box)
|
||||||
#FIXME: why does this have to be frame+1?
|
# FIXME: why does this have to be frame+1?
|
||||||
frame_image = Image.open(item.frame((frame+1)/fps))
|
frame_image = Image.open(item.frame((frame+1)/fps))
|
||||||
frame_image = frame_image.crop(box).resize((widths[s], timeline_height), Image.ANTIALIAS)
|
frame_image = frame_image.crop(box).resize((widths[s], timeline_height), Image.ANTIALIAS)
|
||||||
for x_ in range(widths[s]):
|
for x_ in range(widths[s]):
|
||||||
|
@ -569,8 +608,8 @@ def chop(video, start, end):
|
||||||
settings.FFMPEG,
|
settings.FFMPEG,
|
||||||
'-y',
|
'-y',
|
||||||
'-i', video,
|
'-i', video,
|
||||||
'-ss', '%.3f'%start,
|
'-ss', '%.3f' % start,
|
||||||
'-t', '%.3f'%t,
|
'-t', '%.3f' % t,
|
||||||
'-c:v', 'copy',
|
'-c:v', 'copy',
|
||||||
'-c:a', 'copy',
|
'-c:a', 'copy',
|
||||||
'-f', ext[1:],
|
'-f', ext[1:],
|
||||||
|
|
|
@ -165,6 +165,7 @@ LOGGING = {
|
||||||
AUTH_PROFILE_MODULE = 'user.UserProfile'
|
AUTH_PROFILE_MODULE = 'user.UserProfile'
|
||||||
AUTH_CHECK_USERNAME = True
|
AUTH_CHECK_USERNAME = True
|
||||||
FFMPEG = 'ffmpeg'
|
FFMPEG = 'ffmpeg'
|
||||||
|
FFMPEG_SUPPORTS_VP9 = True
|
||||||
|
|
||||||
#=========================================================================
|
#=========================================================================
|
||||||
#Pan.do/ra related settings settings
|
#Pan.do/ra related settings settings
|
||||||
|
|
Loading…
Reference in a new issue