pandora_render/ffmpeg.py

158 lines
4.7 KiB
Python
Raw Normal View History

2017-08-09 10:30:58 +00:00
#!/usr/bin/python3
2018-02-09 11:20:28 +00:00
from argparse import ArgumentParser
2017-09-06 12:48:37 +00:00
import math
2017-08-09 10:30:58 +00:00
import json
import os
import subprocess
import sys
2017-08-13 17:24:50 +00:00
import ox
2017-08-09 10:30:58 +00:00
def run(cmd):
2017-08-09 14:40:28 +00:00
#print(' '.join('"%s"' % c for c in cmd))
subprocess.call(cmd)
2017-08-09 10:30:58 +00:00
2018-02-09 11:20:28 +00:00
usage = "usage: %(prog)s [options] edit.json"
parser = ArgumentParser(usage=usage)
parser.add_argument('-r', '--resolution', dest='height', type=int,
help="output height, default 480", default=480)
parser.add_argument('-a', '--aspect', dest='aspect', type=str,
help="aspect ratio, default 16/9 (float or /)", default='16/9')
parser.add_argument('-c', '--config', dest='config',
help='config.json containing config',
default='~/.ox/client.json')
parser.add_argument('path', metavar='path', type=str,
help='edit.json path')
opts = parser.parse_args()
edit_json = opts.path
2017-08-09 10:30:58 +00:00
edit = json.load(open(edit_json))
2017-08-13 17:24:50 +00:00
render = './cache'
2017-08-11 10:08:34 +00:00
output = os.path.splitext(edit_json)[0] + '.mp4'
2018-02-09 11:20:28 +00:00
height = opts.height
if '/' in opts.aspect:
aspect = [int(p) for p in opts.aspect.split('/')]
aspect = aspect[0] / aspect[1]
else:
aspect = float(opts.aspect)
2017-08-09 10:30:58 +00:00
width = int(height * aspect)
2017-08-09 18:27:23 +00:00
width -= width % 2
2017-08-09 10:30:58 +00:00
2017-08-11 10:08:34 +00:00
if not os.path.exists(render):
os.makedirs(render)
2017-08-09 10:30:58 +00:00
files = []
2017-08-13 17:24:50 +00:00
edit_duration = 0
subtitles = []
position = 0
2017-08-09 10:30:58 +00:00
for clip in edit:
2017-08-09 14:40:28 +00:00
out = render + '/%s_%0.3f-%0.3f.ts' % (clip['oshash'], clip['in'], clip['out'])
2017-08-13 17:24:50 +00:00
edit_duration += (clip['out']-clip['in'])
2017-08-09 14:40:28 +00:00
if os.path.exists(out):
2018-02-09 11:20:28 +00:00
try:
duration = ox.avinfo(out)['duration']
except KeyError:
os.unlink(out)
duration = None
if duration:
files.append(out)
src_duration = clip['out']-clip['in']
if abs(src_duration-duration) > 1:
print(clip.get('annotation', clip['item']), src_duration, duration)
if clip.get('subtitles'):
subtitles.append({
'in': position,
'out': position+duration,
'value': clip['subtitles']
})
position += duration
continue
2017-08-09 10:30:58 +00:00
clip_aspect = clip['resolution'][0] / clip['resolution'][1]
if clip_aspect < aspect:
x = width
y = int(x / clip_aspect)
2017-08-09 18:27:23 +00:00
y -= y % 2
2017-08-09 10:30:58 +00:00
else:
y = height
x = int(y * clip_aspect)
2017-08-09 18:27:23 +00:00
x -= x % 2
2017-08-09 10:30:58 +00:00
vf = 'scale=%s:%s' % (x, y)
if x != width:
vf += ',crop=%s:%s' % (width, height) # crop center
elif y != height:
2017-08-09 17:02:37 +00:00
offset = int(((y - height) / 3))
2017-08-09 10:30:58 +00:00
vf += ',crop=w=%s:h=%s:x=0:y=%s' % (width, height, offset)
options = [
2017-08-13 17:24:50 +00:00
'-map_metadata', '-1',
2017-08-09 10:30:58 +00:00
'-vf', vf,
2017-08-09 14:40:28 +00:00
'-aspect', str(aspect),
2017-08-09 10:30:58 +00:00
'-c:v', 'libx264',
2017-08-09 17:02:37 +00:00
'-b:v', '2M',
'-preset:v', 'medium', '-profile:v', 'high', '-level:v', '4.0',
2017-08-13 17:24:50 +00:00
'-r', '25',
2017-08-09 10:30:58 +00:00
'-c:a', 'aac',
'-ar', '48000',
'-ac', '2',
'-b:a', '192k',
]
2017-09-06 12:48:37 +00:00
clip_duration = math.ceil((clip['out'] - clip['in']) / (1/25)) * 1/25
2018-02-09 11:20:28 +00:00
if not clip_duration:
print('skip empty clip', clip)
else:
files.append(out)
src_info = ox.avinfo(clip['path'])
if not src_info['audio']:
audio = ['-f', 'lavfi', '-i', 'anullsrc=channel_layout=stereo:sample_rate=48000']
audio_map = []
else:
audio = []
audio_map = ['-map', '0:0,0:0', '-map', '0:1,0:1']
cmd = [
'ffmpeg',
'-nostats', '-loglevel', 'error',
'-ss', str(clip['in']),
] + audio + [
'-i', clip['path']
] + audio_map + options + [
'-t', str(clip_duration),
out
]
run(cmd)
try:
duration = ox.avinfo(out)['duration']
except:
print('invalid file:', out)
print('try again?:')
print(' '.join(cmd).replace('-nostats -loglevel error', ''))
sys.exit(1)
print(clip, duration, clip['out']-clip['in'])
if clip.get('subtitles'):
subtitles.append({
'in': position,
'out': position+duration,
'value': clip['subtitles']
})
position += duration
print(out, duration, (clip['out'] - clip['in']))
2017-08-09 10:30:58 +00:00
2017-08-09 14:40:28 +00:00
txt = output + '.txt'
with open(txt, 'w') as fd:
fd.write('file ' + '\nfile '.join(files))
2018-02-09 11:20:28 +00:00
cmd = ['ffmpeg',
'-nostats', '-loglevel', 'error',
'-y', '-f', 'concat', '-safe', '0', '-i', txt, '-c', 'copy', output]
2017-08-09 10:30:58 +00:00
run(cmd)
2017-08-09 14:40:28 +00:00
os.unlink(txt)
2017-08-13 17:24:50 +00:00
2018-02-09 11:20:28 +00:00
if subtitles:
srt = output.replace('.mp4', '.srt')
with open(srt, 'wb') as fd:
fd.write(ox.srt.encode(subtitles))
2017-08-13 17:24:50 +00:00
duration = ox.avinfo(output)['duration']
2018-02-09 11:20:28 +00:00
print('file duration is %d, edit should be %d' % (duration, edit_duration))
print('created:', output)