Пример #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()
Пример #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)
Пример #3
0
class State(object):
	def _updateCurSongHandler(self):
		self.__class__.curSongStr.updateEvent(self).push()
		self.__class__.curSongPos.updateEvent(self).push()
		self.__class__.curSongDisplay.updateEvent(self).push()

	def __init__(self):
		self.__class__.curSong.updateEvent(self).register(self._updateCurSongHandler)

	def playPauseUpdate(self, attrib):
		if self.player.playing:
			attrib.name = "❚❚"
		else:
			attrib.name = "▶"

	@UserAttrib(type=Traits.Action, name="▶", updateHandler=playPauseUpdate, addUpdateEvent=True)
	def playPause(self):
		self.player.playing = not self.player.playing

	@UserAttrib(type=Traits.Action, name="▶▶|", alignRight=True)
	def nextSong(self):
		self.player.nextSong()

	@UserAttrib(type=Traits.OneLineText, alignRight=True, variableWidth=True, withBorder=True, addUpdateEvent=True)
	@property
	def curSongStr(self):
		if not self.player.curSong: return ""
		try: return self.player.curSong.userString
		except Exception: return "???"

	@UserAttrib(type=Traits.OneLineText, alignRight=True, autosizeWidth=True, withBorder=True, addUpdateEvent=True)
	@property
	def curSongPos(self):
		if not self.player.curSong: return ""
		try: return formatTime(self.player.curSongPos) + " / " + formatTime(self.player.curSong.duration)
		except Exception: return "???"

	@UserAttrib(type=Traits.SongDisplay, variableWidth=True, addUpdateEvent=True)
	def curSongDisplay(self): pass

	@initBy
	def _volume(self): return PersistentObject(float, "volume.dat", defaultArgs=(0.9,))

	@UserAttrib(type=Traits.Real(min=0, max=2), alignRight=True, height=80, width=25)
	@property
	def volume(self):
		return self._volume

	@volume.callDeco.setter
	def volume(self, updateValue):
		self._volume = updateValue
		self._volume.save()
		self.player.volume = updateValue

	@UserAttrib(type=Traits.List, lowlight=True, autoScrolldown=True)
	@initBy
	def recentlyPlayedList(self): return PersistentObject(RecentlyplayedList, "recentlyplayed.dat")

	@UserAttrib(type=Traits.Object, spaceY=0, highlight=True, addUpdateEvent=True)
	@initBy
	def curSong(self): return PersistentObject(Song, "cursong.dat")

	@UserAttrib(type=Traits.Object, spaceY=0, variableHeight=True)
	@initBy
	def queue(self):
		import Queue
		return Queue.queue

	@initBy
	def updates(self):
		import Queue
		return Queue.OnRequestQueue()

	@initBy
	def player(self):
		from Player import loadPlayer
		return loadPlayer(self)

	def quit(self):
		# XXX: Is this still used?
		# XXX: doesn't really work. OSX ignores the SIGINT if the cocoa mainloop runs
		def doQuit():
			""" This works in all threads except the main thread. It will quit the whole app.
			For more information about why we do it this way, read the comment in main.py.
			"""
			import sys, os, signal
			os.kill(0, signal.SIGINT)
			sys.stdin.close() # so that the terminal closes, if it is used
		import thread
		thread.start_new_thread(doQuit, ())
Пример #4
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...
Пример #5
0
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())