morphbr@577: import os
morphbr@577: import shlex
morphbr@577: import signal
morphbr@577: import subprocess
morphbr@595: import time
morphbr@651: import fcntl
morphbr@577: 
morphbr@565: import lib.utils as utils
morphbr@565: import lib.server as server
morphbr@653: import plugins.transcoders.mencoder_lib.mythtv as mythtv
morphbr@565: 
morphbr@595: from select import select
morphbr@595: 
morphbr@565: __all__ = ("TranscoderMencoder",)
morphbr@565: 
morphbr@565: class TranscoderMencoder(server.Transcoder):
morphbr@585:     """Transcoder class that implements a transcoder using Mencoder"""
morphbr@565:     mencoder_path = utils.which("mencoder")
morphbr@565:     name = "mencoder"
morphbr@565:     priority = -1
morphbr@577:     args = {}
morphbr@577:     proc = None
morphbr@605:     gmyth = None
morphbr@565: 
morphbr@595:     # only works with avi container
morphbr@595:     status = 0
morphbr@595: 
morphbr@569:     def _setup_params(self):
morphbr@569:         params_first = self.params_first
morphbr@569: 
morphbr@569:         # general_opts
morphbr@569:         self.args["local"]    = params_first("local", False)
morphbr@569:         self.args["language"] = params_first("language", False)
morphbr@569:         self.args["subtitle"] = params_first("subtitle", False)
morphbr@628:         self.args["format"]   = params_first("format", "mpeg1")
morphbr@569:         self.args["outfile"]  = params_first("outfile", "-")
morphbr@569: 
morphbr@569:         # input_opt
morphbr@664:         self.args["type"]     = params_first("type", "file")
morphbr@664:         self.args["input"]    = params_first("uri", "-")
morphbr@569: 
morphbr@569:         # audio_opts
morphbr@569:         self.args["acodec"]   = params_first("acodec", "mp2")
morphbr@569:         self.args["abitrate"] = params_first("abitrate", 192)
morphbr@569:         self.args["volume"]   = params_first("volume", 5)
morphbr@569: 
morphbr@569:         # video_opts
morphbr@569:         self.args["mux"]      = params_first("mux", "mpeg")
morphbr@569:         self.args["fps"]      = params_first("fps", 25)
morphbr@569:         self.args["vcodec"]   = params_first("vcodec", "mpeg1video")
morphbr@569:         self.args["vbitrate"] = params_first("vbitrate", 400)
morphbr@569:         self.args["width"]    = params_first("width", 320)
morphbr@569:         self.args["height"]   = params_first("height", 240)
morphbr@569:     # _setup_params()
morphbr@569: 
morphbr@569: 
morphbr@569:     def _setup_audio(self):
morphbr@569:         if self.args["acodec"] == "mp3lame":
morphbr@569:             audio = "-oac mp3lame -lameopts cbr:br=%s vol=%s" % (
morphbr@569:                 self.args["abitrate"], self.args["volume"])
morphbr@569:         else:
morphbr@569:             audio = "-oac lavc -lavcopts acodec=%s:abitrate=%s" % (
morphbr@569:                 self.args["acodec"], self.args["abitrate"])
morphbr@569: 
morphbr@569:         return audio
morphbr@569:     # _setup_audio()
morphbr@569: 
morphbr@569: 
morphbr@569:     def _setup_video(self):
morphbr@577:         video = " -of %s" % self.args["mux"]
morphbr@577:         video += " -ofps %s" % self.args["fps"]
morphbr@569: 
morphbr@569:         vcodec = self.args["vcodec"]
morphbr@569:         if vcodec == "nuv" or vcodec == "xvid"\
morphbr@569:                or vcodec == "qtvideo" or vcodec == "copy":
morphbr@569:             video += " -ovc %s" % vcodec
morphbr@569:         else:
morphbr@569:             video += " -ovc lavc -lavcopts vcodec=%s:vbitrate=%s" % (
morphbr@569:                 vcodec, self.args["vbitrate"])
morphbr@569: 
morphbr@628:         if self.args["mux"] == "mpeg":
morphbr@628:             video += " -mpegopts format=%s" % self.args["format"]
morphbr@569:         video += " -vf scale=%s:%s" % (self.args["width"], self.args["height"])
morphbr@569: 
morphbr@569:         return video
morphbr@569:     # _setup_video()
morphbr@569: 
morphbr@569: 
morphbr@569:     def _arg_append(self, args, options):
morphbr@577:         for arg in shlex.split(options):
morphbr@569:             args.append(arg)
morphbr@569:     # arg_append()
morphbr@569: 
morphbr@569:     def _setup_mencoder_opts(self, args):
morphbr@569:         args.append(self.mencoder_path)
morphbr@595: 
morphbr@595:         if self.args["outfile"] == "-" and self.args["type"]:
morphbr@595:             args.append(self.args["input"])
morphbr@595:         else:
morphbr@595:             args.append("-")
morphbr@569: 
morphbr@569:         if self.args["language"]:
morphbr@569:             self._arg_append(args, "-alang %s" % self.args["language"])
morphbr@569: 
morphbr@569:         if self.args["subtitle"]:
morphbr@569:             self._arg_append(args, "-slang %s" % self.args["subtitle"])
morphbr@569:             self._arg_append(args, "-subfps %s" % self.args["fps"])
morphbr@569: 
morphbr@569:         self._arg_append(args, "-idx")
morphbr@628:         self._arg_append(args, "-cache 1024")
morphbr@577:         self._arg_append(args, self._setup_audio())
morphbr@577:         self._arg_append(args, self._setup_video())
morphbr@569: 
morphbr@569:         self._arg_append(args, "-really-quiet")
morphbr@569:         self._arg_append(args, "-o %s" % self.args["outfile"])
morphbr@595:         self._arg_append(args, "2>%s" % os.devnull)
morphbr@569:     # _setup_args()
morphbr@569: 
morphbr@664:     def _setup_filename(self):
morphbr@664:         """This function setups the file to encode parsing the uri.
morphbr@664:         So, type can be:
morphbr@664:         * file
morphbr@664:         * dvd
morphbr@664:         * myth
morphbr@569: 
morphbr@664:         If the last one is detected we have to parse the uri to find args.
morphbr@664:         Then we store all the args inside a dictionary: self.args['gmyth-cat']
morphbr@664:         """
morphbr@569:         _type = self.args["type"]
morphbr@569: 
morphbr@569:         if _type == "file":
morphbr@569:             if not os.path.exists(self.args["input"]):
morphbr@577:                 raise IOError,\
morphbr@577:                       "File requested does not exist: %s." % self.args["input"]
morphbr@605:             else:
morphbr@605:                 self.args["input"] = "file://%s" % self.args["input"]
morphbr@569: 
morphbr@569:         elif _type == "dvd":
morphbr@569:             self.args["input"] = "dvd://".join(self.args["input"])
morphbr@595: 
morphbr@595:         elif _type == "myth":
morphbr@664:             self.args["gmyth-cat"] = mythtv._setup_mythfilename(self)
morphbr@569:     # _setup_filename()
morphbr@569: 
morphbr@569: 
morphbr@572:     def __init__(self, params):
morphbr@565:         server.Transcoder.__init__(self, params)
morphbr@577:         self.mencoder_opts = []
morphbr@565: 
morphbr@569:         try:
morphbr@569:             self._setup_params()
morphbr@569:             self._setup_filename()
morphbr@569:             self._setup_mencoder_opts(self.mencoder_opts)
morphbr@569:         except Exception, e:
morphbr@569:             self.log.error(e)
morphbr@565:     # __init__()
morphbr@565: 
morphbr@565: 
morphbr@595:     def _check_opened_file(self, stdw, _stdin):
morphbr@595:         loop = True
morphbr@595:         while loop:
morphbr@595:             try:
morphbr@595:                 return open(self.args["outfile"])
morphbr@595:             except:
morphbr@595:                 os.write(stdw, _stdin.read(1024))
morphbr@595:     # _check_opened_file
morphbr@595: 
morphbr@595: 
morphbr@595:     def _start_outfile(self, outfd):
morphbr@595:         finished = False
morphbr@595: 
morphbr@595:         # fix this (not necessary)
morphbr@595:         outfd.write("OK")
morphbr@595: 
morphbr@595:         # Configuring stdin
morphbr@638:         try:
morphbr@638:             _stdin = open(self.args["input"])
morphbr@638:             size = int(os.path.getsize(self.args["input"]))
morphbr@638:         except Exception, e:
morphbr@638:             self.log.error("Mencoder stdin setup error: %s" % e)
morphbr@638:             return False
morphbr@638: 
morphbr@595:         self.status = 0
morphbr@595:         total_read = 0
morphbr@595: 
morphbr@595:         # Configuring pipes
morphbr@595:         stdr, stdw = os.pipe()
morphbr@569: 
morphbr@605:         if not self._run_mencoder(input=stdr):
morphbr@565:             return False
morphbr@565: 
morphbr@595:         stdout = self._check_opened_file(stdw, _stdin)
morphbr@595: 
morphbr@595:         try:
morphbr@595:             while self.proc and self.proc.poll() == None:
morphbr@595:                 if not finished:
morphbr@595:                     data_in = _stdin.read(4096)
morphbr@595:                     if data_in != "":
morphbr@595:                         os.write(stdw, data_in)
morphbr@595:                         total_read += 4096
morphbr@595:                         d = stdout.read(4096)
morphbr@660:                         self.status = utils.progress_bar(self.log,
morphbr@660:                                                          int(total_read),
morphbr@660:                                                          int(size), 50)
morphbr@595:                     else:
morphbr@595:                         finished = True
morphbr@595:                         os.close(stdw)
morphbr@595: 
morphbr@595:                 else:
morphbr@595:                     d = stdout.read(4096)
morphbr@595: 
morphbr@595:         except Exception, e:
morphbr@595:             self.log.error("Problems handling data: %s" % e)
morphbr@628:             self.stop()
morphbr@595:             return False
morphbr@595: 
morphbr@595:         self.log.info("%s: Finished sending data to client" % repr(self))
morphbr@595:         return True
morphbr@595:     # _start_outfile()
morphbr@595: 
morphbr@595:     def _start(self, outfd):
morphbr@651:         # Play a file on disk or DVD
morphbr@605:         if not self._run_mencoder(output=subprocess.PIPE):
morphbr@595:             return False
morphbr@595: 
morphbr@565:         try:
morphbr@565:             while self.proc and self.proc.poll() == None:
morphbr@577:                 d = self.proc.stdout.read(1024)
morphbr@565:                 outfd.write(d)
morphbr@565:         except Exception, e:
morphbr@565:             self.log.error("Problems handling data: %s" % e)
morphbr@565:             return False
morphbr@565: 
morphbr@595:         self.log.info("%s: Finished sending data to client" % repr(self))
morphbr@565:         return True
morphbr@595:     # _start()
morphbr@595: 
morphbr@605:     def _run_mencoder(self, input=None, output=None):
morphbr@605:         try:
morphbr@605:             self.proc = subprocess.Popen(self.mencoder_opts, stdin=input,
morphbr@605:                                          stdout=output, close_fds=True)
morphbr@605:         except Exception, e:
morphbr@605:             self.log.error("Error executing mencoder: %s" % e)
morphbr@605:             return False
morphbr@605: 
morphbr@605:         return True
morphbr@605:     # _run_mencoder()
morphbr@595: 
morphbr@595:     def start(self, outfd):
morphbr@595:         cmd = " ".join(self.mencoder_opts)
morphbr@595:         self.log.debug("Mencoder: %s" % cmd)
morphbr@595: 
morphbr@605:         ret = False
morphbr@605: 
morphbr@717:         if self.args["outfile"] == "-" and \
morphbr@717:                self.args["type"] in ["file", "dvd"]:
morphbr@605:             ret = self._start(outfd)
morphbr@605: 
morphbr@605:         elif self.args["type"] == "myth":
morphbr@653:             ret = mythtv.start_myth(self, outfd)
morphbr@605: 
morphbr@595:         else:
morphbr@605:             ret = self._start_outfile(outfd)
morphbr@605: 
morphbr@605:         self.stop()
morphbr@717: 
morphbr@717:         if not ret:
morphbr@717:             self.log.error("Problems while starting streaming.")
morphbr@717: 
morphbr@605:         return ret
morphbr@565:     # start()
morphbr@565: 
morphbr@660:     def _aux_stop(self, obj):
morphbr@660:         if obj:
morphbr@565:             try:
morphbr@660:                 os.kill(obj.pid, signal.SIGKILL)
morphbr@565:             except OSError, e:
morphbr@565:                 pass
morphbr@565: 
morphbr@565:             try:
morphbr@660:                 obj.wait()
morphbr@565:             except Exception, e:
morphbr@565:                 pass
morphbr@565: 
morphbr@660:             obj = None
morphbr@660:     # _aux_stop
morphbr@605: 
morphbr@660:     def stop(self):
morphbr@660:         self._aux_stop(self.proc)
morphbr@660:         self._aux_stop(self.gmyth)
morphbr@569:     # stop()
morphbr@565: 
morphbr@565: # TranscoderMencoder