]> code.delx.au - transcoding/commitdiff
Many improvements!
authorJames Bunton <jamesbunton@fastmail.fm>
Fri, 23 Jul 2010 04:55:11 +0000 (14:55 +1000)
committerJames Bunton <jamesbunton@fastmail.fm>
Fri, 23 Jul 2010 04:55:11 +0000 (14:55 +1000)
 - 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.

encode.py

index fceca4184fe79d9e40f7bf2b554c0f23b9b5e39f..6e35071580f4b5bd3d835ac1503d1a2e844d93f6 100755 (executable)
--- 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:])