Exemple #1
0
 def __init__(self,
              queue,
              notify,
              lock,
              database,
              server=None,
              direct_path=None,
              *args):
     self.queue = queue
     self.notify_output = notify
     self.notify = settings('newContent.bool')
     self.lock = lock
     self.database = Database(database)
     self.args = args
     self.server = server
     self.direct_path = direct_path
     threading.Thread.__init__(self)
    def libraries(self, library_id=None, update=False):
        ''' Map the syncing process and start the sync. Ensure only one sync is running.
        '''
        self.direct_path = settings('useDirectPaths') == "1"
        self.update_library = update
        self.sync = get_sync()

        if library_id:

            # Look up library in local Jellyfin database
            library = self.get_library(library_id)

            if library:
                if library.media_type == 'mixed':
                    self.sync['Libraries'].append("Mixed:%s" % library_id)
                    # Include boxsets library
                    libraries = self.get_libraries()
                    boxsets = [
                        row.view_id for row in libraries
                        if row.media_type == 'boxsets'
                    ]
                    if boxsets:
                        self.sync['Libraries'].append('Boxsets:%s' %
                                                      boxsets[0])
                elif library.media_type == 'movies':
                    self.sync['Libraries'].append(library_id)
                    # Include boxsets library
                    libraries = self.get_libraries()
                    boxsets = [
                        row.view_id for row in libraries
                        if row.media_type == 'boxsets'
                    ]
                    if boxsets:
                        self.sync['Libraries'].append('Boxsets:%s' %
                                                      boxsets[0])
                else:
                    # Only called if the library isn't already known about
                    self.sync['Libraries'].append(library_id)
            else:
                self.sync['Libraries'].append(library_id)
        else:
            self.mapping()

        if not xmls.advanced_settings() and self.sync['Libraries']:
            self.start()
    def delete_item(self):
        delete = True

        if not settings('skipContextMenu.bool'):

            if not dialog("yesno", heading="{emby}", line1=_(33015)):
                delete = False

        if delete:

            self.server['api'].delete_item(self.item['Id'])
            event(
                "LibraryChanged", {
                    'ItemsRemoved': [self.item['Id']],
                    'ItemsVerify': [self.item['Id']],
                    'ItemsUpdated': [],
                    'ItemsAdded': []
                })
    def set_progress_dialog(self):

        queue_size = self.worker_queue_size()
        try:
            self.progress_percent = int(
                (float(self.total_updates - queue_size) /
                 float(self.total_updates)) * 100)
        except Exception:
            self.progress_percent = 0

        LOG.debug("--[ pdialog (%s/%s) ]", queue_size, self.total_updates)

        if self.total_updates < int(settings('syncProgress') or 50):
            return

        if self.progress_updates is None:
            LOG.info("-->[ pdialog ]")
            self.progress_updates = xbmcgui.DialogProgressBG()
            self.progress_updates.create(_('addon_name'), _(33178))
    def __init__(self, monitor):

        self.media = {
            'Movies': Movies,
            'TVShows': TVShows,
            'MusicVideos': MusicVideos,
            'Music': Music
        }
        self.kodi_media = {
            'Movies': kMovies,
            'TVShows': kTVShows,
            'MusicVideos': kMusicVideos,
            'Music': kMusic,
            'Kodi': Kodi
        }
        self.MEDIA = MEDIA

        self.direct_path = settings('useDirectPaths') == "1"
        self.monitor = monitor
        self.player = monitor.monitor.player
        self.server = Emby().get_client()
        self.updated_queue = Queue.Queue()
        self.userdata_queue = Queue.Queue()
        self.removed_queue = Queue.Queue()
        self.updated_output = self.__new_queues__()
        self.userdata_output = self.__new_queues__()
        self.removed_output = self.__new_queues__()
        self.notify_output = Queue.Queue()
        self.add_lib_queue = Queue.Queue()
        self.remove_lib_queue = Queue.Queue()
        self.verify_queue = Queue.Queue()

        self.emby_threads = []
        self.download_threads = []
        self.notify_threads = []
        self.writer_threads = {'updated': [], 'userdata': [], 'removed': []}
        self.database_lock = threading.Lock()
        self.music_database_lock = threading.Lock()
        self.sync = Sync

        threading.Thread.__init__(self)
        self.start()
    def set_playlist(self,
                     item,
                     listitem,
                     db_id=None,
                     transcode=False,
                     *args,
                     **kwargs):
        ''' Verify seektime, set intros, set main item and set additional parts.
            Detect the seektime for video type content.
            Verify the default video action set in Kodi for accurate resume behavior.
        '''
        seektime = window('emby.resume.bool')
        window('emby.resume', clear=True)

        if item['MediaType'] in ('Video', 'Audio'):
            resume = item['UserData'].get('PlaybackPositionTicks')

            if resume:
                if get_play_action() == "Resume":
                    seektime = True

                if transcode and not seektime:
                    choice = self.resume_dialog(
                        api.API(item, self.server).adjust_resume(
                            (resume or 0) / 10000000.0))

                    if choice is None:
                        raise Exception("User backed out of resume dialog.")

                    seektime = False if not choice else True

        if settings('enableCinema.bool') and not seektime:
            self._set_intros(item)

        self.set_listitem(item, listitem, db_id, seektime)
        playutils.set_properties(item, item['PlaybackInfo']['Method'],
                                 self.server_id)
        self.stack.append([item['PlaybackInfo']['Path'], listitem])

        if item.get('PartCount'):
            self._set_additional_parts(item['Id'])
    def play(self, item, db_id=None, transcode=False, playlist=False):
        ''' Play item based on if playback started from widget ot not.
            To get everything to work together, play the first item in the stack with setResolvedUrl,
            add the rest to the regular playlist.
        '''
        listitem = xbmcgui.ListItem()
        LOG.info("[ play/%s ] %s", item['Id'], item['Name'])

        transcode = transcode or settings('playFromTranscode.bool')
        kodi_playlist = self.get_playlist(item)
        play = playutils.PlayUtils(item, transcode, self.server_id,
                                   self.server)
        source = play.select_source(play.get_sources())
        play.set_external_subs(source, listitem)

        self.set_playlist(item, listitem, db_id, transcode)
        index = max(kodi_playlist.getposition(), 0) + 1  # Can return -1
        force_play = False

        self.stack[0][1].setPath(self.stack[0][0])
        try:
            if not playlist and self.detect_widgets(item):
                LOG.info(" [ play/widget ]")

                raise IndexError

            xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, self.stack[0][1])
            self.stack.pop(0)
        except IndexError:
            force_play = True

        for stack in self.stack:

            kodi_playlist.add(url=stack[0], listitem=stack[1], index=index)
            index += 1

        if force_play:
            if len(sys.argv) > 1:
                xbmcplugin.setResolvedUrl(int(sys.argv[1]), False,
                                          self.stack[0][1])
            xbmc.Player().play(kodi_playlist, windowed=False)
Exemple #8
0
    def setup(self):

        minimum = "3.0.23"

        if settings('MinimumSetup') == minimum:
            return

        self._is_mode()
        LOG.info("Add-on playback: %s", settings('useDirectPaths') == "0")
        self._is_artwork_caching()
        LOG.info("Artwork caching: %s", settings('enableTextureCache.bool'))
        self._is_empty_shows()
        LOG.info("Sync empty shows: %s", settings('syncEmptyShows.bool'))

        # Setup completed
        settings('MinimumSetup', minimum)
Exemple #9
0
    def _set_intros(self, item):
        ''' if we have any play them when the movie/show is not being resumed.
        '''
        intros = TheVoid('GetIntros', {
            'ServerId': self.server_id,
            'Id': item['Id']
        }).get()

        if intros['Items']:
            enabled = True

            if settings('askCinema') == "true":

                resp = dialog("yesno",
                              heading="{jellyfin}",
                              line1=translate(33016))
                if not resp:

                    enabled = False
                    LOG.info("Skip trailers.")

            if enabled:
                for intro in intros['Items']:

                    listitem = xbmcgui.ListItem()
                    LOG.info("[ intro/%s ] %s", intro['Id'], intro['Name'])

                    play = playutils.PlayUtils(intro, False, self.server_id,
                                               self.server)
                    source = play.select_source(play.get_sources())
                    self.set_listitem(intro, listitem, intro=True)
                    listitem.setPath(intro['PlaybackInfo']['Path'])
                    playutils.set_properties(intro,
                                             intro['PlaybackInfo']['Method'],
                                             self.server_id)

                    self.stack.append(
                        [intro['PlaybackInfo']['Path'], listitem])

                window('jellyfin.skip.%s' % intro['Id'], value="true")
Exemple #10
0
    def register(self, server_id=None, options={}):
        ''' Login into server. If server is None, then it will show the proper prompts to login, etc.
            If a server id is specified then only a login dialog will be shown for that server.
        '''
        LOG.info("--[ server/%s ]", server_id or 'default')
        credentials = dict(get_credentials())
        servers = credentials['Servers']

        if server_id is None and credentials['Servers']:
            credentials['Servers'] = [credentials['Servers'][0]]

        elif credentials['Servers']:

            for server in credentials['Servers']:

                if server['Id'] == server_id:
                    credentials['Servers'] = [server]

        server_select = True if server_id is None and not settings(
            'SyncInstallRunDone.bool') else False
        new_credentials = self.register_client(credentials, options, server_id,
                                               server_select)

        for server in servers:
            if server['Id'] == new_credentials['Servers'][0]['Id']:
                server = new_credentials['Servers'][0]

                break
        else:
            servers = new_credentials['Servers']

        credentials['Servers'] = servers
        save_credentials(credentials)

        try:
            Jellyfin(server_id).start(True)
        except ValueError as error:
            LOG.error(error)
Exemple #11
0
    def libraries(self, library_id=None, update=False, forced=False):
        ''' Map the syncing process and start the sync. Ensure only one sync is running.
            force to resume any previous sync
        '''
        self.direct_path = settings('useDirectPaths') == "1"
        self.update_library = update
        self.sync = get_sync()

        if library_id:
            libraries = library_id.split(',')

            for selected in libraries:

                if selected not in [
                        x.replace('Mixed:', "") for x in self.sync['Libraries']
                ]:
                    library = self.get_libraries(selected)

                    if library:

                        self.sync['Libraries'].append("Mixed:%s" %
                                                      selected if library[1] ==
                                                      'mixed' else selected)

                        if library[1] in ('mixed', 'movies'):
                            self.sync['Libraries'].append('Boxsets:%s' %
                                                          selected)
                    else:
                        self.sync['Libraries'].append(selected)
        else:
            self.mapping(forced)

        xmls.sources()

        if not xmls.advanced_settings() and self.sync['Libraries']:
            self.start()
Exemple #12
0
    def setup(self):

        minimum = "3.0.24"
        cached = settings('MinimumSetup')

        if cached == minimum:
            return

        if not cached:

            self._is_mode()
            LOG.info("Add-on playback: %s", settings('useDirectPaths') == "0")
            self._is_artwork_caching()
            LOG.info("Artwork caching: %s",
                     settings('enableTextureCache.bool'))
            self._is_empty_shows()
            LOG.info("Sync empty shows: %s", settings('syncEmptyShows.bool'))
            self._is_multiep()
            LOG.info("Enable multi episode label: %s",
                     settings('displayMultiEpLabel.bool'))

        # Setup completed
        settings('MinimumSetup', minimum)
Exemple #13
0
def get_themes():

    ''' Add theme media locally, via strm. This is only for tv tunes.
        If another script is used, adjust this code.
    '''
    from helper.utils import normalize_string
    from helper.playutils import PlayUtils
    from helper.xmls import tvtunes_nfo

    library = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/library").decode('utf-8')
    play = settings('useDirectPaths') == "1"

    if not xbmcvfs.exists(library + '/'):
        xbmcvfs.mkdir(library)

    if xbmc.getCondVisibility('System.HasAddon(script.tvtunes)'):

        tvtunes = xbmcaddon.Addon(id="script.tvtunes")
        tvtunes.setSetting('custom_path_enable', "true")
        tvtunes.setSetting('custom_path', library)
        LOG.info("TV Tunes custom path is enabled and set.")
    else:
        dialog("ok", heading="{emby}", line1=_(33152))

        return

    with Database('emby') as embydb:
        all_views = emby_db.EmbyDatabase(embydb.cursor).get_views()
        views = [x[0] for x in all_views if x[2] in ('movies', 'tvshows', 'mixed')]


    items = {}
    server = TheVoid('GetServerAddress', {'ServerId': None}).get()
    token = TheVoid('GetToken', {'ServerId': None}).get()

    for view in views:
        result = TheVoid('GetThemes', {'Type': "Video", 'Id': view}).get()

        for item in result['Items']:

            folder = normalize_string(item['Name'].encode('utf-8'))
            items[item['Id']] = folder

        result = TheVoid('GetThemes', {'Type': "Song", 'Id': view}).get()

        for item in result['Items']:

            folder = normalize_string(item['Name'].encode('utf-8'))
            items[item['Id']] = folder

    for item in items:

        nfo_path = os.path.join(library, items[item])
        nfo_file = os.path.join(nfo_path, "tvtunes.nfo")

        if not xbmcvfs.exists(nfo_path):
            xbmcvfs.mkdir(nfo_path)

        themes = TheVoid('GetTheme', {'Id': item}).get()
        paths = []

        for theme in themes['ThemeVideosResult']['Items'] + themes['ThemeSongsResult']['Items']:
            putils = PlayUtils(theme, False, None, server, token)

            if play:
                paths.append(putils.direct_play(theme['MediaSources'][0]).encode('utf-8'))
            else:
                paths.append(putils.direct_url(theme['MediaSources'][0]).encode('utf-8'))

        tvtunes_nfo(nfo_file, paths)

    dialog("notification", heading="{emby}", message=_(33153), icon="{emby}", time=1000, sound=False)
Exemple #14
0
def get_next_episodes(item_id, limit):

    ''' Only for synced content.
    '''
    with Database('emby') as embydb:

        db = emby_db.EmbyDatabase(embydb.cursor)
        library = db.get_view_name(item_id)

        if not library:
            return

    result = JSONRPC('VideoLibrary.GetTVShows').execute({
                'sort': {'order': "descending", 'method': "lastplayed"},
                'filter': {
                    'and': [
                        {'operator': "true", 'field': "inprogress", 'value': ""},
                        {'operator': "is", 'field': "tag", 'value': "%s" % library}
                    ]},
                'properties': ['title', 'studio', 'mpaa', 'file', 'art']
             })

    try:
        items = result['result']['tvshows']
    except (KeyError, TypeError):
        return

    list_li = []

    for item in items:
        if settings('ignoreSpecialsNextEpisodes.bool'):
            params = {
                'tvshowid': item['tvshowid'],
                'sort': {'method': "episode"},
                'filter': {
                    'and': [
                        {'operator': "lessthan", 'field': "playcount", 'value': "1"},
                        {'operator': "greaterthan", 'field': "season", 'value': "0"}
                ]},
                'properties': [
                    "title", "playcount", "season", "episode", "showtitle",
                    "plot", "file", "rating", "resume", "tvshowid", "art",
                    "streamdetails", "firstaired", "runtime", "writer",
                    "dateadded", "lastplayed"
                ],
                'limits': {"end": 1}
            }
        else:
            params = {
                'tvshowid': item['tvshowid'],
                'sort': {'method': "episode"},
                'filter': {'operator': "lessthan", 'field': "playcount", 'value': "1"},
                'properties': [
                    "title", "playcount", "season", "episode", "showtitle",
                    "plot", "file", "rating", "resume", "tvshowid", "art",
                    "streamdetails", "firstaired", "runtime", "writer",
                    "dateadded", "lastplayed"
                ],
                'limits': {"end": 1}
            }

        result = JSONRPC('VideoLibrary.GetEpisodes').execute(params)

        try:
            episodes = result['result']['episodes']
        except (KeyError, TypeError):
            pass
        else:
            for episode in episodes:

                li = create_listitem(episode)
                list_li.append((episode['file'], li))

        if len(list_li) == limit:
            break

    xbmcplugin.addDirectoryItems(int(sys.argv[1]), list_li, len(list_li))
    xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
    xbmcplugin.endOfDirectory(int(sys.argv[1]))
Exemple #15
0
def add_user(permanent=False):

    ''' Add or remove users from the default server session.
        permanent=True from the add-on settings.
    '''
    if not window('emby_online.bool'):
        return

    session = TheVoid('GetSession', {}).get()
    users = TheVoid('GetUsers', {'IsDisabled': False, 'IsHidden': False}).get()

    for user in users:

        if user['Id'] == session[0]['UserId']:
            users.remove(user)

            break

    while True:

        session = TheVoid('GetSession', {}).get()
        additional = current = session[0]['AdditionalUsers']
        add_session = True

        if permanent:

            perm_users = settings('addUsers').split(',') if settings('addUsers') else []
            current = []

            for user in users:
                for perm_user in perm_users:

                    if user['Id'] == perm_user:
                        current.append({'UserName': user['Name'], 'UserId': user['Id']})

        result = dialog("select", _(33061), [_(33062), _(33063)] if current else [_(33062)])

        if result < 0:
            break

        if not result: # Add user

            eligible = [x for x in users if x['Id'] not in [current_user['UserId'] for current_user in current]]
            resp = dialog("select", _(33064), [x['Name'] for x in eligible])

            if resp < 0:
                break

            user = eligible[resp]

            if permanent:

                perm_users.append(user['Id'])
                settings('addUsers', ','.join(perm_users))

                if user['Id'] in [current_user['UserId'] for current_user in additional]:
                    add_session = False
            
            if add_session:
                event('AddUser', {'Id': user['Id'], 'Add': True})

            dialog("notification", heading="{emby}", message="%s %s" % (_(33067), user['Name']), icon="{emby}", time=1000, sound=False)
        else: # Remove user
            resp = dialog("select", _(33064), [x['UserName'] for x in current])

            if resp < 0:
                break

            user = current[resp]

            if permanent:

                perm_users.remove(user['UserId'])
                settings('addUsers', ','.join(perm_users))
            
            if add_session:
                event('AddUser', {'Id': user['UserId'], 'Add': False})

            dialog("notification", heading="{emby}", message="%s %s" % (_(33066), user['UserName']), icon="{emby}", time=1000, sound=False)
Exemple #16
0
    def set_web_server(self):
        ''' Enable the webserver if not enabled. This is used to cache artwork.
            Will only test once, if it fails, user will be notified only once.
        '''
        if settings('enableTextureCache.bool'):

            get_setting = JSONRPC('Settings.GetSettingValue')

            if not self.get_web_server():

                set_setting = JSONRPC('Settings.SetSetingValue')
                set_setting.execute({
                    'setting': "services.webserverport",
                    'value': 8080
                })
                set_setting.execute({
                    'setting': "services.webserver",
                    'value': True
                })

                if not self.get_web_server():

                    settings('enableTextureCache.bool', False)
                    dialog("ok", heading="{jellyfin}", line1=_(33103))

                    return

            result = get_setting.execute({'setting': "services.webserverport"})
            settings('webServerPort', str(result['result']['value'] or ""))
            result = get_setting.execute(
                {'setting': "services.webserverusername"})
            settings('webServerUser', str(result['result']['value'] or ""))
            result = get_setting.execute(
                {'setting': "services.webserverpassword"})
            settings('webServerPass', str(result['result']['value'] or ""))
            settings('useWebServer.bool', True)
Exemple #17
0
def listing():

    ''' Display all emby nodes and dynamic entries when appropriate.
    '''
    total = int(window('Emby.nodes.total') or 0)
    sync = get_sync()
    whitelist = [x.replace('Mixed:', "") for x in sync['Whitelist']]
    servers = get_credentials()['Servers'][1:]

    for i in range(total):

        window_prop = "Emby.nodes.%s" % i
        path = window('%s.index' % window_prop)

        if not path:
            path = window('%s.content' % window_prop) or window('%s.path' % window_prop)

        label = window('%s.title' % window_prop)
        node = window('%s.type' % window_prop)
        artwork = window('%s.artwork' % window_prop)
        view_id = window('%s.id' % window_prop)
        context = []

        if view_id and node in ('movies', 'tvshows', 'musicvideos', 'music', 'mixed') and view_id not in whitelist:
            label = "%s %s" % (label.decode('utf-8'), _(33166))
            context.append((_(33123), "RunPlugin(plugin://plugin.video.emby/?mode=synclib&id=%s)" % view_id))

        if view_id and node in ('movies', 'tvshows', 'musicvideos', 'music') and view_id in whitelist:

            context.append((_(33136), "RunPlugin(plugin://plugin.video.emby/?mode=updatelib&id=%s)" % view_id))
            context.append((_(33132), "RunPlugin(plugin://plugin.video.emby/?mode=repairlib&id=%s)" % view_id))
            context.append((_(33133), "RunPlugin(plugin://plugin.video.emby/?mode=removelib&id=%s)" % view_id))

        LOG.debug("--[ listing/%s/%s ] %s", node, label, path)

        if path:
            if xbmc.getCondVisibility('Window.IsActive(Pictures)') and node in ('photos', 'homevideos'):
                directory(label, path, artwork=artwork)
            elif xbmc.getCondVisibility('Window.IsActive(Videos)') and node not in ('photos', 'music', 'audiobooks'):
                directory(label, path, artwork=artwork, context=context)
            elif xbmc.getCondVisibility('Window.IsActive(Music)') and node in ('music'):
                directory(label, path, artwork=artwork, context=context)
            elif not xbmc.getCondVisibility('Window.IsActive(Videos) | Window.IsActive(Pictures) | Window.IsActive(Music)'):
                directory(label, path, artwork=artwork)

    for server in servers:
        context = []

        if server.get('ManualAddress'):
            context.append((_(33141), "RunPlugin(plugin://plugin.video.emby/?mode=removeserver&server=%s)" % server['Id']))

        if 'AccessToken' not in server:
            directory("%s (%s)" % (server['Name'], _(30539)), "plugin://plugin.video.emby/?mode=login&server=%s" % server['Id'], False, context=context)
        else:
            directory(server['Name'], "plugin://plugin.video.emby/?mode=browse&server=%s" % server['Id'], context=context)


    directory(_(33194), "plugin://plugin.video.emby/?mode=managelibs", True)
    directory(_(33134), "plugin://plugin.video.emby/?mode=addserver", False)
    directory(_(33054), "plugin://plugin.video.emby/?mode=adduser", False)
    directory(_(5), "plugin://plugin.video.emby/?mode=settings", False)
    directory(_(33059), "plugin://plugin.video.emby/?mode=texturecache", False)
    directory(_(33058), "plugin://plugin.video.emby/?mode=reset", False)
    directory(_(33192), "plugin://plugin.video.emby/?mode=restartservice", False)

    if settings('backupPath'):
        directory(_(33092), "plugin://plugin.video.emby/?mode=backup", False)

    directory(_(33163), None, False, artwork="special://home/addons/plugin.video.emby/donations.png")

    xbmcplugin.setContent(int(sys.argv[1]), 'files')
    xbmcplugin.endOfDirectory(int(sys.argv[1]))
Exemple #18
0
def listing():
    ''' Display all jellyfin nodes and dynamic entries when appropriate.
    '''
    total = int(window('Jellyfin.nodes.total') or 0)
    sync = get_sync()
    whitelist = [x.replace('Mixed:', "") for x in sync['Whitelist']]
    servers = get_credentials()['Servers'][1:]

    for i in range(total):

        window_prop = "Jellyfin.nodes.%s" % i
        path = window('%s.index' % window_prop)

        if not path:
            path = window('%s.content' % window_prop) or window(
                '%s.path' % window_prop)

        label = window('%s.title' % window_prop)
        node = window('%s.type' % window_prop)
        artwork = window('%s.artwork' % window_prop)
        view_id = window('%s.id' % window_prop)
        context = []

        if view_id and node in ('movies', 'tvshows', 'musicvideos', 'music',
                                'mixed') and view_id not in whitelist:
            label = "%s %s" % (label, translate(33166))
            context.append((
                translate(33123),
                "RunPlugin(plugin://plugin.video.jellyfin/?mode=synclib&id=%s)"
                % view_id))

        if view_id and node in ('movies', 'tvshows', 'musicvideos',
                                'music') and view_id in whitelist:

            context.append((translate(
                33136
            ), "RunPlugin(plugin://plugin.video.jellyfin/?mode=updatelib&id=%s)"
                            % view_id))
            context.append((translate(
                33132
            ), "RunPlugin(plugin://plugin.video.jellyfin/?mode=repairlib&id=%s)"
                            % view_id))
            context.append((translate(
                33133
            ), "RunPlugin(plugin://plugin.video.jellyfin/?mode=removelib&id=%s)"
                            % view_id))

        LOG.debug("--[ listing/%s/%s ] %s", node, label, path)

        if path:
            directory(label, path, artwork=artwork, context=context)

    for server in servers:
        context = []

        if server.get('ManualAddress'):
            context.append((translate(
                33141
            ), "RunPlugin(plugin://plugin.video.jellyfin/?mode=removeserver&server=%s)"
                            % server['Id']))

        if 'AccessToken' not in server:
            directory("%s (%s)" % (server['Name'], translate(30539)),
                      "plugin://plugin.video.jellyfin/?mode=login&server=%s" %
                      server['Id'],
                      False,
                      context=context)
        else:
            directory(server['Name'],
                      "plugin://plugin.video.jellyfin/?mode=browse&server=%s" %
                      server['Id'],
                      context=context)

    directory(translate(33194),
              "plugin://plugin.video.jellyfin/?mode=managelibs", True)
    directory(translate(33134),
              "plugin://plugin.video.jellyfin/?mode=addserver", False)
    directory(translate(33054), "plugin://plugin.video.jellyfin/?mode=adduser",
              False)
    directory(translate(5), "plugin://plugin.video.jellyfin/?mode=settings",
              False)
    directory(translate(33161),
              "plugin://plugin.video.jellyfin/?mode=updatepassword", False)
    directory(translate(33058), "plugin://plugin.video.jellyfin/?mode=reset",
              False)
    directory(translate(33180),
              "plugin://plugin.video.jellyfin/?mode=restartservice", False)

    if settings('backupPath'):
        directory(translate(33092),
                  "plugin://plugin.video.jellyfin/?mode=backup", False)

    xbmcplugin.setContent(PROCESS_HANDLE, 'files')
    xbmcplugin.endOfDirectory(PROCESS_HANDLE)
Exemple #19
0
    def _is_music(self):

        value = dialog("yesno", heading="{jellyfin}", line1=_(33039))
        settings('enableMusic.bool', value=value)
Exemple #20
0
    def __init__(self):

        window('jellyfin_should_stop', clear=True)

        self.settings['addon_version'] = client.get_version()
        self.settings['profile'] = xbmc.translatePath('special://profile')
        self.settings['mode'] = settings('useDirectPaths')
        self.settings['log_level'] = settings('logLevel') or "1"
        self.settings['auth_check'] = True
        self.settings['enable_context'] = settings('enableContext.bool')
        self.settings['enable_context_transcode'] = settings(
            'enableContextTranscode.bool')
        self.settings['kodi_companion'] = settings('kodiCompanion.bool')
        window('jellyfin_logLevel', value=str(self.settings['log_level']))
        window('jellyfin_kodiProfile', value=self.settings['profile'])
        settings('platformDetected', client.get_platform())

        if self.settings['enable_context']:
            window('jellyfin_context.bool', True)
        if self.settings['enable_context_transcode']:
            window('jellyfin_context_transcode.bool', True)

        LOG.info("--->>>[ %s ]", client.get_addon_name())
        LOG.info("Version: %s", client.get_version())
        LOG.info("KODI Version: %s", xbmc.getInfoLabel('System.BuildVersion'))
        LOG.info("Platform: %s", settings('platformDetected'))
        LOG.info("Python Version: %s", sys.version)
        LOG.info("Using dynamic paths: %s", settings('useDirectPaths') == "0")
        LOG.info("Log Level: %s", self.settings['log_level'])

        verify_kodi_defaults()

        try:
            Views().get_nodes()
        except Exception as error:
            LOG.exception(error)

        window('jellyfin.connected.bool', True)
        settings('groupedSets.bool', objects.utils.get_grouped_set())
        xbmc.Monitor.__init__(self)
Exemple #21
0
    def onNotification(self, sender, method, data):
        ''' All notifications are sent via NotifyAll built-in or Kodi.
            Central hub.
        '''
        if sender.lower() not in ('plugin.video.jellyfin', 'xbmc'):
            return

        if sender == 'plugin.video.jellyfin':
            method = method.split('.')[1]

            if method not in ('ServerUnreachable', 'ServerShuttingDown',
                              'UserDataChanged', 'ServerConnect',
                              'LibraryChanged', 'ServerOnline', 'SyncLibrary',
                              'RepairLibrary', 'RemoveLibrary',
                              'SyncLibrarySelection', 'RepairLibrarySelection',
                              'AddServer', 'Unauthorized', 'UpdateServer',
                              'UserConfigurationUpdated', 'ServerRestarting',
                              'RemoveServer', 'AddLibrarySelection',
                              'RemoveLibrarySelection'):
                return

            data = json.loads(data)[0]
        else:
            if method not in ('System.OnQuit', 'System.OnSleep',
                              'System.OnWake'):
                return

            data = json.loads(data)

        LOG.debug("[ %s: %s ] %s", sender, method, json.dumps(data, indent=4))

        if method == 'ServerOnline':
            if data.get('ServerId') is None:

                window('jellyfin_online.bool', True)
                self.settings['auth_check'] = True
                self.warn = True

                if settings('connectMsg.bool'):

                    users = [
                        user for user in (
                            settings('additionalUsers') or "").split(',')
                        if user
                    ]
                    users.insert(0, settings('username'))
                    dialog("notification",
                           heading="{jellyfin}",
                           message="%s %s" %
                           (translate(33000), ", ".join(users)),
                           icon="{jellyfin}",
                           time=1500,
                           sound=False)

                if self.library_thread is None:

                    self.library_thread = library.Library(self)
                    self.library_thread.start()

        elif method in ('ServerUnreachable', 'ServerShuttingDown'):

            if self.warn or data.get('ServerId'):

                self.warn = data.get('ServerId') is not None
                dialog("notification",
                       heading="{jellyfin}",
                       message=translate(33146)
                       if data.get('ServerId') is None else translate(33149),
                       icon=xbmcgui.NOTIFICATION_ERROR)

            if data.get('ServerId') is None:
                self.stop_default()

                if self.waitForAbort(120):
                    return

                self.start_default()

        elif method == 'Unauthorized':
            dialog("notification",
                   heading="{jellyfin}",
                   message=translate(33147)
                   if data['ServerId'] is None else translate(33148),
                   icon=xbmcgui.NOTIFICATION_ERROR)

            if data.get('ServerId') is None and self.settings['auth_check']:

                self.settings['auth_check'] = False
                self.stop_default()

                if self.waitForAbort(5):
                    return

                self.start_default()

        elif method == 'ServerRestarting':
            if data.get('ServerId'):
                return

            if settings('restartMsg.bool'):
                dialog("notification",
                       heading="{jellyfin}",
                       message=translate(33006),
                       icon="{jellyfin}")

            self.stop_default()

            if self.waitForAbort(15):
                return

            self.start_default()

        elif method == 'ServerConnect':
            self.connect.register(data['Id'])
            xbmc.executebuiltin("Container.Refresh")

        elif method == 'AddServer':

            self.connect.setup_manual_server()
            xbmc.executebuiltin("Container.Refresh")

        elif method == 'RemoveServer':

            self.connect.remove_server(data['Id'])
            xbmc.executebuiltin("Container.Refresh")

        elif method == 'UpdateServer':

            dialog("ok", heading="{jellyfin}", line1=translate(33151))
            self.connect.setup_manual_server()

        elif method == 'UserDataChanged' and self.library_thread:
            if data.get('ServerId') or not window('jellyfin_startup.bool'):
                return

            LOG.info("[ UserDataChanged ] %s", data)
            self.library_thread.userdata(data['UserDataList'])

        elif method == 'LibraryChanged' and self.library_thread:
            if data.get('ServerId') or not window('jellyfin_startup.bool'):
                return

            LOG.info("[ LibraryChanged ] %s", data)
            self.library_thread.updated(data['ItemsUpdated'] +
                                        data['ItemsAdded'])
            self.library_thread.removed(data['ItemsRemoved'])

        elif method == 'System.OnQuit':
            window('jellyfin_should_stop.bool', True)
            self.running = False

        elif method in ('SyncLibrarySelection', 'RepairLibrarySelection',
                        'AddLibrarySelection', 'RemoveLibrarySelection'):
            self.library_thread.select_libraries(method)

        elif method == 'SyncLibrary':
            if not data.get('Id'):
                return

            self.library_thread.add_library(data['Id'],
                                            data.get('Update', False))
            xbmc.executebuiltin("Container.Refresh")

        elif method == 'RepairLibrary':
            if not data.get('Id'):
                return

            libraries = data['Id'].split(',')

            for lib in libraries:

                if not self.library_thread.remove_library(lib):
                    return

            self.library_thread.add_library(data['Id'])
            xbmc.executebuiltin("Container.Refresh")

        elif method == 'RemoveLibrary':
            libraries = data['Id'].split(',')

            for lib in libraries:

                if not self.library_thread.remove_library(lib):
                    return

            xbmc.executebuiltin("Container.Refresh")

        elif method == 'System.OnSleep':

            LOG.info("-->[ sleep ]")
            window('jellyfin_should_stop.bool', True)

            if self.library_thread is not None:

                self.library_thread.stop_client()
                self.library_thread = None

            Jellyfin.close_all()
            self.monitor.server = []
            self.monitor.sleep = True

        elif method == 'System.OnWake':

            if not self.monitor.sleep:
                LOG.warning(
                    "System.OnSleep was never called, skip System.OnWake")

                return

            LOG.info("--<[ sleep ]")
            xbmc.sleep(10000)  # Allow network to wake up
            self.monitor.sleep = False
            window('jellyfin_should_stop', clear=True)

            try:
                self.connect.register()
            except Exception as error:
                LOG.exception(error)

        elif method == 'GUI.OnScreensaverDeactivated':

            LOG.info("--<[ screensaver ]")
            xbmc.sleep(5000)

            if self.library_thread is not None:
                self.library_thread.fast_sync()

        elif method == 'UserConfigurationUpdated':

            if data.get('ServerId') is None:
                Views().get_views()
Exemple #22
0
    def movie(self, item, e_item, library):
        ''' If item does not exist, entry will be added.
            If item exists, entry will be updated.
        '''
        server_data = self.server.auth.get_server_info(
            self.server.auth.server_id)
        server_address = self.server.auth.get_server_address(
            server_data, server_data['LastConnectionMode'])
        API = api.API(item, server_address)
        obj = self.objects.map(item, 'Movie')
        update = True

        try:
            obj['MovieId'] = e_item[0]
            obj['FileId'] = e_item[1]
            obj['PathId'] = e_item[2]
        except TypeError:
            update = False
            LOG.debug("MovieId %s not found", obj['Id'])
            obj['MovieId'] = self.create_entry()
        else:
            if self.get(*values(obj, QU.get_movie_obj)) is None:

                update = False
                LOG.info("MovieId %s missing from kodi. repairing the entry.",
                         obj['MovieId'])

        if not settings('syncRottenTomatoes.bool'):
            obj['CriticRating'] = None

        obj['Path'] = API.get_file_path(obj['Path'])
        obj['LibraryId'] = library['Id']
        obj['LibraryName'] = library['Name']
        obj['Genres'] = obj['Genres'] or []
        obj['Studios'] = [
            API.validate_studio(studio) for studio in (obj['Studios'] or [])
        ]
        obj['People'] = obj['People'] or []
        obj['Genre'] = " / ".join(obj['Genres'])
        obj['Writers'] = " / ".join(obj['Writers'] or [])
        obj['Directors'] = " / ".join(obj['Directors'] or [])
        obj['Plot'] = API.get_overview(obj['Plot'])
        obj['Mpaa'] = API.get_mpaa(obj['Mpaa'])
        obj['Resume'] = API.adjust_resume((obj['Resume'] or 0) / 10000000.0)
        obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6)
        obj['People'] = API.get_people_artwork(obj['People'])
        obj['DateAdded'] = Local(obj['DateAdded']).split('.')[0].replace(
            'T', " ")
        obj['DatePlayed'] = None if not obj['DatePlayed'] else Local(
            obj['DatePlayed']).split('.')[0].replace('T', " ")
        obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount'])
        obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'Artwork'))
        obj['Video'] = API.video_streams(obj['Video'] or [], obj['Container'])
        obj['Audio'] = API.audio_streams(obj['Audio'] or [])
        obj['Streams'] = API.media_streams(obj['Video'], obj['Audio'],
                                           obj['Subtitles'])

        self.get_path_filename(obj)
        self.trailer(obj)

        if obj['Countries']:
            self.add_countries(*values(obj, QU.update_country_obj))

        tags = []
        tags.extend(obj['Tags'] or [])
        tags.append(obj['LibraryName'])

        if obj['Favorite']:
            tags.append('Favorite movies')

        obj['Tags'] = tags

        if update:
            self.movie_update(obj)
        else:
            self.movie_add(obj)

        self.update_path(*values(obj, QU.update_path_movie_obj))
        self.update_file(*values(obj, QU.update_file_obj))
        self.add_tags(*values(obj, QU.add_tags_movie_obj))
        self.add_genres(*values(obj, QU.add_genres_movie_obj))
        self.add_studios(*values(obj, QU.add_studios_movie_obj))
        self.add_playstate(*values(obj, QU.add_bookmark_obj))
        self.add_people(*values(obj, QU.add_people_movie_obj))
        self.add_streams(*values(obj, QU.add_streams_obj))
        self.artwork.add(obj['Artwork'], obj['MovieId'], "movie")
        self.item_ids.append(obj['Id'])

        return not update
    def tvshow(self, item, e_item, library):
        ''' If item does not exist, entry will be added.
            If item exists, entry will be updated.

            If the show is empty, try to remove it.
            Process seasons.
            Apply series pooling.
        '''
        API = api.API(item, self.server['auth/server-address'])
        obj = self.objects.map(item, 'Series')
        update = True

        if not settings('syncEmptyShows.bool') and not obj['RecursiveCount']:

            LOG.info("Skipping empty show %s: %s", obj['Title'], obj['Id'])
            self.remove(obj['Id'])

            return False

        try:
            obj['ShowId'] = e_item[0]
            obj['PathId'] = e_item[2]
        except TypeError as error:

            update = False
            LOG.debug("ShowId %s not found", obj['Id'])
            obj['ShowId'] = self.create_entry()
        else:
            if self.get(*values(obj, QU.get_tvshow_obj)) is None:

                update = False
                LOG.info("ShowId %s missing from kodi. repairing the entry.",
                         obj['ShowId'])

        obj['Path'] = API.get_file_path(obj['Path'])
        obj['LibraryId'] = library['Id']
        obj['LibraryName'] = library['Name']
        obj['Genres'] = obj['Genres'] or []
        obj['People'] = obj['People'] or []
        obj['Mpaa'] = API.get_mpaa(obj['Mpaa'])
        obj['Studios'] = [
            API.validate_studio(studio) for studio in (obj['Studios'] or [])
        ]
        obj['Genre'] = " / ".join(obj['Genres'])
        obj['People'] = API.get_people_artwork(obj['People'])
        obj['Plot'] = API.get_overview(obj['Plot'])
        obj['Studio'] = " / ".join(obj['Studios'])
        obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'Artwork'))

        self.get_path_filename(obj)

        if obj['Premiere']:
            obj['Premiere'] = str(obj['Premiere']).split('.')[0].replace(
                'T', " ")

        tags = []
        tags.extend(obj['Tags'] or [])
        tags.append(obj['LibraryName'])

        if obj['Favorite']:
            tags.append('Favorite tvshows')

        obj['Tags'] = tags

        if update:
            self.tvshow_update(obj)
        else:
            self.tvshow_add(obj)

        self.link(*values(obj, QU.update_tvshow_link_obj))
        self.update_path(*values(obj, QU.update_path_tvshow_obj))
        self.add_tags(*values(obj, QU.add_tags_tvshow_obj))
        self.add_people(*values(obj, QU.add_people_tvshow_obj))
        self.add_genres(*values(obj, QU.add_genres_tvshow_obj))
        self.add_studios(*values(obj, QU.add_studios_tvshow_obj))
        self.artwork.add(obj['Artwork'], obj['ShowId'], "tvshow")
        self.item_ids.append(obj['Id'])

        season_episodes = {}

        for season in self.server['api'].get_seasons(obj['Id'])['Items']:

            if season['SeriesId'] != obj['Id']:
                obj['SeriesId'] = season['SeriesId']
                self.item_ids.append(season['SeriesId'])

                try:
                    self.emby_db.get_item_by_id(
                        *values(obj, QUEM.get_item_series_obj))[0]

                    if self.update_library:
                        season_episodes[season['Id']] = season['SeriesId']
                except TypeError:

                    self.emby_db.add_reference(
                        *values(obj, QUEM.add_reference_pool_obj))
                    LOG.info("POOL %s [%s/%s]", obj['Title'], obj['Id'],
                             obj['SeriesId'])
                    season_episodes[season['Id']] = season['SeriesId']

            try:
                self.emby_db.get_item_by_id(season['Id'])[0]
                self.item_ids.append(season['Id'])
            except TypeError:
                self.season(season, obj['ShowId'])
        else:
            season_id = self.get_season(
                *values(obj, QU.get_season_special_obj))
            self.artwork.add(obj['Artwork'], season_id, "season")

        for season in season_episodes:
            for episodes in server.get_episode_by_season(
                    season_episodes[season], season):

                for episode in episodes['Items']:
                    self.episode(episode)
Exemple #24
0
def get_themes(api_client):
    ''' Add theme media locally, via strm. This is only for tv tunes.
        If another script is used, adjust this code.
    '''
    from helper.utils import normalize_string
    from helper.playutils import PlayUtils
    from helper.xmls import tvtunes_nfo

    library = xbmc.translatePath(
        "special://profile/addon_data/plugin.video.jellyfin/library")
    play = settings('useDirectPaths') == "1"

    if not xbmcvfs.exists(library + '/'):
        xbmcvfs.mkdir(library)

    if xbmc.getCondVisibility('System.HasAddon(script.tvtunes)'):

        tvtunes = xbmcaddon.Addon(id="script.tvtunes")
        tvtunes.setSetting('custom_path_enable', "true")
        tvtunes.setSetting('custom_path', library)
        LOG.info("TV Tunes custom path is enabled and set.")
    else:
        dialog("ok", "{jellyfin}", translate(33152))

        return

    with Database('jellyfin') as jellyfindb:
        all_views = jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_views()
        views = [
            x.view_id for x in all_views
            if x.media_type in ('movies', 'tvshows', 'mixed')
        ]

    items = {}
    server = api_client.config.data['auth.server']

    for view in views:
        result = api_client.get_items_theme_video(view)

        for item in result['Items']:

            folder = normalize_string(item['Name'])
            items[item['Id']] = folder

        result = api_client.get_items_theme_song(view)

        for item in result['Items']:

            folder = normalize_string(item['Name'])
            items[item['Id']] = folder

    for item in items:

        nfo_path = os.path.join(library, items[item])
        nfo_file = os.path.join(nfo_path, "tvtunes.nfo")

        if not xbmcvfs.exists(nfo_path):
            xbmcvfs.mkdir(nfo_path)

        themes = api_client.get_themes(item)
        paths = []

        for theme in themes['ThemeVideosResult']['Items'] + themes[
                'ThemeSongsResult']['Items']:
            putils = PlayUtils(theme, False, None, server, api_client)

            if play:
                paths.append(putils.direct_play(theme['MediaSources'][0]))
            else:
                paths.append(putils.direct_url(theme['MediaSources'][0]))

        tvtunes_nfo(nfo_file, paths)

    dialog("notification",
           heading="{jellyfin}",
           message=translate(33153),
           icon="{jellyfin}",
           time=1000,
           sound=False)
Exemple #25
0
    def onSettingsChanged(self):
        ''' React to setting changes that impact window values.
        '''
        if window('jellyfin_should_stop.bool'):
            return

        if settings('logLevel') != self.settings['log_level']:

            log_level = settings('logLevel')
            window('jellyfin_logLevel', str(log_level))
            self.settings['logLevel'] = log_level
            LOG.info("New log level: %s", log_level)

        if settings('enableContext.bool') != self.settings['enable_context']:

            window('jellyfin_context', settings('enableContext'))
            self.settings['enable_context'] = settings('enableContext.bool')
            LOG.info("New context setting: %s",
                     self.settings['enable_context'])

        if settings('enableContextTranscode.bool'
                    ) != self.settings['enable_context_transcode']:

            window('jellyfin_context_transcode',
                   settings('enableContextTranscode'))
            self.settings['enable_context_transcode'] = settings(
                'enableContextTranscode.bool')
            LOG.info("New context transcode setting: %s",
                     self.settings['enable_context_transcode'])

        if settings('useDirectPaths'
                    ) != self.settings['mode'] and self.library_thread.started:

            self.settings['mode'] = settings('useDirectPaths')
            LOG.info("New playback mode setting: %s", self.settings['mode'])

            if not self.settings.get('mode_warn'):

                self.settings['mode_warn'] = True
                dialog("yesno", heading="{jellyfin}", line1=translate(33118))

        if settings('kodiCompanion.bool') != self.settings['kodi_companion']:
            self.settings['kodi_companion'] = settings('kodiCompanion.bool')

            if not self.settings['kodi_companion']:
                dialog("ok", heading="{jellyfin}", line1=translate(33138))
Exemple #26
0
    def _is_empty_shows(self):

        value = dialog("yesno", heading="{jellyfin}", line1=_(33100))
        settings('syncEmptyShows.bool', value)
Exemple #27
0
sys.path.insert(0, __cache__)
sys.path.insert(0, __pcache__)
sys.path.append(__base__)

#################################################################################################

from entrypoint import Service
from helper import settings
from emby import Emby

#################################################################################################

LOG = logging.getLogger("EMBY.service")
DELAY = int(
    settings('startupDelay') if settings('SyncInstallRunDone.bool') else 4 or 0
)

#################################################################################################


class ServiceManager(threading.Thread):
    ''' Service thread. 
        To allow to restart and reload modules internally. 
    '''
    exception = None

    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
Exemple #28
0
    def _is_rotten_tomatoes(self):

        value = dialog("yesno", heading="{jellyfin}", line1=_(33188))
        settings('syncRottenTomatoes.bool', value)
Exemple #29
0
    def general_commands(self, data):
        ''' General commands from Jellyfin to control the Kodi interface.
        '''
        command = data['Name']
        args = data['Arguments']

        if command in ('Mute', 'Unmute', 'SetVolume', 'SetSubtitleStreamIndex',
                       'SetAudioStreamIndex', 'SetRepeatMode'):

            if command == 'Mute':
                xbmc.executebuiltin('Mute')
            elif command == 'Unmute':
                xbmc.executebuiltin('Mute')
            elif command == 'SetVolume':
                xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' %
                                    args['Volume'])
            elif command == 'SetRepeatMode':
                xbmc.executebuiltin('xbmc.PlayerControl(%s)' %
                                    args['RepeatMode'])
            elif command == 'SetAudioStreamIndex':
                self.player.set_audio_subs(args['Index'])
            elif command == 'SetSubtitleStreamIndex':
                self.player.set_audio_subs(None, args['Index'])

            self.player.report_playback()

        elif command == 'DisplayMessage':
            dialog("notification",
                   heading=args['Header'],
                   message=args['Text'],
                   icon="{jellyfin}",
                   time=int(settings('displayMessage')) * 1000)

        elif command == 'SendString':
            JSONRPC('Input.SendText').execute({
                'text': args['String'],
                'done': False
            })

        elif command == 'GoHome':
            JSONRPC('GUI.ActivateWindow').execute({'window': "home"})

        elif command == 'Guide':
            JSONRPC('GUI.ActivateWindow').execute({'window': "tvguide"})

        elif command in ('MoveUp', 'MoveDown', 'MoveRight', 'MoveLeft'):
            actions = {
                'MoveUp': "Input.Up",
                'MoveDown': "Input.Down",
                'MoveRight': "Input.Right",
                'MoveLeft': "Input.Left"
            }
            JSONRPC(actions[command]).execute()

        else:
            builtin = {
                'ToggleFullscreen': 'Action(FullScreen)',
                'ToggleOsdMenu': 'Action(OSD)',
                'ToggleContextMenu': 'Action(ContextMenu)',
                'Select': 'Action(Select)',
                'Back': 'Action(back)',
                'PageUp': 'Action(PageUp)',
                'NextLetter': 'Action(NextLetter)',
                'GoToSearch': 'VideoLibrary.Search',
                'GoToSettings': 'ActivateWindow(Settings)',
                'PageDown': 'Action(PageDown)',
                'PreviousLetter': 'Action(PrevLetter)',
                'TakeScreenshot': 'TakeScreenshot',
                'ToggleMute': 'Mute',
                'VolumeUp': 'Action(VolumeUp)',
                'VolumeDown': 'Action(VolumeDown)',
            }
            if command in builtin:
                xbmc.executebuiltin(builtin[command])
Exemple #30
0
    def _is_artwork_caching(self):

        value = dialog("yesno", heading="{jellyfin}", line1=_(33117))
        settings('enableTextureCache.bool', value)