def __init__(self, stream_url):

        # Make absolute paths from relative ones
        if stream_url.startswith('file://.'):
            stream_url = "file://" + os.path.abspath(stream_url.lstrip('file:/'))

        self.url                    = stream_url
        self._cache_file            = CONSTANTS.CACHE_DIR + "streaminfo"
        self.codec_name             = ""
        self.height                 = 0
        self.width                  = 0
        self.framerate              = 0
        self.has_audio              = False
        self.force_udp              = False
        self._parse_stream_details()

        self.valid_url              = self._is_url_valid()
        self.valid_video_windowed   = self._is_video_valid(windowed=True)
        self.valid_video_fullscreen = self._is_video_valid(windowed=False)
        self.weight                 = self._calculate_weight()
        self.quality                = self.width * self.height

        LOG.INFO(self._LOG_NAME, "stream properties '%s', resolution '%ix%i@%i', codec '%s', "
                              "calculated weight '%i', valid url '%i', has audio '%s', "
                              "valid video 'windowed %i fullscreen %i', force UDP '%s'" % (
                                self.printable_url(), self.width, self.height, self.framerate,
                                self.codec_name, self.weight, self.valid_url, self.has_audio,
                                self.valid_video_windowed, self.valid_video_fullscreen, self.force_udp))
        LOG.INFO(self._LOG_NAME, "RUN 'camplayer --rebuild-cache' IF THIS STREAM INFORMATION IS OUT OF DATE!!")
    def __init__(self, x1, y1, x2, y2, gridindex, screen_idx, window_idx,
                 display_idx):

        self.x1 = x1  # Upper left x-position of window
        self.y1 = y1  # Upper left y-position of window
        self.x2 = x2  # Lower right x-position of window
        self.y2 = y2  # Lower right y-position of window
        self.gridindex = gridindex  # Grid indices covered by this window
        self.omx_player_pid = 0  # OMXplayer  PID
        self._omx_audio_enabled = False  # OMXplayer audio stream enabled
        self._omx_duration = 0  # OMXplayer reported stream duration
        self._layer = 0  # Player dispmanx layer
        self.visible = False  # Is window in visible area?
        self._forced_fullscreen = self.native_fullscreen  # Is window forced in fullscreen mode?
        self._fail_rate_hr = 0  # Stream failure rate of last hour
        self._time_playstatus = 0  # Timestamp of last playstatus check
        self._time_streamstart = 0  # Timestamp of last stream start
        self.streams = []  # Assigned stream(s)
        self.active_stream = None  # Currently playing stream
        self._display_name = ""  # Video OSD display name
        self._player = PLAYER.NONE  # Currently active player for this window (OMX or VLC)
        self.playstate = PLAYSTATE.NONE  # Current stream play state for this window
        self._window_num = window_idx + 1
        self._screen_num = screen_idx + 1
        self._display_num = display_idx + 1

        self._omx_dbus_ident = str(
            "org.mpris.MediaPlayer2.omxplayer_D%02d_S%02d_W%02d" %
            (self._display_num, self._screen_num, self._window_num))

        LOG.DEBUG(
            self._LOG_NAME,
            "init window with position '%i %i %i %i', gridindex '%s', "
            "omxplayer dbus name '%s'" %
            (x1, y1, x2, y2, str(gridindex), self._omx_dbus_ident))
    def stream_switch_quality_down(self, check_only=False):
        """Switch to the next lower quality stream, if any"""

        if self.active_stream and self.playstate != PLAYSTATE.NONE:

            resolution = 10000
            stream = None

            # Select the the next lower resolution stream
            for strm in self.streams:

                video_valid = strm.valid_video_fullscreen \
                    if self.fullscreen_mode else strm.valid_video_windowed

                if resolution < strm.quality < self.active_stream.quality and video_valid:
                    resolution = strm.quality
                    stream = strm

            # The lowest quality stream is already playing
            if not stream:
                LOG.INFO(self._LOG_NAME,
                         "lowest quality stream already playing")
                return False

            if not check_only:
                self.stream_stop()
                time.sleep(0.1)
                self._stream_start(stream)

            return stream

        return False
    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?")
Exemple #5
0
    def load_icons(cls):
        """Load pipng icon queue"""

        if not CONFIG.ENABLE_ICONS or not GLOBALS.PIPNG_SUPPORT:
            return

        for display_idx in range(GLOBALS.NUM_DISPLAYS):

            if len(cls._icons[display_idx]) <= 0:
                continue

            pngview_cmd = ["pipng",
                "-b", "0",                                  # No 2nd background layer under image
                "-l", str(cls._foreground_layer),           # Set layer number
                "-d", "2" if display_idx == 0 else "7",     # Set display number
                "-i",                                       # Start with all images invisible
                "-x", str(CONSTANTS.ICON_OFFSET_X),         # 60px offset x-axis
                "-y", str(CONSTANTS.ICON_OFFSET_Y),         # 60px offset y-axis
            ]

            # Add all images, currently limited to 10
            for image in cls._icons[display_idx]:
                pngview_cmd.append(CONSTANTS.RESOURCE_DIR_ICONS + image)

            LOG.DEBUG(cls._MODULE, "Loading pipng for display '%i' with command '%s'" %
                (display_idx, pngview_cmd))

            cls._proc_icons[display_idx] = \
                subprocess.Popen(pngview_cmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    def pidpool_update(cls):
        """Update the PID pool of OMXplayer and VLC media player instances"""

        cls._player_pid_pool_cmdline = [[], []]

        try:
            player_pids = subprocess.check_output(['pidof', 'vlc'],
                                                  universal_newlines=True,
                                                  timeout=5).split()

            LOG.DEBUG("PIDpool", "active VLCplayer PIDs '%s'" % player_pids)

            for player_pid in player_pids:
                cls._player_pid_pool_cmdline[0].append(int(player_pid))
                cls._player_pid_pool_cmdline[1].append(
                    subprocess.check_output(
                        ['cat', str('/proc/%s/cmdline' % player_pid)],
                        universal_newlines=True,
                        timeout=5))

        except subprocess.CalledProcessError:
            pass

        try:
            player_pids = subprocess.check_output(['pidof', 'omxplayer.bin'],
                                                  universal_newlines=True,
                                                  timeout=5).split()

            LOG.DEBUG("PIDpool", "active OMXplayer PIDs '%s'" % player_pids)

            for player_pid in player_pids:
                cls._player_pid_pool_cmdline[0].append(int(player_pid))
                cls._player_pid_pool_cmdline[1].append(
                    subprocess.check_output(
                        ['cat', str('/proc/%s/cmdline' % player_pid)],
                        universal_newlines=True,
                        timeout=5))

        except subprocess.CalledProcessError:
            pass
    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
    def _pidpool_remove_pid(cls, pid):
        """Remove Player PID from pidpool"""

        for idx, _pid in enumerate(cls._player_pid_pool_cmdline[0]):

            if _pid == pid:
                LOG.DEBUG("PIDpool", "removed Player PID '%i' from pool" % pid)

                del cls._player_pid_pool_cmdline[0][idx]
                del cls._player_pid_pool_cmdline[1][idx]
                return True

        return False
Exemple #9
0
    def load_backgrounds(cls):
        """Load pipng background queue"""

        if CONFIG.BACKGROUND_MODE == BACKGROUND.OFF or not GLOBALS.PIPNG_SUPPORT:
            return

        if CONFIG.BACKGROUND_MODE == BACKGROUND.HIDE_FRAMEBUFFER:

            for display_idx in range(GLOBALS.NUM_DISPLAYS):

                if len(cls._backgrounds[display_idx]) <= 0:
                    continue

                subprocess.Popen(["pipng", "-b", "000F", "-n", "-d", "2" if display_idx == 0 else "7"],
                    shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE)

        else:

            static_background = CONFIG.BACKGROUND_MODE == BACKGROUND.STATIC

            for display_idx in range(GLOBALS.NUM_DISPLAYS):

                if len(cls._backgrounds[display_idx]) <= 0:
                    continue

                pngview_cmd = ["pipng",
                    "-b", "0",                                  # No 2nd background layer under image
                    "-l", str(cls._background_layer),           # Set layer number
                    "-d", "2" if display_idx == 0 else "7",     # Set display number
                    "-h",                                       # Hide lower layers (less GPU performance impact)
                ]

                if not static_background:
                    pngview_cmd.append("-i")                    # Start with all images invisible

                # Add all background images, currently limited to 10
                for image in cls._backgrounds[display_idx]:
                    pngview_cmd.append(image)

                    # TODO: find best match with static backgrounds and multiple screens
                    if static_background:
                        break

                LOG.DEBUG(cls._MODULE, "Loading pipng for display '%i' with command '%s'" %
                    (display_idx + 1, pngview_cmd))

                cls._proc_background[display_idx] = \
                    subprocess.Popen(pngview_cmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Exemple #10
0
    def show_icon(cls, filename, display_idx=0):
        """Show pipng icon from queue"""

        if not CONFIG.ENABLE_ICONS or not GLOBALS.PIPNG_SUPPORT:
            return

        display_idx = 1 if display_idx == 1 else 0

        # Show new image/icon
        for idx, image in enumerate(cls._icons[display_idx]):
            if filename == image:
                LOG.DEBUG(cls._MODULE, "setting icon '%s' visible for display '%i" % (filename, display_idx))
                cls._proc_icons[display_idx].stdin.write(str(idx).encode('utf-8'))
                cls._proc_icons[display_idx].stdin.flush()

        cls.active_icon[display_idx] = filename
Exemple #11
0
    def stream_set_invisible(self, _async=False):
        """Keep the stream open but set it off screen"""

        if self.playstate == PLAYSTATE.NONE:
            return

        if self.visible:
            LOG.INFO(
                self._LOG_NAME, "stream set invisible '%s' '%s'" %
                (self._omx_dbus_ident, self.active_stream.printable_url()))

            if self._player == PLAYER.OMXPLAYER:
                # OMXplayer instance is playing inside the visible screen area.
                # Sending the position command with offset will move this instance out of the visible screen area.

                if self._omx_audio_enabled:
                    self.visible = False
                    self.stream_refresh()
                    return

                videopos_arg = str(
                    "%i %i %i %i" %
                    (self.x1 + CONSTANTS.WINDOW_OFFSET, self.y1,
                     self.x2 + CONSTANTS.WINDOW_OFFSET, self.y2))

                if _async:
                    setinvisible_thread = threading.Thread(
                        target=self._send_dbus_command,
                        args=(
                            DBUS_COMMAND.OMXPLAYER_VIDEOPOS,
                            videopos_arg,
                        ))

                    setinvisible_thread.start()
                else:
                    self._send_dbus_command(DBUS_COMMAND.OMXPLAYER_VIDEOPOS,
                                            videopos_arg)

            else:

                # It's possible that another window hijacked our vlc instance, so do not send 'stop' then.
                if self.active_stream.url == Window._vlc_active_stream_url[
                        self._display_num - 1]:
                    self._send_dbus_command(DBUS_COMMAND.PLAY_STOP)
                    Window._vlc_active_stream_url[self._display_num - 1] = ""

        self.visible = False
Exemple #12
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
Exemple #13
0
    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))
Exemple #14
0
    def show_background(cls, filename, display_idx=0):
        """Show pipng background from queue"""

        if CONFIG.BACKGROUND_MODE != BACKGROUND.DYNAMIC or not GLOBALS.PIPNG_SUPPORT:
            return

        display_idx = 1 if display_idx == 1 else 0

        if cls.active_background[display_idx] == filename:
            return

        # Show new image/icon
        for idx, image in enumerate(cls._backgrounds[display_idx]):
            if filename == image:
                LOG.DEBUG(cls._MODULE, "setting background '%s' visible for display '%i" % (filename, display_idx))
                cls._proc_background[display_idx].stdin.write(str(idx).encode('utf-8'))
                cls._proc_background[display_idx].stdin.flush()

        cls.active_background[display_idx] = filename
Exemple #15
0
    def hide_icon(cls, display_idx=0):
        """Hide active pipng iconn"""

        if not CONFIG.ENABLE_ICONS or not GLOBALS.PIPNG_SUPPORT:
            return

        display_idx = 1 if display_idx == 1 else 0

        if not cls.active_icon[display_idx]:
            return

        LOG.DEBUG(cls._MODULE, "hiding icon '%s' for display '%i" % (cls.active_icon[display_idx], display_idx))

        cls._proc_icons[display_idx].stdin.write("i".encode('utf-8'))
        cls._proc_icons[display_idx].stdin.flush()

        cls.active_icon[display_idx] = ""

        # pipng needs some milliseconds to read stdin
        # Especially important when hide_icon() will be immediately followed by show_icon()
        time.sleep(0.025)
Exemple #16
0
    def stream_switch_quality_up(self, check_only=False, limit_default=True):
        """Switch to the next higher quality stream, if any"""

        if self.active_stream and self.playstate != PLAYSTATE.NONE:

            resolution = sys.maxsize
            stream = None

            # Limit max quality to the default for performance reasons
            if limit_default:
                resolution = self.get_default_stream(
                    windowed=not self.fullscreen_mode).quality + 1

            # Select the the next higher resolution stream
            for strm in self.streams:

                video_valid = strm.valid_video_fullscreen \
                    if self.fullscreen_mode else strm.valid_video_windowed

                if resolution > strm.quality > self.active_stream.quality and video_valid:
                    resolution = strm.quality
                    stream = strm

            # The highest quality stream is already playing
            if not stream:
                LOG.INFO(self._LOG_NAME,
                         "highest quality stream already playing")
                return False

            if not check_only:
                self.stream_stop()
                time.sleep(0.1)
                self._stream_start(stream)
            return stream

        return False
Exemple #17
0
 def log(comp, params, add_trailing_row=False, add_heading_row=False):
     if add_heading_row:
         LOG(horizontal_row)
     LOG(template.format(comp, str(params)))
     if add_trailing_row:
         LOG(horizontal_row)
Exemple #18
0
    def get_stream_playstate(self):
        """
        Get and update the stream's playstate,
        don't use this time consuming method too often,
        use 'self.playstate' when you can.
        """

        if self.playstate == PLAYSTATE.NONE:
            return self.playstate

        # Allow at least 1 second for the player to startup
        if self.playstate == PLAYSTATE.INIT1 and self.playtime < 1:
            return self.playstate

        old_playstate = self.playstate

        # Assign the player PID
        if self.playstate == PLAYSTATE.INIT1:

            if self._player == PLAYER.VLCPLAYER:
                pid = self.get_vlc_pid(self._display_num)
            else:
                pid = self.get_omxplayer_pid()

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

                self.playstate = PLAYSTATE.INIT2

                LOG.DEBUG(
                    self._LOG_NAME, "assigned PID '%i' for stream '%s' '%s'" %
                    (pid, self._omx_dbus_ident,
                     self.active_stream.printable_url()))

            elif self.playtime > CONSTANTS.PLAYER_INITIALIZE_MS / 1000:
                self.playstate = PLAYSTATE.BROKEN

        # Check if the player is actually playing media
        # DBus calls are time consuming, so limit them
        elif time.monotonic() > (self._time_playstatus + 10) or \
                (self.playstate == PLAYSTATE.INIT2 and time.monotonic() > (self._time_playstatus + 1)):

            LOG.DEBUG(
                self._LOG_NAME, "fetching playstate for stream '%s' '%s'" %
                (self._omx_dbus_ident, self.active_stream.printable_url()))

            duration_diff = 0
            output = ""

            # Check playstate and kill the player if it does not respond properly
            # 04/04/2020: Under some circumstances omxplayer freezes with corrupt streams (bad wifi/network quality etc.),
            # while it still reports its playstate as 'playing',
            # therefore we monitor will monitor the reported 'duration' (for livestreams) from now on.
            if not self.active_stream.url.startswith(
                    'file://') and self._player == PLAYER.OMXPLAYER:

                output = self._send_dbus_command(
                    DBUS_COMMAND.PLAY_DURATION,
                    kill_player_on_error=self.playtime >
                    CONFIG.PLAYTIMEOUT_SEC)

                try:
                    duration = int(output.split("int64")[1].strip())
                    duration_diff = duration - self._omx_duration
                    self._omx_duration = duration
                except Exception:
                    self._omx_duration = 0

            else:
                output = self._send_dbus_command(
                    DBUS_COMMAND.PLAY_STATUS,
                    kill_player_on_error=self.playtime >
                    CONFIG.PLAYTIMEOUT_SEC)

            if (output
                    and "playing" in str(output).lower()) or duration_diff > 0:
                self.playstate = PLAYSTATE.PLAYING

            else:
                # Only set broken after a timeout period,
                # so keep the init state the first seconds
                if self.playtime > CONFIG.PLAYTIMEOUT_SEC:

                    if self._player == PLAYER.OMXPLAYER or self.visible:
                        # Don't set broken when VLC is in "stopped" state
                        # Stopped state occurs when not visible

                        self.playstate = PLAYSTATE.BROKEN

            self._time_playstatus = time.monotonic()

        if old_playstate != self.playstate:
            LOG.INFO(
                self._LOG_NAME, "stream playstate '%s' for stream '%s' '%s'" %
                (self.playstate.name, self._omx_dbus_ident,
                 self.active_stream.printable_url()))

        return self.playstate
Exemple #19
0
    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
Exemple #20
0
    def train_mc(self,
                 dataloader,
                 physics,
                 epochs,
                 lr,
                 ckp_interval,
                 schedule,
                 residual=True,
                 pretrained=None,
                 task='',
                 loss_type='l2',
                 cat=True,
                 report_psnr=False,
                 lr_cos=False):
        save_path = './ckp/{}_mc_{}'.format(get_timestamp(),
                                            'res' if residual else '', task)

        os.makedirs(save_path, exist_ok=True)

        generator = UNet(in_channels=self.in_channels,
                         out_channels=self.out_channels,
                         compact=4,
                         residual=residual,
                         circular_padding=True,
                         cat=cat).to(self.device)

        if pretrained:
            checkpoint = torch.load(pretrained)
            generator.load_state_dict(checkpoint['state_dict'])

        if loss_type == 'l2':
            criterion_mc = torch.nn.MSELoss().to(self.device)
        if loss_type == 'l1':
            criterion_mc = torch.nn.L1Loss().to(self.device)

        optimizer = Adam(generator.parameters(),
                         lr=lr['G'],
                         weight_decay=lr['WD'])

        if report_psnr:
            log = LOG(save_path,
                      filename='training_loss',
                      field_name=['epoch', 'loss_fc', 'psnr', 'mse'])
        else:
            log = LOG(save_path,
                      filename='training_loss',
                      field_name=['epoch', 'loss_fc'])

        for epoch in range(epochs):
            adjust_learning_rate(optimizer, epoch, lr['G'], lr_cos, epochs,
                                 schedule)
            loss = closure_mc(generator, dataloader, physics, optimizer,
                              criterion_mc, self.dtype, self.device,
                              report_psnr)

            log.record(epoch + 1, *loss)

            if report_psnr:
                print('{}\tEpoch[{}/{}]\tmc={:.4e}\tpsnr={:.4f}\tmse={:.4e}'.
                      format(get_timestamp(), epoch, epochs, *loss))
            else:
                print('{}\tEpoch[{}/{}]\tmc={:.4e}'.format(
                    get_timestamp(), epoch, epochs, *loss))

            if epoch % ckp_interval == 0 or epoch + 1 == epochs:
                state = {
                    'epoch': epoch,
                    'state_dict': generator.state_dict(),
                    'optimizer': optimizer.state_dict()
                }
                torch.save(
                    state,
                    os.path.join(save_path, 'ckp_{}.pth.tar'.format(epoch)))
        log.close()
Exemple #21
0
    def stream_set_visible(self, _async=False, fullscreen=None):
        """Set an active off screen stream back on screen"""

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

        if self.playstate == PLAYSTATE.NONE:
            return

        if fullscreen is None:
            fullscreen = self.fullscreen_mode

        if not self.visible or (fullscreen != self.fullscreen_mode):

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

            self.fullscreen_mode = fullscreen

            if self._player == PLAYER.OMXPLAYER:
                # OMXplayer instance is playing outside the visible screen area.
                # Sending the position command will move this instance into the visible screen area.

                if fullscreen:
                    videopos_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:
                    videopos_arg = str("%i %i %i %i" %
                                       (self.x1, self.y1, self.x2, self.y2))

                # Re-open OMXplayer with the audio stream enabled
                if CONFIG.AUDIO_MODE == AUDIOMODE.FULLSCREEN and fullscreen \
                        and not self._omx_audio_enabled and self.active_stream.has_audio:
                    self.visible = True
                    self.stream_refresh()
                    return

                # Re-open OMXplayer with the audio stream disabled
                if self._omx_audio_enabled and not fullscreen:
                    self.visible = True
                    self.stream_refresh()
                    return

                if _async:
                    setvisible_thread = threading.Thread(
                        target=self._send_dbus_command,
                        args=(
                            DBUS_COMMAND.OMXPLAYER_VIDEOPOS,
                            videopos_arg,
                        ))

                    setvisible_thread.start()
                else:
                    self._send_dbus_command(DBUS_COMMAND.OMXPLAYER_VIDEOPOS,
                                            videopos_arg)

            elif fullscreen:
                # 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.

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

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

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

            else:
                # Windowed with VLC not supported -> stop video
                self.stream_stop()

        self.visible = True
Exemple #22
0
 def log(msg, trailing_line=False):
     LOG(msg, trailing_line)
Exemple #23
0
    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))
Exemple #24
0
    def train_ei_adv(self,
                     dataloader,
                     physics,
                     transform,
                     epochs,
                     lr,
                     alpha,
                     ckp_interval,
                     schedule,
                     residual=True,
                     pretrained=None,
                     task='',
                     loss_type='l2',
                     cat=True,
                     report_psnr=False,
                     lr_cos=False):
        save_path = './ckp/{}_ei_adv_{}'.format(get_timestamp(), task)

        os.makedirs(save_path, exist_ok=True)

        generator = UNet(in_channels=self.in_channels,
                         out_channels=self.out_channels,
                         compact=4,
                         residual=residual,
                         circular_padding=True,
                         cat=cat)

        if pretrained:
            checkpoint = torch.load(pretrained)
            generator.load_state_dict(checkpoint['state_dict'])

        discriminator = Discriminator(
            (self.in_channels, self.img_width, self.img_height))

        generator = generator.to(self.device)
        discriminator = discriminator.to(self.device)

        if loss_type == 'l2':
            criterion_mc = torch.nn.MSELoss().to(self.device)
            criterion_ei = torch.nn.MSELoss().to(self.device)
        if loss_type == 'l1':
            criterion_mc = torch.nn.L1Loss().to(self.device)
            criterion_ei = torch.nn.L1Loss().to(self.device)

        criterion_gan = torch.nn.MSELoss().to(self.device)

        optimizer_G = Adam(generator.parameters(),
                           lr=lr['G'],
                           weight_decay=lr['WD'])
        optimizer_D = Adam(discriminator.parameters(),
                           lr=lr['D'],
                           weight_decay=0)

        if report_psnr:
            log = LOG(save_path,
                      filename='training_loss',
                      field_name=[
                          'epoch', 'loss_mc', 'loss_ei', 'loss_g', 'loss_G',
                          'loss_D', 'psnr', 'mse'
                      ])
        else:
            log = LOG(save_path,
                      filename='training_loss',
                      field_name=[
                          'epoch', 'loss_mc', 'loss_ei', 'loss_g', 'loss_G',
                          'loss_D'
                      ])

        for epoch in range(epochs):
            adjust_learning_rate(optimizer_G, epoch, lr['G'], lr_cos, epochs,
                                 schedule)
            adjust_learning_rate(optimizer_D, epoch, lr['D'], lr_cos, epochs,
                                 schedule)

            loss = closure_ei_adv(generator, discriminator, dataloader,
                                  physics, transform, optimizer_G, optimizer_D,
                                  criterion_mc, criterion_ei, criterion_gan,
                                  alpha, self.dtype, self.device, report_psnr)

            log.record(epoch + 1, *loss)

            if report_psnr:
                print(
                    '{}\tEpoch[{}/{}]\tfc={:.4e}\tti={:.4e}\tg={:.4e}\tG={:.4e}\tD={:.4e}\tpsnr={:.4f}\tmse={:.4e}'
                    .format(get_timestamp(), epoch, epochs, *loss))
            else:
                print(
                    '{}\tEpoch[{}/{}]\tfc={:.4e}\tti={:.4e}\tg={:.4e}\tG={:.4e}\tD={:.4e}'
                    .format(get_timestamp(), epoch, epochs, *loss))

            if epoch % ckp_interval == 0 or epoch + 1 == epochs:
                state = {
                    'epoch': epoch,
                    'state_dict_G': generator.state_dict(),
                    'state_dict_D': discriminator.state_dict(),
                    'optimizer_G': optimizer_G.state_dict(),
                    'optimizer_D': optimizer_D.state_dict()
                }
                torch.save(
                    state,
                    os.path.join(save_path, 'ckp_{}.pth.tar'.format(epoch)))
        log.close()
Exemple #25
0
def main():
    """Application entry point"""

    global running

    num_array = []
    last_added = time.monotonic()
    ignore_quit = False

    if not platform.system() == "Linux":
        sys.exit("'%s' OS not supported!" % platform.system())

    if os.geteuid() == 0:
        sys.exit("Camplayer is not supposed to be run as root!")

    GLOBALS.PYTHON_VER = sys.version_info
    if GLOBALS.PYTHON_VER < CONSTANTS.PYTHON_VER_MIN:
        sys.exit("Python version '%i.%i' or newer required!"
                 % (CONSTANTS.PYTHON_VER_MIN[0], CONSTANTS.PYTHON_VER_MIN[1]))

    # Started with arguments?
    if len(sys.argv) > 1:
        for idx, arg in enumerate(sys.argv):

            # 1st argument is application
            if idx == 0:
                continue

            # Help info
            if arg == "-h" or arg == "--help":
                print("         -h  --help                  Print this help")
                print("         -v  --version               Print version info")
                print("         -c  --config                Use a specific config file")
                print("             --rebuild-cache         Rebuild cache on startup")
                print("             --rebuild-cache-exit    Rebuild cache and exit afterwards")
                print("         -d  --demo                  Demo mode")
                print("             --ignorequit            Don't quit when the 'Q' key is pressed")
                sys.exit(0)

            # Run in a specific mode
            if arg == "--rebuild-cache" or arg == "--rebuild-cache-exit":

                # Clearing the cache
                clear_cache()

                # Rebuild cache only and exit
                if arg == "--rebuild-cache-exit":

                    # Exit when reaching the main loop
                    running = False

            # Run with a specific config file
            if arg == "-c" or arg == "--config" and (idx + 1) < len(sys.argv):
                CONSTANTS.CONFIG_PATH = sys.argv[idx + 1]

            # Display version info
            if arg == "-v" or arg == "--version":
                print("version " + __version__)
                sys.exit(0)

            # Run demo mode
            if arg == "-d" or arg == "--demo":
                CONSTANTS.CONFIG_PATH = CONSTANTS.DEMO_CONFIG_PATH

            # Ignore keyboard 'quit' command
            if arg == "--ignorequit":
                ignore_quit = True

    # Load settings from config file
    CONFIG.load()

    # Signal handlers
    signal.signal(signal.SIGTERM, signal_handler)
    signal.signal(signal.SIGINT, signal_handler)

    LOG.INFO(_LOG_NAME, "Starting camplayer version %s" % __version__)
    LOG.INFO(_LOG_NAME, "Using config file '%s' and cache directory '%s'"
             % (CONSTANTS.CONFIG_PATH, CONSTANTS.CACHE_DIR))

    # Cleanup some stuff in case something went wrong on the previous run
    utils.kill_service('omxplayer.bin', force=True)
    utils.kill_service('vlc', force=True)
    utils.kill_service('pipng', force=True)

    # OMXplayer is absolutely required!
    if not utils.os_package_installed("omxplayer.bin"):
        sys.exit("OMXplayer not installed but required!")

    # ffprobe is absolutely required!
    if not utils.os_package_installed("ffprobe"):
        sys.exit("ffprobe not installed but required!")

    # Get system info
    sys_info = utils.get_system_info()
    gpu_mem = utils.get_gpu_memory()
    hw_info = utils.get_hardware_info()

    # Set some globals for later use
    GLOBALS.PI_SOC          = hw_info.get("soc")    # Not very reliable, usually reports BCM2835
    GLOBALS.PI_MODEL        = hw_info.get("model")
    GLOBALS.PI_SOC_HEVC     = hw_info.get('hevc')
    GLOBALS.NUM_DISPLAYS    = 2 if hw_info.get('dual_hdmi') else 1
    GLOBALS.VLC_SUPPORT     = utils.os_package_installed("vlc")
    GLOBALS.PIPNG_SUPPORT   = utils.os_package_installed("pipng")
    GLOBALS.FFMPEG_SUPPORT  = utils.os_package_installed("ffmpeg")
    GLOBALS.USERNAME        = os.getenv('USER')

    # Log system info
    LOG.INFO(_LOG_NAME, "********************** SYSTEM INFO **********************")
    LOG.INFO(_LOG_NAME, str("Camplayer version             = %s" % __version__))
    LOG.INFO(_LOG_NAME, str("Operating system              = %s" % sys_info))
    LOG.INFO(_LOG_NAME, str("Raspberry Pi SoC              = %s" % hw_info.get("soc")))
    LOG.INFO(_LOG_NAME, str("Raspberry Pi revision         = %s" % hw_info.get("revision")))
    LOG.INFO(_LOG_NAME, str("Raspberry Pi model name       = %s" % hw_info.get("model")))
    LOG.INFO(_LOG_NAME, str("GPU memory allocation         = %i MB" % gpu_mem))
    LOG.INFO(_LOG_NAME, str("Python version                = %s MB" % sys.version.splitlines()[0]))
    LOG.INFO(_LOG_NAME, str("VLC installed                 = %s" % GLOBALS.VLC_SUPPORT))
    LOG.INFO(_LOG_NAME, str("pipng installed               = %s" % GLOBALS.PIPNG_SUPPORT))
    LOG.INFO(_LOG_NAME, str("ffmpeg installed              = %s" % GLOBALS.FFMPEG_SUPPORT))
    LOG.INFO(_LOG_NAME, "*********************************************************")

    # Register for keyboard 'press' events, requires root
    # TODO: check privileges?
    keyboard = InputMonitor(event_type=['press'])

    # Log overwrites for debugging purpose
    for setting in CONFIG.advanced_overwritten:
        LOG.INFO(_LOG_NAME, "advanced setting overwritten for '%s' is '%s'" % (setting[0], setting[1]))

    # Does this system fulfill the minimal requirements
    if CONFIG.HARDWARE_CHECK:
        if not hw_info.get("supported"):
            sys.exit("Unsupported hardware with revision %s ..." % hw_info.get("revision"))

        if gpu_mem < CONSTANTS.MIN_GPU_MEM:
            sys.exit("GPU memory of '%i' MB insufficient ..." % gpu_mem)

    # Auto detect screen resolution
    # For the raspberry pi 4:
    #   both HDMI displays are supposed to have the same configuration
    if CONFIG.SCREEN_HEIGHT == 0 or CONFIG.SCREEN_WIDTH == 0:
        display_conf = utils.get_display_mode()
        CONFIG.SCREEN_HEIGHT = display_conf.get('res_height')
        CONFIG.SCREEN_WIDTH = display_conf.get('res_width')
        LOG.INFO(_LOG_NAME, "Detected screen resolution for HDMI0 is '%ix%i@%iHz'" % (
            CONFIG.SCREEN_WIDTH, CONFIG.SCREEN_HEIGHT, display_conf.get('framerate')))

        if CONFIG.SCREEN_HEIGHT <= 0:
            CONFIG.SCREEN_HEIGHT = 1080
        if CONFIG.SCREEN_WIDTH <= 0:
            CONFIG.SCREEN_WIDTH = 1920

    # Are we sure the 2nd HDMI is on for dual HDMI versions?
    if GLOBALS.NUM_DISPLAYS == 2:
        # Check for resolution instead of display name as the latter one is empty with force HDMI hotplug
        if not utils.get_display_mode(display=7).get('res_height'):
            GLOBALS.NUM_DISPLAYS = 1

    # Calculate the virtual screen size now that we now the physical screen size
    CONSTANTS.VIRT_SCREEN_WIDTH = int(CONFIG.SCREEN_WIDTH * (100 - CONFIG.SCREEN_DOWNSCALE) / 100)
    CONSTANTS.VIRT_SCREEN_HEIGHT = int(CONFIG.SCREEN_HEIGHT * (100 - CONFIG.SCREEN_DOWNSCALE) / 100)
    CONSTANTS.VIRT_SCREEN_OFFSET_X = int((CONFIG.SCREEN_WIDTH - CONSTANTS.VIRT_SCREEN_WIDTH) / 2)
    CONSTANTS.VIRT_SCREEN_OFFSET_Y = int((CONFIG.SCREEN_HEIGHT - CONSTANTS.VIRT_SCREEN_HEIGHT) / 2)
    LOG.INFO(_LOG_NAME, "Using a virtual screen resolution of '%ix%i'" %
             (CONSTANTS.VIRT_SCREEN_WIDTH, CONSTANTS.VIRT_SCREEN_HEIGHT))

    # Workaround: srt subtitles have a maximum display time of 99 hours
    if CONFIG.VIDEO_OSD and (not CONFIG.REFRESHTIME_MINUTES or CONFIG.REFRESHTIME_MINUTES >= 99 * 60):
        CONFIG.REFRESHTIME_MINUTES = 99 * 60
        LOG.WARNING(_LOG_NAME, "Subtitle based OSD enabled, forcing 'refreshtime' to '%i'" % CONFIG.REFRESHTIME_MINUTES)

    # Show 'loading' on master display
    BackGroundManager.show_icon_instant(BackGround.LOADING, display_idx=0)

    # Initialize screens and windows
    screenmanager = ScreenManager()
    if screenmanager.valid_screens < 1:
        sys.exit("No valid screen configuration found, check your config file!")

    # Hide 'loading' message on master display
    BackGroundManager.hide_icon_instant(display_idx=0)

    # Working loop
    while running:

        # Trigger screenmanager working loop
        screenmanager.do_work()

        for event in keyboard.get_events():
            last_added = time.monotonic()

            if event.code in KEYCODE.KEY_NUM.keys():
                LOG.DEBUG(_LOG_NAME, "Numeric key event: %i" % KEYCODE.KEY_NUM.get(event.code))

                num_array.append(KEYCODE.KEY_NUM.get(event.code))

                # Two digit for numbers from 0 -> 99
                if len(num_array) > 2:
                    num_array.pop(0)
            else:

                # Non numeric key, clear numeric num_array
                num_array.clear()

                if event.code == KEYCODE.KEY_RIGHT:
                    screenmanager.on_action(Action.SWITCH_NEXT)

                elif event.code == KEYCODE.KEY_LEFT:
                    screenmanager.on_action(Action.SWITCH_PREV)

                elif event.code == KEYCODE.KEY_UP:
                    screenmanager.on_action(Action.SWITCH_QUALITY_UP)

                elif event.code == KEYCODE.KEY_DOWN:
                    screenmanager.on_action(Action.SWITCH_QUALITY_DOWN)

                elif event.code == KEYCODE.KEY_ENTER or event.code == KEYCODE.KEY_KPENTER:
                    screenmanager.on_action(Action.SWITCH_SINGLE, 0)

                elif event.code == KEYCODE.KEY_ESC or event.code == KEYCODE.KEY_EXIT:
                    screenmanager.on_action(Action.SWITCH_GRID)

                elif event.code == KEYCODE.KEY_SPACE:
                    screenmanager.on_action(Action.SWITCH_PAUSE_UNPAUSE)

                elif event.code == KEYCODE.KEY_D:
                    screenmanager.on_action(Action.SWITCH_DISPLAY_CONTROL)

                elif event.code == KEYCODE.KEY_Q and not ignore_quit:
                    running = False

                break

        # Timeout between key presses expired?
        if time.monotonic() > (last_added + (CONSTANTS.KEY_TIMEOUT_MS / 1000)):
            num_array.clear()

        # 1 second delay to accept multiple digit numbers
        elif time.monotonic() > (last_added + (CONSTANTS.KEY_MULTIDIGIT_MS / 1000)) and len(num_array) > 0:

            LOG.INFO(_LOG_NAME, "Process numeric key input '%s'" % str(num_array))

            number = 0
            number += num_array[-2] * 10 if len(num_array) > 1 else 0
            number += num_array[-1]

            if number == 0:
                num_array.clear()
                screenmanager.on_action(Action.SWITCH_GRID)
            else:
                num_array.clear()
                screenmanager.on_action(Action.SWITCH_SINGLE, number - 1)

        time.sleep(0.1)

    # Cleanup stuff before exit
    keyboard.destroy()
    BackGroundManager.destroy()
    utils.kill_service('omxplayer.bin', force=True)
    utils.kill_service('vlc', force=True)
    utils.kill_service('pipng', force=True)

    LOG.INFO(_LOG_NAME, "Exiting raspberry pi camplayer, have a nice day!")
    sys.exit(0)
 def log(msg, add_trailing=True):
     LOG(msg, add_trailing)
Exemple #27
0
    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
Exemple #28
0
 def log(msg, new_section=False):
     LOG(msg, new_section)