]>
code.delx.au - transcoding/blob - encode.py
3 from functools
import partial
12 class FatalException(Exception):
16 if re
.match("^[a-zA-Z0-9\-\\.,/@_:=]*$", arg
):
29 def midentify(source
, field
):
30 process
= subprocess
.Popen(
33 "-ao", "null", "-vo", "null",
34 "-frames", "0", "-identify",
36 stdout
=subprocess
.PIPE
,
37 stderr
=subprocess
.PIPE
,
39 for line
in process
.stdout
:
41 key
, value
= line
.split("=")
47 def append_cmd(cmd
, opt
, var
):
53 class Command(object):
62 def __init__(self
, profile
, opts
):
63 self
.profile
= profile
66 self
.audio_tmp
= "audio." + self
.codec2exts
[profile
.acodec
]
67 self
.video_tmp
= "video." + self
.codec2exts
[profile
.vcodec
]
72 def print_install_message(self
):
73 print >>sys
.stderr
, "Problem with command: %s", self
.name
75 print >>sys
.stderr
, "Try running:\n# aptitude install %s", self
.package
77 def check_command(self
, cmd
):
80 if subprocess
.Popen(["which", cmd
], stdout
=open("/dev/null", "w")).wait() != 0:
81 raise FatalException("Command '%s' is required" % cmd
)
83 def check_no_file(self
, path
):
84 if os
.path
.exists(path
):
85 raise FatalException("Output file '%s' exists." % path
)
87 def do_exec(self
, args
):
89 print " ".join(map(mkarg
, args
))
91 if subprocess
.Popen(args
).wait() != 0:
92 raise FatalException("Failure executing command: %s" % args
)
95 class MP4Box(Command
):
97 self
.check_command("MP4Box")
98 self
.check_no_file(self
.opts
.output
+ ".mp4")
104 fps
= midentify(self
.video_tmp
, "ID_VIDEO_FPS")
106 output
= self
.opts
.output
+ ".mp4"
110 "-add", self
.video_tmp
,
111 "-add", self
.audio_tmp
,
117 class MKVMerge(Command
):
119 self
.check_command("mkvmerge")
120 self
.check_no_file(self
.opts
.output
+ ".mkv")
126 fps
= midentify(self
.video_tmp
, "ID_VIDEO_FPS")
130 "-o", self
.opts
.output
+ ".mkv",
131 "--default-duration", "0:%sfps"%fps
,
138 class MencoderFixRemux(Command
):
141 self
.opts
= optparse
.Values(orig
.__dict
__)
142 orig
.input = "remux.avi"
143 orig
.dvd
= orig
.chapter
= orig
.startpos
= orig
.endpos
= None
146 self
.check_command("mencoder")
147 self
.check_no_file("remux.avi")
154 "-oac", "copy", "-ovc", "copy",
158 do_opt
= partial(append_cmd
, cmd
)
159 do_opt("-dvd-device", o
.dvd
)
160 do_opt("-chapter", o
.chapter
)
161 do_opt("-ss", o
.startpos
)
162 do_opt("-endpos", o
.endpos
)
167 class MencoderMux(Command
):
169 self
.check_command("mencoder")
170 self
.check_no_file(self
.opts
.output
+ ".tmp.avi")
175 "-o", self
.opts
.output
+ ".avi",
176 "-oac", "copy", "-ovc", "copy",
177 "-noskip", "-mc", "0",
178 "-audiofile", self
.audio_tmp
,
184 class Mencoder(Command
):
186 "xvid": "-xvidencopts",
187 "x264": "-x264encopts",
189 "mp3lame": "-lameopts",
193 if self
.opts
.copyac3
:
194 self
.profile
.acodec
= "copyac3"
195 self
.profile
.aopts
= None
198 def check_options(self
):
200 if o
.detelecine
and o
.ofps
:
201 raise FatalException("Cannot use --detelecine with --ofps")
202 if o
.deinterlace
and o
.detelecine
:
203 raise FatalException("Cannot use --detelecine with --deinterlace")
205 def insert_options(self
, cmd
):
207 do_opt
= partial(append_cmd
, cmd
)
210 cmd
+= ["-vf-add", "yadif"]
212 o
.ofps
= "24000/1001"
213 cmd
+= ["-vf-add", "pullup,softskip"]
217 cmd
+= ["-sb", str(o
.skipkb
* 1024)]
220 do_opt("-fps", o
.ifps
)
221 do_opt("-ofps", o
.ofps
)
222 do_opt("-ss", o
.startpos
)
223 do_opt("-endpos", o
.endpos
)
224 do_opt("-dvd-device", o
.dvd
)
225 do_opt("-chapter", o
.chapter
)
226 do_opt("-aid", o
.audioid
)
227 do_opt("-sid", o
.subtitleid
)
228 do_opt("-vf-add", o
.vfilters
)
229 do_opt("-af-add", o
.afilters
)
230 cmd
+= ["-vf-add", "harddup"]
232 def subst_values(self
, cmd
, vpass
):
234 "vbitrate": self
.opts
.vbitrate
,
235 "abitrate": self
.opts
.abitrate
,
239 return [x
% subst
for x
in cmd
]
245 if acodec
== "copyac3":
249 cmd
+= ["mencoder", self
.opts
.input]
250 self
.insert_options(cmd
)
251 cmd
+= ["-ovc", p
.vcodec
, self
.codec2opts
[p
.vcodec
], p
.vopts
]
252 cmd
+= ["-oac", acodec
]
254 cmd
+= [self
.codec2opts
[p
.acodec
], p
.aopts
]
255 cmd
+= self
.profile
.extra1
+ self
.profile
.extra
256 cmd
= self
.subst_values(cmd
, vpass
=n
)
263 cmd
+= ["-o", self
.audio_tmp
, "-of", "rawaudio"]
268 cmd
+= ["-o", self
.video_tmp
, "-of", "rawvideo"]
272 self
.check_command("mencoder")
273 self
.check_no_file(self
.audio_tmp
)
274 self
.check_no_file(self
.video_tmp
)
277 self
.do_exec(self
.pass1())
278 self
.do_exec(self
.pass2())
282 class Profile(object):
283 def __init__(self
, commands
, **kwargs
):
284 self
.default_opts
= {
291 self
.commands
= commands
292 self
.__dict
__.update(kwargs
)
294 def __contains__(self
, keyname
):
295 return hasattr(self
, keyname
)
301 commands
=[Mencoder
, MKVMerge
],
303 vopts
="pass=%(vpass)d:bitrate=%(vbitrate)d:subq=6:frameref=6:me=umh:partitions=all:bframes=4:b_adapt:qcomp=0.7:keyint=250",
305 aopts
="abr:br=%(abitrate)d",
310 commands
=[Mencoder
, MencoderMux
],
312 vopts
="pass=%(vpass)d:bitrate=%(vbitrate)d:vhq=4:autoaspect",
314 aopts
="abr:br=%(abitrate)d",
319 commands
=[Mencoder
, MP4Box
],
321 vopts
="pass=%(vpass)d:bitrate=%(vbitrate)d:me=umh:partitions=all:trellis=1:subq=7:bframes=1:direct_pred=auto",
323 aopts
="br=%(abitrate)d:mpeg=4:object=2",
328 commands
=[Mencoder
, MP4Box
],
330 vopts
="pass=%(vpass)d:bitrate=%(vbitrate)d:vhq=4:autoaspect:max_bframes=0",
332 aopts
="br=%(abitrate)d:mpeg=4:object=2",
333 extra
=["-vf-add", "scale=480:-10"],
338 commands
=[Mencoder
, MP4Box
],
340 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",
342 aopts
="br=%(abitrate)d:mpeg=4:object=2",
343 extra
=["-vf-add", "scale=480:-10"],
344 extra2
=["-channels", "2", "-srate", "48000"],
349 commands
=[Mencoder
, MP4Box
],
355 vopts
="pass=%(vpass)d:bitrate=%(vbitrate)d:vhq=4:autoaspect:max_bframes=0",
357 aopts
="br=%(abitrate)d:mpeg=4:object=2",
358 extra
=["-vf-add", "scale=640:-10"],
366 for profile_name
in profiles
.keys():
367 if sys
.argv
[0].find(profile_name
) >= 0:
370 profile_name
= "xvid"
372 parser
= optparse
.OptionParser(usage
="%prog [options] input [output]")
373 parser
.add_option("--dvd", action
="store", dest
="dvd")
374 parser
.add_option("--deinterlace", action
="store_true", dest
="deinterlace")
375 parser
.add_option("--detelecine", action
="store_true", dest
="detelecine")
376 parser
.add_option("--fixmux", action
="store_true", dest
="fixmux")
377 parser
.add_option("--copyac3", action
="store_true", dest
="copyac3")
378 parser
.add_option("--mc", action
="store", dest
="mc", type="int")
379 parser
.add_option("--noskip", action
="store_true", dest
="noskip")
380 parser
.add_option("--vfilters", action
="store", dest
="vfilters")
381 parser
.add_option("--afilters", action
="store", dest
="afilters")
382 parser
.add_option("--vbitrate", action
="store", dest
="vbitrate", type="int")
383 parser
.add_option("--abitrate", action
="store", dest
="abitrate", type="int")
384 parser
.add_option("--chapter", action
="store", dest
="chapter")
385 parser
.add_option("--ifps", action
="store", dest
="ifps")
386 parser
.add_option("--ofps", action
="store", dest
="ofps")
387 parser
.add_option("--skipkb", action
="store", dest
="skipkb", type="int")
388 parser
.add_option("--startpos", action
="store", dest
="startpos")
389 parser
.add_option("--endpos", action
="store", dest
="endpos")
390 parser
.add_option("--audioid", action
="store", dest
="audioid")
391 parser
.add_option("--subtitleid", action
="store", dest
="subtitleid")
392 parser
.add_option("--profile", action
="store", dest
="profile_name", default
=profile_name
)
393 parser
.add_option("--dump", action
="store_true", dest
="dump")
395 opts
, args
= parser
.parse_args(sys
.argv
[1:])
398 output
= os
.path
.splitext(os
.path
.basename(input))[0]
407 if "://" not in input:
408 opts
.input = os
.path
.abspath(input)
411 opts
.dvd
= os
.path
.abspath(opts
.dvd
)
414 opts
.output
= os
.path
.abspath(output
)
425 profile
= profiles
[opts
.profile_name
]
427 print >>sys
.stderr
, "Profile '%s' not found!" % opts
.profile_name
430 # Pull in default option values from the profile
431 for key
, value
in profile
.default_opts
.iteritems():
432 if getattr(opts
, key
) is None:
433 setattr(opts
, key
, value
)
435 # Run in a temp dir so that multiple instances can be run simultaneously
436 tempdir
= tempfile
.mkdtemp()
443 profile
.commands
.insert(0, MencoderFixRemux
)
444 for CommandClass
in profile
.commands
:
445 command
= CommandClass(profile
, opts
)
446 commands
.append(command
)
448 for command
in commands
:
451 except FatalException
, e
:
452 print >>sys
.stderr
, "Error:", str(e
)
457 shutil
.rmtree(tempdir
)
459 if __name__
== "__main__":