263 lines
No EOL
9.3 KiB
Python
263 lines
No EOL
9.3 KiB
Python
#!/usr/bin/env python
|
|
# chapparse.py
|
|
import sys, re, getopt, os
|
|
from string import Template
|
|
|
|
name = 'chapparse.py'
|
|
version = '0.4'
|
|
rat = re.compile('(\d+)(?:/|:)(\d+)')
|
|
chapre = re.compile("CHAPTER\d+=(\S+)",re.I)
|
|
x264 = 'x264-64'
|
|
ffmpeg = 'ffmpeg'
|
|
mkvmerge = 'mkvmerge'
|
|
avs2yuv = 'avs2yuv'
|
|
timeCodes = frameNumbers = merge = []
|
|
|
|
def main():
|
|
try:
|
|
opts, args = getopt.getopt(sys.argv[1:], "i:o:f:b:e:s:a:x:c:hmr",['help','avs=','test','x264opts='])
|
|
except getopt.GetoptError as err:
|
|
print(err)
|
|
help()
|
|
sys.exit()
|
|
|
|
set = dict(input='video.mkv',output='',audio='',index='',
|
|
fps='24000/1001',batch='',method='x264',resize='',avs='',mergeFiles=False,removeFiles=False,
|
|
x264opts='--preset placebo --crf 16 --level 41 --rc-lookahead 250',test=False,
|
|
x264=x264,ffmpeg=ffmpeg,mkvmerge=mkvmerge,avs2yuv=avs2yuv,chapters='chapters.txt',crop='0,0,0,0')
|
|
|
|
for o, v in opts:
|
|
if o == '-i':
|
|
set['input'] = v
|
|
elif o == '-o':
|
|
set['output'] = v[:-4]
|
|
elif o == '-f':
|
|
set['fps'] = v
|
|
elif o == '-b':
|
|
set['batch'] = v
|
|
elif o == '-e':
|
|
set['method'] = v
|
|
elif o == '-s':
|
|
set['resize'] = v
|
|
elif o == '-c':
|
|
set['crop'] = v
|
|
elif o in ('-x','--x264opts'):
|
|
set['x264opts'] = v
|
|
elif o == '-a':
|
|
set['audio'] = v
|
|
elif o in ('-h','--help'):
|
|
help()
|
|
sys.exit()
|
|
elif o == '-m':
|
|
set['mergeFiles'] = True
|
|
elif o == '-r':
|
|
set['removeFiles'] = True
|
|
elif o == '--avs':
|
|
set['avs'] = v
|
|
elif o == '--test':
|
|
set['test'] = True
|
|
else:
|
|
assert False, "unhandled option"
|
|
|
|
set['chapters'] = set['chapters'] if len(args) != 1 else args[0]
|
|
|
|
if set['output'] == '':
|
|
set['output'] = set['input'][:-4]+'.encode'
|
|
if set['avs'] == '' and set['method'] == 'avisynth':
|
|
set['avs'] = set['output']+'.avs'
|
|
if set['avs'] != '' and set['method'] == 'x264':
|
|
set['method'] = 'avisynth'
|
|
if set['batch'] == '':
|
|
set['batch'] = set['output']+'.bat'
|
|
if os.path.isfile(set['chapters']) != True:
|
|
print("You must set a valid OGM chapters file.")
|
|
sys.exit(2)
|
|
|
|
if set['test'] == True:
|
|
for key in sorted(set):
|
|
print(key.ljust(8),'=',set[key])
|
|
print()
|
|
|
|
timeStrings = parseOgm(args[0])
|
|
|
|
timeCodes = [time2ms(timeString) for timeString in timeStrings]
|
|
|
|
frameNumbers = [ms2frame(timeCode,set['fps']) for timeCode in timeCodes]
|
|
|
|
set['cmd'] = Template('${piper}"${x264}" ${x264opts} --demuxer y4m${end} - -o "${output}-part${part}.mkv"')
|
|
|
|
if set['method'] == 'avisynth':
|
|
set['avs'] = '"%s"' % set['avs']
|
|
if set['test'] == False:
|
|
set = writeAvisynth(set,frameNumbers)
|
|
else:
|
|
print('Writing avisynth script')
|
|
elif set['method'] == 'ffmpeg':
|
|
set['resize'] = ' -s '+set['resize'] if (set['method'] == 'ffmpeg' and set['resize'] != '') else ''
|
|
elif set['method'] == 'x264':
|
|
set['cmd'] = Template('"${x264}" ${x264opts}${seek}${end} $xinput -o "${output}-part${part}.mkv"')
|
|
set['index'] = '"%s.x264.ffindex"' % set['input'] if set['input'][-3:] in ('mkv','mp4','wmv') else ''
|
|
set['xinput'] = '"%s" --index %s' % (set['input'],set['index']) if set['index'] != '' else '"%s"' % set['input']
|
|
x264crop = 'crop:'+set['crop'] if (set['method'] == 'x264' and set['crop'] != '0,0,0,0') else ''
|
|
x264resize='resize:'+','.join(set['resize'].split('x')) if (set['method'] == 'x264' and set['resize'] != '') else ''
|
|
sep = '/' if (x264crop != '' and x264resize != '') else ''
|
|
set['x264opts'] = set['x264opts']+' --vf %s%s%s' % (x264crop,sep,x264resize) if (x264crop != '' or x264resize != '') else set['x264opts']
|
|
|
|
writeBatch(set,frameNumbers,timeStrings)
|
|
|
|
def help():
|
|
print("""
|
|
%s %s
|
|
Usage: chapparse.py [options] chapters.txt
|
|
chapters.txt is an OGM chapters file to get chapter points from whence to
|
|
separate the encodes
|
|
|
|
Options:
|
|
-i video.mkv
|
|
Video to be encoded
|
|
-o encode.mkv
|
|
Encoded video
|
|
-f 24000/1001
|
|
Frames per second
|
|
-s 1280x720
|
|
Resolution to resize to (no default)
|
|
-e x264
|
|
Method of resizing [avisynth,ffmpeg,x264]
|
|
-a audio.m4a
|
|
Audio to mux in the final file
|
|
-b encode.bat
|
|
Batch file with the instructions for chapter-separated encode
|
|
-x "--preset placebo --crf 16 --level 41 --rc-lookahead 250", --x264opts
|
|
x264 options (don't use --demuxer, --input, --output or --frames)
|
|
--avs encode.avs
|
|
If using avisynth method
|
|
-m
|
|
Merge parts
|
|
-r
|
|
Remove extra files
|
|
-h, --help
|
|
This help file""" % (name,version))
|
|
|
|
def time2ms(ts):
|
|
|
|
t = ts.split(':')
|
|
h = int(t[0]) * 3600000
|
|
m = h + int(t[1]) * 60000
|
|
ms = round(m + float(t[2]) * 1000)
|
|
|
|
return ms
|
|
|
|
def ms2frame(ms,fps):
|
|
|
|
s = ms / 1000
|
|
fps = rat.search(fps).groups() if rat.search(fps) else \
|
|
[re.search('(\d+)',fps).group(0),'1']
|
|
frame = round((int(fps[0])/int(fps[1])) * s)
|
|
|
|
return frame
|
|
|
|
def parseOgm(file):
|
|
|
|
timeStrings = []
|
|
|
|
with open(file) as chapFile:
|
|
for line in chapFile:
|
|
timeString = chapre.match(line)
|
|
if timeString != None:
|
|
timeStrings.append(timeString.group(1))
|
|
|
|
return timeStrings
|
|
|
|
def writeAvisynth(set,frameNumbers):
|
|
# needs dict with 'avs', 'input', 'resize' (if needed) and list with frameNumbers
|
|
if os.path.isfile(set['avs'][1:-1]) != True:
|
|
with open(set['avs'][1:-1],'w') as avs:
|
|
if set['input'][:-4] in ('.mkv','.wmv','.mp4'):
|
|
avs.write('FFVideoSource("%s")\n' % set['input'])
|
|
elif set['input'][:-4] == '.avi':
|
|
avs.write('AviSource("%s")\n' % set['input'])
|
|
elif set['input'] != '':
|
|
avs.write('DirectShowSource("%s")\n' % set['input'])
|
|
if set['resize'] != '':
|
|
avs.write('Spline36Resize(%s)\n' % ','.join(set['resize'].split('x')))
|
|
avs.write('+'.join(['Trim(%d,%d)' % (frameNumbers[i],frameNumbers[i+1]-1) for i in range(len(frameNumbers)-1)]))
|
|
avs.write('+Trim(%d,0)\n' % frameNumbers[-1])
|
|
else:
|
|
with open(set['avs'][1:-1],'a') as avs:
|
|
avs.write('\n')
|
|
avs.write('+'.join(['Trim(%d,%d)' % (frameNumbers[i],frameNumbers[i+1]-1) for i in range(len(frameNumbers)-1)]))
|
|
avs.write('+Trim(%d,0)\n' % frameNumbers[-1])
|
|
|
|
set['resize'] = ''
|
|
if set['input'][:-3] in ('mkv','wmv','mp4'):
|
|
set['index'] = '"%s.mkv.ffindex"' % set['output']
|
|
|
|
return set
|
|
|
|
def cmdMake(set,frameNumbers,timeStrings,i):
|
|
begin = frameNumbers[i]
|
|
frames = frameNumbers[i+1]-begin if i != len(frameNumbers)-1 else 0
|
|
|
|
if set['method'] == 'avisynth':
|
|
set['seek'] = ' -seek %d' % begin
|
|
elif set['method'] == 'ffmpeg':
|
|
set['seek'] = ' -ss %s' % timeStrings[i]
|
|
elif set['method'] == 'x264':
|
|
set['seek'] = ' --seek %d' % begin
|
|
if frames != 0:
|
|
if set['method'] == 'avisynth':
|
|
set['frames'] = ' -frames %d' % frames
|
|
elif set['method'] == 'ffmpeg':
|
|
set['frames'] = ' -vframes %d' % frames
|
|
elif set['method'] == 'x264':
|
|
set['frames'] = ''
|
|
set['end'] = ' --frames %d' % frames
|
|
else:
|
|
set['end'] = set['frames'] = ''
|
|
|
|
set['merge'] = '"%s-part%d.mkv"' % (set['output'],i+1)
|
|
|
|
set['part'] = i+1
|
|
|
|
if set['method'] == 'avisynth':
|
|
set['piper'] = Template('"${avs2yuv}"${seek}${frames} $avs -o - | ')
|
|
elif set['method'] == 'ffmpeg':
|
|
set['piper'] = Template('"${ffmpeg}" -i "${input}"${resize}${seek}${frames} -f yuv4mpegpipe -sws_fags spline - | ')
|
|
|
|
if set['method'] in ('avisynth','ffmpeg'):
|
|
set['piper'] = set['piper'].substitute(set)
|
|
|
|
return set
|
|
|
|
def writeBatch(set,frameNumbers,timeStrings):
|
|
if set['test'] == False:
|
|
with open(set['batch'],'w') as batch:
|
|
merge = []
|
|
if os.name == 'posix':
|
|
batch.write('#!/bin/sh\n\n')
|
|
for i in range(len(frameNumbers)):
|
|
set2 = cmdMake(set,frameNumbers,timeStrings,i)
|
|
batch.write(set['cmd'].substitute(set2)+'\n')
|
|
merge.append(set2['merge'])
|
|
|
|
if set['mergeFiles'] == True:
|
|
batch.write('\n"%s" -o "%s" %s --default-duration "1:%sfps"' % (set['mkvmerge'],set['output']+'.mkv',' +'.join(merge),set['fps']))
|
|
if set['audio'] != '':
|
|
batch.write(' -D --no-chapters "%s"' % set['audio'])
|
|
batch.write(' --chapters "%s"' % set['chapters'])
|
|
batch.write('\n')
|
|
rem = ' '.join(merge)
|
|
if set['removeFiles'] == True and os.name == 'nt':
|
|
batch.write('del %s' % rem)
|
|
elif set['removeFiles'] == True and os.name == 'posix':
|
|
batch.write('rm %s' % rem)
|
|
else:
|
|
print('Writing batch file')
|
|
#print('Example:',set['cmd'].format(cmdMake(set,frameNumbers,timeStrings,3)))
|
|
|
|
if __name__ == '__main__':
|
|
if len(sys.argv) > 1:
|
|
main()
|
|
else:
|
|
print('Usage: chapparse.py [options] chapters.txt')
|
|
sys.exit() |