]>
code.delx.au - transcoding/blob - encode.py
11 class FatalException(Exception):
15 if re
.match("^[a-zA-Z0-9\-\\.,/@_:=]*$", arg
):
28 class Command(object):
29 def __init__(self
, profile
, opts
):
30 self
.profile
= profile
33 def print_install_message(self
):
34 print >>sys
.stderr
, "Problem with command: %s", self
.name
36 print >>sys
.stderr
, "Try running:\n# aptitude install %s", self
.package
38 def check_command(self
, cmd
):
41 if subprocess
.Popen(["which", cmd
], stdout
=open("/dev/null", "w")).wait() != 0:
42 raise FatalException("Command '%s' is required" % cmd
)
44 def check_no_file(self
, path
):
45 if os
.path
.exists(path
):
46 raise FatalException("Output file '%s' exists." % path
)
48 def do_exec(self
, args
):
50 print " ".join(map(mkarg
, args
))
52 if subprocess
.Popen(args
).wait() != 0:
53 raise FatalException("Failure executing command: %s" % args
)
56 class MP4Box(Command
):
64 self
.check_command("mencoder")
65 self
.check_command("MP4Box")
66 self
.check_no_file(self
.opts
.output
+ ".mp4")
70 input = self
.opts
.output
+ ".avi" # From Mencoder command
71 output
= self
.opts
.output
+ ".mp4"
75 if not self
.opts
.dump
:
76 process
= subprocess
.Popen(
77 ["mplayer", "-frames", "0", "-identify", input],
78 stdout
=subprocess
.PIPE
,
79 stderr
=subprocess
.PIPE
,
81 for line
in process
.stdout
:
83 key
, value
= line
.split("=")
86 if key
== "ID_VIDEO_FPS":
89 # Strip out video & audio
90 video
= "video.%s" % self
.codec2exts
[p
.vcodec
]
91 audio
= "audio.%s" % self
.codec2exts
[p
.acodec
]
92 mencoder
= ["mencoder", input, "-ovc", "copy", "-oac", "copy", "-of"]
93 self
.do_exec(["rm", "-f", output
])
94 self
.do_exec(mencoder
+ ["rawvideo", "-o", video
])
95 self
.do_exec(mencoder
+ ["rawaudio", "-o", audio
])
97 # Mux them back together
98 self
.do_exec(["MP4Box", "-add", video
, "-add", audio
, "-fps", fps
, output
])
100 # Clean up temp files
101 self
.do_exec(["rm", "-f", video
, audio
, input])
105 class MKVMerge(Command
):
107 self
.check_command("mkvmerge")
108 self
.check_no_file(self
.opts
.output
+ ".mkv")
111 input = self
.opts
.output
+ ".avi" # From Mencoder command
112 output
= self
.opts
.output
+ ".mkv"
113 self
.do_exec(["mkvmerge", "-o", output
, input])
114 self
.do_exec(["rm", "-f", input])
118 class Mencoder(Command
):
120 "xvid": "-xvidencopts",
121 "x264": "-x264encopts",
123 "mp3lame": "-lameopts",
126 def insert_options(self
, cmd
):
127 def try_opt(opt
, var
):
131 if self
.opts
.deinterlace
:
132 cmd
+= ["-vf-add", "pp=lb"]
133 if self
.opts
.detelecine
:
134 self
.opts
.ofps
= "24000/1001"
135 cmd
+= ["-vf-add", "pullup,softskip"]
136 try_opt("-fps", self
.opts
.ifps
)
137 try_opt("-ofps", self
.opts
.ofps
)
138 try_opt("-ss", self
.opts
.startpos
)
139 try_opt("-endpos", self
.opts
.endpos
)
140 try_opt("-dvd-device", self
.opts
.dvd
)
141 try_opt("-chapter", self
.opts
.chapter
)
142 try_opt("-aid", self
.opts
.audioid
)
143 try_opt("-sid", self
.opts
.subtitleid
)
144 try_opt("-vf-add", self
.opts
.vfilters
)
145 try_opt("-af-add", self
.opts
.afilters
)
147 def subst_values(self
, cmd
, vpass
):
149 "vbitrate": self
.opts
.vbitrate
,
150 "abitrate": self
.opts
.abitrate
,
151 "input": self
.opts
.input,
152 "output": self
.opts
.output
+ ".avi",
156 return [x
% subst
for x
in cmd
]
161 cmd
+= ["mencoder", "%(input)s", "-o", "/dev/null"]
162 self
.insert_options(cmd
)
163 cmd
+= ["-ovc", p
.vcodec
, self
.codec2opts
[p
.vcodec
], p
.vopts
]
164 cmd
+= ["-oac", "copy"]
165 cmd
+= self
.profile
.extra1
+ self
.profile
.extra
166 cmd
= self
.subst_values(cmd
, vpass
=1)
172 cmd
+= ["mencoder", "%(input)s", "-o", "%(output)s"]
173 self
.insert_options(cmd
)
174 cmd
+= ["-ovc", p
.vcodec
, self
.codec2opts
[p
.vcodec
], p
.vopts
]
175 cmd
+= ["-oac", p
.acodec
]
177 cmd
+= [self
.codec2opts
[p
.acodec
], p
.aopts
]
178 if self
.opts
.episode_name
:
179 cmd
+= ["-info", "name='%s'" % self
.opts
.episode_name
]
180 cmd
+= self
.profile
.extra2
+ self
.profile
.extra
181 cmd
= self
.subst_values(cmd
, vpass
=2)
185 self
.check_command("mencoder")
186 self
.check_no_file(self
.opts
.output
+ ".avi")
189 self
.do_exec(self
.pass1())
190 self
.do_exec(self
.pass2())
194 class Profile(object):
195 def __init__(self
, commands
, **kwargs
):
196 self
.default_opts
= {
203 self
.commands
= commands
204 self
.__dict
__.update(kwargs
)
206 def __contains__(self
, keyname
):
207 return hasattr(self
, keyname
)
213 commands
=[Mencoder
, MKVMerge
],
215 vopts
="pass=%(vpass)d:bitrate=%(vbitrate)d:subq=6:frameref=6:me=umh:partitions=all:bframes=4:b_adapt:qcomp=0.7:keyint=250",
217 aopts
="abr:br=%(abitrate)d",
218 extra
=["-vf-add", "harddup"],
225 vopts
="pass=%(vpass)d:bitrate=%(vbitrate)d:vhq=4:autoaspect",
227 aopts
="abr:br=%(abitrate)d",
228 extra2
=["-ffourcc", "DX50"],
233 commands
=[Mencoder
, MP4Box
],
235 vopts
="pass=%(vpass)d:bitrate=%(vbitrate)d:me=umh:partitions=all:trellis=1:subq=7:bframes=1:direct_pred=auto",
237 aopts
="br=%(abitrate)d:mpeg=4:object=2",
238 extra
=["-vf-add", "harddup"],
243 commands
=[Mencoder
, MP4Box
],
245 vopts
="pass=%(vpass)d:bitrate=%(vbitrate)d:vhq=4:autoaspect:max_bframes=0",
247 aopts
="br=%(abitrate)d:mpeg=4:object=2",
248 extra
=["-vf-add", "scale=480:-10,harddup"],
253 commands
=[Mencoder
, MP4Box
],
255 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",
257 aopts
="br=%(abitrate)d:mpeg=4:object=2",
258 extra
=["-vf-add", "scale=480:-10,harddup"],
259 extra2
=["-channels", "2", "-srate", "48000"],
264 commands
=[Mencoder
, MP4Box
],
270 vopts
="pass=%(vpass)d:bitrate=%(vbitrate)d:vhq=4:autoaspect:max_bframes=0",
272 aopts
="br=%(abitrate)d:mpeg=4:object=2",
273 extra
=["-vf-add", "scale=640:-10,harddup"],
281 for profile_name
in profiles
.keys():
282 if sys
.argv
[0].find(profile_name
) >= 0:
285 profile_name
= "xvid"
287 parser
= optparse
.OptionParser(usage
="%prog [options] input [output]")
288 parser
.add_option("--dvd", action
="store", dest
="dvd")
289 parser
.add_option("--deinterlace", action
="store_true", dest
="deinterlace")
290 parser
.add_option("--detelecine", action
="store_true", dest
="detelecine")
291 parser
.add_option("--vfilters", action
="store", dest
="vfilters")
292 parser
.add_option("--afilters", action
="store", dest
="afilters")
293 parser
.add_option("--vbitrate", action
="store", dest
="vbitrate", type="int")
294 parser
.add_option("--abitrate", action
="store", dest
="abitrate", type="int")
295 parser
.add_option("--chapter", action
="store", dest
="chapter")
296 parser
.add_option("--ifps", action
="store", dest
="ifps")
297 parser
.add_option("--ofps", action
="store", dest
="ofps")
298 parser
.add_option("--startpos", action
="store", dest
="startpos")
299 parser
.add_option("--endpos", action
="store", dest
="endpos")
300 parser
.add_option("--audioid", action
="store", dest
="audioid")
301 parser
.add_option("--subtitleid", action
="store", dest
="subtitleid")
302 parser
.add_option("--profile", action
="store", dest
="profile_name", default
=profile_name
)
303 parser
.add_option("--episode-name", action
="store", dest
="episode_name")
304 parser
.add_option("--dump", action
="store_true", dest
="dump")
306 opts
, args
= parser
.parse_args(sys
.argv
[1:])
309 output
= os
.path
.splitext(os
.path
.basename(input))[0]
318 if "://" not in input:
319 opts
.input = os
.path
.abspath(input)
322 opts
.dvd
= os
.path
.abspath(opts
.dvd
)
325 opts
.output
= os
.path
.abspath(output
)
334 profile
= profiles
[opts
.profile_name
]
336 print >>sys
.stderr
, "Profile '%s' not found!" % opts
.profile_name
339 # Pull in default option values from the profile
340 for key
, value
in profile
.default_opts
.iteritems():
341 if getattr(opts
, key
) is None:
342 setattr(opts
, key
, value
)
344 # Run in a temp dir so that multiple instances can be run simultaneously
345 tempdir
= tempfile
.mkdtemp()
351 for CommandClass
in profile
.commands
:
352 command
= CommandClass(profile
, opts
)
353 commands
.append(command
)
355 for command
in commands
:
358 except FatalException
, e
:
359 print >>sys
.stderr
, "Error:", str(e
)
364 shutil
.rmtree(tempdir
)
366 if __name__
== "__main__":