X-Git-Url: https://code.delx.au/transcoding/blobdiff_plain/bc457a06c0135884a406ed4226ee6e4043af62e9..a9536f5dc5e382234c9ac8dc5a59a441f7fcf594:/encode.py diff --git a/encode.py b/encode.py index 04ebfe8..c788bb6 100755 --- a/encode.py +++ b/encode.py @@ -1,20 +1,64 @@ #!/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", source, + "-ao", "null", "-vo", "null", + "-frames", "0", "-identify", + ], + 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,41 +77,74 @@ 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 + ]) + + + +class MKVMerge(Command): + def check(self): + self.check_command("mkvmerge") + self.check_no_file(self.opts.output + ".mkv") + + def run(self): + 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", @@ -80,7 +157,12 @@ class Mencoder(Command): cmd.append(opt) cmd.append(var) if self.opts.deinterlace: - cmd += ["-vf-add", "pp=ci"] + 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) @@ -88,46 +170,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()) @@ -152,66 +242,60 @@ class Profile(object): profiles = { - "qt7" : + "x264" : Profile( - commands=[Mencoder, MP4Box], + commands=[Mencoder, MKVMerge], 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", + 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", @@ -219,20 +303,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"], - ), } @@ -248,17 +318,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:]) @@ -285,6 +358,8 @@ def parse_args(): return opts def main(): + os.nice(1) + opts = parse_args() # Find our profile @@ -314,7 +389,7 @@ def main(): command.run() except FatalException, e: - print >>sys.stderr, "Error:", e.message + print >>sys.stderr, "Error:", str(e) sys.exit(1) finally: