X-Git-Url: https://code.delx.au/transcoding/blobdiff_plain/703ccfd29eef2cb5e5553842c9973590398b8760..4ce9b29e4e2544e9c1a44a58aaa47acae74799ba:/encode.py diff --git a/encode.py b/encode.py index 28767bf..32e6451 100755 --- a/encode.py +++ b/encode.py @@ -1,20 +1,60 @@ #!/usr/bin/env python -import commands import optparse +import re import subprocess import sys import os -import tempfile import shutil +import tempfile class FatalException(Exception): pass +def mkarg(arg): + if re.match("^[a-zA-Z0-9\-\\.,/@_:=]*$", arg): + return arg + + if "'" not in arg: + return "'%s'" % arg + out = "\"" + for c in arg: + if c in "\\$\"`": + out += "\\" + out += c + out += "\"" + return out + +def midentify(source, field): + process = subprocess.Popen( + ["mplayer", "-frames", "0", "-identify", source], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + for line in process.stdout: + try: + key, value = line.split("=") + except ValueError: + continue + if key == field: + return value.strip() + + + class Command(object): + codec2exts = { + "xvid": "m4v", + "x264": "h264", + "faac": "aac", + "mp3lame": "mp3", + "copyac3": "ac3", + } + def __init__(self, profile, opts): self.profile = profile self.opts = opts + self.audio_tmp = "audio." + self.codec2exts[profile.acodec] + self.video_tmp = "video." + self.codec2exts[profile.vcodec] def print_install_message(self): print >>sys.stderr, "Problem with command: %s", self.name @@ -33,36 +73,31 @@ class Command(object): def do_exec(self, args): if self.opts.dump: - print "".join(map(commands.mkarg, args))[1:] + print " ".join(map(mkarg, args)) else: if subprocess.Popen(args).wait() != 0: raise FatalException("Failure executing command: %s" % args) class MP4Box(Command): - codec2exts = { - "xvid": "m4v", - "x264": "h264", - "faac": "aac", - } - def check(self): - self.check_command("mencoder") self.check_command("MP4Box") self.check_no_file(self.opts.output + ".mp4") def run(self): - p = self.profile - video = "video.%s" % self.codec2exts[p.vcodec] - audio = "audio.%s" % self.codec2exts[p.acodec] - input = self.opts.output + ".avi" # From Mencoder command + if self.opts.dump: + fps = "???" + else: + fps = midentify(self.video_tmp, "ID_VIDEO_FPS") + output = self.opts.output + ".mp4" - mencoder = ["mencoder", input, "-ovc", "copy", "-oac", "copy", "-of"] - self.do_exec(["rm", "-f", output]) - self.do_exec(mencoder + ["rawvideo", "-o", video]) - self.do_exec(mencoder + ["rawaudio", "-o", audio]) - self.do_exec(["MP4Box", "-add", video, "-add", audio, output]) - self.do_exec(["rm", "-f", video, audio, input]) + self.do_exec([ + "MP4Box", + "-fps", fps, + "-add", self.video_tmp, + "-add", self.audio_tmp, + output + ]) @@ -72,16 +107,40 @@ class MKVMerge(Command): self.check_no_file(self.opts.output + ".mkv") def run(self): - input = self.opts.output + ".avi" # From Mencoder command - output = self.opts.output + ".mkv" - self.do_exec(["mkvmerge", "-o", output, input]) - self.do_exec(["rm", "-f", input]) + if self.opts.dump: + fps = "???" + else: + fps = midentify(self.video_tmp, "ID_VIDEO_FPS") + + self.do_exec([ + "mkvmerge", + "-o", self.opts.output + ".mkv", + "--default-duration", "0:%sfps"%fps, + self.video_tmp, + self.audio_tmp, + ]) + + + +class MencoderMux(Command): + def check(self): + self.check_command("mencoder") + self.check_no_file(self.opts.output + ".avi") + + def run(self): + self.do_exec([ + "mencoder", + "-o", self.opts.output + ".avi", + "-oac", "copy", "-ovc", "copy", + "-noskip", "-mc", "0", + "-audiofile", self.audio_tmp, + self.video_tmp, + ]) class Mencoder(Command): codec2opts = { - "lavc": "-lavcopts", "xvid": "-xvidencopts", "x264": "-x264encopts", "faac": "-faacopts", @@ -95,6 +154,11 @@ class Mencoder(Command): cmd.append(var) if self.opts.deinterlace: cmd += ["-vf-add", "pp=lb"] + if self.opts.detelecine: + self.opts.ofps = "24000/1001" + cmd += ["-vf-add", "pullup,softskip"] + try_opt("-fps", self.opts.ifps) + try_opt("-ofps", self.opts.ofps) try_opt("-ss", self.opts.startpos) try_opt("-endpos", self.opts.endpos) try_opt("-dvd-device", self.opts.dvd) @@ -102,46 +166,54 @@ class Mencoder(Command): try_opt("-aid", self.opts.audioid) try_opt("-sid", self.opts.subtitleid) try_opt("-vf-add", self.opts.vfilters) - try_opt("-af", self.opts.afilters) + try_opt("-af-add", self.opts.afilters) + cmd += ["-vf-add", "harddup"] def subst_values(self, cmd, vpass): subst = { "vbitrate": self.opts.vbitrate, "abitrate": self.opts.abitrate, - "input": self.opts.input, - "output": self.opts.output + ".avi", "vpass": vpass, } return [x % subst for x in cmd] - - def pass1(self): + + def passn(self, n): p = self.profile + + acodec = p.acodec + if self.opts.copyac3: + acodec = "copy" + p.acodec = "copyac3" + p.aopts = None + cmd = [] - cmd += ["mencoder", "%(input)s", "-o", "/dev/null"] + cmd += ["mencoder", self.opts.input] self.insert_options(cmd) cmd += ["-ovc", p.vcodec, self.codec2opts[p.vcodec], p.vopts] - cmd += ["-oac", "copy"] - cmd += self.profile.extra + self.profile.extra1 - cmd = self.subst_values(cmd, vpass=1) + cmd += ["-oac", acodec] + if p.aopts: + cmd += [self.codec2opts[p.acodec], p.aopts] + cmd += self.profile.extra1 + self.profile.extra + cmd = self.subst_values(cmd, vpass=n) + + return cmd + + + def pass1(self): + cmd = self.passn(1) + cmd += ["-o", self.audio_tmp, "-of", "rawaudio"] return cmd def pass2(self): - p = self.profile - cmd = [] - cmd += ["mencoder", "%(input)s", "-o", "%(output)s"] - self.insert_options(cmd) - cmd += ["-ovc", p.vcodec, self.codec2opts[p.vcodec], p.vopts] - cmd += ["-oac", p.acodec, self.codec2opts[p.acodec], p.aopts] - if self.opts.episode_name: - cmd += ["-info", "name='%s'" % self.opts.episode_name] - cmd += self.profile.extra + self.profile.extra2 - cmd = self.subst_values(cmd, vpass=2) + cmd = self.passn(2) + cmd += ["-o", self.video_tmp, "-of", "rawvideo"] return cmd def check(self): self.check_command("mencoder") - self.check_no_file(self.opts.output + ".avi") + self.check_no_file(self.audio_tmp) + self.check_no_file(self.video_tmp) def run(self): self.do_exec(self.pass1()) @@ -166,75 +238,60 @@ class Profile(object): profiles = { - "qt7" : - Profile( - commands=[Mencoder, MP4Box], - vcodec="x264", - vopts="pass=%(vpass)d:bitrate=%(vbitrate)d:me=umh:partitions=all:trellis=1:subq=7:bframes=1:direct_pred=auto", - acodec="faac", - aopts="br=%(abitrate)d:mpeg=4:object=2", - ), - "x264" : Profile( commands=[Mencoder, MKVMerge], vcodec="x264", - vopts="pass=%(vpass)d:bitrate=%(vbitrate)d:subq=6:frameref=6:me=umh:partitions=all:bframes=4:b_adapt:qcomp=0.7:keyint=250:threads=2", + vopts="pass=%(vpass)d:bitrate=%(vbitrate)d:subq=6:frameref=6:me=umh:partitions=all:bframes=4:b_adapt:qcomp=0.7:keyint=250", acodec="mp3lame", aopts="abr:br=%(abitrate)d", ), "xvid" : Profile( - commands=[Mencoder], + commands=[Mencoder, MencoderMux], vcodec="xvid", vopts="pass=%(vpass)d:bitrate=%(vbitrate)d:vhq=4:autoaspect", acodec="mp3lame", aopts="abr:br=%(abitrate)d", - extra2=["-ffourcc", "DX50"], ), - "ipodxvid" : + "apple-quicktime" : Profile( commands=[Mencoder, MP4Box], - vcodec="xvid", - vopts="pass=%(vpass)d:bitrate=%(vbitrate)d:vhq=4:autoaspect:max_bframes=0", + vcodec="x264", + vopts="pass=%(vpass)d:bitrate=%(vbitrate)d:me=umh:partitions=all:trellis=1:subq=7:bframes=1:direct_pred=auto", acodec="faac", aopts="br=%(abitrate)d:mpeg=4:object=2", - extra=["-vf-add", "scale=480:-10"], ), - "ipodx264" : + "ipod-xvid" : Profile( commands=[Mencoder, MP4Box], - vcodec="x264", - vopts="pass=%(vpass)d:bitrate=%(vbitrate)d:vbv_maxrate=1500:vbv_bufsize=2000:nocabac:me=umh:partitions=all:trellis=1:subq=7:bframes=0:direct_pred=auto:level_idc=30:turbo", + vcodec="xvid", + vopts="pass=%(vpass)d:bitrate=%(vbitrate)d:vhq=4:autoaspect:max_bframes=0", acodec="faac", aopts="br=%(abitrate)d:mpeg=4:object=2", extra=["-vf-add", "scale=480:-10"], - extra2=["-channels", "2", "-srate", "48000"], ), - "nokiax264" : + "ipod-x264" : Profile( commands=[Mencoder, MP4Box], - default_opts={ - "vbitrate": 256, - "abitrate": 96, - }, vcodec="x264", - vopts="pass=%(vpass)d:bitrate=%(vbitrate)d:nocabac:me=umh:partitions=all:trellis=1:subq=7:bframes=0:direct_pred=auto", + vopts="pass=%(vpass)d:bitrate=%(vbitrate)d:vbv_maxrate=1500:vbv_bufsize=2000:nocabac:me=umh:partitions=all:trellis=1:subq=7:bframes=0:direct_pred=auto:level_idc=30:turbo", acodec="faac", aopts="br=%(abitrate)d:mpeg=4:object=2", - extra=["-vf-add", "scale=320:-10"], + extra=["-vf-add", "scale=480:-10"], + extra2=["-channels", "2", "-srate", "48000"], ), - "n97xvid" : + "nokia-n97" : Profile( commands=[Mencoder, MP4Box], default_opts={ - "vbitrate": 1000, - "abitrate": 96, + "vbitrate": 256, + "abitrate": 64, }, vcodec="xvid", vopts="pass=%(vpass)d:bitrate=%(vbitrate)d:vhq=4:autoaspect:max_bframes=0", @@ -242,20 +299,6 @@ profiles = { aopts="br=%(abitrate)d:mpeg=4:object=2", extra=["-vf-add", "scale=640:-10"], ), - - "n97x264" : - Profile( - commands=[Mencoder, MP4Box], - default_opts={ - "vbitrate": 1000, - "abitrate": 96, - }, - vcodec="x264", - vopts="pass=%(vpass)d:bitrate=%(vbitrate)d:vbv_maxrate=2000:vbv_bufsize=2000:nocabac:me=umh:partitions=all:trellis=1:subq=7:bframes=0:direct_pred=auto:level_idc=20", - acodec="faac", - aopts="br=%(abitrate)d:mpeg=4:object=2", - extra=["-vf-add", "scale=640:-10"], - ), } @@ -271,17 +314,20 @@ def parse_args(): parser = optparse.OptionParser(usage="%prog [options] input [output]") parser.add_option("--dvd", action="store", dest="dvd") parser.add_option("--deinterlace", action="store_true", dest="deinterlace") + parser.add_option("--detelecine", action="store_true", dest="detelecine") + parser.add_option("--copyac3", action="store_true", dest="copyac3") parser.add_option("--vfilters", action="store", dest="vfilters") parser.add_option("--afilters", action="store", dest="afilters") parser.add_option("--vbitrate", action="store", dest="vbitrate", type="int") parser.add_option("--abitrate", action="store", dest="abitrate", type="int") parser.add_option("--chapter", action="store", dest="chapter") + parser.add_option("--ifps", action="store", dest="ifps") + parser.add_option("--ofps", action="store", dest="ofps") parser.add_option("--startpos", action="store", dest="startpos") parser.add_option("--endpos", action="store", dest="endpos") parser.add_option("--audioid", action="store", dest="audioid") parser.add_option("--subtitleid", action="store", dest="subtitleid") parser.add_option("--profile", action="store", dest="profile_name", default=profile_name) - parser.add_option("--episode-name", action="store", dest="episode_name") parser.add_option("--dump", action="store_true", dest="dump") try: opts, args = parser.parse_args(sys.argv[1:]) @@ -308,6 +354,8 @@ def parse_args(): return opts def main(): + os.nice(1) + opts = parse_args() # Find our profile @@ -337,7 +385,7 @@ def main(): command.run() except FatalException, e: - print >>sys.stderr, "Error:", e.message + print >>sys.stderr, "Error:", str(e) sys.exit(1) finally: