def printDistinctLocalTags(self): if (len(self.localTagLibrary) == 0): return disptags = [] for localtag in self.localTagLibrary: disptags.append((self.localTagLibrary[localtag]['disp'], self.localTagLibrary[localtag]['localhits'])) common.safeStdout('\nDistinct Update-stream Tags (most to least frequent, in your library): \n\t'+'\n\t'.join(map(lambda pair: pair[0]+' ('+str(pair[1])+')', common.sortWeightedTagTuples(disptags))))
def printDistinctLastTags(self): if (len(self.lastTagLibrary) == 0): return disptags = [] for lasttag in self.lastTagLibrary: disptags.append((lasttag, self.lastTagLibrary[lasttag])) common.safeStdout('\nDistinct In-library LastFM Tags (most to least popular, on LastFM): \n\t'+'\n\t'.join(map(lambda pair: pair[0]+' ('+str(pair[1])+')', common.sortWeightedTagTuples(disptags))))
def fetchTrackTags(self, lastfm): verbose = self.config.getboolean('verbose') refetch = self.config.getboolean('refetchCachedTags') minWeight = self.config.getint('minTrackTagWeight') niceness = self.config.getint('niceness') / 1000 maxTagsToSave = self.config.getint('getTrackTags') if (maxTagsToSave <= 0): return print 'Fetching track tags from LastFM' for artist in sorted(self.mediaLibrary): for album in sorted(self.mediaLibrary[artist]['albums']): for track in sorted(self.mediaLibrary[artist]['albums'][album]['tracks']): tagpairs = self.mediaLibrary[artist]['albums'][album]['tracks'][track]['tags'] if (tagpairs is not None and refetch is False): if (verbose): common.safeStdout('\tSkipping already-tagged track ['+artist+':'+track+']') continue tagpairs = lastfm.fetchTrackTags(artist, track, maxTagsToSave, minWeight) if (tagpairs is not None): map(self.addToLastFMTagLibrary, map(lambda pair: pair[0], tagpairs)) if (verbose): common.safeStdout('\tFetched ['+artist+':'+track+'] ('+', '.join(map(lambda pair: pair[0], tagpairs))+')') self.mediaLibrary[artist]['albums'][album]['tracks'][track]['tags'] = tagpairs time.sleep(niceness)
def updateTags(self, filename, tagPayload): try: mediawrapper = self.getMediawrapper(filename) for bucket in tagPayload: tagPayload[bucket] = self.tagSep.join( tagPayload[bucket][0:self.maxTags[bucket]]) if (isinstance(mediawrapper, ID3)): return self.updateTagsHelperID3(mediawrapper, tagPayload, self.formatFieldMap['id3']) elif (isinstance(mediawrapper, MP4)): return self.updateTagsHelper(mediawrapper, tagPayload, self.formatFieldMap['mp4']) elif (isinstance(mediawrapper, OggVorbis)): return self.updateTagsHelper(mediawrapper, tagPayload, self.formatFieldMap['oggvorbis']) elif (isinstance(mediawrapper, FLAC)): return self.updateTagsHelper(mediawrapper, tagPayload, self.formatFieldMap['flac']) else: common.safeStdout( 'Skipping unknown/incompatible media file type [' + filename + ']') except Exception, err: common.safeStderr('Error seen during update processing: ' + str(err))
def extractMetadata(self, filename): try: mediawrapper = self.getMediawrapper(filename) if (isinstance(mediawrapper, ID3)): return self.extractMetadataHelper(mediawrapper, self.formatFieldMap['id3'], filename) elif (isinstance(mediawrapper, MP4)): return self.extractMetadataHelper(mediawrapper, self.formatFieldMap['mp4'], filename) elif (isinstance(mediawrapper, OggVorbis)): return self.extractMetadataHelper( mediawrapper, self.formatFieldMap['oggvorbis'], filename) elif (isinstance(mediawrapper, FLAC)): return self.extractMetadataHelper(mediawrapper, self.formatFieldMap['flac'], filename) else: if (self.config.getboolean('verbose')): common.safeStdout( '\tSkipping unknown/incompatible media file type [' + filename + ']') except Exception, err: common.safeStderr('Error seen during media reading: ' + str(err))
def readMedia(self): mediadir = self.config.get('mediaDir') verbose = self.config.getboolean('verbose') skipExtensions = map(lambda x: '.'+x.lower().strip(), self.config.get('skipExtensions').split(',')) common.safeStdout('Reading existing metadata from ['+mediadir+']') numfiles = 0 for root, dirs, files in os.walk(mediadir): for filename in files: fname, ext = os.path.splitext(filename.lower()) if (ext is not None and ext in skipExtensions): continue metadata = self.mediaHelper.extractMetadata(os.path.join(root, filename)) if (metadata is None or len(metadata['artists']) == 0 or metadata['album'] is None or metadata['track'] is None): continue for artist in metadata['artists']: self.addToMediaLibrary(artist, metadata['album'], metadata['track']) numfiles += 1 if (verbose): common.safeStdout('\tProcessed: '+os.path.join(root, filename)) print 'Read ['+str(numfiles)+'] media files'
def printLibrary(self): for artist in self.mediaLibrary: print common.safeStdout(artist + ' ('+ ', '.join(map(lambda pair: pair[0], self.mediaLibrary[artist]['tags'] or [])) + ')') for album in self.mediaLibrary[artist]['albums']: print common.safeStdout('\t'+album) for track in self.mediaLibrary[artist]['albums'][album]['tracks']: print common.safeStdout('\t\t'+track + ' ('+ ', '.join(map(lambda pair: pair[0], self.mediaLibrary[artist]['albums'][album]['tracks'][track]['tags'] or [])) + ')')
def updateTags(self): ''' This pushes the tags back into the underlying media files ''' verbose = self.config.getboolean('verbose') mediadir = self.config.get('mediaDir') startDelim = self.config.get('tagStartDelim') endDelim = self.config.get('tagEndDelim') artistTagFields = set(map(string.strip, self.config.get('artistTagFields').lower().split(','))) trackTagFields = set(map(string.strip, self.config.get('trackTagFields').lower().split(','))) touchedFields = artistTagFields.union(trackTagFields) skipExtensions = map(lambda x: '.'+x.lower().strip(), self.config.get('skipExtensions').split(',')) writeUntaggedArtist = (self.config.get('writeUntaggedTag').lower() == 'artist' or self.config.get('writeUntaggedTag').lower() == 'both') writeUntaggedTrack = (self.config.get('writeUntaggedTag').lower() == 'track' or self.config.get('writeUntaggedTag').lower() == 'both') if (touchedFields is None or len(touchedFields) == 0): common.safeStderr('Perhaps you should configure a destination field...') return self.loadSynonyms() self.generateLocalTags() common.safeStdout('Updating tags in ['+mediadir+']') numfiles = 0 for root, dirs, files in os.walk(mediadir): for filename in files: fname, ext = os.path.splitext(filename.lower()) if (ext is not None and ext in skipExtensions): continue metadata = self.mediaHelper.extractMetadata(os.path.join(root, filename)) if (metadata is None or len(metadata['artists']) == 0 or metadata['album'] is None or metadata['track'] is None): continue album, track = metadata['album'].lower(), metadata['track'].lower() artistTags = [] trackTags = [] for artist in map(string.lower, metadata['artists']): if (artist not in self.mediaLibrary or album not in self.mediaLibrary[artist]['albums'] or track not in self.mediaLibrary[artist]['albums'][album]['tracks']): common.safeStderr('Entry not found in library: ['+artist+']['+album+']['+track+']') continue artistTags.extend(self.mediaLibrary[artist]['tags'] or []) trackTags.extend(self.mediaLibrary[artist]['albums'][album]['tracks'][track]['tags'] or []) localArtistTags = self.lastTagsToLocalTags(artistTags) localTrackTags = self.lastTagsToLocalTags(trackTags) # Use untagged tags, if requested and appropriate if (len(localArtistTags) == 0 and writeUntaggedArtist): localArtistTags = [(u'untagged artist', 0)] if (len(localTrackTags) == 0 and writeUntaggedTrack): localTrackTags = [(u'untagged track', 0)] tagPayload = {} for touchedField in touchedFields: if (touchedField in artistTagFields and touchedField in trackTagFields): fieldTags = common.distinctTagSeq(localArtistTags + localTrackTags) elif (touchedField in artistTagFields): fieldTags = localArtistTags else: fieldTags = localTrackTags if (fieldTags is None or len(fieldTags) == 0) : continue # The following section is mostly to deal with multi-column sorting # Store the record weights somewhere we can look them up (the list should already be distinct) recordWeights = {} for tagpair in fieldTags: recordWeights[tagpair[0].lower()] = tagpair[1] # Pull out just the tag names as singleton tuples, we'll tack on sort weights next tagWeightsList = map(lambda tuple: (tuple[0],), fieldTags) # Pull out the list of sort rules (e.g. record, library) and append each appropriate weight to the tuple list, in succession sortRules = map(string.strip, self.config.get(touchedField + 'Sort').lower().split(',')) for sortRule in sortRules: if (sortRule == 'record'): tagWeightsList = map(lambda tagtuple: tagtuple + (recordWeights[tagtuple[0].lower()],), tagWeightsList) elif (sortRule == 'library'): tagWeightsList = map(lambda tagtuple: tagtuple + (self.getLibraryWeight(tagtuple[0].lower()),), tagWeightsList) elif (sortRule == 'popularity'):tagWeightsList = map(lambda tagtuple: tagtuple + (self.getPopularityWeight(tagtuple[0].lower()),), tagWeightsList) common.sortWeightedTagTuples(tagWeightsList) tagPayload[touchedField] = self.formattedTagList(tagWeightsList, startDelim, endDelim) if (self.mediaHelper.updateTags(os.path.join(root, filename), tagPayload)): numfiles += 1 if (verbose): common.safeStdout('\tUpdated: '+os.path.join(root, filename)) elif (verbose): common.safeStdout('\tSkipped: '+os.path.join(root, filename)+' (nothing to update)') print 'Updated ['+str(numfiles)+'] media files'