Beispiel #1
0
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()
Beispiel #2
0
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)
Beispiel #3
0
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())