]>
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
):
52 def duplicate_opts(opts
):
53 return optparse
.Values(opts
.__dict
__)
55 def insert_mplayer_options(cmd
, o
):
59 do_opt
= partial(append_cmd
, cmd
)
61 do_opt("-ss", o
.startpos
)
62 do_opt("-endpos", o
.endpos
)
63 do_opt("-dvd-device", o
.dvd
)
64 do_opt("-chapter", o
.chapter
)
65 do_opt("-aid", o
.audioid
)
66 do_opt("-sid", o
.subtitleid
)
67 do_opt("-vf", o
.vfilters
)
68 do_opt("-af", o
.afilters
)
71 cmd
+= ["-vf-pre", "yadif"]
73 cmd
+= ["-vf-pre", "pullup,softskip", "-ofps", "24000/1001"]
77 cmd
+= ["-sb", str(o
.skipkb
* 1024)]
80 class Command(object):
81 def __init__(self
, profile
, opts
):
82 self
.profile
= profile
90 def check_command(self
, cmd
):
93 if subprocess
.Popen(["which", cmd
], stdout
=open("/dev/null", "w")).wait() != 0:
94 raise FatalException("Command '%s' is required" % cmd
)
96 def check_no_file(self
, path
):
97 if os
.path
.exists(path
):
98 raise FatalException("Output file '%s' exists." % path
)
100 def do_exec(self
, args
, wait
=True):
102 print " ".join(map(mkarg
, args
))
104 self
.__process
= subprocess
.Popen(args
)
110 if self
.__process
== None:
112 if self
.__process
.wait() != 0:
113 raise FatalException("Failure executing command: %s" % self
.__args
)
114 self
.__process
= None
117 class MP4Box(Command
):
119 self
.check_command("MP4Box")
120 self
.check_no_file(self
.opts
.output
+ ".mp4")
129 fps
= midentify(p
.video_tmp
, "ID_VIDEO_FPS")
141 class MKVMerge(Command
):
143 self
.check_command("mkvmerge")
144 self
.check_no_file(self
.opts
.output
+ ".mkv")
153 fps
= midentify(p
.video_tmp
, "ID_VIDEO_FPS")
157 "-o", o
.output
+ ".mkv",
158 "--default-duration", "0:%sfps"%fps
,
165 class MencoderLossless(Command
):
167 self
.check_command("mencoder")
168 self
.check_no_file("lossless.avi")
171 self
.opts
= duplicate_opts(ofut
)
172 ofut
.input = "lossless.avi"
173 ofut
.mplayer_done
= True
178 os
.mkfifo("lossless.avi")
181 cmd
+= ["mencoder", self
.opts
.input, "-o", "lossless.avi"]
182 cmd
+= ["-noconfig", "all"]
183 cmd
+= ["-oac", "copy", "-ovc", "lavc", "-lavcopts", "vcodec=ffv1:autoaspect"]
184 insert_mplayer_options(cmd
, self
.opts
)
185 cmd
+= ["-vf-add", "harddup"]
186 self
.do_exec(cmd
, wait
=not fifo
)
190 class MPlayer(Command
):
192 self
.check_command("mplayer")
193 self
.check_no_file("video.y4m")
194 self
.check_no_file("audio.wav")
197 os
.mkfifo("video.y4m")
198 os
.mkfifo("audio.wav")
200 cmd
+= ["mplayer", self
.opts
.input]
201 cmd
+= ["-benchmark", "-noconsolecontrols", "-noconfig", "all"]
202 cmd
+= ["-vo", "yuv4mpeg:file=video.y4m"]
203 cmd
+= ["-ao", "pcm:waveheader:file=audio.wav"]
204 insert_mplayer_options(cmd
, self
.opts
)
205 cmd
+= self
.profile
.mplayeropts
206 self
.do_exec(cmd
, wait
=False)
209 class MencoderCopyAC3(Command
):
211 self
.check_command("mplayer")
212 self
.check_no_file("audio.ac3")
213 self
.profile
.audio_tmp
= "audio.ac3"
217 cmd
+= ["mencoder", self
.opts
.input]
218 cmd
+= ["-noconfig", "all"]
219 cmd
+= ["-ovc", "copy", "-oac", "copy"]
220 cmd
+= ["-of", "rawaudio", "-o", "audio.ac3"]
221 insert_mplayer_options(cmd
, self
.opts
)
227 self
.check_command("x264")
228 self
.profile
.video_tmp
= "video.h264"
233 cmd
+= ["x264", "--no-progress"]
235 cmd
+= ["-o", p
.video_tmp
]
237 self
.do_exec(cmd
, wait
=False)
242 self
.check_command("lame")
243 self
.profile
.audio_tmp
= "audio.mp3"
248 cmd
+= ["lame", "--quiet"]
252 self
.do_exec(cmd
, wait
=False)
257 self
.check_command("faac")
258 self
.profile
.audio_tmp
= "audio.aac"
264 cmd
+= ["-o", p
.audio_tmp
]
267 self
.do_exec(cmd
, wait
=False)
270 class Mencoder(Command
):
272 "xvid": "-xvidencopts",
273 "x264": "-x264encopts",
275 "mp3lame": "-lameopts",
282 self
.check_command("mencoder")
283 self
.check_no_file(o
.output
+ ".avi")
285 p
.video_tmp
= o
.output
+ ".avi"
286 p
.audio_tmp
= o
.output
+ ".avi"
293 cmd
+= ["mencoder", o
.input]
294 cmd
+= ["-noconfig", "all"]
295 insert_mplayer_options(cmd
, o
)
296 cmd
+= ["-vf-add", "harddup"]
297 cmd
+= ["-ovc", p
.vcodec
, self
.codec2opts
[p
.vcodec
], p
.vopts
]
298 cmd
+= ["-oac", p
.acodec
]
300 cmd
+= [self
.codec2opts
[p
.acodec
], p
.aopts
]
301 cmd
+= self
.profile
.mplayeropts
302 cmd
+= ["-o", self
.opts
.output
+ ".avi"]
307 class MencoderDemux(Command
):
320 self
.check_command("mencoder")
321 p
.audio_tmp
= "audio." + self
.codec2exts
[p
.acodec
]
322 p
.video_tmp
= "video." + self
.codec2exts
[p
.vcodec
]
323 self
.check_no_file(p
.audio_tmp
)
324 self
.check_no_file(p
.video_tmp
)
330 cmd
= ["mencoder", "-ovc", "copy", "-oac", "copy", o
.output
+ ".avi"]
331 cmd
+= ["-noconfig", "all", "-noskip", "-mc", "0"]
332 self
.do_exec(cmd
+ ["-of", "rawaudio", "-o", p
.audio_tmp
])
333 self
.do_exec(cmd
+ ["-of", "rawvideo", "-o", p
.video_tmp
])
334 self
.do_exec(["rm", "-f", o
.output
+ ".avi"])
338 class Profile(object):
339 def __init__(self
, commands
, **kwargs
):
340 self
.commands
= commands
341 self
.__dict
__.update(kwargs
)
343 def __contains__(self
, keyname
):
344 return hasattr(self
, keyname
)
347 def __init__(self
, commands
):
348 self
.commands
= commands
[:]
351 for command
in self
.commands
:
359 commands
=[MPlayer
, X264
, Lame
, Wait
, MKVMerge
],
361 x264opts
=["--preset", "veryslow", "--crf", "20"],
362 lameopts
=["--preset", "medium"],
367 commands
=[MPlayer
, X264
, Wait
, MencoderCopyAC3
, MKVMerge
],
368 mplayeropts
=["-nosound"],
369 x264opts
=["--preset", "veryslow", "--crf", "20"],
374 commands
=[MPlayer
, X264
, Lame
, Wait
, MKVMerge
],
376 x264opts
=["--preset", "fast", "--crf", "23"],
377 lameopts
=["--preset", "medium"],
383 mplayeropts
=["-ffourcc", "DX50"],
385 vopts
="fixed_quant=2:vhq=4:autoaspect",
392 commands
=[MPlayer
, X264
, Faac
, Wait
, MP4Box
],
394 x264opts
=["--crf", "20", "--bframes", "1"],
395 faacopts
=["-q", "100", "--mpeg-vers", "4"],
400 commands
=[Mencoder
, MencoderDemux
, MP4Box
],
401 mplayeropts
=["-vf-add", "scale=640:-10"],
403 vopts
="bitrate=384:vhq=4:autoaspect:max_bframes=0",
405 aopts
="br=64:mpeg=4:object=2",
413 for x
, y
in mappings
.iteritems():
414 profiles
[x
] = profiles
[y
]
420 for profile_name
in profiles
.keys():
421 if sys
.argv
[0].find(profile_name
) >= 0:
424 profile_name
= "xvid/lame"
426 parser
= optparse
.OptionParser(usage
="%prog [options] input [output]")
427 parser
.add_option("--dvd", action
="store", dest
="dvd")
428 parser
.add_option("--fixmux", action
="store_true", dest
="fixmux")
429 parser
.add_option("--deinterlace", action
="store_true", dest
="deinterlace")
430 parser
.add_option("--detelecine", action
="store_true", dest
="detelecine")
431 parser
.add_option("--mc", action
="store", dest
="mc", type="float")
432 parser
.add_option("--noskip", action
="store_true", dest
="noskip")
433 parser
.add_option("--vfilters", action
="store", dest
="vfilters")
434 parser
.add_option("--afilters", action
="store", dest
="afilters")
435 parser
.add_option("--chapter", action
="store", dest
="chapter")
436 parser
.add_option("--skipkb", action
="store", dest
="skipkb", type="int")
437 parser
.add_option("--startpos", action
="store", dest
="startpos")
438 parser
.add_option("--endpos", action
="store", dest
="endpos")
439 parser
.add_option("--audioid", action
="store", dest
="audioid")
440 parser
.add_option("--subtitleid", action
="store", dest
="subtitleid")
441 parser
.add_option("--profile", action
="store", dest
="profile_name", default
=profile_name
)
442 parser
.add_option("--dump", action
="store_true", dest
="dump")
444 opts
, args
= parser
.parse_args(sys
.argv
[1:])
447 output
= os
.path
.splitext(os
.path
.basename(input))[0]
456 if "://" not in input:
457 opts
.input = os
.path
.abspath(input)
460 opts
.dvd
= os
.path
.abspath(opts
.dvd
)
463 opts
.output
= os
.path
.abspath(output
)
464 opts
.mplayer_done
= False
475 profile
= profiles
[opts
.profile_name
]
477 print >>sys
.stderr
, "Profile '%s' not found!" % opts
.profile_name
480 # Run in a temp dir so that multiple instances can be run simultaneously
481 tempdir
= tempfile
.mkdtemp()
487 if opts
.fixmux
or opts
.detelecine
:
488 profile
.commands
.insert(0, MencoderLossless
)
489 for CommandClass
in profile
.commands
:
490 if Command
in CommandClass
.__bases
__:
491 command
= CommandClass(profile
, opts
)
493 command
= CommandClass(commands
)
494 commands
.append(command
)
495 for command
in commands
:
498 except FatalException
, e
:
499 print >>sys
.stderr
, "Error:", str(e
)
504 shutil
.rmtree(tempdir
)
506 if __name__
== "__main__":