from __future__ import absolute_import import os import random import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error import json import time import threading from kodi_six import xbmc from kodi_six import xbmcgui from . import kodigui from lib import colors from lib import util from lib import backgroundthread from . import busy from . import subitems from . import preplay from . import search import plexnet from . import dropdown from . import opener from . import windowutils from plexnet import playqueue from lib.util import T import six from six.moves import range CHUNK_SIZE = 200 # CHUNK_SIZE = 30 KEYS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' MOVE_SET = frozenset( ( xbmcgui.ACTION_MOVE_LEFT, xbmcgui.ACTION_MOVE_RIGHT, xbmcgui.ACTION_MOVE_UP, xbmcgui.ACTION_MOVE_DOWN, xbmcgui.ACTION_MOUSE_MOVE, xbmcgui.ACTION_PAGE_UP, xbmcgui.ACTION_PAGE_DOWN, xbmcgui.ACTION_FIRST_PAGE, xbmcgui.ACTION_LAST_PAGE, xbmcgui.ACTION_MOUSE_WHEEL_DOWN, xbmcgui.ACTION_MOUSE_WHEEL_UP ) ) THUMB_POSTER_DIM = (268, 397) THUMB_AR16X9_DIM = (619, 348) THUMB_SQUARE_DIM = (355, 355) ART_AR16X9_DIM = (630, 355) TYPE_KEYS = { 'episode': { 'fallback': 'show', 'thumb_dim': THUMB_POSTER_DIM, }, 'season': { 'fallback': 'show', 'thumb_dim': THUMB_POSTER_DIM }, 'movie': { 'fallback': 'movie', 'thumb_dim': THUMB_POSTER_DIM, 'art_dim': ART_AR16X9_DIM }, 'show': { 'fallback': 'show', 'thumb_dim': THUMB_POSTER_DIM, 'art_dim': ART_AR16X9_DIM }, 'album': { 'fallback': 'music', 'thumb_dim': THUMB_SQUARE_DIM }, 'artist': { 'fallback': 'music', 'thumb_dim': THUMB_SQUARE_DIM }, 'track': { 'fallback': 'music', 'thumb_dim': THUMB_SQUARE_DIM }, 'photo': { 'fallback': 'photo', 'thumb_dim': THUMB_SQUARE_DIM }, 'clip': { 'fallback': 'movie16x9', 'thumb_dim': THUMB_POSTER_DIM }, } TYPE_PLURAL = { 'artist': T(32347, 'artists'), 'album': T(32461, 'albums'), 'movie': T(32348, 'movies'), 'photo': T(32349, 'photos'), 'show': T(32350, 'Shows'), 'episode': T(32458, 'Episodes') } SORT_KEYS = { 'movie': { 'addedAt': {'title': T(32351, 'By Date Added'), 'display': T(32352, 'Date Added')}, 'originallyAvailableAt': {'title': T(32353, 'By Release Date'), 'display': T(32354, 'Release Date')}, 'lastViewedAt': {'title': T(32355, 'By Date Viewed'), 'display': T(32356, 'Date Viewed')}, 'titleSort': {'title': T(32357, 'By Name'), 'display': T(32358, 'Name')}, 'rating': {'title': T(32359, 'By Rating'), 'display': T(32360, 'Rating')}, 'resolution': {'title': T(32361, 'By Resolution'), 'display': T(32362, 'Resolution')}, 'duration': {'title': T(32363, 'By Duration'), 'display': T(32364, 'Duration')}, 'unwatched': {'title': T(32367, 'By Unplayed'), 'display': T(32368, 'Unplayed')}, 'viewCount': {'title': T(32371, 'By Play Count'), 'display': T(32372, 'Play Count')} }, 'show': { 'originallyAvailableAt': {'title': T(32365, 'By First Aired'), 'display': T(32366, 'First Aired')}, 'unviewedLeafCount': {'title': T(32367, 'By Unplayed'), 'display': T(32368, 'Unplayed')}, 'show.titleSort': {'title': T(32457, 'By Show'), 'display': T(32456, 'Show')}, }, 'artist': { 'lastViewedAt': {'title': T(32369, 'By Date Played'), 'display': T(32370, 'Date Played')}, 'artist.titleSort': {'title': T(32463, 'By Artist'), 'display': T(32462, 'Artist')}, }, 'photo': { 'originallyAvailableAt': {'title': T(32373, 'By Date Taken'), 'display': T(32374, 'Date Taken')} }, 'photodirectory': {} } ITEM_TYPE = None def setItemType(type_=None): assert type_ is not None, "Invalid type: None" global ITEM_TYPE ITEM_TYPE = type_ util.setGlobalProperty('item.type', str(ITEM_TYPE)) class ChunkRequestTask(backgroundthread.Task): def setup(self, section, start, size, callback, filter_=None, sort=None, unwatched=False): self.section = section self.start = start self.size = size self.callback = callback self.filter = filter_ self.sort = sort self.unwatched = unwatched return self def contains(self, pos): return self.start <= pos <= (self.start + self.size) def run(self): if self.isCanceled(): return try: type_ = None if ITEM_TYPE == 'episode': type_ = 4 elif ITEM_TYPE == 'album': type_ = 9 items = self.section.all(self.start, self.size, self.filter, self.sort, self.unwatched, type_=type_) if self.isCanceled(): return self.callback(items, self.start) except plexnet.exceptions.BadRequest: util.DEBUG_LOG('404 on section: {0}'.format(repr(self.section.title))) class PhotoPropertiesTask(backgroundthread.Task): def setup(self, photo, callback): self.photo = photo self.callback = callback return self def run(self): if self.isCanceled(): return try: self.photo.reload() self.callback(self.photo) except plexnet.exceptions.BadRequest: util.DEBUG_LOG('404 on photo reload: {0}'.format(self.photo)) class LibrarySettings(object): def __init__(self, section_or_server_id): if isinstance(section_or_server_id, six.string_types): self.serverID = section_or_server_id self.sectionID = None else: self.serverID = section_or_server_id.getServer().uuid self.sectionID = section_or_server_id.key self._loadSettings() def _loadSettings(self): if not self.sectionID: return jsonString = util.getSetting('library.settings.{0}'.format(self.serverID), '') self._settings = {} try: self._settings = json.loads(jsonString) except ValueError: pass except: util.ERROR() setItemType(self.getItemType() or ITEM_TYPE) def getItemType(self): if not self._settings or self.sectionID not in self._settings: return None return self._settings[self.sectionID].get('ITEM_TYPE') def setItemType(self, item_type): setItemType(item_type) if self.sectionID not in self._settings: self._settings[self.sectionID] = {} self._settings[self.sectionID]['ITEM_TYPE'] = item_type self._saveSettings() def _saveSettings(self): jsonString = json.dumps(self._settings) util.setSetting('library.settings.{0}'.format(self.serverID), jsonString) def setSection(self, section_id): self.sectionID = section_id def getSetting(self, setting, default=None): if not self._settings or self.sectionID not in self._settings: return default if ITEM_TYPE not in self._settings[self.sectionID]: return default return self._settings[self.sectionID][ITEM_TYPE].get(setting, default) def setSetting(self, setting, value): if self.sectionID not in self._settings: self._settings[self.sectionID] = {} if ITEM_TYPE not in self._settings[self.sectionID]: self._settings[self.sectionID][ITEM_TYPE] = {} self._settings[self.sectionID][ITEM_TYPE][setting] = value self._saveSettings() class ChunkedWrapList(kodigui.ManagedControlList): LIST_MAX = CHUNK_SIZE * 3 def __getitem__(self, idx): # if isinstance(idx, slice): # return self.items[idx] # else: idx = idx % self.LIST_MAX return self.items[idx] # return self.getListItem(idx) class ChunkModeWrapped(object): ALL_MAX = CHUNK_SIZE * 2 def __init__(self): self.reset() def reset(self): self.midStart = 0 self.itemCount = 0 self.keys = {} def addKeyRange(self, key, krange): self.keys[key] = krange def getKey(self, pos): for k, krange in self.keys.items(): if krange[0] <= pos <= krange[1]: return k def isAtBeginning(self): return self.midStart == 0 def posIsForward(self, pos): if self.itemCount <= self.ALL_MAX: return False return pos >= self.midStart + CHUNK_SIZE def posIsBackward(self, pos): if self.itemCount <= self.ALL_MAX: return False return pos < self.midStart def posIsValid(self, pos): return self.midStart - CHUNK_SIZE <= pos < self.midStart + (CHUNK_SIZE * 2) def shift(self, mod): if mod < 0 and self.midStart == 0: return None elif mod > 0 and self.midStart + CHUNK_SIZE >= self.itemCount: return None offset = CHUNK_SIZE * mod self.midStart += offset start = self.midStart + offset return start def shiftToKey(self, key, keyStart=None): if keyStart is None: if key not in self.keys: util.DEBUG_LOG('CHUNK MODE: NO ITEMS FOR KEY') return keyStart = self.keys[key][0] self.midStart = keyStart - keyStart % CHUNK_SIZE return keyStart, max(self.midStart - CHUNK_SIZE, 0) def addObjects(self, pos, objects): if not self.posIsValid(pos): return if pos == self.midStart - CHUNK_SIZE: self.objects = objects + self.objects[CHUNK_SIZE:] elif pos == self.midStart: self.objects = self.objects[:CHUNK_SIZE] + objects + self.objects[CHUNK_SIZE * 2:] elif pos == self.midStart + CHUNK_SIZE: self.objects = self.objects[:CHUNK_SIZE * 2] + objects class CustomScrollBar(object): def __init__(self, window, bar_group_id, bar_image_id, bar_image_focus_id, button_id, min_bar_height=20): self._barGroup = window.getControl(bar_group_id) self._barImage = window.getControl(bar_image_id) self._barImageFocus = window.getControl(bar_image_focus_id) self._button = window.getControl(button_id) self.height = self._button.getHeight() self.x, self.y = self._barGroup.getPosition() self._minBarHeight = min_bar_height self._barHeight = min_bar_height self.reset() def reset(self): self.size = 0 self.count = 0 self.pos = 0 def setSizeAndCount(self, size, count): self.size = size self.count = count self._barHeight = min(self.height, max(self._minBarHeight, int(self.height * (count / float(size))))) self._moveHeight = self.height - self._barHeight self._barImage.setHeight(self._barHeight) self._barImageFocus.setHeight(self._barHeight) self.setPosition(0) def setPosition(self, pos): self.pos = pos offset = int((pos / float(max(self.size, 2) - 1)) * self._moveHeight) self._barGroup.setPosition(self.x, self.y + offset) def getPosFromY(self, y): y -= int(self._barHeight / 2) + 150 y = min(max(y, 0), self._moveHeight) return int((self.size - 1) * (y / float(self._moveHeight))) def onMouseDrag(self, window, action): y = window.mouseYTrans(action.getAmount2()) y -= int(self._barHeight / 2) + 150 y = min(max(y, 0), self._moveHeight) self._barGroup.setPosition(self.x, self.y) class LibraryWindow(kodigui.MultiWindow, windowutils.UtilMixin): bgXML = 'script-plex-blank.xml' path = util.ADDON.getAddonInfo('path') theme = 'Main' res = '1080i' def __init__(self, *args, **kwargs): kodigui.MultiWindow.__init__(self, *args, **kwargs) windowutils.UtilMixin.__init__(self) self.section = kwargs.get('section') self.filter = kwargs.get('filter_') self.keyItems = {} self.firstOfKeyItems = {} self.tasks = backgroundthread.Tasks() self.backgroundSet = False self.showPanelControl = None self.keyListControl = None self.lastItem = None self.lastFocusID = None self.lastNonOptionsFocusID = None self.dcpjPos = 0 self.dcpjThread = None self.dcpjTimeout = 0 self.dragging = False self.cleared = True self.librarySettings = LibrarySettings(self.section) self.reset() self.lock = threading.Lock() def reset(self): util.setGlobalProperty('sort', '') self.filterUnwatched = self.librarySettings.getSetting('filter.unwatched', False) if ITEM_TYPE == 'episode': self.sort = self.librarySettings.getSetting('sort', 'show.titleSort') elif ITEM_TYPE == 'album': self.sort = self.librarySettings.getSetting('sort', 'artist.titleSort') else: self.sort = self.librarySettings.getSetting('sort', 'titleSort') self.sortDesc = self.librarySettings.getSetting('sort.desc', False) self.chunkMode = None if ITEM_TYPE in ('episode', 'album'): self.chunkMode = ChunkModeWrapped() key = self.section.key if not key.isdigit(): key = self.section.getLibrarySectionId() viewtype = util.getSetting('viewtype.{0}.{1}'.format(self.section.server.uuid, key)) if self.chunkMode: if self.section.TYPE in ('artist', 'photo', 'photodirectory'): self.setWindows(VIEWS_SQUARE_CHUNKED.get('all')) self.setDefault(VIEWS_SQUARE_CHUNKED.get(viewtype)) else: self.setWindows(VIEWS_POSTER_CHUNKED.get('all')) self.setDefault(VIEWS_POSTER_CHUNKED.get(viewtype)) else: if self.section.TYPE in ('artist', 'photo', 'photodirectory'): self.setWindows(VIEWS_SQUARE.get('all')) self.setDefault(VIEWS_SQUARE.get(viewtype)) else: self.setWindows(VIEWS_POSTER.get('all')) self.setDefault(VIEWS_POSTER.get(viewtype)) def doClose(self): self.tasks.cancel() kodigui.MultiWindow.doClose(self) def onFirstInit(self): self.scrollBar = None if ITEM_TYPE in ('episode', 'album'): self.scrollBar = CustomScrollBar(self, 950, 952, 953, 951) if self.showPanelControl: self.showPanelControl.newControl(self) self.keyListControl.newControl(self) self.showPanelControl.selectItem(0) self.setFocusId(self.VIEWTYPE_BUTTON_ID) else: if self.chunkMode: self.showPanelControl = ChunkedWrapList(self, self.POSTERS_PANEL_ID, 5) else: self.showPanelControl = kodigui.ManagedControlList(self, self.POSTERS_PANEL_ID, 5) self.keyListControl = kodigui.ManagedControlList(self, self.KEY_LIST_ID, 27) self.setProperty('no.options', self.section.TYPE != 'photodirectory' and '1' or '') self.setProperty('unwatched.hascount', self.section.TYPE == 'show' and '1' or '') util.setGlobalProperty('sort', self.sort) self.setProperty('filter1.display', self.filterUnwatched and T(32368, 'UNPLAYED') or T(32345, 'All')) self.setProperty('sort.display', SORT_KEYS[self.section.TYPE].get(self.sort, SORT_KEYS['movie'].get(self.sort))['title']) self.setProperty('media.type', TYPE_PLURAL.get(ITEM_TYPE or self.section.TYPE, self.section.TYPE)) self.setProperty('media', self.section.TYPE) self.setProperty('hide.filteroptions', self.section.TYPE == 'photodirectory' and '1' or '') self.setTitle() self.fill() if self.getProperty('no.content') or self.getProperty('no.content.filtered'): self.setFocusId(self.HOME_BUTTON_ID) else: self.setFocusId(self.POSTERS_PANEL_ID) def onAction(self, action): try: if self.dragging: if not action == xbmcgui.ACTION_MOUSE_DRAG: self.dragging = False self.setBoolProperty('dragging', self.dragging) if action.getId() in MOVE_SET: controlID = self.getFocusId() if controlID == self.POSTERS_PANEL_ID or controlID == self.SCROLLBAR_ID: self.updateKey() self.checkChunkedNav(action) elif controlID == self.CUSTOM_SCOLLBAR_BUTTON_ID: if action == xbmcgui.ACTION_MOVE_UP: self.shiftSelection(-12) elif action == xbmcgui.ACTION_MOVE_DOWN: self.shiftSelection(12) # elif action == xbmcgui.KEY_MOUSE_DRAG_START: # self.onMouseDragStart(action) # elif action == xbmcgui.KEY_MOUSE_DRAG_END: # self.onMouseDragEnd(action) elif action == xbmcgui.ACTION_MOUSE_DRAG: self.onMouseDrag(action) elif action == xbmcgui.ACTION_CONTEXT_MENU: if not xbmc.getCondVisibility('ControlGroup({0}).HasFocus(0)'.format(self.OPTIONS_GROUP_ID)): self.lastNonOptionsFocusID = self.lastFocusID self.setFocusId(self.OPTIONS_GROUP_ID) return else: if self.lastNonOptionsFocusID: self.setFocusId(self.lastNonOptionsFocusID) self.lastNonOptionsFocusID = None return elif action in(xbmcgui.ACTION_NAV_BACK, xbmcgui.ACTION_CONTEXT_MENU): if not xbmc.getCondVisibility('ControlGroup({0}).HasFocus(0)'.format(self.OPTIONS_GROUP_ID)) and \ (not util.advancedSettings.fastBack or action == xbmcgui.ACTION_CONTEXT_MENU): if xbmc.getCondVisibility('Integer.IsGreater(Container(101).ListItem.Property(index),5)'): self.setFocusId(self.OPTIONS_GROUP_ID) return self.updateItem() except: util.ERROR() kodigui.MultiWindow.onAction(self, action) def onClick(self, controlID): if controlID == self.HOME_BUTTON_ID: self.goHome() elif controlID == self.POSTERS_PANEL_ID: self.showPanelClicked() elif controlID == self.KEY_LIST_ID: self.keyClicked() elif controlID == self.PLAYER_STATUS_BUTTON_ID: self.showAudioPlayer() elif controlID == self.PLAY_BUTTON_ID: self.playButtonClicked() elif controlID == self.SHUFFLE_BUTTON_ID: self.shuffleButtonClicked() elif controlID == self.OPTIONS_BUTTON_ID: self.optionsButtonClicked() elif controlID == self.VIEWTYPE_BUTTON_ID: self.viewTypeButtonClicked() elif controlID == self.SORT_BUTTON_ID: self.sortButtonClicked() elif controlID == self.FILTER1_BUTTON_ID: self.filter1ButtonClicked() elif controlID == self.ITEM_TYPE_BUTTON_ID: self.itemTypeButtonClicked() elif controlID == self.SEARCH_BUTTON_ID: self.searchButtonClicked() def onFocus(self, controlID): self.lastFocusID = controlID if controlID == self.KEY_LIST_ID: self.selectKey() def onItemChanged(self, mli): if not mli: return if not mli.dataSource or not mli.dataSource.TYPE == 'photo': return self.showPhotoItemProperties(mli.dataSource) def onMouseDragStart(self, action): if not self.scrollBar: return controlID = self.getFocusId() if controlID != self.CUSTOM_SCOLLBAR_BUTTON_ID: return self.dragging = True self.setBoolProperty('dragging', self.dragging) def onMouseDragEnd(self, action): if not self.scrollBar: return if not self.dragging: return self.dragging = False self.setBoolProperty('dragging', self.dragging) y = self.mouseYTrans(action.getAmount2()) pos = self.scrollBar.getPosFromY(y) self.shiftSelection(pos=pos) def onMouseDrag(self, action): if not self.scrollBar: return if not self.dragging: controlID = self.getFocusId() if controlID != self.CUSTOM_SCOLLBAR_BUTTON_ID: return self.onMouseDragStart(action) if not self.dragging: return # self.scrollBar.onMouseDrag(self, action) y = self.mouseYTrans(action.getAmount2()) pos = self.scrollBar.getPosFromY(y) if self.chunkMode.posIsForward(pos) or self.chunkMode.posIsBackward(pos): self.shiftSelection(pos=pos) else: self.showPanelControl.selectItem(pos) self.checkChunkedNav() def shiftSelection(self, offset=0, pos=None): if pos is not None: self.scrollBar.setPosition(pos) return self.delayedChunkedPosJump(pos) else: mli = self.showPanelControl.getSelectedItem() try: idx = int(mli.getProperty('index')) except ValueError: return target = idx + offset if target >= self.chunkMode.itemCount: pos = self.chunkMode.itemCount - 1 elif target < 0: pos = 0 else: pos = self.showPanelControl.getSelectedPosition() pos += offset if pos < 0 or pos >= self.showPanelControl.size(): pos = pos % self.showPanelControl.size() self.showPanelControl.selectItem(pos) self.checkChunkedNav(idx=pos) def updateKey(self, mli=None): mli = mli or self.showPanelControl.getSelectedItem() if not mli: return if self.lastItem != mli: self.lastItem = mli self.onItemChanged(mli) util.setGlobalProperty('key', mli.getProperty('key')) self.selectKey(mli) def checkChunkedNav(self, action=None, idx=None): if not self.chunkMode: return # if action == xbmcgui.ACTION_PAGE_DOWN: # idx = self.showPanelControl.getSelectedPosition() - 5 # if idx < 0: # idx += self.showPanelControl.size() # mli = self.showPanelControl.getListItem(idx) # self.showPanelControl.selectItem(idx) # elif action == xbmcgui.ACTION_PAGE_UP: # idx = self.showPanelControl.getSelectedPosition() + 5 # if idx >= self.showPanelControl.size(): # idx %= self.showPanelControl.size() # mli = self.showPanelControl.getListItem(idx) # self.showPanelControl.selectItem(idx) # else: mli = self.showPanelControl.getSelectedItem() try: if idx is not None: pos = int(self.showPanelControl[idx].getProperty('index')) else: pos = int(mli.getProperty('index')) if pos >= self.chunkMode.itemCount: raise ValueError except ValueError: if self.chunkMode.isAtBeginning() and action not in (xbmcgui.ACTION_MOVE_DOWN, xbmcgui.ACTION_PAGE_DOWN): idx = 0 else: idx = ((self.chunkMode.itemCount - 1) % self.showPanelControl.LIST_MAX) self.showPanelControl.selectItem(idx) mli = self.showPanelControl[idx] self.updateKey(mli) if self.scrollBar: try: pos = int(mli.getProperty('index')) self.scrollBar.setPosition(pos) except ValueError: pass if idx == 0 and action == xbmcgui.ACTION_MOVE_UP: self.setFocusId(600) return if self.scrollBar: self.scrollBar.setPosition(pos) if self.chunkMode.posIsForward(pos): self.shiftChunks() elif self.chunkMode.posIsBackward(pos): self.shiftChunks(-1) def shiftChunks(self, mod=1): start = self.chunkMode.shift(mod) if start is None: return if start < 0: self.chunkCallback([None] * CHUNK_SIZE, -CHUNK_SIZE) else: self.chunkCallback([False] * CHUNK_SIZE, start) task = ChunkRequestTask().setup( self.section, start, CHUNK_SIZE, self.chunkCallback, filter_=self.getFilterOpts(), sort=self.getSortOpts(), unwatched=self.filterUnwatched ) self.tasks.add(task) backgroundthread.BGThreader.addTasksToFront([task]) def selectKey(self, mli=None): if not mli: mli = self.showPanelControl.getSelectedItem() if not mli: return li = self.keyItems.get(mli.getProperty('key')) if not li: return self.keyListControl.selectItem(li.pos()) def searchButtonClicked(self): self.processCommand(search.dialog(self, section_id=self.section.key)) def delayedChunkedPosJump(self, pos): if not self.cleared: self.chunkCallback(None, None, clear=True) self.dcpjTimeout = time.time() + 0.5 self.dcpjPos = pos if not self.dcpjThread or not self.dcpjThread.isAlive(): self.dcpjThread = threading.Thread(target=self._chunkedPosJump) self.dcpjThread.start() def _chunkedPosJump(self): while not util.MONITOR.waitForAbort(0.1): if time.time() >= self.dcpjTimeout: break else: return keyStart_start = self.chunkMode.shiftToKey(None, keyStart=self.dcpjPos) if not keyStart_start: return keyStart, start = keyStart_start pos = keyStart % self.showPanelControl.LIST_MAX self.chunkedPosJump(pos, start) self.showPanelControl.selectItem(pos) def chunkedPosJump(self, pos, start=None): if start is None: start = max(pos - CHUNK_SIZE, 0) mul = 3 if not start: mul = 2 tasks = [] for x in range(mul): task = ChunkRequestTask().setup( self.section, start + (CHUNK_SIZE * x), CHUNK_SIZE, self.chunkCallback, filter_=self.getFilterOpts(), sort=self.getSortOpts(), unwatched=self.filterUnwatched ) self.tasks.add(task) tasks.append(task) mid = tasks.pop(1) backgroundthread.BGThreader.addTasksToFront([mid] + tasks) def keyClicked(self): li = self.keyListControl.getSelectedItem() if not li: return if self.chunkMode: keyStart_start = self.chunkMode.shiftToKey(li.dataSource) if not keyStart_start: return keyStart, start = keyStart_start pos = keyStart % self.showPanelControl.LIST_MAX self.chunkedPosJump(pos, start) else: mli = self.firstOfKeyItems.get(li.dataSource) if not mli: return pos = mli.pos() self.showPanelControl.selectItem(pos) self.setFocusId(self.POSTERS_PANEL_ID) util.setGlobalProperty('key', li.dataSource) def playButtonClicked(self, shuffle=False): filter_ = self.getFilterOpts() sort = self.getSortOpts() args = {} if filter_: args[filter_[0]] = filter_[1] if sort: args['sort'] = '{0}:{1}'.format(*sort) if self.section.TYPE == 'movie': args['sourceType'] = '1' elif self.section.TYPE == 'show': args['sourceType'] = '2' else: args['sourceType'] = '8' # When the list is filtered by unwatched, play and shuffle button should only play unwatched videos if self.filterUnwatched: args['unwatched'] = '1' pq = playqueue.createPlayQueueForItem(self.section, options={'shuffle': shuffle}, args=args) opener.open(pq) def shuffleButtonClicked(self): self.playButtonClicked(shuffle=True) def optionsButtonClicked(self): options = [] if xbmc.getCondVisibility('Player.HasAudio + MusicPlayer.HasNext'): options.append({'key': 'play_next', 'display': T(32325, 'Play Next')}) # if self.section.TYPE not in ('artist', 'photo', 'photodirectory'): # options.append({'key': 'mark_watched', 'display': 'Mark All Watched'}) # options.append({'key': 'mark_unwatched', 'display': 'Mark All Unwatched'}) # if xbmc.getCondVisibility('Player.HasAudio') and self.section.TYPE == 'artist': # options.append({'key': 'add_to_queue', 'display': 'Add To Queue'}) # if False: # options.append({'key': 'add_to_playlist', 'display': 'Add To Playlist'}) if self.section.TYPE == 'photodirectory': if options: options.append(dropdown.SEPARATOR) options.append({'key': 'to_section', 'display': T(32324, u'Go to {0}').format(self.section.getLibrarySectionTitle())}) choice = dropdown.showDropdown(options, (255, 205)) if not choice: return if choice['key'] == 'play_next': xbmc.executebuiltin('PlayerControl(Next)') elif choice['key'] == 'to_section': self.goHome(self.section.getLibrarySectionId()) def itemTypeButtonClicked(self): options = [] if self.section.TYPE == 'show': for t in ('show', 'episode'): options.append({'type': t, 'display': TYPE_PLURAL.get(t, t)}) elif self.section.TYPE == 'artist': for t in ('artist', 'album'): options.append({'type': t, 'display': TYPE_PLURAL.get(t, t)}) else: return result = dropdown.showDropdown(options, (1280, 106), with_indicator=True) if not result: return choice = result['type'] if choice == ITEM_TYPE: return self.tasks.cancel() self.showPanelControl = None # TODO: Need to do some check here I think self.librarySettings.setItemType(choice) self.reset() self.clearFilters() self.resetSort() if not self.nextWindow(False): self.setProperty('media.type', TYPE_PLURAL.get(ITEM_TYPE or self.section.TYPE, self.section.TYPE)) self.setProperty('sort.display', SORT_KEYS[self.section.TYPE].get(self.sort, SORT_KEYS['movie'].get(self.sort))['title']) self.fill() def sortButtonClicked(self): desc = 'script.plex/indicators/arrow-down.png' asc = 'script.plex/indicators/arrow-up.png' ind = self.sortDesc and desc or asc options = [] if self.section.TYPE == 'movie': for stype in ('addedAt', 'originallyAvailableAt', 'lastViewedAt', 'titleSort', 'rating', 'resolution', 'duration'): option = SORT_KEYS['movie'].get(stype).copy() option['type'] = stype option['indicator'] = self.sort == stype and ind or '' options.append(option) elif self.section.TYPE == 'show': if ITEM_TYPE == 'episode': for stype in ('addedAt', 'originallyAvailableAt', 'lastViewedAt', 'show.titleSort', 'rating'): option = SORT_KEYS['show'].get(stype, SORT_KEYS['movie'].get(stype)).copy() option['type'] = stype option['indicator'] = self.sort == stype and ind or '' options.append(option) else: for stype in ('addedAt', 'lastViewedAt', 'originallyAvailableAt', 'titleSort', 'rating', 'unviewedLeafCount'): option = SORT_KEYS['show'].get(stype, SORT_KEYS['movie'].get(stype)).copy() option['type'] = stype option['indicator'] = self.sort == stype and ind or '' options.append(option) elif self.section.TYPE == 'artist': if ITEM_TYPE == 'album': for stype in ('addedAt', 'lastViewedAt', 'viewCount', 'originallyAvailableAt', 'artist.titleSort', 'titleSort', 'rating'): option = SORT_KEYS['artist'].get(stype, SORT_KEYS['movie'].get(stype)).copy() option['type'] = stype option['indicator'] = self.sort == stype and ind or '' options.append(option) else: for stype in ('addedAt', 'lastViewedAt', 'viewCount', 'titleSort'): option = SORT_KEYS['artist'].get(stype, SORT_KEYS['movie'].get(stype)).copy() option['type'] = stype option['indicator'] = self.sort == stype and ind or '' options.append(option) elif self.section.TYPE == 'photo': for stype in ('addedAt', 'originallyAvailableAt', 'titleSort', 'rating'): option = SORT_KEYS['photo'].get(stype, SORT_KEYS['movie'].get(stype)).copy() option['type'] = stype option['indicator'] = self.sort == stype and ind or '' options.append(option) else: return result = dropdown.showDropdown(options, (1280, 106), with_indicator=True) if not result: return choice = result['type'] forceRefresh = False if choice == self.sort: self.sortDesc = not self.sortDesc else: self.sortDesc = False if choice == 'titleSort': forceRefresh = True self.sort = choice self.librarySettings.setSetting('sort', self.sort) self.librarySettings.setSetting('sort.desc', self.sortDesc) util.setGlobalProperty('sort', choice) self.setProperty('sort.display', result['title']) self.sortShowPanel(choice, forceRefresh) def viewTypeButtonClicked(self): with self.lock: self.showPanelControl.invalidate() win = self.nextWindow() key = self.section.key if not key.isdigit(): key = self.section.getLibrarySectionId() util.setSetting('viewtype.{0}.{1}'.format(self.section.server.uuid, key), win.VIEWTYPE) def sortShowPanel(self, choice, force_refresh=False): if force_refresh or self.chunkMode or self.showPanelControl.size() == 0: self.fillShows() return if choice == 'addedAt': self.showPanelControl.sort(lambda i: i.dataSource.addedAt, reverse=self.sortDesc) elif choice == 'originallyAvailableAt': self.showPanelControl.sort(lambda i: i.dataSource.get('originallyAvailableAt'), reverse=self.sortDesc) elif choice == 'lastViewedAt': self.showPanelControl.sort(lambda i: i.dataSource.get('lastViewedAt'), reverse=self.sortDesc) elif choice == 'viewCount': self.showPanelControl.sort(lambda i: i.dataSource.get('titleSort') or i.dataSource.title) self.showPanelControl.sort(lambda i: i.dataSource.get('viewCount').asInt(), reverse=self.sortDesc) elif choice == 'titleSort': self.showPanelControl.sort(lambda i: i.dataSource.get('titleSort') or i.dataSource.title, reverse=self.sortDesc) self.keyListControl.sort(lambda i: i.getProperty('original'), reverse=self.sortDesc) elif choice == 'rating': self.showPanelControl.sort(lambda i: i.dataSource.get('titleSort') or i.dataSource.title) self.showPanelControl.sort(lambda i: i.dataSource.get('rating').asFloat(), reverse=self.sortDesc) elif choice == 'resolution': self.showPanelControl.sort(lambda i: i.dataSource.maxHeight, reverse=self.sortDesc) elif choice == 'duration': self.showPanelControl.sort(lambda i: i.dataSource.duration.asInt(), reverse=self.sortDesc) elif choice == 'unviewedLeafCount': self.showPanelControl.sort(lambda i: i.dataSource.unViewedLeafCount, reverse=self.sortDesc) for i, mli in enumerate(self.showPanelControl): mli.setProperty('index', str(i)) def subOptionCallback(self, option): check = 'script.plex/home/device/check.png' options = None subKey = None if self.filter: if self.filter.get('sub'): subKey = self.filter['sub']['val'] if option['type'] in ( 'year', 'decade', 'genre', 'contentRating', 'collection', 'director', 'actor', 'country', 'studio', 'resolution', 'labels', 'make', 'model', 'aperture', 'exposure', 'iso', 'lens' ): options = [{'val': o.key, 'display': o.title, 'indicator': o.key == subKey and check or ''} for o in self.section.listChoices(option['type'])] if not options: options = [{'val': None, 'display': T(32375, 'No filters available'), 'ignore': True}] return options def hasFilter(self, ftype): if not self.filter: return False return self.filter['type'] == ftype def filter1ButtonClicked(self): check = 'script.plex/home/device/check.png' options = [] if self.section.TYPE in ('movie', 'show'): options.append({'type': 'unwatched', 'display': T(32368, 'UNPLAYED').upper(), 'indicator': self.filterUnwatched and check or ''}) if self.filter: options.append({'type': 'clear_filter', 'display': T(32376, 'CLEAR FILTER').upper(), 'indicator': 'script.plex/indicators/remove.png'}) if options: options.append(None) # Separator optionsMap = { 'year': {'type': 'year', 'display': T(32377, 'Year'), 'indicator': self.hasFilter('year') and check or ''}, 'decade': {'type': 'decade', 'display': T(32378, 'Decade'), 'indicator': self.hasFilter('decade') and check or ''}, 'genre': {'type': 'genre', 'display': T(32379, 'Genre'), 'indicator': self.hasFilter('genre') and check or ''}, 'contentRating': {'type': 'contentRating', 'display': T(32380, 'Content Rating'), 'indicator': self.hasFilter('contentRating') and check or ''}, 'network': {'type': 'studio', 'display': T(32381, 'Network'), 'indicator': self.hasFilter('studio') and check or ''}, 'collection': {'type': 'collection', 'display': T(32382, 'Collection'), 'indicator': self.hasFilter('collection') and check or ''}, 'director': {'type': 'director', 'display': T(32383, 'Director'), 'indicator': self.hasFilter('director') and check or ''}, 'actor': {'type': 'actor', 'display': T(32384, 'Actor'), 'indicator': self.hasFilter('actor') and check or ''}, 'country': {'type': 'country', 'display': T(32385, 'Country'), 'indicator': self.hasFilter('country') and check or ''}, 'studio': {'type': 'studio', 'display': T(32386, 'Studio'), 'indicator': self.hasFilter('studio') and check or ''}, 'resolution': {'type': 'resolution', 'display': T(32362, 'Resolution'), 'indicator': self.hasFilter('resolution') and check or ''}, 'labels': {'type': 'labels', 'display': T(32387, 'Labels'), 'indicator': self.hasFilter('labels') and check or ''}, 'make': {'type': 'make', 'display': T(32388, 'Camera Make'), 'indicator': self.hasFilter('make') and check or ''}, 'model': {'type': 'model', 'display': T(32389, 'Camera Model'), 'indicator': self.hasFilter('model') and check or ''}, 'aperture': {'type': 'aperture', 'display': T(32390, 'Aperture'), 'indicator': self.hasFilter('aperture') and check or ''}, 'exposure': {'type': 'exposure', 'display': T(32391, 'Shutter Speed'), 'indicator': self.hasFilter('exposure') and check or ''}, 'iso': {'type': 'iso', 'display': 'ISO', 'indicator': self.hasFilter('iso') and check or ''}, 'lens': {'type': 'lens', 'display': T(32392, 'Lens'), 'indicator': self.hasFilter('lens') and check or ''} } if self.section.TYPE == 'movie': for k in ('year', 'decade', 'genre', 'contentRating', 'collection', 'director', 'actor', 'country', 'studio', 'resolution', 'labels'): options.append(optionsMap[k]) elif self.section.TYPE == 'show': if ITEM_TYPE == 'episode': for k in ('year', 'collection', 'resolution'): options.append(optionsMap[k]) elif ITEM_TYPE == 'album': for k in ('genre', 'year', 'decade', 'collection', 'labels'): options.append(optionsMap[k]) else: for k in ('year', 'genre', 'contentRating', 'network', 'collection', 'actor', 'labels'): options.append(optionsMap[k]) elif self.section.TYPE == 'artist': for k in ('genre', 'country', 'collection'): options.append(optionsMap[k]) elif self.section.TYPE == 'photo': for k in ('year', 'make', 'model', 'aperture', 'exposure', 'iso', 'lens', 'labels'): options.append(optionsMap[k]) result = dropdown.showDropdown(options, (980, 106), with_indicator=True, suboption_callback=self.subOptionCallback) if not result: return choice = result['type'] if choice == 'clear_filter': self.filter = None elif choice == 'unwatched': self.filterUnwatched = not self.filterUnwatched self.librarySettings.setSetting('filter.unwatched', self.filterUnwatched) else: self.filter = result self.updateFilterDisplay() if self.filter or choice in ('clear_filter', 'unwatched'): self.fill() def clearFilters(self): self.filter = None self.filterUnwatched = False self.librarySettings.setSetting('filter.unwatched', self.filterUnwatched) self.updateFilterDisplay() def resetSort(self): if ITEM_TYPE == 'episode': self.sort = 'show.titleSort' elif ITEM_TYPE == 'album': self.sort = 'artist.titleSort' else: self.sort = 'titleSort' self.sortDesc = False self.librarySettings.setSetting('sort', self.sort) self.librarySettings.setSetting('sort.desc', self.sortDesc) util.setGlobalProperty('sort', self.sort) self.setProperty('sort.display', SORT_KEYS[self.section.TYPE].get(self.sort, SORT_KEYS['movie'].get(self.sort))['title']) def updateFilterDisplay(self): if self.filter: disp = self.filter['display'] if self.filter.get('sub'): disp = u'{0}: {1}'.format(disp, self.filter['sub']['display']) self.setProperty('filter1.display', disp) self.setProperty('filter2.display', self.filterUnwatched and T(32368, 'Unplayed') or '') else: self.setProperty('filter2.display', '') self.setProperty('filter1.display', self.filterUnwatched and T(32368, 'Unplayed') or T(32345, 'All')) def showPanelClicked(self): mli = self.showPanelControl.getSelectedItem() if not mli or not mli.dataSource: return updateWatched = False if self.section.TYPE == 'show': if ITEM_TYPE == 'episode': self.openItem(mli.dataSource) else: self.processCommand(opener.handleOpen(subitems.ShowWindow, media_item=mli.dataSource, parent_list=self.showPanelControl)) updateWatched = True elif self.section.TYPE == 'movie': self.processCommand(opener.handleOpen(preplay.PrePlayWindow, video=mli.dataSource, parent_list=self.showPanelControl)) updateWatched = True elif self.section.TYPE == 'artist': if ITEM_TYPE == 'album': self.openItem(mli.dataSource) else: self.processCommand(opener.handleOpen(subitems.ArtistWindow, media_item=mli.dataSource, parent_list=self.showPanelControl)) elif self.section.TYPE in ('photo', 'photodirectory'): self.showPhoto(mli.dataSource) if not mli: return if not mli.dataSource.exists(): self.showPanelControl.removeItem(mli.pos()) return if updateWatched: self.updateUnwatched(mli) def showPhoto(self, photo): if isinstance(photo, plexnet.photo.Photo) or photo.TYPE == 'clip': self.processCommand(opener.open(photo)) else: self.processCommand(opener.sectionClicked(photo)) def updateUnwatched(self, mli): mli.dataSource.reload() if mli.dataSource.isWatched: mli.setProperty('unwatched', '') mli.setProperty('unwatched.count', '') else: if self.section.TYPE == 'show': mli.setProperty('unwatched.count', str(mli.dataSource.unViewedLeafCount)) else: mli.setProperty('unwatched', '1') def setTitle(self): if self.section.TYPE == 'artist': self.setProperty('screen.title', T(32394, 'MUSIC').upper()) elif self.section.TYPE in ('photo', 'photodirectory'): self.setProperty('screen.title', T(32349, 'photos').upper()) else: self.setProperty('screen.title', self.section.TYPE == 'show' and T(32393, 'TV SHOWS').upper() or T(32348, 'movies').upper()) self.updateFilterDisplay() def updateItem(self, mli=None): mli = mli or self.showPanelControl.getSelectedItem() if not mli or mli.dataSource: return for task in self.tasks: if task.contains(mli.pos()): util.DEBUG_LOG('Moving task to front: {0}'.format(task)) backgroundthread.BGThreader.moveToFront(task) break def setBackground(self, items): if self.backgroundSet: return self.backgroundSet = True item = random.choice(items) self.setProperty('background', item.art.asTranscodedImageURL(self.width, self.height, blur=128, opacity=60, background=colors.noAlpha.Background)) def fill(self): if self.chunkMode: self.chunkMode.reset() if self.section.TYPE in ('photo', 'photodirectory'): self.fillPhotos() else: self.fillShows() def getFilterOpts(self): if not self.filter: return None if not self.filter.get('sub'): util.DEBUG_LOG('Filter missing sub-filter data') return None return (self.filter['type'], six.moves.urllib.parse.unquote_plus(self.filter['sub']['val'])) def getSortOpts(self): if not self.sort: return None return (self.sort, self.sortDesc and 'desc' or 'asc') @busy.dialog() def fillShows(self): self.setBoolProperty('no.content', False) self.setBoolProperty('no.content.filtered', False) items = [] jitems = [] self.keyItems = {} self.firstOfKeyItems = {} totalSize = 0 type_ = None if ITEM_TYPE == 'episode': type_ = 4 elif ITEM_TYPE == 'album': type_ = 9 idx = 0 fallback = 'script.plex/thumb_fallbacks/{0}.png'.format(TYPE_KEYS.get(self.section.type, TYPE_KEYS['movie'])['fallback']) if self.sort != 'titleSort': sectionAll = self.section.all(0, 0, filter_=self.getFilterOpts(), sort=self.getSortOpts(), unwatched=self.filterUnwatched, type_=type_) totalSize = sectionAll.totalSize.asInt() if not self.chunkMode: for x in range(totalSize): mli = kodigui.ManagedListItem('') mli.setProperty('thumb.fallback', fallback) mli.setProperty('index', str(x)) items.append(mli) else: jumpList = self.section.jumpList(filter_=self.getFilterOpts(), sort=self.getSortOpts(), unwatched=self.filterUnwatched, type_=type_) if not jumpList: self.showPanelControl.reset() self.keyListControl.reset() if self.filter or self.filterUnwatched: self.setBoolProperty('no.content.filtered', True) else: self.setBoolProperty('no.content', True) if jumpList is None: util.messageDialog("Error", "There was an error.") return for kidx, ji in enumerate(jumpList): mli = kodigui.ManagedListItem(ji.title, data_source=ji.key) mli.setProperty('key', ji.key) mli.setProperty('original', '{0:02d}'.format(kidx)) self.keyItems[ji.key] = mli jitems.append(mli) totalSize += ji.size.asInt() if self.chunkMode: self.chunkMode.addKeyRange(ji.key, (idx, (idx + ji.size.asInt()) - 1)) idx += ji.size.asInt() else: for x in range(ji.size.asInt()): mli = kodigui.ManagedListItem('') mli.setProperty('key', ji.key) mli.setProperty('thumb.fallback', fallback) mli.setProperty('index', str(idx)) items.append(mli) if not x: # i.e. first item self.firstOfKeyItems[ji.key] = mli idx += 1 util.setGlobalProperty('key', jumpList[0].key) if self.scrollBar: self.scrollBar.setSizeAndCount(totalSize, 12) if self.chunkMode: self.chunkMode.itemCount = totalSize items = [ kodigui.ManagedListItem('', properties={'index': str(i)}) for i in range(CHUNK_SIZE * 2) ] + [ kodigui.ManagedListItem('') for i in range(CHUNK_SIZE) ] self.showPanelControl.reset() self.keyListControl.reset() self.showPanelControl.addItems(items) self.keyListControl.addItems(jitems) self.showPanelControl.selectItem(0) tasks = [] ct = 0 for start in range(0, totalSize, CHUNK_SIZE): tasks.append( ChunkRequestTask().setup( self.section, start, CHUNK_SIZE, self.chunkCallback, filter_=self.getFilterOpts(), sort=self.getSortOpts(), unwatched=self.filterUnwatched ) ) ct += 1 if self.chunkMode and ct > 1: break self.tasks.add(tasks) backgroundthread.BGThreader.addTasksToFront(tasks) def showPhotoItemProperties(self, photo): if photo.isFullObject(): return task = PhotoPropertiesTask().setup(photo, self._showPhotoItemProperties) self.tasks.add(task) backgroundthread.BGThreader.addTasksToFront([task]) def _showPhotoItemProperties(self, photo): mli = self.showPanelControl.getSelectedItem() if not mli or not mli.dataSource.TYPE == 'photo': for mli in self.showPanelControl: if mli.dataSource == photo: break else: return mli.setProperty('camera.model', photo.media[0].model) mli.setProperty('camera.lens', photo.media[0].lens) attrib = [] if photo.media[0].height: attrib.append(u'{0} x {1}'.format(photo.media[0].width, photo.media[0].height)) orientation = photo.media[0].parts[0].orientation if orientation: attrib.append(u'{0} Mo'.format(orientation)) container = photo.media[0].container_ or os.path.splitext(photo.media[0].parts[0].file)[-1][1:].lower() if container == 'jpg': container = 'jpeg' attrib.append(container.upper()) if attrib: mli.setProperty('photo.dims', u' \u2022 '.join(attrib)) settings = [] if photo.media[0].iso: settings.append('ISO {0}'.format(photo.media[0].iso)) if photo.media[0].aperture: settings.append('{0}'.format(photo.media[0].aperture)) if photo.media[0].exposure: settings.append('{0}'.format(photo.media[0].exposure)) mli.setProperty('camera.settings', u' \u2022 '.join(settings)) mli.setProperty('photo.summary', photo.get('summary')) @busy.dialog() def fillPhotos(self): self.setBoolProperty('no.content', False) self.setBoolProperty('no.content.filtered', False) items = [] keys = [] self.firstOfKeyItems = {} idx = 0 if self.section.TYPE == 'photodirectory': photos = self.section.all() else: photos = self.section.all(filter_=self.getFilterOpts(), sort=self.getSortOpts(), unwatched=self.filterUnwatched) if not photos: return photo = random.choice(photos) self.setProperty('background', photo.art.asTranscodedImageURL(self.width, self.height, blur=128, opacity=60, background=colors.noAlpha.Background)) thumbDim = TYPE_KEYS.get(self.section.type, TYPE_KEYS['movie'])['thumb_dim'] fallback = 'script.plex/thumb_fallbacks/{0}.png'.format(TYPE_KEYS.get(self.section.type, TYPE_KEYS['movie'])['fallback']) if not photos: if self.filter or self.filterUnwatched: self.setBoolProperty('no.content.filtered', True) else: self.setBoolProperty('no.content', True) return for photo in photos: title = photo.title if photo.TYPE == 'photodirectory': thumb = photo.composite.asTranscodedImageURL(*thumbDim) mli = kodigui.ManagedListItem(title, thumbnailImage=thumb, data_source=photo) mli.setProperty('is.folder', '1') else: thumb = photo.defaultThumb.asTranscodedImageURL(*thumbDim) label2 = util.cleanLeadingZeros(photo.originallyAvailableAt.asDatetime('%d %B %Y')) mli = kodigui.ManagedListItem(title, label2, thumbnailImage=thumb, data_source=photo) mli.setProperty('thumb.fallback', fallback) mli.setProperty('index', str(idx)) key = title[0].upper() if key not in KEYS: key = '#' if key not in keys: self.firstOfKeyItems[key] = mli keys.append(key) mli.setProperty('key', str(key)) items.append(mli) idx += 1 litems = [] self.keyItems = {} for i, key in enumerate(keys): mli = kodigui.ManagedListItem(key, data_source=key) mli.setProperty('key', key) mli.setProperty('original', '{0:02d}'.format(i)) self.keyItems[key] = mli litems.append(mli) self.showPanelControl.reset() self.keyListControl.reset() self.showPanelControl.addItems(items) self.keyListControl.addItems(litems) if keys: util.setGlobalProperty('key', keys[0]) def chunkCallback(self, items, start, clear=False): if clear: with self.lock: items = [kodigui.ManagedListItem('') for i in range(CHUNK_SIZE * 3)] self.showPanelControl.reset() self.showPanelControl.addItems(items) self.cleared = True return if self.cleared: self.cleared = False busy.widthDialog(self._chunkCallback, self, items, start) else: self._chunkCallback(items, start) def _chunkCallback(self, items, start): if self.chunkMode and not self.chunkMode.posIsValid(start): return with self.lock: if self.chunkMode and not self.chunkMode.posIsValid(start): return pos = start self.setBackground(items) thumbDim = TYPE_KEYS.get(self.section.type, TYPE_KEYS['movie'])['thumb_dim'] artDim = TYPE_KEYS.get(self.section.type, TYPE_KEYS['movie']).get('art_dim', (256, 256)) showUnwatched = True if self.section.TYPE in ('movie', 'show') else False if self.chunkMode and len(items) < CHUNK_SIZE: items += [None] * (CHUNK_SIZE - len(items)) if ITEM_TYPE == 'episode': for offset, obj in enumerate(items): mli = self.showPanelControl[pos] if obj: mli.dataSource = obj mli.setProperty('index', str(pos)) if obj.index: subtitle = u' - {0}{1} \u2022 {2}{3}'.format(T(32310, 'S'), obj.parentIndex, T(32311, 'E'), obj.index) else: subtitle = ' - ' + obj.originallyAvailableAt.asDatetime('%m/%d/%y') mli.setLabel((obj.defaultTitle or '') + subtitle) # mli.setThumbnailImage(obj.defaultThumb.asTranscodedImageURL(*thumbDim)) mli.setProperty('summary', obj.summary) # # mli.setProperty('key', self.chunkMode.getKey(pos)) mli.setLabel2(util.durationToText(obj.fixedDuration())) mli.setProperty('art', obj.defaultArt.asTranscodedImageURL(*artDim)) if not obj.isWatched: mli.setProperty('unwatched', '1') else: mli.clear() if obj is False: mli.setProperty('index', str(pos)) else: mli.setProperty('index', '') pos += 1 elif ITEM_TYPE == 'album': for offset, obj in enumerate(items): mli = self.showPanelControl[pos] if obj: mli.dataSource = obj mli.setProperty('index', str(pos)) mli.setLabel(u'{0} \u2022 {1}'.format(obj.parentTitle, obj.title)) mli.setThumbnailImage(obj.defaultThumb.asTranscodedImageURL(*thumbDim)) mli.setProperty('summary', obj.summary) # # mli.setProperty('key', self.chunkMode.getKey(pos)) mli.setLabel2(obj.year) if self.chunkMode: mli.setProperty('key', self.chunkMode.getKey(pos)) else: mli.clear() if obj is False: mli.setProperty('index', str(pos)) else: mli.setProperty('index', '') pos += 1 else: for offset, obj in enumerate(items): mli = self.showPanelControl[pos] if obj: mli.setProperty('index', str(pos)) mli.setLabel(obj.defaultTitle or '') mli.setThumbnailImage(obj.defaultThumb.asTranscodedImageURL(*thumbDim)) mli.dataSource = obj mli.setProperty('summary', obj.get('summary')) if self.chunkMode: mli.setProperty('key', self.chunkMode.getKey(pos)) if showUnwatched: mli.setLabel2(util.durationToText(obj.fixedDuration())) mli.setProperty('art', obj.defaultArt.asTranscodedImageURL(*artDim)) if not obj.isWatched: if self.section.TYPE == 'show': mli.setProperty('unwatched.count', str(obj.unViewedLeafCount)) else: mli.setProperty('unwatched', '1') else: mli.clear() if obj is False: mli.setProperty('index', str(pos)) else: mli.setProperty('index', '') pos += 1 class PostersWindow(kodigui.ControlledWindow): xmlFile = 'script-plex-posters.xml' path = util.ADDON.getAddonInfo('path') theme = 'Main' res = '1080i' width = 1920 height = 1080 POSTERS_PANEL_ID = 101 KEY_LIST_ID = 151 SCROLLBAR_ID = 152 OPTIONS_GROUP_ID = 200 HOME_BUTTON_ID = 201 SEARCH_BUTTON_ID = 202 PLAYER_STATUS_BUTTON_ID = 204 SORT_BUTTON_ID = 210 FILTER1_BUTTON_ID = 211 FILTER2_BUTTON_ID = 212 ITEM_TYPE_BUTTON_ID = 312 PLAY_BUTTON_ID = 301 SHUFFLE_BUTTON_ID = 302 OPTIONS_BUTTON_ID = 303 VIEWTYPE_BUTTON_ID = 304 VIEWTYPE = 'panel' MULTI_WINDOW_ID = 0 CUSTOM_SCOLLBAR_BUTTON_ID = 951 class PostersChunkedWindow(PostersWindow): xmlFile = 'script-plex-listview-16x9-chunked.xml' VIEWTYPE = 'list' MULTI_WINDOW_ID = 0 class ListView16x9Window(PostersWindow): xmlFile = 'script-plex-listview-16x9.xml' VIEWTYPE = 'list' MULTI_WINDOW_ID = 1 class ListView16x9ChunkedWindow(PostersWindow): xmlFile = 'script-plex-listview-16x9-chunked.xml' VIEWTYPE = 'list' MULTI_WINDOW_ID = 1 class SquaresWindow(PostersWindow): xmlFile = 'script-plex-squares.xml' VIEWTYPE = 'panel' MULTI_WINDOW_ID = 0 class SquaresChunkedWindow(PostersWindow): xmlFile = 'script-plex-listview-square-chunked.xml' VIEWTYPE = 'list' MULTI_WINDOW_ID = 0 class ListViewSquareWindow(PostersWindow): xmlFile = 'script-plex-listview-square.xml' VIEWTYPE = 'list' MULTI_WINDOW_ID = 1 class ListViewSquareChunkedWindow(PostersWindow): xmlFile = 'script-plex-listview-square-chunked.xml' VIEWTYPE = 'list' MULTI_WINDOW_ID = 1 VIEWS_POSTER = { 'panel': PostersWindow, 'list': ListView16x9Window, 'all': (PostersWindow, ListView16x9Window) } VIEWS_POSTER_CHUNKED = { 'panel': PostersChunkedWindow, 'list': ListView16x9ChunkedWindow, 'all': (PostersChunkedWindow, ListView16x9ChunkedWindow) } VIEWS_SQUARE = { 'panel': SquaresWindow, 'list': ListViewSquareWindow, 'all': (SquaresWindow, ListViewSquareWindow) } VIEWS_SQUARE_CHUNKED = { 'panel': SquaresChunkedWindow, 'list': ListViewSquareChunkedWindow, 'all': (SquaresChunkedWindow, ListViewSquareChunkedWindow) }