2 pass encoding, merge encoding settings from server

This commit is contained in:
j 2017-08-30 14:46:34 +02:00
parent d8c4de473c
commit 3c10815073
2 changed files with 148 additions and 131 deletions

View file

@ -439,9 +439,12 @@ class Client(object):
if os.path.exists(p): if os.path.exists(p):
info = utils.avinfo(p) info = utils.avinfo(p)
profile = self.profile(info) profile = self.profile(info)
cmd = encode_cmd(p, self.media_cache(), profile, info) cmds = encode_cmd(p, self.media_cache(), profile, info)
cmd = [' ' in c and u'"%s"' % c or c for c in cmd] output = []
print(u' '.join(cmd)) for cmd in cmds:
cmd = [' ' in c and u'"%s"' % c or c for c in cmd]
output.append(u' '.join(cmd))
print(' && '.join(output))
def save_config(self): def save_config(self):
if not self._configfile: if not self._configfile:

View file

@ -1,18 +1,20 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4 # vi:si:et:sw=4:sts=4:ts=4
# GPL 2010 # GPL 2017
from __future__ import division, with_statement, print_function, absolute_import from __future__ import division, print_function, absolute_import
from glob import glob
import os import os
import shutil
import subprocess import subprocess
import sys import sys
import shutil
import ox import ox
from .utils import AspectRatio, run_command, basedir from .utils import AspectRatio, run_command, basedir
FFMPEG_SUPPORTS_VP9 = False
def command(program): def command(program):
local = os.path.join(basedir(), 'bin', program) local = os.path.join(basedir(), 'bin', program)
@ -27,33 +29,6 @@ def frame(video, target, position):
if fdir and not os.path.exists(fdir): if fdir and not os.path.exists(fdir):
os.makedirs(fdir) os.makedirs(fdir)
'''
# oxframe
cmd = ['oxframe', '-i', video, '-p', str(position), '-o', target]
print(cmd)
r = run_command(cmd)
return r == 0
'''
'''
# mplayer
cwd = os.getcwd()
target = os.path.abspath(target)
framedir = tempfile.mkdtemp()
os.chdir(framedir)
cmd = ['mplayer', '-noautosub', video, '-ss', str(position), '-frames', '2', '-vo', 'png:z=9', '-ao', 'null']
print(cmd)
r = run_command(cmd)
images = glob('%s%s*.png' % (framedir, os.sep))
if images:
shutil.move(images[-1], target)
r = 0
else:
r = 1
os.chdir(cwd)
shutil.rmtree(framedir)
return r == 0
'''
# ffmpeg # ffmpeg
pre = position - 2 pre = position - 2
if pre < 0: if pre < 0:
@ -67,14 +42,20 @@ def frame(video, target, position):
return r == 0 return r == 0
def supported_formats(): def supported_formats():
p = subprocess.Popen([command('ffmpeg'), '-codecs'], ffmpeg = command('ffmpeg')
stdout=subprocess.PIPE, stderr=subprocess.PIPE) if ffmpeg is None:
return None
p = subprocess.Popen([ffmpeg, '-codecs'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
stdout, stderr = p.communicate() stdout, stderr = p.communicate()
stdout = stdout.decode() stdout = stdout.decode('utf-8')
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,
'mp4': 'libx264' in stdout and 'libvo_aacenc' 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,
'h264': 'libx264' in stdout and 'DEA.L. aac' in stdout,
} }
def video_cmd(video, target, profile, info): def video_cmd(video, target, profile, info):
@ -92,8 +73,12 @@ def video_cmd(video, target, profile, info):
level / speedlevel level / speedlevel
bt? bt?
''' '''
support = supported_formats()
profile, format = profile.split('.') profile, format = profile.split('.')
bpp = 0.17 bpp = 0.17
video_codec = 'libvpx'
audio_codec = 'libvorbis'
if profile == '1080p': if profile == '1080p':
height = 1080 height = 1080
@ -153,22 +138,43 @@ def video_cmd(video, target, profile, info):
else: else:
height = 96 height = 96
#if support['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'])
dar = AspectRatio(info['video'][0]['display_aspect_ratio']) dar = AspectRatio(info['video'][0]['display_aspect_ratio'])
width = int(dar * height)
width += width % 2
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)
if info['video'][0]['framerate'] == '0:0': if info['video'][0]['framerate'] == '0:0':
fps = 25 fps = 25
else: else:
fps = AspectRatio(info['video'][0]['framerate']) fps = AspectRatio(info['video'][0]['framerate'])
width = int(dar * height)
width += width % 2
extra = [] extra = []
if fps == 50: if fps == 50:
fps = 25 fps = 25
@ -177,11 +183,8 @@ def video_cmd(video, target, profile, info):
fps = 30 fps = 30
extra += ['-r', '30'] extra += ['-r', '30']
fps = min(float(fps), 30) fps = min(float(fps), 30)
bitrate = height*width*fps*bpp/1000 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)
video_settings = [ video_settings = [
'-vb', '%dk' % bitrate, '-vb', '%dk' % bitrate,
@ -191,74 +194,54 @@ def video_cmd(video, target, profile, info):
] + extra ] + extra
if format == 'webm': if format == 'webm':
video_settings += [ video_settings += [
'-vcodec', 'libvpx', '-vcodec', video_codec,
'-quality', 'good', '-quality', 'good',
'-cpu-used', '0', '-cpu-used', '1' if video_codec == 'libvpx-vp9' else '0',
'-lag-in-frames', '16', '-lag-in-frames', '16',
'-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':
# quicktime does not support bpyramid
'''
video_settings += [ video_settings += [
'-vcodec', 'libx264', '-c:v', 'libx264',
'-flags', '+loop+mv4', '-preset:v', 'medium',
'-cmp', '256', '-profile:v', 'high',
'-partitions', '+parti4x4+parti8x8+partp4x4+partp8x8+partb8x8', '-level', '4.0',
'-me_method', 'hex',
'-subq', '7',
'-trellis', '1',
'-refs', '5',
'-bf', '3',
'-flags2', '+bpyramid+wpred+mixed_refs+dct8x8',
'-coder', '1',
'-me_range', '16',
'-keyint_min', '25', #FIXME: should this be related to fps?
'-sc_threshold','40',
'-i_qfactor', '0.71',
'-qmin', '10', '-qmax', '51',
'-qdiff', '4'
]
'''
video_settings += [
'-vcodec', 'libx264',
'-flags', '+loop+mv4',
'-cmp', '256',
'-partitions', '+parti4x4+parti8x8+partp4x4+partp8x8+partb8x8',
'-me_method', 'hex',
'-subq', '7',
'-trellis', '1',
'-refs', '5',
'-bf', '0',
'-flags2', '+mixed_refs',
'-coder', '0',
'-me_range', '16',
'-sc_threshold', '40',
'-i_qfactor', '0.71',
'-qmin', '10', '-qmax', '51',
'-qdiff', '4'
] ]
video_settings += ['-map', '0:%s,0:0' % info['video'][0]['id']] video_settings += ['-map', '0:%s,0:0' % info['video'][0]['id']]
audio_only = False
else: else:
video_settings = ['-vn'] video_settings = ['-vn']
audio_only = True
if info['audio']: # ignore some unsupported audio codecs
if info['audio'] and info['audio'][0].get('codec') in ('qdmc', ):
audio_settings = ['-an']
elif info['audio']:
if video_settings == ['-vn'] or not info['video']: if video_settings == ['-vn'] or not info['video']:
n = 0 n = 0
else: else:
n = 1 n = 1
audio_settings = []
# mix 2 mono channels into stereo(common for fcp dv mov files) # mix 2 mono channels into stereo(common for fcp dv mov files)
if len(info['audio']) == 2 \ audio_track = 0
if audio_track == 0 and len(info['audio']) == 2 \
and len(list(filter(None, [a['channels'] == 1 or None for a in info['audio']]))) == 2: and len(list(filter(None, [a['channels'] == 1 or None for a in info['audio']]))) == 2:
video_settings += [ audio_settings += [
'-filter_complex', '-filter_complex',
'[0:%s][0:%s] amerge' % (info['audio'][0]['id'], info['audio'][1]['id']) '[0:%s][0:%s] amerge' % (info['audio'][0]['id'], info['audio'][1]['id'])
] ]
mono_mix = True mono_mix = True
else: else:
video_settings += ['-map', '0:%s,0:%s' % (info['audio'][0]['id'], n)]
mono_mix = False mono_mix = False
audio_settings = ['-ar', str(audiorate), '-aq', str(audioquality)] audio_settings += ['-map', '0:%s,0:%s' % (info['audio'][audio_track]['id'], n)]
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:
@ -271,57 +254,88 @@ def video_cmd(video, target, profile, info):
if audiobitrate: if audiobitrate:
audio_settings += ['-ab', audiobitrate] audio_settings += ['-ab', audiobitrate]
if format == 'mp4': if format == 'mp4':
audio_settings += ['-acodec', 'libvo_aacenc'] audio_settings += ['-c:a', 'aac', '-strict', '-2']
elif audio_codec == 'libopus':
audio_settings += ['-c:a', 'libopus', '-frame_duration', '60']
else: else:
audio_settings += ['-acodec', 'libvorbis'] audio_settings += ['-c:a', audio_codec]
else: else:
audio_settings = ['-an'] audio_settings = ['-an']
cmd = [command('ffmpeg'), '-y', '-i', video, '-threads', '4', '-map_metadata', '-1'] \ base = [command('ffmpeg'),
+ audio_settings \ #'-nostats', '-loglevel', 'error',
+ video_settings '-y', '-i', video, '-threads', '4', '-map_metadata', '-1', '-sn']
if format == 'webm': if format == 'webm':
cmd += ['-f', 'webm', target] post = ['-f', 'webm', target]
elif format == 'mp4': elif format == 'mp4':
# mp4 needs postprocessing(qt-faststart), write to temp file post = ['-movflags', '+faststart', '-f', 'mp4', target]
cmd += ["%s.mp4" % target]
else: else:
cmd += [target] post = [target]
return cmd
cmds = []
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)
if not support.get(format):
if format == 'webm':
print("ffmpeg is compiled without WebM support")
elif format == 'mp4':
print("ffmpeg is compiled without H.264 support")
else:
print('trying to encode in an unsupported format')
sys.exit(1)
return cmds
def video(video, target, profile, info): def video(video, target, profile, info):
enc_target = target if target.endswith('.mp4') else target + '.tmp.webm' enc_target = target + 'tmp.mp4' if target.endswith('.mp4') else target + '.tmp.webm'
cmd = video_cmd(video, enc_target, profile, info) cmds = video_cmd(video, enc_target, profile, info)
profile, format = profile.split('.') profile, format = profile.split('.')
# r = run_command(cmd, -1) for cmd in cmds:
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) p = subprocess.Popen(cmd, stdin=subprocess.PIPE,
try: stdout=subprocess.PIPE,
p.wait() close_fds=True)
r = p.returncode try:
if format == 'mp4': p.wait()
cmd = [command('qt-faststart'), "%s.mp4" % target, target] r = p.returncode
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, print('Input:\t', video)
stdout=subprocess.PIPE if sys.platform.startswith('win') else open('/dev/null', 'w'), print('Output:\t', target)
stderr=subprocess.STDOUT) except KeyboardInterrupt:
p.communicate() p.kill()
os.unlink("%s.mp4" % target) r = 1
elif r == 0 and target != enc_target: if os.path.exists(enc_target):
shutil.move(enc_target, target) print("\n\ncleanup unfinished encoding:\nremoving", enc_target)
print('Input:\t', video) print("\n")
print('Output:\t', target) os.unlink(enc_target)
except KeyboardInterrupt: if os.path.exists(target):
p.kill() print("\n\ncleanup unfinished encoding:\nremoving", target)
r = 1 print("\n")
if os.path.exists(enc_target): os.unlink(target)
print("\n\ncleanup unfinished encoding:\nremoving", enc_target) if format == 'mp4' and os.path.exists("%s.mp4" % target):
print("\n") os.unlink("%s.mp4" % target)
os.unlink(enc_target) sys.exit(1)
if os.path.exists(target): if p.returncode != 0:
print("\n\ncleanup unfinished encoding:\nremoving", target) if os.path.exists(enc_target):
print("\n") os.unlink(enc_target)
os.unlink(target) if os.path.exists(target):
if format == 'mp4' and os.path.exists("%s.mp4" % target): os.unlink(target)
os.unlink("%s.mp4" % target) for f in glob('%s.log*' % enc_target):
sys.exit(1) os.unlink(f)
return False
else:
r = 0
if r == 0 and enc_target != target:
shutil.move(enc_target, target)
for f in glob('%s.log*' % enc_target):
os.unlink(f)
return r == 0 return r == 0