Difference between revisions of "Transcode Mpeg2 to H264"
Line 81: | Line 81: | ||
output = task('-i "%s"' % tmpfile, | output = task('-i "%s"' % tmpfile, | ||
'-c:v libx264', | '-c:v libx264', | ||
− | '-preset:v | + | '-preset:v slow', |
'-crf:v 18', | '-crf:v 18', | ||
'-threads 0', | '-threads 0', |
Revision as of 02:01, 28 February 2015
This python script is designed to be used as a User Job of the form "/<path to script>/transcode-h264.py %JOBID%" and is known to work on Mythtv 0.27. I got the idea for this script from the shell script referred to in this thread:
http://www.gossamer-threads.com/lists/mythtv/users/578181
I used Raymond Wagner's transcode wrapper stub http://www.mythtv.org/wiki/Transcode_wrapper_stub as the starting point for converting the shell script to python. If a cutlist is found mythtranscode will be used to remove the marked segments and ffmpeg is used to transcode the result to h264. The original recording is replaced (example: 1501_20141014000000.mpg becomes 1501_20141014000000.mp4), the transcoded flag is set, the old preview files (*.png) are removed, and the seektable is cleared.
I compiled ffmpeg using these instructions https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu to make sure I had the latest features.
--Mike Stucky 21:09, 26 February 2015 (UTC)
#!/usr/bin/env python # -*- coding: UTF-8 -*- # 2015 Michael Stucky # This script is based on Raymond Wagner's transcode wrapper stub. # Designed to be a USERJOB of the form </path to script/transcode-h264.py %JOBID%> from MythTV import Job, Recorded, System, MythDB, findfile, MythError, MythLog, datetime from optparse import OptionParser from glob import glob from shutil import copyfile import sys import os import errno import time transcoder = '/usr/bin/ffmpeg' flush_commskip = True build_seektable = True def runjob(jobid=None, chanid=None, starttime=None): db = MythDB() if jobid: job = Job(jobid, db=db) chanid = job.chanid starttime = job.starttime rec = Recorded((chanid, starttime), db=db) sg = findfile('/'+rec.basename, rec.storagegroup, db=db) if sg is None: print 'Local access to recording not found.' sys.exit(1) infile = os.path.join(sg.dirname, rec.basename) tmpfile = '%s.tmp' % infile.rsplit('.',1)[0] outfile = '%s.mp4' % infile.rsplit('.',1)[0] # reformat 'starttime' for use with mythtranscode/ffmpeg/mythcommflag starttime = str(starttime.utcisoformat().replace(u':', '').replace(u' ', '').replace(u'T', '').replace('-', '')) # Lossless transcode to strip cutlist if rec.cutlist == 1: if jobid: job.update({'status':4, 'comment':'Removing Cutlist'}) task = System(path='mythtranscode', db=db) try: output = task('--chanid "%s"' % chanid, '--starttime "%s"' % starttime, '--mpeg2', '--honorcutlist', '-o "%s"' % tmpfile, '2> /dev/null') except MythError, e: print 'Command failed with output:\n%s' % e.stderr if jobid: job.update({'status':304, 'comment':'Removing Cutlist failed'}) sys.exit(e.retcode) else: copyfile('%s' % infile, '%s' % tmpfile) # Transcode to mp4 if jobid: job.update({'status':4, 'comment':'Transcoding to mp4'}) task = System(path=transcoder, db=db) try: output = task('-i "%s"' % tmpfile, '-c:v libx264', '-preset:v slow', '-crf:v 18', '-threads 0', '-c:a copy', '-metadata:s:a:0', 'language="eng"', '"%s"' % outfile, '2> /dev/null') except MythError, e: print 'Command failed with output:\n%s' % e.stderr if jobid: job.update({'status':304, 'comment':'Transcoding to mp4 failed'}) sys.exit(e.retcode) rec.basename = os.path.basename(outfile) os.remove(infile) # Cleanup the old *.png files for filename in glob('%s*.png' % infile): os.remove(filename) os.remove(tmpfile) try: os.remove('%s.map' % tmpfile) except OSError: pass rec.filesize = os.path.getsize(outfile) rec.transcoded = 1 rec.seek.clean() if flush_commskip: for index,mark in reversed(list(enumerate(rec.markup))): if mark.type in (rec.markup.MARK_COMM_START, rec.markup.MARK_COMM_END): del rec.markup[index] rec.bookmark = 0 rec.cutlist = 0 rec.markup.commit() rec.update() if jobid: job.update({'status':4, 'comment':'Rebuilding seektable'}) if build_seektable: task = System(path='mythcommflag') task.command('--chanid %s' % chanid, '--starttime %s' % starttime, '--rebuild', '2> /dev/null') if jobid: job.update({'status':272, 'comment':'Transcode Completed'}) def main(): parser = OptionParser(usage="usage: %prog [options] [jobid]") parser.add_option('--chanid', action='store', type='int', dest='chanid', help='Use chanid for manual operation') parser.add_option('--starttime', action='store', type='int', dest='starttime', help='Use starttime for manual operation') parser.add_option('-v', '--verbose', action='store', type='string', dest='verbose', help='Verbosity level') opts, args = parser.parse_args() if opts.verbose: if opts.verbose == 'help': print MythLog.helptext sys.exit(0) MythLog._setlevel(opts.verbose) if len(args) == 1: runjob(jobid=args[0]) elif opts.chanid and opts.starttime: runjob(chanid=opts.chanid, starttime=opts.starttime) else: print 'Script must be provided jobid, or chanid and starttime.' sys.exit(1) if __name__ == '__main__': main()