class Search: Keys = ("artist", "title", "duration", "rating") _searchText = "" _searchResults = [] @UserAttrib(type=Traits.EditableText, searchLook=True) def searchText(self, updateText=None): if updateText is not None and self._searchText != updateText: self._searchText = updateText self._searchResults = songdb.search(updateText) self.searchResults_updateEvent.push() return self._searchText @UserAttrib(type=Traits.Table( keys=Keys, format_duration=lambda d: formatTime(d) if d > 0 else "", format_rating=lambda r: "★" * int(round(r * 5)))) @property def searchResults(self): return list(self._searchResults) @searchResults.setUpdateEvent @initBy def searchResults_updateEvent(self): return Event()
class Search: Keys = ("artist", "title", "duration", "rating") def __init__(self): self._searchText = "" self._searchResults = [] import threading self._lock = threading.RLock() def _startSearch(self, txt): def search(): with self._lock: if self._searchText != txt: return res = songdb.search(txt) with self._lock: if self._searchText == txt: self._searchResults = res self.__class__.searchResults.updateEvent(self).push() with self._lock: self._searchText = txt TaskSystem.daemonThreadCall(search, name="Song DB search") @UserAttrib(type=Traits.EditableText, searchLook=True) def searchText(self, updateText=None): with self._lock: if updateText is not None and self._searchText != updateText: self._startSearch(updateText) return self._searchText @UserAttrib(type=Traits.Table( keys=Keys, format_duration=lambda d: formatTime(d) if d > 0 else "", format_rating=lambda r: "★" * int(round(r * 5))), variableHeight=True, addUpdateEvent=True) @property def searchResults(self): with self._lock: return list(self._searchResults)
class SongEdit: @initBy def _updateEvent(self): return Event() def __init__(self, ctx=None): if not ctx: import gui ctx = gui.ctx() assert ctx, "no gui context" self.ctx = ctx self._updateHandler = lambda: self._updateEvent.push() ctx.curSelectedSong_updateEvent.register(self._updateHandler) @UserAttrib(type=Traits.Object) @property def song(self): return self.ctx.curSelectedSong @UserAttrib(type=Traits.EditableText) def artist(self, updateText=None): if self.song: if updateText: self.song.artist = updateText return self.song.artist return "" @UserAttrib(type=Traits.EditableText) def title(self, updateText=None): if self.song: if updateText: self.song.title = updateText return self.song.title return "" @staticmethod def _convertTagsToText(tags): def txtForTag(tag): value = tags[tag] if value >= 1: return tag return tag + ":" + str(value) return " ".join(map(txtForTag, sorted(tags.keys()))) @staticmethod def _convertTextToTags(txt): pass # todo... #@UserAttrib(type=Traits.EditableText) def tags(self, updateText=None): if self.song: return self._convertTagsToText(self.song.tags) return "" @staticmethod def _formatGain(gain): factor = 10.0 ** (gain / 20.0) return "%f dB (factor %f)" % (gain, factor) @UserAttrib(type=Traits.Table(keys=("key", "value")), variableHeight=True) @property def metadata(self): d = dict(self.song.metadata) for (key,func) in ( ("artist",None), ("title",None), ("album",None), ("duration",utils.formatTime), ("url",None), ("rating",None), ("tags",self._convertTagsToText), ("gain",self._formatGain), ("completedCount",None), ("skipCount",None), ("lastPlayedDate",utils.formatDate), ("id",repr), ): try: value = getattr(self.song, key) except AttributeError: pass else: if func: value = func(value) d[key] = unicode(value) l = [] for key,value in sorted(d.items()): l += [{"key": key, "value": value}] return l @metadata.setUpdateEvent @property def metadata_updateEvent(self): return self.song._updateEvent def _queryAcoustId(self): fingerprint = self.song.get("fingerprint_AcoustId", timeout=None)[0] duration = self.song.get("duration", timeout=None, accuracy=0.5)[0] import base64 fingerprint = base64.urlsafe_b64encode(fingerprint) api_url = "http://api.acoustid.org/v2/lookup" # "8XaBELgH" is the one from the web example from AcoustID. # "cSpUJKpD" is from the example from pyacoustid # get an own one here: http://acoustid.org/api-key client_api_key = "cSpUJKpD" params = { 'format': 'json', 'client': client_api_key, 'duration': int(duration), 'fingerprint': fingerprint, 'meta': 'recordings recordingids releasegroups releases tracks compress', } import urllib body = urllib.urlencode(params) import urllib2 req = urllib2.Request(api_url, body) import contextlib with contextlib.closing(urllib2.urlopen(req)) as f: data = f.read() headers = f.info() import json data = json.loads(data) return data def queryAcoustIdResults_selectionChangeHandler(self, selection): self._queryAcoustId_selection = selection @UserAttrib(type=Traits.Table(keys=("artist", "title", "album", "track", "score")), selectionChangeHandler=queryAcoustIdResults_selectionChangeHandler) @property def queryAcoustIdResults(self): if getattr(self, "_queryAcoustIdResults_songId", "") != getattr(self.song, "id", ""): return [] return list(getattr(self, "_queryAcoustIdResults", [])) @queryAcoustIdResults.setUpdateEvent @initBy def queryAcoustIdResults_updateEvent(self): return Event() @UserAttrib(type=Traits.Action, variableWidth=False) def queryAcoustId(self): data = self._queryAcoustId() self._queryAcoustIdResults_songId = self.song.id self._queryAcoustIdResults = [] for result in data.get("results", []): for recording in result.get("recordings", []): for resGroup in recording.get("releasegroups", []): artist = resGroup["artists"][0] release = resGroup["releases"][0] medium = release["mediums"][0] track = medium["tracks"][0] if artist["name"] == "Various Artists": artist = track["artists"][0] entry = { "id": result["id"], "score": result["score"], "recording-id": recording["id"], "releasegroup-id": resGroup["id"], "artist-id": artist["id"], "artist": artist["name"], "title": track["title"], "album": resGroup["title"], "track": "%i/%i" % (track["position"], medium["track_count"]) } self._queryAcoustIdResults += [entry] if not self._queryAcoustIdResults: self._queryAcoustIdResults += [{"artist":"- None found -","title":"","album":"","track":""}] self.queryAcoustIdResults_updateEvent.push() @UserAttrib(type=Traits.Action, variableWidth=False, alignRight=True) def apply(self): if getattr(self, "_queryAcoustIdResults_songId", "") != getattr(self.song, "id", ""): return sel = getattr(self, "_queryAcoustId_selection", []) if not sel: return sel = sel[0] for key in ("artist","title"): if not sel[key]: return for key in ("artist","title","album","track"): setattr(self.song, key, sel[key]) self._updateEvent.push() # the song is updating itself - but the edit fields aren't atm...
class Preferences(object): def __init__(self): self._sampleRateStr = None self.lastFm_update(self.__class__.lastFm) @UserAttrib(type=Traits.OneLineText, autosizeWidth=True) @property def soundDeviceLabel(self): return "Preferred sound device:" @UserAttrib(type=Traits.EditableText, autosizeWidth=True, alignRight=True) def preferredSoundDevice(self, updateText=None): from State import state player = state.player if updateText is not None: player.preferredSoundDevice = updateText from appinfo import config config.preferredSoundDevice = updateText config.save() return player.preferredSoundDevice @UserAttrib(type=Traits.OneLineText, variableWidth=True) @property def soundDeviceDescr(self): return "(Playback needs to be stopped to set this into effect.)" @UserAttrib(type=Traits.OneLineText, autosizeWidth=True) @property def actualSoundDeviceLabel(self): return "Current sound device:" @UserAttrib(type=Traits.OneLineText, autosizeWidth=True, withBorder=True, alignRight=True) @safe_property @property def actualSoundDevice(self): from State import state return state.player.actualSoundDevice @UserAttrib(type=Traits.OneLineText, autosizeWidth=True) @property def availableSoundDevicesLabel(self): return "Available sound devices:" @UserAttrib(type=Traits.Table(keys=("Name", )), autosizeWidth=True, alignRight=True) @safe_property @property def availableSoundDevices(self): import musicplayer l = musicplayer.getSoundDevices() return [{"Name": dev} for dev in l] @UserAttrib(type=Traits.OneLineText, autosizeWidth=True) @property def sampleRateLabel(self): return "Sample rate in Hz:" @property def _sampleRate(self): from State import state return state.player.outSamplerate @UserAttrib( type=Traits.EditableText, alignRight=True, variableWidth=True, width=200, # this forces a min-width addUpdateEvent=True) def sampleRate(self, updateText=None): if updateText is not None and self._sampleRateStr != updateText: self._sampleRateStr = updateText if self._sampleRateStr is not None: return self._sampleRateStr rate = str(self._sampleRate / 1000) return rate + "k" @UserAttrib(type=Traits.Action, name="apply", alignRight=True, variableWidth=False) def applySampleRate(self): self.__class__.sampleRate.updateEvent(self).push() self._sampleRateStr, rate = None, self._sampleRateStr if rate is None: return rate = rate.strip() if rate[-1:] == "k": factor = 1000 rate = rate[:-1] else: factor = 1 try: rate = int(rate) * factor except Exception: return # no valid integer # do some very basic check on the number. # later, our musicplayer module should allow us to check this. if rate in (44100, 48000, 88200, 96000, 176400, 192000): pass else: return from State import state state.player.playing = False # can only change that when not playing state.player.outSamplerate = rate from appinfo import config config.sampleRate = rate config.save() def lastFm_update(self, attrib): from appinfo import config if config.lastFm: attrib.name = "Last.fm is enabled. Disable it." else: attrib.name = "Last.fm is disabled. Enable it." @UserAttrib(type=Traits.Action, updateHandler=lastFm_update, variableWidth=False, addUpdateEvent=True) def lastFm(self): self.__class__.lastFm.updateEvent(self).push() from appinfo import config config.lastFm = not config.lastFm config.save() from State import getModule getModule("tracker_lastfm").reload() @UserAttrib(type=Traits.Action, alignRight=True, name="reset Last.fm login", variableWidth=False) def resetLastFm(self): import lastfm, os, sys try: os.remove(lastfm.StoredSession.TOKEN_FILE) except Exception: sys.excepthook(*sys.exc_info())