コード例 #1
0
ファイル: windowmanager.py プロジェクト: willtejeda/camplayer
    def set_display_name(self, display_name):
        """Set player OSD text for this window"""

        if not display_name or self._display_name:
            return

        sub_file = CONSTANTS.CACHE_DIR + display_name + ".srt"

        try:
            # Create folder if not exist
            if not os.path.isdir(os.path.dirname(sub_file)):
                os.system("mkdir -p %s" % os.path.dirname(sub_file))

            # Create subtitle file if not exist
            if not os.path.isfile(sub_file):
                with open(sub_file, 'w+') as file:
                    # Important note: we can only show subs for a 99 hour period!
                    file.write('00:00:00,00 --> 99:00:00,00\n')
                    file.write(display_name + '\n')

            self._display_name = display_name
        except:

            # TODO: filter for read-only error only
            LOG.ERROR(self._LOG_NAME,
                      "writing subtitle file failed, read only?")
コード例 #2
0
ファイル: windowmanager.py プロジェクト: willtejeda/camplayer
    def stop_all_players(cls, sigkill=False):
        """Stop all players the fast and hard way"""

        term_cmd = '-9' if sigkill else '-15'

        try:
            subprocess.Popen(['killall', term_cmd, 'omxplayer.bin'],
                             shell=False,
                             stdout=subprocess.DEVNULL,
                             stderr=subprocess.DEVNULL)

            subprocess.Popen(['killall', term_cmd, 'vlc'],
                             shell=False,
                             stdout=subprocess.DEVNULL,
                             stderr=subprocess.DEVNULL)
        except Exception as error:
            LOG.ERROR(cls._LOG_NAME,
                      "stop_all_players pid kill error: %s" % str(error))
コード例 #3
0
    def scale_background(cls, src_path, dest_path, dest_width, dest_height):
        """Scale background image to the requested width and height"""

        if not GLOBALS.FFMPEG_SUPPORT:
            return False

        ffmpeg_cmd = str("ffmpeg -i '%s' -vf scale=%i:%i '%s'" % (src_path, dest_width, dest_height, dest_path))

        try:
            subprocess.check_output(ffmpeg_cmd, shell=True, stderr=subprocess.STDOUT, timeout=5)

        except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
            LOG.ERROR(cls._MODULE, "Scaling background image '%s' failed" % src_path)

        if os.path.isfile(dest_path):
            return True

        return False
コード例 #4
0
ファイル: windowmanager.py プロジェクト: willtejeda/camplayer
    def stream_stop(self):
        """Stop the playing stream"""

        if self.playstate == PLAYSTATE.NONE:
            return

        LOG.INFO(
            self._LOG_NAME, "stopping stream '%s' '%s'" %
            (self._omx_dbus_ident, self.active_stream.printable_url()))

        # VLC:
        # - send Dbus stop command, vlc stays idle in the background
        # - not every window has it's own vlc instance as vlc can only be used for fullscreen playback,
        #   therefore we have to be sure that 'our instance' isn't already playing another stream.
        if self._player == PLAYER.VLCPLAYER and \
                Window._vlc_active_stream_url[self._display_num - 1] == self.active_stream.url:

            # Stop playback but do not quit
            self._send_dbus_command(DBUS_COMMAND.PLAY_STOP)

            Window._vlc_active_stream_url[self._display_num - 1] = ""

        # OMXplayer:
        # - omxplayer doen't support an idle state, stopping playback will close omxplayer,
        #   so in this case we also have to cleanup the pids.
        elif self._player == PLAYER.OMXPLAYER and self.omx_player_pid:
            try:
                os.kill(self.omx_player_pid, signal.SIGTERM)
            except Exception as error:
                LOG.ERROR(self._LOG_NAME, "pid kill error: %s" % str(error))

            self._pidpool_remove_pid(self.omx_player_pid)
            self.omx_player_pid = 0

        if self.active_stream:
            Window._total_weight -= self.get_weight(self.active_stream)

        self.active_stream = None
        self.playstate = PLAYSTATE.NONE
        self._omx_duration = 0
コード例 #5
0
ファイル: windowmanager.py プロジェクト: willtejeda/camplayer
    def _stream_start(self, stream=None):
        """Start the specified stream if any, else the default will be played"""

        if self.playstate != PLAYSTATE.NONE:
            return

        if len(self.streams) <= 0:
            return

        if not stream:
            stream = self.get_default_stream()

        if not stream:
            return

        win_width = CONSTANTS.VIRT_SCREEN_WIDTH if self.fullscreen_mode else self.window_width
        win_height = CONSTANTS.VIRT_SCREEN_HEIGHT if self.fullscreen_mode else self.window_height
        sub_file = ""

        if self._display_name and CONFIG.VIDEO_OSD:
            sub_file = CONSTANTS.CACHE_DIR + self._display_name + ".srt"

        LOG.INFO(
            self._LOG_NAME,
            "starting stream '%s' '%s' with resolution '%ix%i' and weight '%i' in a window '%ix%i'"
            % (self._omx_dbus_ident, stream.printable_url(), stream.width,
               stream.height, self.get_weight(stream), win_width, win_height))

        # OMXplayer can play in fullscreen and windowed mode
        # One instance per window
        if stream.valid_video_windowed:
            self._player = PLAYER.OMXPLAYER

            # Layer should be unique to avoid visual glitches/collisions
            omx_layer_arg = (self._screen_num *
                             CONSTANTS.MAX_WINDOWS) + self._window_num

            if self.fullscreen_mode and self.visible:
                # Window position also required for fullscreen playback,
                # otherwise lower layers will be disabled when moving the window position later on

                omx_pos_arg = str("%i %i %i %i" %
                                  (CONSTANTS.VIRT_SCREEN_OFFSET_X,
                                   CONSTANTS.VIRT_SCREEN_OFFSET_Y,
                                   CONSTANTS.VIRT_SCREEN_OFFSET_X +
                                   CONSTANTS.VIRT_SCREEN_WIDTH,
                                   CONSTANTS.VIRT_SCREEN_OFFSET_Y +
                                   CONSTANTS.VIRT_SCREEN_HEIGHT))

            else:
                omx_pos_arg = str(
                    "%i %i %i %i" %
                    (self.x1 +
                     (0 if self.visible else CONSTANTS.WINDOW_OFFSET), self.y1,
                     self.x2 +
                     (0 if self.visible else CONSTANTS.WINDOW_OFFSET),
                     self.y2))

            player_cmd = [
                'omxplayer',
                '--no-keys',  # No keyboard input
                '--no-osd',  # No OSD
                '--aspect-mode',
                'stretch',  # Stretch video if aspect doesn't match
                '--dbus_name',
                self.
                _omx_dbus_ident,  # Dbus name for controlling position etc.
                '--threshold',
                str(CONFIG.BUFFERTIME_MS /
                    1000),  # Threshold of buffer in seconds
                '--layer',
                str(omx_layer_arg),  # Dispmanx layer
                '--alpha',
                '255',  # No transparency
                '--nodeinterlace',  # Assume progressive streams
                '--nohdmiclocksync',  # Clock sync makes no sense with multiple clock sources
                '--avdict',
                'rtsp_transport:tcp',  # Force RTSP over TCP
                '--display',
                '7' if self._display_num == 2 else
                '2',  # 2 is HDMI0 (default), 7 is HDMI1 (pi4)
                '--timeout',
                str(CONFIG.PLAYTIMEOUT_SEC
                    ),  # Give up playback after this period of trying
                '--win',
                omx_pos_arg  # Window position
            ]

            if stream.url.startswith('file://'):
                player_cmd.append(
                    '--loop')  # Loop for local files (demo/test mode)
            else:
                player_cmd.append(
                    '--live')  # Avoid sync issues with long playing streams

            if CONFIG.AUDIO_MODE == AUDIOMODE.FULLSCREEN and \
                    self.visible and self.fullscreen_mode and stream.has_audio:
                # OMXplayer can only open 8 instances instead of 16 when audio is enabled,
                # this can also lead to total system lockups...
                # Work around this by disabling the audio stream when in windowed mode,
                # in fullscreen mode, we can safely enable audio again.
                # set_visible() and set_invisible() methods are also adopted for this.

                # Volume % to millibels conversion
                volume = int(2000 *
                             math.log10(max(CONFIG.AUDIO_VOLUME, 0.001) / 100))
                player_cmd.extend(['--vol', str(volume)])  # Set audio volume

                self._omx_audio_enabled = True
            else:
                player_cmd.extend(['--aidx', '-1'])  # Disable audio stream
                self._omx_audio_enabled = False

            # Show our channel name with a custom subtitle file?
            # OMXplayer OSD not supported on pi4 hardware
            if sub_file and not "4B" in GLOBALS.PI_MODEL:
                if os.path.isfile(sub_file):
                    player_cmd.extend(['--subtitles', sub_file
                                       ])  # Add channel name as subtitle
                    player_cmd.extend([
                        '--no-ghost-box', '--align', 'center', '--lines', '1'
                    ])  # Set subtitle properties

        # VLC media player can play only in fullscreen mode
        # One fullscreen instance per display
        elif self.fullscreen_mode and stream.valid_video_fullscreen:
            self._player = PLAYER.VLCPLAYER

            player_cmd = [
                'cvlc',
                '--fullscreen',  # VLC does not support windowed mode without X11
                '--network-caching=' +
                str(CONFIG.BUFFERTIME_MS
                    ),  # Threshold of buffer in miliseconds
                '--rtsp-tcp',  # Force RTSP over TCP
                '--no-keyboard-events',  # No keyboard events
                '--mmal-display=hdmi-' +
                str(self._display_num),  # Select the correct display
                '--mmal-layer=0',  # OMXplayer uses layers starting from 0, don't interference
                '--input-timeshift-granularity=0',  # Disable timeshift feature
                '--vout=mmal_vout',  # Force MMAL mode
                '--gain=1',  # Audio gain
                '--no-video-title-show'  # Disable filename popup on start
            ]

            # Keep in mind that VLC instances can be reused for
            # other windows with possibly other audio settings!
            # So don't disable the audio output to quickly!
            if CONFIG.AUDIO_MODE == AUDIOMODE.FULLSCREEN:

                # VLC does not have a command line volume argument??
                pass

            else:
                player_cmd.append('--no-audio')  # Disable audio stream

            if stream.url.startswith('file://'):
                player_cmd.append(
                    '--repeat')  # Loop for local files (demo/test mode)

            # Show our channel name with a custom subtitle file?
            if sub_file and os.path.isfile(sub_file):
                player_cmd.extend(['--sub-file',
                                   sub_file])  # Add channel name as subtitle

            # TODO: we need te reopen VLC every time for the correct sub?
            if ((sub_file and os.path.isfile(sub_file)) or Window._vlc_subs_enabled[self._display_num - 1]) and \
                    self.get_vlc_pid(self._display_num):

                LOG.WARNING(
                    self._LOG_NAME,
                    "closing already active VLC instance for display '%i' "
                    "as subtitles (video OSD) are enabled" % self._display_num)

                player_pid = self.get_vlc_pid(self._display_num)

                utils.terminate_process(player_pid, force=True)
                self._pidpool_remove_pid(player_pid)
                Window.vlc_player_pid[self._display_num - 1] = 0

        else:
            LOG.ERROR(
                self._LOG_NAME,
                "stream '%s' with codec '%s' is not valid for playback" %
                (stream.printable_url(), stream.codec_name))
            return

        # Check hardware video decoder impact
        if Window._total_weight + self.get_weight(
                stream) > CONSTANTS.HW_DEC_MAX_WEIGTH and CONFIG.HARDWARE_CHECK:
            LOG.ERROR(
                self._LOG_NAME,
                "current hardware decoder weight is '%i', max decoder weight is '%i'"
                % (Window._total_weight, CONSTANTS.HW_DEC_MAX_WEIGTH))
            return
        else:
            Window._total_weight += self.get_weight(stream)

        # Set URL before stripping
        self.active_stream = stream
        url = stream.url

        if self._player == PLAYER.VLCPLAYER and self.get_vlc_pid(
                self._display_num):

            LOG.DEBUG(
                self._LOG_NAME,
                "reusing already active VLC instance for display '%i'" %
                self._display_num)

            if self.visible:
                # VLC player instance can be playing or in idle state.
                # Sending the play command will start fullscreen playback of our video/stream.
                # When VLC is playing other content, we will hijack it.

                # Enable/disable audio
                if CONFIG.AUDIO_MODE == AUDIOMODE.FULLSCREEN:
                    volume = CONFIG.AUDIO_VOLUME / 100
                    self._send_dbus_command(DBUS_COMMAND.PLAY_VOLUME, volume)

                # Start our stream
                self._send_dbus_command(DBUS_COMMAND.PLAY_PLAY)

                # Mark our steam as the active one for this display
                Window._vlc_active_stream_url[self._display_num -
                                              1] = self.active_stream.url

            else:
                # Play command will be sent by 'stream_set_visible' later on.
                pass

            # Pretend like the player just started again
            self.playstate = PLAYSTATE.INIT2
            self._time_streamstart = time.monotonic()
            return

        else:

            LOG.DEBUG(self._LOG_NAME,
                      "starting player with arguments '%s'" % player_cmd)

            # Add URL now, as we don't want sensitive credentials in the logfile...
            if self._player == PLAYER.OMXPLAYER:
                player_cmd.append(url)

            elif self._player == PLAYER.VLCPLAYER and self.visible:
                player_cmd.append(url)
                Window._vlc_active_stream_url[self._display_num - 1] = url

            if self._player == PLAYER.VLCPLAYER:
                # VLC changes its DBus string to 'org.mpris.MediaPlayer2.vlc.instancePID'
                # when opening a second instance, so we have to adjust it later on when we know the PID
                # Max number of VLC instances = number of displays = 2

                if self._pidpool_get_pid("--mmal-display=hdmi-"):
                    Window._vlc_dbus_ident[
                        self._display_num -
                        1] = "org.mpris.MediaPlayer2.vlc.instance"
                else:
                    Window._vlc_dbus_ident[self._display_num -
                                           1] = "org.mpris.MediaPlayer2.vlc"

                # Save the subtitle state for later use
                Window._vlc_subs_enabled[self._display_num -
                                         1] = (sub_file
                                               and os.path.isfile(sub_file))

            subprocess.Popen(player_cmd,
                             shell=False,
                             stdout=subprocess.DEVNULL,
                             stderr=subprocess.DEVNULL)

            if self._player == PLAYER.VLCPLAYER:
                # VLC does not have a command line argument for volume control??
                # As workaround, wait for VLC startup and send the desired volume with DBus
                time.sleep(0.5)
                self._send_dbus_command(DBUS_COMMAND.PLAY_VOLUME,
                                        CONFIG.AUDIO_VOLUME / 100,
                                        retries=5)

        self._time_streamstart = time.monotonic()
        self.playstate = PLAYSTATE.INIT1
        self._omx_duration = 0
コード例 #6
0
ファイル: windowmanager.py プロジェクト: willtejeda/camplayer
    def _send_dbus_command(self,
                           command,
                           argument="",
                           kill_player_on_error=True,
                           retries=CONSTANTS.DBUS_RETRIES):
        """Send command to player with DBus"""

        response = ""
        command_destination = ""
        command_prefix = ""

        if self._player == PLAYER.OMXPLAYER:
            command_destination = self._omx_dbus_ident

            # OMXplayer needs some environment variables
            command_prefix = str(
                "export DBUS_SESSION_BUS_ADDRESS=`cat /tmp/omxplayerdbus.%s` && "
                "export DBUS_SESSION_BUS_PID=`cat /tmp/omxplayerdbus.%s.pid` && "
                % (GLOBALS.USERNAME, GLOBALS.USERNAME))

        elif self._player == PLAYER.VLCPLAYER:
            command_destination = Window._vlc_dbus_ident[self._display_num - 1]

            # VLC changes its DBus string to 'org.mpris.MediaPlayer2.vlc.instancePID'
            # when opening a second instance, so we have to append this PID first.
            if 'instance' in command_destination:
                command_destination += str(
                    Window.vlc_player_pid[self._display_num - 1])

        for i in range(retries + 1):
            try:
                if command == DBUS_COMMAND.OMXPLAYER_VIDEOPOS:
                    response = subprocess.check_output(
                        command_prefix +
                        "dbus-send --print-reply=literal --reply-timeout=%i "
                        "--dest=%s /org/mpris/MediaPlayer2 "
                        "org.mpris.MediaPlayer2.Player.%s objpath:/not/used "
                        "string:'%s'" %
                        (CONSTANTS.DBUS_TIMEOUT_MS, command_destination,
                         command, argument),
                        shell=True,
                        stderr=subprocess.STDOUT).decode().strip()

                elif command == DBUS_COMMAND.PLAY_STOP:
                    response = subprocess.check_output(
                        command_prefix +
                        "dbus-send --print-reply=literal --reply-timeout=%i "
                        "--dest=%s /org/mpris/MediaPlayer2 "
                        "org.mpris.MediaPlayer2.Player.%s" %
                        (CONSTANTS.DBUS_TIMEOUT_MS, command_destination,
                         command),
                        shell=True,
                        stderr=subprocess.STDOUT).decode().strip()

                elif command == DBUS_COMMAND.PLAY_PLAY:
                    response = subprocess.check_output(
                        command_prefix +
                        "dbus-send --print-reply=literal --reply-timeout=%i "
                        "--dest=%s /org/mpris/MediaPlayer2 "
                        "org.mpris.MediaPlayer2.Player.%s string:'%s'" %
                        (CONSTANTS.DBUS_TIMEOUT_MS, command_destination,
                         command, self.active_stream.url),
                        shell=True,
                        stderr=subprocess.STDOUT).decode().strip()

                elif command == DBUS_COMMAND.PLAY_VOLUME:
                    response = subprocess.check_output(
                        command_prefix +
                        "dbus-send --print-reply=literal --reply-timeout=%i "
                        "--dest=%s /org/mpris/MediaPlayer2 "
                        "org.freedesktop.DBus.Properties.Set "
                        "string:'org.mpris.MediaPlayer2.Player' string:'%s' variant:double:%f"
                        % (CONSTANTS.DBUS_TIMEOUT_MS, command_destination,
                           command, argument),
                        shell=True,
                        stderr=subprocess.STDOUT).decode().strip()

                else:
                    response = subprocess.check_output(
                        command_prefix +
                        "dbus-send --print-reply=literal --reply-timeout=%i "
                        "--dest=%s /org/mpris/MediaPlayer2 "
                        "org.freedesktop.DBus.Properties.Get "
                        "string:'org.mpris.MediaPlayer2.Player' string:'%s'" %
                        (CONSTANTS.DBUS_TIMEOUT_MS, command_destination,
                         command),
                        shell=True,
                        stderr=subprocess.STDOUT).decode().strip()

                LOG.DEBUG(
                    self._LOG_NAME,
                    "DBus response to command '%s:%s %s' is '%s'" %
                    (command_destination, command, argument, response))

            except (subprocess.CalledProcessError,
                    subprocess.TimeoutExpired) as ex:

                if i == retries:

                    if self._player == PLAYER.VLCPLAYER:
                        player_pid = Window.vlc_player_pid[self._display_num -
                                                           1]
                    else:
                        player_pid = self.omx_player_pid

                    LOG.ERROR(
                        self._LOG_NAME,
                        "DBus '%s' is not responding correctly after '%i' attemps, "
                        "give up now" % (command_destination, retries + 1))

                    if kill_player_on_error and player_pid > 0:
                        LOG.ERROR(
                            self._LOG_NAME,
                            "DBus '%s' closing the associated player "
                            "with PID '%i' now" %
                            (command_destination, player_pid))

                        try:
                            os.kill(player_pid, signal.SIGKILL)
                        except ProcessLookupError:
                            LOG.DEBUG(self._LOG_NAME,
                                      "killing PID '%i' failed" % player_pid)

                        self._pidpool_remove_pid(player_pid)

                        if self._player == PLAYER.VLCPLAYER:
                            Window.vlc_player_pid[self._display_num - 1] = 0
                        else:
                            self.omx_player_pid = 0

                else:
                    LOG.WARNING(
                        self._LOG_NAME,
                        "DBus '%s' is not responding correctly, "
                        "retrying within 250ms" % command_destination)
                    time.sleep(0.25)
                    continue

            break

        return response
コード例 #7
0
ファイル: streaminfo.py プロジェクト: willtejeda/camplayer
    def _parse_stream_details(self):
        """Read stream details from cache file or parse stream directly"""
        
        if not self._is_url_valid():
            return

        parsed_ok = False
        video_found = False
        
        if os.path.isfile(self._cache_file):
            with open(self._cache_file, 'r') as stream_file:
                data = json.load(stream_file)
                
                if self.printable_url() in data.keys():
                    stream_props        = data.get(self.printable_url())
                    self.codec_name     = stream_props.get('codec_name')
                    self.height         = stream_props.get('height')
                    self.width          = stream_props.get('width')
                    self.framerate      = stream_props.get('framerate')
                    self.has_audio      = stream_props.get('audio')
                    parsed_ok = True

        if not parsed_ok:
            try:

                # Invoke ffprobe, 20s timeout required for pi zero
                streams = subprocess.check_output([
                        'ffprobe', '-v', 'error', '-show_entries',
                        'stream=codec_type,height,width,codec_name,bit_rate,max_bit_rate,avg_frame_rate',
                        self.url], universal_newlines=True, timeout=10, stderr=subprocess.STDOUT).split("[STREAM]")

                for stream in streams:
                    streamprops = stream.split()
                    
                    if "codec_type=video" in stream and not video_found:
                        video_found = True

                        for streamproperty in streamprops:
                            if "codec_name" in streamproperty:
                                self.codec_name = streamproperty.split("=")[1]
                            if "height" in streamproperty:
                                self.height = int(streamproperty.split("=")[1])
                            if "width" in streamproperty:
                                self.width = int(streamproperty.split("=")[1])
                            if "avg_frame_rate" in streamproperty:
                                try:
                                    framerate = streamproperty.split("=")[1]
                                
                                    # ffprobe returns framerate as fraction,
                                    # a zero division exception is therefore possible
                                    self.framerate = int(
                                        framerate.split("/")[0])/int(framerate.split("/")[1])
                                except Exception:
                                    self.framerate = 0

                    elif "codec_type=audio" in stream and not self.has_audio:
                        self.has_audio = True

                if video_found:
                    try:
                        self._write_stream_details()

                    # TODO: filter read-only exception
                    except Exception:
                        LOG.ERROR(self._LOG_NAME, "writing ffprobe results to file failed, read only?")

            # TODO: logging exceptions can spawn credentials??
            except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as ex:
                LOG.ERROR(self._LOG_NAME, "ffprobe exception: %s" % str(ex))