text: import logging
  import subprocess
  import os.path
  
  from operator import itemgetter
  from streamlink.stream import Stream
  from streamlink.stream.wrappers import StreamIOThreadWrapper
  from streamlink.compat import devnull, which
  from streamlink.exceptions import StreamError
  
  import time
  import tempfile
  
  log = logging.getLogger(__name__)
  
  
  class StreamProcessIO(StreamIOThreadWrapper):
      def __init__(self, session, process, fd, **kwargs):
          self.process = process
  
          super(StreamProcessIO, self).__init__(session, fd, **kwargs)
  
      def close(self):
          try:
              self.process.kill()
          except Exception:
              pass
          finally:
              super(StreamProcessIO, self).close()
  
  
  class StreamProcess(Stream):
      def __init__(self, session, params=None, args=None, timeout=60.0):
          """
  
          :param session: Streamlink session
          :param params: keyword arguments mapped to process argument
          :param args: positional arguments
          :param timeout: timeout for process
          """
          super(StreamProcess, self).__init__(session)
  
          self.parameters = params or {}
          self.arguments = args or []
          self.timeout = timeout
          self.errorlog = self.session.options.get("subprocess-errorlog")
          self.errorlog_path = self.session.options.get("subprocess-errorlog-path")
  
          if self.errorlog_path:
              self.stderr = open(self.errorlog_path, "w")
          elif self.errorlog:
              self.stderr = tempfile.NamedTemporaryFile(prefix="streamlink", suffix=".err", delete=False)
          else:
              self.stderr = devnull()
  
      @property
      def cmd(self):
          raise NotImplementedError
  
      @property
      def params(self):
          return self.parameters
  
      @classmethod
      def is_usable(cls, session):
          raise NotImplementedError
  
      def open(self):
          if self.is_usable(self.session):
              process = self.spawn(self.parameters, self.arguments)
  
              # Wait 0.5 seconds to see if program exited prematurely
              time.sleep(0.5)
  
              if not process.poll() is None:
                  if hasattr(self.stderr, "name"):
                      raise StreamError(("Error while executing subprocess, "
                                         "error output logged to: {0}").format(self.stderr.name))
                  else:
                      raise StreamError("Error while executing subprocess")
  
              return StreamProcessIO(self.session, process, process.stdout, timeout=self.timeout)
          else:
              raise StreamError(
                  "{0} is not installed or not supported on your system".format(os.path.basename(self.cmd))
              )
  
      @classmethod
      def bake(cls, cmd, parameters=None, arguments=None, short_option_prefix="-", long_option_prefix="--"):
          cmdline = [cmd]
          parameters = parameters or {}
          arguments = arguments or []
  
          def to_option(key):
              if len(key) == 1:  # short argument
                  return "{0}{1}".format(short_option_prefix, key)
              else:  # long argument
                  return "{0}{1}".format(long_option_prefix, key.replace("_", "-"))
  
          # sorted for stability
          for k, v in sorted(parameters.items(), key=itemgetter(0)):
              if not isinstance(v, list):  # long argument
                  cmdline.append(to_option(k))
                  if v is not True:
                      cmdline.append("{0}".format(v))
              else:  # duplicate the argument if given a list of values
                  for sv in v:
                      cmdline.append(to_option(k))
                      cmdline.append("{0}".format(sv))
  
          # positional arguments last
          cmdline.extend(arguments)
  
          return cmdline
  
      def spawn(self, parameters=None, arguments=None, stderr=None, timeout=None,
                short_option_prefix="-", long_option_prefix="--"):
          """
          Spawn the process defined in `cmd`
  
          parameters is converted to options the short and long option prefixes
          if a list is given as the value, the parameter is repeated with each
          value
  
          If timeout is set the spawn will block until the process returns or
          the timeout expires.
  
          :param parameters: optional parameters
          :param arguments: positional arguments
          :param stderr: where to redirect stderr to
          :param timeout: timeout for short lived process
          :param long_option_prefix: option prefix, default -
          :param short_option_prefix: long option prefix, default --
          :return: spawned process
          """
          stderr = stderr or self.stderr
          cmd = self.bake(self._check_cmd(), parameters, arguments, short_option_prefix, long_option_prefix)
          log.debug("Spawning command: {0}", subprocess.list2cmdline(cmd))
  
          try:
              process = subprocess.Popen(cmd, stderr=stderr, stdout=subprocess.PIPE)
          except (OSError, IOError) as err:
              raise StreamError("Failed to start process: {0} ({1})".format(self._check_cmd(), str(err)))
  
          if timeout:
              elapsed = 0
              while elapsed < timeout and not process.poll():
                  time.sleep(0.25)
                  elapsed += 0.25
  
              # kill after the timeout has expired and the process still hasn't ended
              if not process.poll():
                  try:
                      log.debug("Process timeout expired ({0}s), killing process".format(timeout))
                      process.kill()
                  except Exception:
                      pass
  
              process.wait()
  
          return process
  
      def cmdline(self):
          return subprocess.list2cmdline(self.bake(self._check_cmd(), self.parameters, self.arguments))
  
      def _check_cmd(self):
          if not self.cmd:
              raise StreamError("`cmd' attribute not set")
  
          cmd = which(self.cmd)
  
          if not cmd:
              raise StreamError("Unable to find `{0}' command".format(self.cmd))
  
          return cmd
  
  
  __all__ = ["StreamProcess"]
  