morphbr@718: #!/usr/bin/env python
morphbr@718: 
morphbr@718: __author__ = "Artur Duque de Souza"
morphbr@718: __author_email__ = "artur.souza@indt.org.br"
morphbr@718: __license__ = "GPL"
morphbr@740: __version__ = "0.3"
morphbr@718: 
morphbr@718: import os
morphbr@740: import time
morphbr@740: import fcntl
morphbr@718: import shlex
morphbr@740: import socket
morphbr@740: import struct
morphbr@718: import signal
morphbr@718: import subprocess
morphbr@718: 
morphbr@718: import lib.utils as utils
morphbr@718: import lib.server as server
morphbr@718: import plugins.transcoders.mencoder_lib.mythtv as mythtv
morphbr@718: 
morphbr@718: from select import select
morphbr@718: import lib.transcoder as transcoder
morphbr@718: 
morphbr@718: __all__ = ("TranscoderMencoder",)
morphbr@718: 
morphbr@718: class TranscoderMencoder(transcoder.Transcoder):
morphbr@718:     """Transcoder class that implements a transcoder using Mencoder"""
morphbr@718:     mencoder_path = utils.which("mencoder")
morphbr@718:     name = "mencoder"
morphbr@718:     priority = -1
morphbr@718:     args = {}
morphbr@718:     proc = None
morphbr@718:     gmyth = None
morphbr@718: 
morphbr@718:     # only works with avi container
morphbr@718:     status = 0
morphbr@718: 
morphbr@718:     def _setup_params(self):
morphbr@718:         params_first = self.params_first
morphbr@718: 
morphbr@718:         # general_opts
morphbr@718:         self.args["local"]    = params_first("local", False)
morphbr@718:         self.args["language"] = params_first("language", False)
morphbr@718:         self.args["subtitle"] = params_first("subtitle", False)
morphbr@718:         self.args["format"]   = params_first("format", "mpeg1")
morphbr@718:         self.args["outfile"]  = params_first("outfile", "-")
morphbr@718: 
morphbr@718:         # input_opt
morphbr@718:         self.args["type"]     = params_first("type", "file")
morphbr@718:         self.args["input"]    = params_first("uri", "-")
morphbr@718: 
morphbr@718:         # audio_opts
morphbr@718:         self.args["acodec"]   = params_first("acodec", "mp2")
morphbr@718:         self.args["abitrate"] = params_first("abitrate", 192)
morphbr@718:         self.args["volume"]   = params_first("volume", 5)
morphbr@718: 
morphbr@718:         # video_opts
morphbr@718:         self.args["mux"]      = params_first("mux", "mpeg")
morphbr@718:         self.args["fps"]      = params_first("fps", 25)
morphbr@718:         self.args["vcodec"]   = params_first("vcodec", "mpeg1video")
morphbr@718:         self.args["vbitrate"] = params_first("vbitrate", 400)
morphbr@718:         self.args["width"]    = params_first("width", 320)
morphbr@718:         self.args["height"]   = params_first("height", 240)
morphbr@718:     # _setup_params()
morphbr@718: 
morphbr@718: 
morphbr@718:     def _setup_audio(self):
morphbr@718:         if self.args["acodec"] == "mp3lame":
morphbr@718:             audio = "-oac mp3lame -lameopts cbr:br=%s vol=%s" % (
morphbr@718:                 self.args["abitrate"], self.args["volume"])
morphbr@718:         else:
morphbr@718:             audio = "-oac lavc -lavcopts acodec=%s:abitrate=%s" % (
morphbr@718:                 self.args["acodec"], self.args["abitrate"])
morphbr@718: 
morphbr@718:         return audio
morphbr@718:     # _setup_audio()
morphbr@718: 
morphbr@718: 
morphbr@718:     def _setup_video(self):
morphbr@718:         video = " -of %s" % self.args["mux"]
morphbr@718:         video += " -ofps %s" % self.args["fps"]
morphbr@718: 
morphbr@718:         vcodec = self.args["vcodec"]
morphbr@718:         if vcodec == "nuv" or vcodec == "xvid"\
morphbr@718:                or vcodec == "qtvideo" or vcodec == "copy":
morphbr@718:             video += " -ovc %s" % vcodec
morphbr@718:         else:
morphbr@718:             video += " -ovc lavc -lavcopts vcodec=%s:vbitrate=%s" % (
morphbr@718:                 vcodec, self.args["vbitrate"])
morphbr@718: 
morphbr@718:         if self.args["mux"] == "mpeg":
morphbr@718:             video += " -mpegopts format=%s" % self.args["format"]
morphbr@736: 
morphbr@779:         video += " -vf kerndint,scale=%s:%s" % (self.args["width"], self.args["height"])
morphbr@718:         return video
morphbr@718:     # _setup_video()
morphbr@718: 
morphbr@718: 
morphbr@718:     def _arg_append(self, args, options):
morphbr@718:         for arg in shlex.split(options):
morphbr@718:             args.append(arg)
morphbr@718:     # arg_append()
morphbr@718: 
morphbr@718:     def _setup_mencoder_opts(self, args):
morphbr@718:         args.append(self.mencoder_path)
morphbr@718: 
morphbr@739:         if self.args["type"] and self.args["type"] == "tv":
morphbr@739:             self._arg_append(args, self.args["tv"])
morphbr@739:         elif self.args["outfile"] == "-" and self.args["type"]:
morphbr@718:             args.append(self.args["input"])
morphbr@718:         else:
morphbr@718:             args.append("-")
morphbr@718: 
morphbr@718:         if self.args["language"]:
morphbr@718:             self._arg_append(args, "-alang %s" % self.args["language"])
morphbr@718: 
morphbr@718:         if self.args["subtitle"]:
morphbr@718:             self._arg_append(args, "-slang %s" % self.args["subtitle"])
morphbr@718:             self._arg_append(args, "-subfps %s" % self.args["fps"])
morphbr@718: 
morphbr@765:         self._arg_append(args, "-idx")
morphbr@765:         self._arg_append(args, "-cache 1024")
morphbr@718:         self._arg_append(args, self._setup_audio())
morphbr@718:         self._arg_append(args, self._setup_video())
morphbr@718: 
morphbr@718:         self._arg_append(args, "-really-quiet")
morphbr@723: 
morphbr@723:         if self.args["outfile"] != "-":
morphbr@723:             self.args["outfile"] = ".transcoded/%s" % (
morphbr@723:                                    os.path.basename(self.args["outfile"]))
morphbr@723: 
morphbr@718:         self._arg_append(args, "-o %s" % self.args["outfile"])
morphbr@718:         self._arg_append(args, "2>%s" % os.devnull)
morphbr@718:     # _setup_args()
morphbr@718: 
morphbr@718:     def _setup_filename(self):
morphbr@718:         """This function setups the file to encode parsing the uri.
morphbr@718:         So, type can be:
morphbr@718:         * file
morphbr@718:         * dvd
morphbr@718:         * myth
morphbr@718: 
morphbr@718:         If the last one is detected we have to parse the uri to find args.
morphbr@718:         Then we store all the args inside a dictionary: self.args['gmyth-cat']
morphbr@718:         """
morphbr@718:         _type = self.args["type"]
morphbr@718: 
morphbr@718:         if _type == "file":
morphbr@718:             if not os.path.exists(self.args["input"]):
morphbr@718:                 raise IOError,\
morphbr@718:                       "File requested does not exist: %s." % self.args["input"]
morphbr@718:             else:
morphbr@718:                 self.args["input"] = "file://%s" % self.args["input"]
morphbr@718: 
morphbr@718:         elif _type == "dvd":
morphbr@736:             self.args["input"] = "dvd://%s" % self.args["input"]
morphbr@718: 
morphbr@718:         elif _type == "myth":
morphbr@718:             self.args["gmyth-cat"] = mythtv._setup_mythfilename(self)
morphbr@736: 
morphbr@736:         elif _type == "tv":
morphbr@736:             driver = self.params_first("driver", "v4l2")
morphbr@736:             norm = self.params_first("norm", "pal-m")
morphbr@736:             channel = self.params_first("channel", "13")
morphbr@736:             chanlist = self.params_first("chanlist", "us-bcast")
morphbr@736:             outfmt = self.params_first("outfmt", "yuy2")
morphbr@736:             vdev = self.params_first("vdev", "/dev/video0")
morphbr@736:             adev = self.params_first("adev", "/dev/dsp")
morphbr@739:             self.args["tv"] = "tv:// -v -tv driver=%s:norm=%s:channel=%s:" \
morphbr@739:                               "chanlist=%s:width=%s:height=%s:outfmt=%s:" \
morphbr@739:                               "device=%s:adevice=%s" % (driver, norm,
morphbr@739:                                                         channel, chanlist,
morphbr@739:                                                         self.args["width"],
morphbr@739:                                                         self.args["height"],
morphbr@739:                                                         outfmt, vdev, adev)
morphbr@718:     # _setup_filename()
morphbr@718: 
morphbr@718: 
morphbr@718:     def __init__(self, params):
morphbr@718:         transcoder.Transcoder.__init__(self, params)
morphbr@718:         self.mencoder_opts = []
morphbr@718: 
morphbr@718:         try:
morphbr@718:             self._setup_params()
morphbr@718:             self._setup_filename()
morphbr@718:             self._setup_mencoder_opts(self.mencoder_opts)
morphbr@718:         except Exception, e:
morphbr@742:             if self.log:
morphbr@742:                 self.log.error(self.tid, "Error: %s" % e)
morphbr@742:             else:
morphbr@742:                 raise
morphbr@718:     # __init__()
morphbr@718: 
morphbr@718: 
morphbr@718:     def _check_opened_file(self, stdw, _stdin):
morphbr@718:         loop = True
morphbr@718:         while loop:
morphbr@718:             try:
morphbr@718:                 return open(self.args["outfile"])
morphbr@718:             except:
morphbr@765:                 os.write(stdw, _stdin.read(1024))
morphbr@718:     # _check_opened_file
morphbr@718: 
morphbr@718: 
morphbr@718:     def _start_outfile(self, outfd):
morphbr@718:         finished = False
morphbr@718: 
morphbr@718:         # Configuring stdin
morphbr@718:         try:
morphbr@723:             filename = self.args["input"].split("://")[1]
morphbr@723:             _stdin = open(filename)
morphbr@723:             size = int(os.path.getsize(filename))
morphbr@718:         except Exception, e:
morphbr@723:             self.log.error(self.tid, "Error: Mencoder stdin"\
morphbr@723:                            " setup error: %s" % e)
morphbr@723:             outfd.write("Error: Mencoder stdin setup error: %s" %e)
morphbr@718:             return False
morphbr@718: 
morphbr@718:         self.status = 0
morphbr@718:         total_read = 0
morphbr@718: 
morphbr@718:         # Configuring pipes
morphbr@718:         stdr, stdw = os.pipe()
morphbr@718: 
morphbr@718:         if not self._run_mencoder(input=stdr):
morphbr@718:             return False
morphbr@718: 
morphbr@718:         stdout = self._check_opened_file(stdw, _stdin)
morphbr@726:         outfd.write("OK   ")
morphbr@726: 
morphbr@718:         try:
morphbr@718:             while self.proc and self.proc.poll() == None:
morphbr@718:                 if not finished:
morphbr@718:                     data_in = _stdin.read(4096)
morphbr@718:                     if data_in != "":
morphbr@718:                         os.write(stdw, data_in)
morphbr@718:                         total_read += 4096
morphbr@718:                         d = stdout.read(4096)
morphbr@744:                         self.status = utils.progress_bar(total_read,
morphbr@744:                                                          size, 50)
morphbr@718:                     else:
morphbr@718:                         finished = True
morphbr@718:                         os.close(stdw)
morphbr@718: 
morphbr@718:                 else:
morphbr@718:                     d = stdout.read(4096)
morphbr@718: 
morphbr@718:         except Exception, e:
morphbr@723:             self.log.error(self.tid, "Error: %s" % e)
morphbr@718:             self.stop()
morphbr@718:             return False
morphbr@718: 
morphbr@723:         self.log.info(self.tid, "OK: Done")
morphbr@718:         return True
morphbr@718:     # _start_outfile()
morphbr@718: 
morphbr@736: 
morphbr@718:     def _start(self, outfd):
morphbr@718:         # Play a file on disk or DVD
morphbr@718:         if not self._run_mencoder(output=subprocess.PIPE):
morphbr@718:             return False
morphbr@718: 
morphbr@759:         try:
morphbr@759:             while self.proc and self.proc.poll() == None:
morphbr@765:                 d = self.proc.stdout.read(1024)
morphbr@759:                 outfd.write(d)
morphbr@759:         except Exception, e:
morphbr@759:             self.log.error(self.tid, "Error: %s" % e)
morphbr@759:             return False
morphbr@718: 
morphbr@723:         self.log.info(self.tid, "OK: Done")
morphbr@718:         return True
morphbr@718:     # _start()
morphbr@718: 
morphbr@738:     def _run_mencoder(self, input=None, output=None):
morphbr@718:         try:
morphbr@718:             self.proc = subprocess.Popen(self.mencoder_opts, stdin=input,
morphbr@738:                                          stdout=output, close_fds=True)
morphbr@718:         except Exception, e:
morphbr@723:             self.log.error(self.tid, "Error: Mencoder: %s" % e)
morphbr@718:             return False
morphbr@718: 
morphbr@718:         return True
morphbr@718:     # _run_mencoder()
morphbr@718: 
morphbr@718:     def start(self, outfd):
morphbr@718:         cmd = " ".join(self.mencoder_opts)
morphbr@718:         self.log.debug(self.tid, "Plugin's tid: %s" % self.tid)
morphbr@718:         self.log.debug(self.tid, "Mencoder: %s" % cmd)
morphbr@718: 
morphbr@718:         ret = False
morphbr@718: 
morphbr@718:         if self.args["outfile"] == "-" and \
morphbr@736:                self.args["type"] in ["file", "dvd", "tv"]:
morphbr@718:             ret = self._start(outfd)
morphbr@718: 
morphbr@718:         elif self.args["type"] == "myth":
morphbr@718:             ret = mythtv.start_myth(self, outfd)
morphbr@718: 
morphbr@718:         else:
morphbr@718:             ret = self._start_outfile(outfd)
morphbr@718: 
morphbr@718:         self.stop()
morphbr@718: 
morphbr@718:         if not ret:
morphbr@723:             self.log.error(self.tid, "Error: Problems while "\
morphbr@723:                            "starting streaming.")
morphbr@718: 
morphbr@718:         return ret
morphbr@718:     # start()
morphbr@718: 
morphbr@718:     def _aux_stop(self, obj, next=False):
morphbr@718:         if obj:
morphbr@718:             try:
morphbr@718:                 os.kill(obj.pid, signal.SIGKILL)
morphbr@718:                 if next:
morphbr@718:                     os.kill(obj.pid+1, signal.SIGKILL)
morphbr@718:             except OSError, e:
morphbr@718:                 pass
morphbr@718: 
morphbr@718:             try:
morphbr@718:                 obj.wait()
morphbr@718:             except Exception, e:
morphbr@718:                 pass
morphbr@718: 
morphbr@718:             obj = None
morphbr@718:     # _aux_stop
morphbr@718: 
morphbr@718:     def stop(self):
morphbr@718:         self._aux_stop(self.proc, True)
morphbr@718:         self._aux_stop(self.gmyth)
morphbr@718:     # stop()
morphbr@718: 
morphbr@718: # TranscoderMencoder