2 pass encoding, merge encoding settings from server
This commit is contained in:
parent
d8c4de473c
commit
3c10815073
2 changed files with 148 additions and 131 deletions
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue