From: James Bunton Date: Fri, 23 Jul 2010 04:55:11 +0000 (+1000) Subject: Many improvements! X-Git-Url: https://code.delx.au/transcoding/commitdiff_plain/dfdf1ac02ed71f25d4bce552aeaae6aea86c5f96 Many improvements! - Mencoder now writes the audio during the first pass, video during the second. This means encodes that end with MKV or MP4 never have to be stuffed into an AVI, which fixes a few problems. - Run audio & video codec during both pass1 & pass2 to ensure frameskipping works properly. This fixes A/V sync issues. - Added option to copy AC3 audio directly from the source without transcoding. Useful for DVDs that have low bitrate audio tracks which would suffer from re-encoding. - Set FPS correctly for MKV files, this fixes A/V sync issues. --- diff --git a/encode.py b/encode.py index fceca41..6e35071 100755 --- a/encode.py +++ b/encode.py @@ -25,10 +25,36 @@ def mkarg(arg): 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 @@ -54,51 +80,24 @@ class Command(object): 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 - input = self.opts.output + ".avi" # From Mencoder command - output = self.opts.output + ".mp4" + if self.opts.dump: + fps = "???" + else: + fps = midentify(self.video_tmp, "ID_VIDEO_FPS") - # Check FPS - fps = "???" - if not self.opts.dump: - process = subprocess.Popen( - ["mplayer", "-frames", "0", "-identify", input], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - for line in process.stdout: - try: - key, value = line.split("=") - except ValueError: - continue - if key == "ID_VIDEO_FPS": - fps = value - - # Strip out video & audio - video = "video.%s" % self.codec2exts[p.vcodec] - audio = "audio.%s" % self.codec2exts[p.acodec] - 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]) - - # Mux them back together - self.do_exec(["MP4Box", "-add", video, "-add", audio, "-fps", fps, output]) - - # Clean up temp files - self.do_exec(["rm", "-f", video, audio, input]) + output = self.opts.output + ".mp4" + self.do_exec([ + "MP4Box", + "-fps", fps, + "-add", self.video_tmp, + "-add", self.audio_tmp, + output + ]) @@ -108,10 +107,35 @@ 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, + ]) @@ -143,47 +167,53 @@ class Mencoder(Command): try_opt("-sid", self.opts.subtitleid) try_opt("-vf-add", self.opts.vfilters) 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 += ["-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=1) + 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] - if "aopts" in p: - cmd += [self.codec2opts[p.acodec], p.aopts] - if self.opts.episode_name: - cmd += ["-info", "name='%s'" % self.opts.episode_name] - cmd += self.profile.extra2 + self.profile.extra - 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()) @@ -215,17 +245,15 @@ profiles = { 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", - extra=["-vf-add", "harddup"], ), "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"], ), "apple-quicktime" : @@ -235,7 +263,6 @@ profiles = { 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", "harddup"], ), "ipod-xvid" : @@ -245,7 +272,7 @@ profiles = { 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,harddup"], + extra=["-vf-add", "scale=480:-10"], ), "ipod-x264" : @@ -255,7 +282,7 @@ profiles = { 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=480:-10,harddup"], + extra=["-vf-add", "scale=480:-10"], extra2=["-channels", "2", "-srate", "48000"], ), @@ -270,7 +297,7 @@ profiles = { 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=640:-10,harddup"], + extra=["-vf-add", "scale=640:-10"], ), } @@ -288,6 +315,7 @@ def parse_args(): 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") @@ -300,7 +328,6 @@ def parse_args(): 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:])