AudioSplitterV2/bin/vfr/chapparse.py
2023-10-29 21:27:43 +09:00

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()