Customising the Red Not Locked Screen

Discussions about the Ryde "Set-top Box" Style Digital ATV Receiver. See https://wiki.batc.org.uk/Ryde_Receiver
Post Reply
G6MNJ
Posts: 38
Joined: Mon Jun 09, 2014 6:25 pm

Customising the Red Not Locked Screen

Post by G6MNJ » Thu Aug 04, 2022 1:16 pm

We use 3 of these for our repeater how easy would it be to prefix the Not Locked with 2m so it reads 2m Not Locked?

Thanks Paul

g8gtz
Posts: 1733
Joined: Wed Apr 07, 2010 6:26 pm

Re: Customising the Red Not Locked Screen

Post by g8gtz » Thu Aug 04, 2022 1:38 pm

viewtopic.php?f=130&t=7024&p=25288&hili ... ack#p25288

Near the bottom of Martin's post...

73

Noel

G6MNJ
Posts: 38
Joined: Mon Jun 09, 2014 6:25 pm

Re: Customising the Red Not Locked Screen

Post by G6MNJ » Thu Aug 04, 2022 2:52 pm

Perfect

Thank you!

pascal
Posts: 4
Joined: Mon May 13, 2019 6:33 pm

Re: Customising the Red Not Locked Screen

Post by pascal » Sat Sep 17, 2022 5:56 pm

Is it also possible to change de red background color ?

Pa3fbx
Posts: 177
Joined: Fri Oct 16, 2020 1:08 pm

Re: Customising the Red Not Locked Screen

Post by Pa3fbx » Sun Sep 18, 2022 8:48 am

yes,

On your own risk you could play with lots of settings...
Have a look at.
viewtopic.php?f=130&t=7421

Be aware and make backup copies before changing.
I use FTP to get into the raspberry and do all the hard work on my windows pc.
this makes it easyer.. I use 4 ryde for our local Pi6MEP repeater..
All in dutch and yes.. white text on black background...

Best regards
Benno

G8YTZ
Posts: 103
Joined: Sun Feb 10, 2019 7:43 pm

Re: Customising the Red Not Locked Screen

Post by G8YTZ » Wed Sep 28, 2022 7:08 pm

Here is my custom colour screen:

player.py

Code: Select all

#    Ryde Player provides a on screen interface and video player for Longmynd compatible tuners.
#    Copyright © 2021 Tim Clark
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <https://www.gnu.org/licenses/>.

import pygame, vlc, select, pydispmanx, yaml, os, pkg_resources, argparse, importlib, functools, sys
import rydeplayer.sources.common
import rydeplayer.sources.longmynd
import rydeplayer.sources.combituner
from . import ir
import rydeplayer.gpio
import rydeplayer.network
import rydeplayer.common
import rydeplayer.states.gui
import rydeplayer.states.playback
import rydeplayer.osd.display
import rydeplayer.osd.modules

# container for the theme
class Theme(object):
    def __init__(self, displaySize):
        self.colours = type('colours', (object,), {
            'transparent': (0,0,0,0),
            'transpBack': (0,0,0,51),
            'black': (0,0,0,255),
            'white': (255,160,0,255),
            'red': (255,0,0,255),
            'textError': (255,0,0,255),
            'backgroundMenu': (192,192,192,255),
            'backgroundSubMenu': (160,160,160,255),
            'backgroundPlayState': (160,160,160,255),
            })
        self.displayWidth = int(displaySize[0])
        self.displayHeight = int(displaySize[1])
        self.menuWidth = int(self.displayWidth/4)
        self.menuHeight = self.displayHeight
        playStateTitleFontSize=self.fontSysSizeOptimize('Not Loaded', displaySize[0]/2, 'freesans')
        menuH1FontSize=self.fontSysSizeOptimize('BATC Ryde Project', self.menuWidth*0.85, 'freesans')
        self.fonts = type('fonts', (object,), {
            'menuH1': pygame.font.SysFont('freesans', menuH1FontSize),
            'playStateTitle' :  pygame.font.SysFont('freesans', playStateTitleFontSize),
            })
        self._circlecache = {}
        self.logofile = pkg_resources.resource_stream('rydeplayer.resources', 'logo_menu.png')
        self.muteicon = pkg_resources.resource_stream('rydeplayer.resources', 'icon_mute.png')

    # calculate the largest font size that you can render the given test in as still be less than width
    def fontSysSizeOptimize(self, text, width, fontname):
        fontsize = -1
        while True:
            fontCandidate = pygame.font.SysFont(fontname, fontsize+1)
            fontwidth = fontCandidate.size(text)[0]
            del(fontCandidate)
            if(fontwidth > width):
                break
            else:
                fontsize += 1
        return fontsize

    # calculate the largest font size that has a line height less than height
    def fontSysSizeOptimizeHeight(self, height, fontname):
        fontsize = -1
        while True:
            fontCandidate = pygame.font.SysFont(fontname, fontsize+1)
            fontheight = fontCandidate.get_linesize()
            del(fontCandidate)
            if(fontheight > height):
                break
            else:
                fontsize += 1
        return fontsize

    # size and position a pygame rectangle using screen size independent units and a datum corner
    def relativeRect(self, datum, xEdgeDistance, yEdgeDistance, width, height):
        outwidth = self.displayHeight*width
        outheight = self.displayHeight*height
        if datum is rydeplayer.common.datumCornerEnum.TR:
            outX = self.displayWidth - ((xEdgeDistance+width)*self.displayHeight)
            outY = yEdgeDistance*self.displayHeight
        elif datum is rydeplayer.common.datumCornerEnum.TC:
            outX = (self.displayWidth/2) - (((width/2)-xEdgeDistance)*self.displayHeight)
            outY = yEdgeDistance*self.displayHeight
        elif datum is rydeplayer.common.datumCornerEnum.TL:
            outX = xEdgeDistance*self.displayHeight
            outY = yEdgeDistance*self.displayHeight
        elif datum is rydeplayer.common.datumCornerEnum.CR:
            outX = self.displayWidth - ((xEdgeDistance+width)*self.displayHeight)
            outY = (self.displayHeight/2) - (((height/2)-yEdgeDistance)*self.displayHeight)
        elif datum is rydeplayer.common.datumCornerEnum.CC:
            outX = (self.displayWidth/2) - (((width/2)-xEdgeDistance)*self.displayHeight)
            outY = (self.displayHeight/2) - (((height/2)-yEdgeDistance)*self.displayHeight)
        elif datum is rydeplayer.common.datumCornerEnum.CL:
            outX = xEdgeDistance*self.displayHeight
            outY = (self.displayHeight/2) - (((height/2)-yEdgeDistance)*self.displayHeight)
        elif datum is rydeplayer.common.datumCornerEnum.BR:
            outX = self.displayWidth - ((xEdgeDistance+width)*self.displayHeight)
            outY = self.displayHeight - ((yEdgeDistance+height)*self.displayHeight)
        elif datum is rydeplayer.common.datumCornerEnum.BC:
            outX = (self.displayWidth/2) - (((width/2)-xEdgeDistance)*self.displayHeight)
            outY = self.displayHeight - ((yEdgeDistance+height)*self.displayHeight)
        elif datum is rydeplayer.common.datumCornerEnum.BL:
            outX = xEdgeDistance*self.displayHeight
            outY = self.displayHeight - ((yEdgeDistance+height)*self.displayHeight)
        return pygame.Rect((outX, outY, outwidth, outheight))

    # helper function for outline font rendering
    def _circlepoints(self,r):
        r = int(round(r))
        if r in self._circlecache:
            return self._circlecache[r]
        x, y, e = r, 0, 1 - r
        self._circlecache[r] = points = []
        while x >= y:
            points.append((x, y))
            y += 1
            if e < 0:
                e += 2 * y - 1
            else:
                x -= 1
                e += 2 * (y - x) - 1
        points += [(y, x) for x, y in points if x > y]
        points += [(-x, y) for x, y in points if x]
        points += [(x, -y) for x, y in points if y]
        points.sort()
        return points

    # render font with different coloured outline
    def outlineFontRender(self, text, font, gfcolor, ocolor, opx=2):
        textsurface = font.render(text, True, gfcolor)
        w = textsurface.get_width() + 2 * opx
        h = font.get_height()

        osurf = pygame.Surface((w, h + 2 * opx), pygame.SRCALPHA)
        osurf.fill((0, 0, 0, 0))

        surf = osurf.copy()

        osurf.blit(font.render(text, True, ocolor), (0, 0))

        for dx, dy in self._circlepoints(opx):
            surf.blit(osurf, (dx + opx, dy + opx))

        surf.blit(textsurface, (opx, opx))
        return surf



# power menu UI state machine
class SubMenuPower(rydeplayer.states.gui.ListSelect):
    def __init__(self, theme, backState, shutdownCallback):
        items = {
                    rydeplayer.common.shutdownBehavior.APPSTOP : 'App Shutdown',
                    rydeplayer.common.shutdownBehavior.APPREST : 'App Restart',
                    rydeplayer.common.shutdownBehavior.SYSSTOP : 'System Shutdown',
                    rydeplayer.common.shutdownBehavior.SYSREST : 'System Restart',
                }
        super().__init__(theme, backState, items, lambda: rydeplayer.common.shutdownBehavior.APPSTOP, shutdownCallback)
    def get_event(self, event):
        if super().get_event(event):
            return True
        else:
            if event == rydeplayer.common.navEvent.POWER:
                return super().get_event(rydeplayer.common.navEvent.SELECT)

            else:
                return False

# main UI state machine
class guiState(rydeplayer.states.gui.SuperStates):
    def __init__(self, theme, shutdownBehaviorDefault, player, osd):
        super().__init__(theme)
        self.done = False
        self.player = player
        self.osd = osd
        self.shutdownBehaviorDefault = shutdownBehaviorDefault
        self.shutdownState = rydeplayer.common.shutdownBehavior.APPSTOP

    # callback to run all the remove and cleanup callback on the active manu states
    def _cleanupMenuStates(self, activeCallbacks):
        for callback in activeCallbacks:
            callback()

    # generate the menu states based on current config capabilites
    def _genMenuStates(self, config, debugFunctions, superMenu):
        # get variables for current config
        tunerConfigVars = config.tuner.getVars()
        # generate config specific menu items
        mainMenuStates = {}
        firstkey = None
        lastkey = None
        # set of cleanup functions for the menu items
        activeCallbacks = set()
        for key in tunerConfigVars:
            validVar = False
            # create sub menu items for supported var types
            if isinstance(tunerConfigVars[key], rydeplayer.sources.common.tunerConfigIntList):
                mainMenuStates[key+'-sel'] = rydeplayer.states.gui.MultipleNumberSelect(self.theme, key, tunerConfigVars[key], config.tuner.runCallbacks)
                validVar = True

            if isinstance(tunerConfigVars[key], rydeplayer.sources.common.tunerConfigInt):
                mainMenuStates[key+'-sel'] = rydeplayer.states.gui.NumberSelect(self.theme, key, tunerConfigVars[key], config.tuner.runCallbacks)
                validVar = True

            if validVar:
                if firstkey is None:
                    firstkey = key
                if lastkey is None:
                    lastkey = key
                # create menu item
                mainMenuStates[key] = rydeplayer.states.gui.MenuItem(self.theme, tunerConfigVars[key].getLongName(), lastkey, firstkey, key+'-sel', tunerConfigVars[key])
                mainMenuStates[lastkey].down = key
                mainMenuStates[firstkey].up = key
                lastkey = key
                # callback to menu header for validity updates
                validCallFunc = functools.partial(superMenu.redrawState, mainMenuStates[key], mainMenuStates[key].getSurfaceRects())
                tunerConfigVars[key].addValidCallback(validCallFunc)
                activeCallbacks.add(functools.partial(tunerConfigVars[key].removeValidCallback,validCallFunc))
        cleanupFunc = functools.partial(self._cleanupMenuStates, activeCallbacks)

        # main menu states, order is important to get menus and sub menus to display in the right place
        if firstkey is None:
            firstkey = 'band'
        if lastkey is None:
            lastkey = 'power'
        baseMenuStates = {
            'band-sel'  : rydeplayer.states.gui.ListSelect(self.theme, 'band', config.bands, config.tuner.getBand, config.tuner.setBand),
            'band'      : rydeplayer.states.gui.MenuItem(self.theme, "Band", lastkey, "preset", "band-sel"),
            'preset-sel'  : rydeplayer.states.gui.ListSelect(self.theme, 'preset', config.presets, lambda:config.tuner, config.tuner.setConfigToMatch),
            'preset'      : rydeplayer.states.gui.MenuItem(self.theme, "Presets", "band", "power", "preset-sel"),
            'power-sel'  : SubMenuPower(self.theme, 'power', self.shutdown),
            'power'      : rydeplayer.states.gui.MenuItem(self.theme, "Power", "preset", firstkey, "power-sel"),
        }
        mainMenuStates.update(baseMenuStates)
        mainMenuStates[lastkey].down = 'band'
        mainMenuStates[firstkey].up = 'power'
        lastkey = 'power'
        
        # add debug menu if enabled in config
        if config.debug.enableMenu:
            # generate debug menu states
            debugMenuStates = {}
            debugPrevState = None
            debugFirstState = None
            for key in debugFunctions:
                menukey = key.strip().replace(" ", "").lower()
                if debugFirstState is None:
                    debugFirstState = menukey
                    debugPrevState = menukey
                debugMenuStates[menukey] = rydeplayer.states.gui.SubMenuItemFunction(self.theme, key, debugPrevState, debugFirstState, debugFunctions[key])
                debugMenuStates[debugPrevState].down = menukey
                debugMenuStates[debugFirstState].up = menukey
                debugPrevState = menukey
            mainMenuStates['debug-sel'] = rydeplayer.states.gui.SubMenuGeneric(self.theme, 'debug', debugMenuStates, debugFirstState)
            mainMenuStates['debug'] = rydeplayer.states.gui.MenuItem(self.theme, "Debug", lastkey, firstkey, "debug-sel")
            mainMenuStates[lastkey].down = 'debug'
            mainMenuStates[firstkey].up = 'debug'
            lastkey = 'debug'
        validCallFunc = superMenu.refreshStates
        config.tuner.addVarChangeCallbackFunction(validCallFunc)
        activeCallbacks.add(functools.partial(config.tuner.removeVarChangeCallbackFunction,validCallFunc))

        return (mainMenuStates, firstkey, cleanupFunc)

    def startup(self, config, debugFunctions):
        # top level state machine
        self.state_dict = {
            'menu': rydeplayer.states.gui.Menu(self.theme, 'home', functools.partial(self._genMenuStates, config, debugFunctions)),
            'home': Home(self.theme, self.osd)
        }
        self.state_name = "home"
        self.state = self.state_dict[self.state_name]
        self.state.startup()

    def shutdown(self, shutdownState):
        self.shutdownState = shutdownState
        self.state.cleanup()
        self.done = True

    def get_event(self, event):
        if not self.state.get_event(event):
            if event == rydeplayer.common.navEvent.POWER:
                self.setStateStack([self.shutdownBehaviorDefault,'power-sel','menu'])
            elif event == rydeplayer.common.navEvent.OSDON:
                self.osd.activate(1)
            elif event == rydeplayer.common.navEvent.OSDOFF:
                self.osd.deactivate(1)
            elif event == rydeplayer.common.navEvent.OSDTOG:
                self.osd.toggle(2)
            elif(event == rydeplayer.common.navEvent.MUTE):
                self.player.toggleMute()
            elif(event == rydeplayer.common.navEvent.VOLU):
                self.player.adjustVolumeByStep(True)
                self.player.setMute(False)
                self.osd.activate(3, rydeplayer.osd.display.TimerLength.USERTRIGGER)
            elif(event == rydeplayer.common.navEvent.VOLD):
                self.player.adjustVolumeByStep(False)
                self.player.setMute(False)
                self.osd.activate(3, rydeplayer.osd.display.TimerLength.USERTRIGGER)
            elif(event == rydeplayer.common.navEvent.CHANU):
                self.player.switchPresetRelative(-1)
            elif(event == rydeplayer.common.navEvent.CHAND):
                self.player.switchPresetRelative(1)


# GUI state for when the menu isnt showing
class Home(rydeplayer.states.gui.States):
    def __init__(self, theme, osd):
        super().__init__(theme)
        self.next = 'menu'
        self.osd = osd
    def cleanup(self):
        None
    def startup(self):
        None
    def get_event(self, event):
        #TODO: add OSD state machine
        if(event == rydeplayer.common.navEvent.SELECT):
            self.osd.activate(3, rydeplayer.osd.display.TimerLength.USERTRIGGER)
        elif(event == rydeplayer.common.navEvent.BACK):
            self.osd.deactivate(2)
        elif(event == rydeplayer.common.navEvent.MENU):
            self.done = True

class rydeConfig(object):
    def __init__(self, theme):
        self.ir = ir.irConfig()
        self.gpio = rydeplayer.gpio.gpioConfig()
        self.tuner = rydeplayer.sources.common.tunerConfig()
        # source specific config
        self.sourceConfigs = dict()
        for thisSource in rydeplayer.sources.common.sources:
            self.sourceConfigs[thisSource]=thisSource.getSource().getConfig()()
        self.bands = {}
        defaultBand = self.tuner.getBand()
        self.bands[defaultBand] = "None"
        self.presets = {}
        self.osd = rydeplayer.osd.display.Config(theme)
        self.network = rydeplayer.network.networkConfig()
        self.shutdownBehavior = rydeplayer.common.shutdownBehavior.APPSTOP
        self.audio = type('audioConfig', (object,), {
            'muteOnStartup': False,
            'volumeOnStartup': 100,
            'volumeStep': 25,
            })
        self.debug = type('debugConfig', (object,), {
            'enableMenu': False,
            'autoplay': True,
            'disableHardwareCodec': True,
            })
        self.configRev = 3
    #setter for default values
    def setAutoplay(self, newval):
        self.debug.autoplay = newval

    # parse config dict
    def loadConfig(self, config):
        perfectConfig = True
        if isinstance(config, dict):
            # parse config revision
            if 'configRev' in config:
                if isinstance(config['configRev'], int):
                    if config['configRev'] != self.configRev:
                        print("Unmatched config revision, config load aborted")
                        return False
                else:
                    print("Config revision not an integer, config load aborted")
                    return False
            else:
                print("WARNING: no config revision present, config load my fail")
            if 'bands' in config:
                if isinstance(config['bands'], dict):
                    newBands = {}
                    exsistingBands = list(self.bands.keys())
                    for bandName in config['bands']:
                        bandDict = config['bands'][bandName]
                        bandParseSuccess, bandObject = rydeplayer.sources.common.tunerBand.loadBand(bandDict)
                        if bandParseSuccess:
                            # dedupe band object with exsisting library
                            if bandObject in exsistingBands:
                                bandObject = exsistingBands[exsistingBands.index(bandObject)]
                            newBands[bandObject] = str(bandName)
                        else:
                            perfectConfig = False
                    if len(newBands) > 1:
                        self.bands = newBands
                    else:
                        print("No valid bands, skipping")
                        perfectConfig = False
                else:
                    print("Invalid band library")
                    perfectConfig = False
            # parse source specific configs
            if 'sources' in config:
                if isinstance(config['sources'], dict):
                    for thisSource in rydeplayer.sources.common.sources:
                        if thisSource.name.lower() in config['sources']:
                            perfectConfig = perfectConfig and self.sourceConfigs[thisSource].loadConfig(config['sources'][thisSource.name.lower()])
                        elif thisSource.name.upper() in config['sources']:
                            perfectConfig = perfectConfig and self.sourceConfigs[thisSource].loadConfig(config['sources'][thisSource.name.upper()])
                else:
                    print("Sources config not a dict")
                    perfectConfig = False
            # parse presets
            if 'presets' in config:
                if isinstance(config['presets'], dict):
                    newPresets = {}
                    exsistingPresets = list(self.presets.keys())
                    for presetName in config['presets']:
                        presetDict = config['presets'][presetName]
                        presetObject = rydeplayer.sources.common.tunerConfig()
                        if presetObject.loadConfig(presetDict, list(self.bands.keys())):
                            # dedupe preset object with exsisting library
                            if presetObject in exsistingPresets:
                                presetObject = exsistingPresets[exsistingPresets.index(presetObject)]
                            newPresets[presetObject] = str(presetName)
                        else:
                            perfectConfig = False
                    if len(newPresets) > 1:
                        self.presets = newPresets
                    else:
                        print("No valid presets, skipping")
                        perfectConfig = False
                else:
                    print("Invalid preset library")
                    perfectConfig = False
            # pass default tuner config to be parsed by longmynd module
            if 'default' in config:
                defaultPreset = rydeplayer.sources.common.tunerConfig()
#                perfectConfig = perfectConfig and defaultPreset.loadConfig(config['default'], list(self.bands.keys()))
                if defaultPreset.loadConfig(config['default'], list(self.bands.keys())):
                    # dedupe preset object with exsisting library
                    exsistingPresets = list(self.presets.keys())
                    if defaultPreset in exsistingPresets:
                        defaultPreset = exsistingPresets[exsistingPresets.index(defaultPreset)]
                    self.tuner.setConfigToMatch(defaultPreset)
                else:
                    perfectConfig = False

            # pass ir config to be parsed by the ir config container
            if 'ir' in config:
                perfectConfig = perfectConfig and self.ir.loadConfig(config['ir'])
            # pass the gpio config to be parsed by the gpio config container
            if 'gpio' in config:
                perfectConfig = perfectConfig and self.gpio.loadConfig(config['gpio'])
            # pass the osd config to be parsed by the osd config container
            if 'osd' in config:
                perfectConfig = perfectConfig and self.osd.loadConfig(config['osd'])
            # pass the network config to be parsed by the network config container
            if 'network' in config:
                perfectConfig = perfectConfig and self.network.loadConfig(config['network'])
            # parse shutdown default shutdown event behavior
            if 'shutdownBehavior' in config:
                if isinstance(config['shutdownBehavior'], str):
                    validShutBehav = False
                    for shutBehavOpt in rydeplayer.common.shutdownBehavior:
                        if shutBehavOpt.name == config['shutdownBehavior'].upper():
                            self.shutdownBehavior = shutBehavOpt
                            validShutBehav = True
                            break
                    if not validShutBehav:
                        print("Shutdown behavior default invalid, skipping")
                        perfectConfig = False
                else:
                    print("Shutdown behavior default invalid, skipping")
                    perfectConfig = False
            # parse audio options
            if 'audio' in config:
                if isinstance(config['audio'], dict):
                    if 'muteOnStartup' in config['audio']:
                        if isinstance(config['audio']['muteOnStartup'], bool):
                            self.audio.muteOnStartup = config['audio']['muteOnStartup']
                        else:
                            print("Invalid mute on startup config, skipping")
                            perfectConfig = False
                    if 'volumeOnStartup' in config['audio']:
                        if isinstance(config['audio']['volumeOnStartup'], int):
                            if config['audio']['volumeOnStartup'] <= 100 and config['audio']['volumeOnStartup'] >= 0:

                                self.audio.volumeOnStartup = config['audio']['volumeOnStartup']
                            else:
                                print("Invalid startup volume config, out of range, skipping")
                                perfectConfig = False

                        else:
                            print("Invalid startup volume config, skipping")
                            perfectConfig = False
                    if 'volumeStep' in config['audio']:
                        if isinstance(config['audio']['volumeStep'], int):
                            if config['audio']['volumeStep'] <= 100 and config['audio']['volumeStep'] >= 0:

                                self.audio.volumeStep = config['audio']['volumeStep']
                            else:
                                print("Invalid volume step config, out of range, skipping")
                                perfectConfig = False

                        else:
                            print("Invalid volume step config, skipping")
                            perfectConfig = False

            # parse debug options
            if 'debug' in config:
                if isinstance(config['debug'], dict):
                    if 'enableMenu' in config['debug']:
                        if isinstance(config['debug']['enableMenu'], bool):
                            self.debug.enableMenu = config['debug']['enableMenu']
                        else:
                            print("Invalid debug menu config, skipping")
                            perfectConfig = False
                    if 'autoplay' in config['debug']:
                        if isinstance(config['debug']['autoplay'], bool):
                            self.debug.autoplay = config['debug']['autoplay']
                        else:
                            print("Invalid debug autoplay config, skipping")
                            perfectConfig = False
                    if 'disableHardwareCodec' in config['debug']:
                        if isinstance(config['debug']['disableHardwareCodec'], bool):
                            self.debug.disableHardwareCodec = config['debug']['disableHardwareCodec']
                        else:
                            print("Invalid debug hardware codec config, skipping")
                            perfectConfig = False
                else:
                    print("Invalid debug config, skipping")
                    perfectConfig = False

                    
        else:
            print("Invalid config, no fields")
            perfectConfig = False
        return perfectConfig

    # load yaml config file
    def loadFile(self, path):
        if os.path.exists(path) and os.path.isfile(path):
            try:
                with open(path, 'r') as ymlconfigfile:
                    self.loadConfig(yaml.load(ymlconfigfile))
            except IOError as e:
                print(e)
        else:
            print("config file not found")

class player(object):

    def __init__(self, configFile = None):
        # Autodetect output display
        if(len(pydispmanx.getDisplays())<1):
            raise RuntimeError('No displays detected')
        else:
            self.displayId = pydispmanx.getDisplays()[0]

        # setup ui core
        pygame.init()
        self.theme = Theme(pydispmanx.getDisplaySize())
        self.playbackState = rydeplayer.states.playback.StateDisplay(self.theme)
        print(pygame.font.get_fonts())

        # load config
        self.config = rydeConfig(self.theme)
        if configFile != None:
            self.config.loadFile(configFile)
        print(self.config.tuner)

        # mute
        self.mute = self.config.audio.muteOnStartup
        self.muteCallbacks = []

        # volume
        self.volume = self.config.audio.volumeOnStartup
        self.volumeCallbacks = []

        # setup source 
        self.sourceMan = rydeplayer.sources.common.sourceManagerThread(self.config.tuner, self.config.sourceConfigs)
        self.config.tuner.addCallbackFunction(self.sourceMan.reconfig)

        self.vlcStartup()

        # setup on screen display
        self.osd = rydeplayer.osd.display.Controller(self.theme, self.config.osd, self.sourceMan.getStatus(), self, self.config.tuner)

        debugFunctions = {'Restart Source':self.sourceMan.restart, 'Force VLC':self.vlcStop, 'Abort VLC': self.vlcAbort}

        # start ui
        self.app = guiState(self.theme, self.config.shutdownBehavior, self, self.osd)
        self.app.startup(self.config, debugFunctions)

        # start network
        self.netMan = rydeplayer.network.networkManager(self.config, self.stepSM, self.setMute, debugFunctions)

        # setup ir
        self.irMan = ir.irManager(self.stepSM, self.config.ir)

        # setup gpio
        self.gpioMan = rydeplayer.gpio.gpioManager(self.stepSM, self.config.gpio, self.config.tuner)
        self.config.tuner.addCallbackFunction(self.gpioMan.setBandOutFromPreset)

        # start source
        self.sourceMan.start()
        print("Ready")
        self.monotonicState = 0;

    def start(self):
        quit = False
        # main event loop
        while not quit:
            # need to regen every loop, lm stdout handler changes on lm restart
            fds = self.irMan.getFDs() + self.sourceMan.getFDs() + self.gpioMan.getFDs() + self.osd.getFDs() + self.netMan.getFDs()
            r, w, x = select.select(fds, [], [])
            for fd in r:
                quit = self.handleEvent(fd)
                self.updateState()
                if quit:
                    break
        self.shutdown(self.app.shutdownState)

    def addMuteCallback(self, callback):
        self.muteCallbacks.append(callback)

    def removeMuteCallback(self, callback):
        self.muteCallbacks.remove(callback)

    def setMute(self,newMute):
        if self.mute != newMute:
            self.mute = newMute
            self.vlcPlayer.audio_set_mute(newMute)
            for callback in self.muteCallbacks:
                callback(newMute)

    def getMute(self):
        return self.mute

    def toggleMute(self):
        self.setMute(not self.mute)

    def addVolumeCallback(self, callback):
        self.volumeCallbacks.append(callback)

    def removeVolumeCallback(self, callback):
        self.volumeCallbacks.remove(callback)

    def setVolume(self,newVolume):
        if newVolume > 100:
            newVolume = 100
        elif newVolume < 0:
            newVolume = 0
        if self.volume != newVolume:
            self.volume = newVolume
            self.vlcPlayer.audio_set_volume(newVolume)
            for callback in self.volumeCallbacks:
                callback(newVolume)

    def getVolume(self):
        return self.volume

    def adjustVolume(self, volumeAdjustment):
        self.setVolume(self.volume + volumeAdjustment)

    def adjustVolumeByStep(self, up):
        if up:
            self.adjustVolume(self.config.audio.volumeStep)
        else:
            self.adjustVolume(-self.config.audio.volumeStep)

    def getPresetName(self, preset):
        if preset in self.config.presets:
            return str(self.config.presets[preset])
        else:
            return ""

    def switchPresetRelative(self, offset):
        presetkeys = list(self.config.presets.keys())
        if len(presetkeys) > 0:
            newindex = None
            if self.config.tuner in presetkeys:
                newindex = (presetkeys.index(self.config.tuner) + offset)%len(presetkeys)
            else:
                if offset > 0:
                    newindex = 0
                elif offset < 0:
                    newindex = len(presetkeys)-1
            if newindex is not None:
                self.config.tuner.setConfigToMatch(presetkeys[newindex])
                self.osd.activate(3, rydeplayer.osd.display.TimerLength.USERTRIGGER)

    def shutdown(self, behaviour):
        del(self.osd)
        del(self.playbackState)
        self.sourceMan.shutdown()
        if behaviour is rydeplayer.common.shutdownBehavior.APPREST:
            os.execv(sys.executable, ['python3', '-m', 'rydeplayer'] + sys.argv[1:])
        elif behaviour is rydeplayer.common.shutdownBehavior.SYSSTOP:
            os.system("sudo shutdown -h now")
        elif behaviour is rydeplayer.common.shutdownBehavior.SYSREST:
            os.system("sudo shutdown -r now")

    def handleEvent(self, fd):
        quit = False
        # handle ready file descriptors
        if(fd in self.irMan.getFDs()):
            quit = self.irMan.handleFD(fd)
        elif(fd in self.sourceMan.getFDs()):
            self.sourceMan.handleFD(fd)
        elif(fd in self.gpioMan.getFDs()):
            quit = self.gpioMan.handleFD(fd)
        elif(fd in self.osd.getFDs()):
            quit = self.osd.handleFD(fd)
        elif(fd in self.netMan.getFDs()):
            quit = self.netMan.handleFD(fd)
        return quit

    def updateState(self):
        # update playback state
        state = self.sourceMan.getCoreState()
        if(state.isRunning):
            if(state.isLocked):
                self.playbackState.setState(rydeplayer.states.playback.States.LOCKED)
                newMonoState = state.monotonicState

                if self.monotonicState != newMonoState:
                    self.monotonicState = newMonoState
                    self.vlcStop()
                    print("Param Restart")
                if self.vlcPlayer.get_state() not in [vlc.State.Playing, vlc.State.Opening] and self.config.debug.autoplay:
                    self.vlcPlay()
                    self.osd.activate(4, rydeplayer.osd.display.TimerLength.PROGRAMTRIGGER)
                self.gpioMan.setRXgood(True)
            else:
                self.playbackState.setState(rydeplayer.states.playback.States.NOLOCK)
                if self.config.debug.autoplay:
                    self.vlcStop()
                self.gpioMan.setRXgood(False)
        else:
            if state.isStarted:
                self.playbackState.setState(rydeplayer.states.playback.States.SOURCELOAD)
            else:
                self.playbackState.setState(rydeplayer.states.playback.States.NOSOURCE)
            if self.config.debug.autoplay:
                self.vlcStop()
#               print("parsed:"+str(vlcMedia.is_parsed()))
            self.gpioMan.setRXgood(False)
        self.vlcPlayer.audio_set_mute(self.mute)
        self.vlcPlayer.audio_set_volume(self.volume)
        print(self.vlcPlayer.get_state())

    # Step the state machine with a navEvent
    def stepSM(self, code):
        self.app.get_event(code)
        if(self.app.done):
            self.app.cleanup()
            return True
        self.app.update()
        return False

    # retrigger vlc to play, mostly exsists as its needed as a callback
    def vlcPlay(self):
        self.vlcPlayer.set_media(self.vlcMedia)
        self.vlcPlayer.play()
    def vlcStop(self):
        self.vlcPlayer.stop()
    def vlcAbort(self):
        del(self.vlcMedia)
        del(self.vlcPlayer)
        del(self.vlcInstance)
        importlib.reload(vlc)
        self.sourceMan.remedia()
#        self.sourceMan.restart()
        self.vlcStartup()

    def vlcStartup(self):
        vlcArgs = ''
        displaySize = pydispmanx.getDisplaySize()
        vlcArgs += '--width '+str(displaySize[0])+' --height '+str(displaySize[1])+' '
        displayPAR = pydispmanx.getPixelAspectRatio()
        vlcArgs += '--monitor-par '+str(displayPAR[0])+':'+str(displayPAR[1])+' '
        # vlc needs manually telling if using the second HDMI
        if self.displayId == 7:
            vlcArgs += '--mmal-display hdmi-2 '
        if self.config.debug.disableHardwareCodec:
            vlcArgs += '--codec ffmpeg '
#        vlcArgs += '--gain 4 --alsa-audio-device hw:CARD=Headphones,DEV=0 '
        print(vlcArgs)
        self.vlcInstance = vlc.Instance(vlcArgs)

        self.vlcPlayer = self.vlcInstance.media_player_new()
        self.vlcMedia = self.vlcInstance.media_new_fd(self.sourceMan.getMediaFd().fileno())

def run():
    parser = argparse.ArgumentParser()
    parser.add_argument(metavar="config filename", dest='conffile', help="YAML config file to try and load. Default: config.yaml", nargs='?', default='config.yaml')
    args = parser.parse_args()
    print(args)
    newplayer = player(args.conffile)
    newplayer.start()

if __name__ == '__main__':
    run()

Post Reply

Return to “The Ryde Digital ATV Receiver”