def getDirContents(self, directory): """ Return a tuple of sorted rows (directories, playlists, mediaFiles) for the given directory """ playlists = [] mediaFiles = [] directories = [] for (file, path) in tools.listDir(directory, self.showHiddenFiles): if isdir(path): directories.append( (icons.dirMenuIcon(), tools.htmlEscape(unicode(file, errors='replace')), TYPE_DIR, path)) elif isfile(path): if media.isSupported(file): mediaFiles.append( (icons.mediaFileMenuIcon(), tools.htmlEscape(unicode(file, errors='replace')), TYPE_FILE, path)) elif playlist.isSupported(file): playlists.append( (icons.mediaFileMenuIcon(), tools.htmlEscape(unicode(file, errors='replace')), TYPE_FILE, path)) playlists.sort(key=self.sortKey) mediaFiles.sort(key=self.sortKey) directories.sort(key=self.sortKey) return (directories, playlists, mediaFiles)
def updateTree(self, discInfo): """ Update the tree using disc information from the cache, if any """ cddb = self.getDiscFromCache(discInfo) # Create fake CDDB information if needed if cddb is None: cddb = {"DTITLE": "%s / %s" % (consts.UNKNOWN_ARTIST, consts.UNKNOWN_ALBUM)} for i in xrange(discInfo[DISC_NB_TRACKS]): cddb["TTITLE%u" % i] = consts.UNKNOWN_TITLE # Compute the length of each track trackLen = [ int(round((discInfo[DISC_FRAME1 + i + 1] - discInfo[DISC_FRAME1 + i]) / 75.0)) for i in xrange(discInfo[DISC_NB_TRACKS] - 1) ] trackLen.append(discInfo[DISC_LENGTH] - int(round(discInfo[DISC_FRAMEn] / 75.0))) # Update the root of the tree disc = cddb["DTITLE"].strip().decode("iso-8859-15", "replace") artist, album = disc.split(" / ") self.tree.setItem((0,), ROW_NAME, "%s" % tools.htmlEscape(disc)) self.tree.setItem((0,), ROW_LENGTH, "[%s]" % sec2str(sum(trackLen))) # Update the explorer name modules.postMsg( consts.MSG_CMD_EXPLORER_RENAME, {"modName": MOD_L10N, "expName": self.expName, "newExpName": disc} ) self.expName = disc # Optional information try: date = int(cddb["DYEAR"].strip().decode("iso-8859-15", "replace")) except: date = None try: genre = cddb["DGENRE"].strip().decode("iso-8859-15", "replace") except: genre = None # Update each track for i, child in enumerate(self.tree.iterChildren((0,))): title = cddb["TTITLE%u" % i].strip().decode("iso-8859-15", "replace") # Create the corresponding Track object track = CDTrack(str(i + 1)) track.setTitle(title) track.setAlbum(album) track.setArtist(artist) track.setLength(trackLen[i]) track.setNumber(i + 1) # Optional information if date is not None: track.setDate(date) if genre is not None: track.setGenre(genre) # Fill the tree self.tree.setItem(child, ROW_NAME, "%02u. %s" % (i + 1, tools.htmlEscape(title))) self.tree.setItem(child, ROW_TRACK, track)
def set_track_playing(self, iter, playing): if not iter: return track = self.tree.getTrack(iter) if not track: return for parent in self.tree.get_all_parents(iter): parent_label = self.tree.getLabel(parent) parent_label = tools.htmlUnescape(parent_label) is_bold = parent_label.startswith('<b>') and parent_label.endswith('</b>') if playing and not is_bold: parent_label = tools.htmlEscape(parent_label) self.tree.setLabel(parent, '<b>%s</b>' % parent_label) elif not playing and is_bold: parent_label = tools.htmlEscape(parent_label[3:-4]) self.tree.setLabel(parent, parent_label) parent = self.tree.store.iter_parent(iter) parent_label = self.tree.getLabel(parent) if parent else None label = track.get_label(parent_label=parent_label, playing=playing) if playing: self.tree.setLabel(iter, label) self.tree.setItem(iter, ROW_ICO, icons.playMenuIcon()) self.tree.expand(iter) else: self.tree.setLabel(iter, label) icon = self.tree.getItem(iter, ROW_ICO) has_error = (icon == icons.errorMenuIcon()) is_dir = (icon == icons.mediaDirMenuIcon()) if not is_dir and not has_error: self.tree.setItem(iter, ROW_ICO, icons.nullMenuIcon())
def getDirContents(self, directory): """ Return a tuple of sorted rows (directories, playlists, mediaFiles) for the given directory """ playlists = [] mediaFiles = [] directories = [] for (file, path) in tools.listDir(unicode(directory)): # Make directory names prettier junk = ['_'] pretty_name = file for item in junk: pretty_name = pretty_name.replace(item, ' ') if isdir(path): directories.append((icons.dirMenuIcon(), tools.htmlEscape(pretty_name), TYPE_DIR, path)) elif isfile(path): if media.isSupported(file): mediaFiles.append((icons.mediaFileMenuIcon(), tools.htmlEscape(pretty_name), TYPE_FILE, path)) ##elif playlist.isSupported(file): ## playlists.append((icons.mediaFileMenuIcon(), tools.htmlEscape(unicode(file, errors='replace')), TYPE_FILE, path)) # Individually sort each type of file by name playlists.sort(key=self._filename) mediaFiles.sort(key=self._filename) directories.sort(key=self._filename) return (directories, playlists, mediaFiles)
def populateFolderList(self): """ Populate the list of known folders """ self.cfgList.replaceContent([ (name, icons.dirToolbarIcon(), '<b>%s</b>\n<small>%s</small>' % (tools.htmlEscape(name), tools.htmlEscape(path))) for name, path in sorted(self.folders.iteritems()) ])
def insertDir(self, trackdir, target=None, drop_mode=None, highlight=False): ''' Insert a directory recursively, return the iter of the first added element ''' model = self.tree.store if trackdir.flat: new = target else: string = trackdir.dirname.replace('_', ' ') string = tools.htmlEscape(string) source_row = (icons.mediaDirMenuIcon(), string, None) new = self.tree.insert(target, source_row, drop_mode) drop_mode = Gtk.TreeViewDropPosition.INTO_OR_AFTER if highlight: self.tree.select(new) dest = new for index, subdir in enumerate(trackdir.subdirs): drop = drop_mode if index == 0 else Gtk.TreeViewDropPosition.AFTER dest = self.insertDir(subdir, dest, drop, highlight) dest = new for index, track in enumerate(trackdir.tracks): drop = drop_mode if index == 0 else Gtk.TreeViewDropPosition.AFTER highlight &= trackdir.flat dest = self.insertTrack(track, dest, drop, highlight) if not trackdir.flat: # Open albums on the first layer if target is None or model.iter_depth(new) == 0: self.tree.expand(new) return new
def insertDir(self, trackdir, target=None, drop_mode=None, highlight=False): ''' Insert a directory recursively, return the iter of the first added element ''' model = self.tree.store if trackdir.flat: new = target else: string = trackdir.dirname.replace('_', ' ') string = tools.htmlEscape(string) source_row = (icons.mediaDirMenuIcon(), string, None) new = self.tree.insert(target, source_row, drop_mode) drop_mode = gtk.TREE_VIEW_DROP_INTO_OR_AFTER if highlight: self.tree.select(new) dest = new for index, subdir in enumerate(trackdir.subdirs): drop = drop_mode if index == 0 else gtk.TREE_VIEW_DROP_AFTER dest = self.insertDir(subdir, dest, drop, highlight) dest = new for index, track in enumerate(trackdir.tracks): drop = drop_mode if index == 0 else gtk.TREE_VIEW_DROP_AFTER highlight &= trackdir.flat dest = self.insertTrack(track, dest, drop, highlight) if not trackdir.flat: # Open albums on the first layer if target is None or model.iter_depth(new) == 0: self.tree.expand(new) return new
def loadTracks(self, tree, node, fakeChild): """ Initial load of all tracks of the given node, assuming it is of type TYPE_ALBUM """ allTracks = pickleLoad(tree.getItem(node, ROW_FULLPATH)) icon = icons.mediaFileMenuIcon() rows = [(icon, None, '%02u. %s' % (track.getNumber(), htmlEscape(track.getTitle())), TYPE_TRACK, track.getFilePath(), track) for track in allTracks] tree.appendRows(rows, node) tree.removeRow(fakeChild)
def add_dir(self, path): ''' Add a directory with one fake child to the tree ''' name = tools.dirname(path) name = tools.htmlEscape(unicode(name, errors='replace')) parent = self.tree.appendRow((icons.dirMenuIcon(), name, TYPE_DIR, path), None) # add fake child self.tree.appendRow((icons.dirMenuIcon(), '', TYPE_NONE, ''), parent)
def fillList(self): """ Fill the list of modules according to the currently selected category """ rows = [] for (name, data) in modules.getModules(): instance = data[modules.MOD_INSTANCE] category = data[modules.MOD_INFO][modules.MODINFO_CATEGORY] mandatory = data[modules.MOD_INFO][modules.MODINFO_MANDATORY] configurable = data[modules.MOD_INFO][modules.MODINFO_CONFIGURABLE] if (configurable or not mandatory) and category == self.currCat: if configurable and instance is not None: icon = icons.prefsBtnIcon() else: icon = None text = '<b>%s</b>\n<small>%s</small>' % (tools.htmlEscape(_(name)), tools.htmlEscape(data[modules.MOD_INFO][modules.MODINFO_DESC])) rows.append((instance is not None, text, icon, not mandatory, instance, data[modules.MOD_INFO])) rows.sort(key=lambda row: row[ROW_TEXT]) self.list.replaceContent(rows)
def fillList(self): """ Fill the list of modules """ rows = [] for (name, data) in modules.getModules(): instance = data[modules.MOD_INSTANCE] mandatory = data[modules.MOD_INFO][modules.MODINFO_MANDATORY] configurable = data[modules.MOD_INFO][modules.MODINFO_CONFIGURABLE] if configurable or not mandatory: if configurable and instance is not None: icon = tools.icons.prefsBtnIcon() else: icon = None text = '<b>%s</b>\n<small>%s</small>' % (tools.htmlEscape(_(name)), tools.htmlEscape(data[modules.MOD_INFO][modules.MODINFO_DESC])) rows.append((instance is not None, text, icon, not mandatory, instance, data[modules.MOD_INFO])) rows.sort(key=lambda row: row[ROW_TEXT]) self.list.store.clear() for row in rows: self.list.store.append(row)
def getDirContents(self, directory): """ Return a tuple of sorted rows (directories, playlists, mediaFiles) for the given directory """ playlists = [] mediaFiles = [] directories = [] for (file, path) in tools.listDir(directory, self.showHiddenFiles): if isdir(path): directories.append((icons.dirMenuIcon(), tools.htmlEscape(unicode(file, errors='replace')), TYPE_DIR, path)) elif isfile(path): if media.isSupported(file): mediaFiles.append((icons.mediaFileMenuIcon(), tools.htmlEscape(unicode(file, errors='replace')), TYPE_FILE, path)) elif playlist.isSupported(file): playlists.append((icons.mediaFileMenuIcon(), tools.htmlEscape(unicode(file, errors='replace')), TYPE_FILE, path)) playlists.sort(key=self.sortKey) mediaFiles.sort(key=self.sortKey) directories.sort(key=self.sortKey) return (directories, playlists, mediaFiles)
def formatHTMLSafe(self, fmtString): """ Replace the special fields in the given string by their corresponding value Also ensure that the fields don't contain HTML special characters (&, <, >) """ result = fmtString result = result.replace( '{path}', tools.htmlEscape(self.getFilePath()) ) result = result.replace( '{album}', tools.htmlEscape(self.getAlbum()) ) result = result.replace( '{track}', str(self.getNumber()) ) result = result.replace( '{title}', tools.htmlEscape(self.getTitle()) ) result = result.replace( '{artist}', tools.htmlEscape(self.getArtist()) ) result = result.replace( '{genre}', tools.htmlEscape(self.getGenre()) ) result = result.replace( '{date}', str(self.getDate()) ) result = result.replace( '{disc}', str(self.getDiscNumber()) ) result = result.replace( '{bitrate}', self.getBitrate() ) result = result.replace( '{sample_rate}', self.getSampleRate() ) result = result.replace( '{duration_sec}', str(self.getLength()) ) result = result.replace( '{duration_str}', sec2str(self.getLength()) ) return result
def get_name(path): # Remove the search path from the name if path == search_path: name = tools.dirname(path) else: name = path.replace(search_path, '') # Only show filename and at most one parent dir for each file. name = '/'.join(name.split('/')[-2:]) name = name.strip('/') name = regex.sub(same_case_bold, unicode(name)) name = tools.htmlEscape(name) name = name.replace('STARTBOLD', '<b>').replace('ENDBOLD', '</b>') return name
def get_name(path): # Remove the search path from the name if path == search_path: name = tools.dirname(path) else: name = path.replace(search_path, '') # Only show filename and at most one parent dir for each file. name = '/'.join(name.split('/')[-2:]) name = name.strip('/') name = regex.sub(same_case_bold, name) name = tools.htmlEscape(name) name = name.replace('STARTBOLD', '<b>').replace('ENDBOLD', '</b>') return name
def fillList(self): """ Fill the list of modules """ rows = [] for (name, data) in modules.getModules(): instance = data[modules.MOD_INSTANCE] mandatory = data[modules.MOD_INFO][modules.MODINFO_MANDATORY] configurable = data[modules.MOD_INFO][modules.MODINFO_CONFIGURABLE] if configurable or not mandatory: if configurable and instance is not None: icon = tools.icons.prefsBtnIcon() else: icon = None text = '<b>%s</b>\n<small>%s</small>' % ( tools.htmlEscape(_(name)), tools.htmlEscape( data[modules.MOD_INFO][modules.MODINFO_DESC])) rows.append((instance is not None, text, icon, not mandatory, instance, data[modules.MOD_INFO])) rows.sort(key=lambda row: row[ROW_TEXT]) self.list.store.clear() for row in rows: self.list.store.append(row)
def loadArtists(self, tree, name): """ Load the given library """ libPath = os.path.join(ROOT_PATH, name) # Make sure the version number is the good one if not os.path.exists(os.path.join(libPath, 'VERSION_%u' % VERSION)): logger.error('[%s] Version number does not match, loading of library "%s" aborted' % (MOD_INFO[modules.MODINFO_NAME], name)) error = _('This library is deprecated, please refresh it.') tree.replaceContent([(icons.errorMenuIcon(), None, error, TYPE_NONE, None, None)]) return rows = [] icon = icons.dirMenuIcon() prevChar = '' allArtists = pickleLoad(os.path.join(libPath, 'artists')) self.allGenres = pickleLoad(os.path.join(libPath, 'genres')) # Filter artists by genre if needed if self.currGenre is not None: allArtists = [artist for artist in allArtists if artist[ART_NAME] in self.allGenres[self.currGenre]] rows.append((icons.infoMenuIcon(), None, '<b>%s</b>' % self.currGenre.capitalize(), TYPE_GENRE_BANNER, None, None)) else: rows.append((icons.infoMenuIcon(), None, '<b>%s</b>' % _('All genres'), TYPE_GENRE_BANNER, None, None)) # Filter artists by favorites if needed if self.showOnlyFavs: allArtists = [artist for artist in allArtists if self.isArtistInFavorites(artist[ART_NAME])] rows.append((icons.starMenuIcon(), None, '<b>%s</b>' % _('My Favorites'), TYPE_FAVORITES_BANNER, None, None)) # Create the rows for artist in allArtists: if len(artist[ART_NAME]) != 0: currChar = unicode(artist[ART_NAME], errors='replace')[0].lower() else: currChar = prevChar if prevChar != currChar and not (prevChar.isdigit() and currChar.isdigit()): prevChar = currChar if currChar.isdigit(): rows.append((None, None, '<b>0 - 9</b>', TYPE_HEADER, None, None)) else: rows.append((None, None, '<b>%s</b>' % currChar.upper(), TYPE_HEADER, None, None)) rows.append((icon, None, htmlEscape(artist[ART_NAME]), TYPE_ARTIST, os.path.join(libPath, artist[ART_INDEX]), artist[ART_NAME])) # Insert all rows, and then add a fake child to each artist tree.replaceContent(rows) for node in tree.iterChildren(None): if tree.getItem(node, ROW_TYPE) == TYPE_ARTIST: tree.appendRow(FAKE_CHILD, node)
def get_label(self, parent_label=None, playing=False): """ Return a treeview representation """ title = self.tags.get(TAG_TIT, '') artist = self.tags.get(TAG_ART, '') album = self.getExtendedAlbum() if album == consts.UNKNOWN_ALBUM: album = '' number = self.tags.get(TAG_NUM, '') length = self.getLength() if number: number = str(number).zfill(2) # Delete whitespace at the end connectors = ['the', 'and', '&', ',', '.', '?', '!', "'", ':', '-', ' '] if parent_label: parent_label = parent_label.lower() short_album = album.lower() short_artist = artist.lower() for connector in connectors: parent_label = parent_label.replace(connector, '') short_album = short_album.replace(connector, '') short_artist = short_artist.replace(connector, '') if short_album.strip() in parent_label: album = '' if short_artist.strip() in parent_label: artist = '' if title: label = ' - '.join([part for part in [artist, album, number, title] if part]) else: label = self.getBasename() label = tools.htmlEscape(label) if playing: label = '<b>%s</b>' % label #label += ' <span foreground="gray">[%s]</span>' % tools.sec2str(length) label += ' [%s]' % tools.sec2str(length) return label
def loadAlbums(self, tree, node, fakeChild): """ Initial load of the albums of the given node, assuming it is of type TYPE_ARTIST """ rows = [] path = tree.getItem(node, ROW_FULLPATH) artist = tree.getItem(node, ROW_DATA) allAlbums = pickleLoad(os.path.join(tree.getItem(node, ROW_FULLPATH), 'albums')) # Filter albums if only favorites should be shown if self.showOnlyFavs: allAlbums = [album for album in allAlbums if self.isAlbumInFavorites(artist, album[ALB_NAME])] # Filter artists by genre if needed if self.currGenre is not None: allAlbums = [album for album in allAlbums if album[ALB_NAME] in self.allGenres[self.currGenre][artist]] # The icon depends on whether the album is in the favorites for album in allAlbums: if self.isAlbumInFavorites(artist, album[ALB_NAME]): icon = icons.starDirMenuIcon() else: icon = icons.mediaDirMenuIcon() rows.append((icon, '[%s]' % tools.sec2str(album[ALB_LENGTH], True), '%s' % htmlEscape(album[ALB_NAME]), TYPE_ALBUM, os.path.join(path, album[ALB_INDEX]), album[ALB_NAME])) # Add all the rows, and then add a fake child to each of them tree.appendRows(rows, node) tree.removeRow(fakeChild) for child in tree.iterChildren(node): tree.appendRow(FAKE_CHILD, child)
def fillLibraryList(self): """ Fill the list of libraries """ if self.cfgWindow is not None: rows = [(name, icons.dirToolbarIcon(), '<b>%s</b>\n<small>%s - %u %s</small>' % (htmlEscape(name), htmlEscape(path), nbTracks, htmlEscape(_('tracks')))) for name, (path, nbArtists, nbAlbums, nbTracks) in sorted(self.libraries.iteritems())] self.cfgList.replaceContent(rows)
def populateFolderList(self): """ Populate the list of known folders """ self.cfgList.replaceContent([(name, icons.dirToolbarIcon(), '<b>%s</b>\n<small>%s</small>' % (tools.htmlEscape(name), tools.htmlEscape(path))) for name, path in sorted(self.folders.iteritems())])
def __setTitle(self, title, length=None): """ Change the title of the current track """ title = tools.htmlEscape(title) if length is None: self.txtTitle.set_markup('<span size="larger"><b>%s</b></span>' % title) else: self.txtTitle.set_markup('<span size="larger"><b>%s</b></span> [%s]' % (title, tools.sec2str(length)))