Esempio n. 1
0
    def register_provider(self, servicename, provider, target=None):
        """
            Registers a provider for a service. The provider object is used
            by consumers of the service.

            Services can be targeted for a specific use. For example, if you
            have a widget that uses a service 'foo', if your object can perform
            a service only for a specific type of widget, then target would be
            set to the widget type.

            If you had a service that could perform 'foo' for all widgets, then
            target would be set to None, and all widgets could use your service.

            It is intended that most services should set target to None, with
            some narrow exceptions.

            :param servicename: the name of the service [string]
            :type servicename: string
            :param provider: the object that is the provider [object]
            :type provider: object
            :param target: a specific target for the service [object]
            :type target: object
        """
        service = self.services.setdefault(servicename, {})
        providers = service.setdefault(target, [])
        if provider not in providers:
            providers.append(provider)
            logger.debug(
                "Provider %(provider)s registered for service %(service)s "
                "with target %(target)s"
                % {'provider': provider.name, 'service': servicename, 'target': target}
            )
            event.log_event("%s_provider_added" % servicename, self, (provider, target))
Esempio n. 2
0
    def rescan(self, notify_interval=None, force_update=False):
        '''
            Called when a library needs to refresh its track list.

            The force_update parameter is not applicable and is ignored.
        '''
        if self.collection is None:
            return True

        if self.scanning:
            return
        t = time.time()
        logger.info('Scanning library: %s' % self.daap_share.name)
        self.scanning = True
        db = self.collection

        # DAAP gives us all the tracks in one dump
        self.daap_share.reload()
        if self.daap_share.all:
            count = len(self.daap_share.all)
        else:
            count = 0

        if count > 0:
            logger.info('Adding %d tracks from %s. (%f s)' % (count, 
                                    self.daap_share.name, time.time()-t))
            self.collection.add_tracks(self.daap_share.all)


        if notify_interval is not None:
            event.log_event('tracks_scanned', self, count)

        # track removal?
        self.scanning = False
Esempio n. 3
0
    def set_tag_raw(self, tag, values, notify_changed=True):
        """
            Set the raw value of a tag.

            :param tag: The name of the tag to set.
            :param values: The value or values to set the tag to.
            :param notify_changed: whether to send a signal to let other
                parts of Exaile know there has been an update. Only set
                this to False if you know that no other parts of Exaile
                need to be updated.
        """
        # handle values that aren't lists
        if not isinstance(values, list):
            if not tag.startswith("__"): # internal tags dont have to be lists
                values = [values]

        # TODO: is this needed? why?
        # for lists, filter out empty values and convert to unicode
        if isinstance(values, list):
            values = [common.to_unicode(x, self.__tags.get('__encoding'))
                for x in values if x not in (None, '')]

        # save some memory by not storing null values.
        if not values:
            try:
                del self.__tags[tag]
            except KeyError:
                pass
        else:
            self.__tags[tag] = values

        self._dirty = True
        if notify_changed:
            event.log_event("track_tags_changed", self, tag)
Esempio n. 4
0
    def transfer(self):
        """
            Tranfer the queued tracks to the library.

            This is NOT asynchronous
        """
        self.transferring = True
        self.current_pos += 1
        try:
            while self.current_pos  < len(self.queue) and not self._stop:
                track = self.queue[self.current_pos]
                loc = track.get_loc_for_io()
                self.library.add(loc)

                # TODO: make this be based on filesize not count
                progress = self.current_pos * 100 / len(self.queue)
                event.log_event('track_transfer_progress', self, progress)

                self.current_pos += 1
        finally:
            self.queue = []
            self.transferring = False
            self.current_pos = -1
            self._stop = False
            event.log_event('track_transfer_progress', self, 100)
Esempio n. 5
0
 def _on_message(self, bus, message, reading_tag=False):
     handled = self._handle_message(bus, message, reading_tag)
     if handled:
         pass
     elif message.type == gst.MESSAGE_TAG:
         """ Update track length and optionally metadata from gstreamer's parser.
             Useful for streams and files mutagen doesn't understand. """
         parsed = message.parse_tag()
         event.log_event('tags_parsed', self, (self.current, parsed))
         if self.current and not self.current.get_tag_raw('__length'):
             try:
                 raw_duration = self._pipe.query_duration(gst.FORMAT_TIME, None)[0]
             except gst.QueryError:
                 logger.error("Couldn't query duration")
                 raw_duration = 0
             duration = float(raw_duration)/gst.SECOND
             if duration > 0:
                 self.current.set_tag_raw('__length', duration)
     elif message.type == gst.MESSAGE_EOS and not self.is_paused():
         self._eos_func()
     elif message.type == gst.MESSAGE_ERROR:
         logger.error("%s %s" %(message, dir(message)) )
         message_text = message.parse_error()[1]
         # The most readable part is always the last..
         message_text = message_text[message_text.rfind(':') + 1:]
         # .. unless there's nothing in it.
         if ' ' not in message_text:
             if message_text.startswith('playsink'):
                 message_text += _(': Possible audio device error, is it plugged in?')
         event.log_event('playback_error', self, message_text)
         self._error_func()
     return True
Esempio n. 6
0
    def get_cddb_info(self):
        try:
            disc = DiscID.open(self.device)
            self.info = DiscID.disc_id(disc)
            status, info = CDDB.query(self.info)
        except IOError:
            return

        if status in (210, 211):
            info = info[0]
            status = 200
        if status != 200:
            return

        (status, info) = CDDB.read(info['category'], info['disc_id'])

        title = info['DTITLE'].split(" / ")
        for i in range(self.info[1]):
            tr = self[i]
            tr.set_tag_raw('title',
                    info['TTITLE' + `i`].decode('iso-8859-15', 'replace'))
            tr.set_tag_raw('album',
                    title[1].decode('iso-8859-15', 'replace'))
            tr.set_tag_raw('artist',
                    title[0].decode('iso-8859-15', 'replace'))
            tr.set_tag_raw('year',
                    info['EXTD'].replace("YEAR: ", ""))
            tr.set_tag_raw('genre',
                    info['DGENRE'])

        self.name = title[1].decode('iso-8859-15', 'replace')
        event.log_event('cddb_info_retrieved', self, True)
Esempio n. 7
0
    def _restore_player_state(self, location):
        if not settings.get_option("%s/resume_playback" % self.player._name, True):
            return

        try:
            f = open(location, 'rb')
            state = pickle.load(f)
            f.close()
        except:
            return

        for req in ['state', 'position', '_playtime_stamp']:
            if req not in state:
                return

        if state['state'] in ['playing', 'paused']:
            event.log_event("playback_player_resume", self.player, None)
            vol = self.player._get_volume()
            self.player._set_volume(0)
            self.play(self.get_current())

            if self.player.current:
                self.player.seek(state['position'])
                if state['state'] == 'paused' or \
                        settings.get_option("%s/resume_paused" % self.player._name, False):
                    self.player.toggle_pause()
                self.player._playtime_stamp = state['_playtime_stamp']

            self.player._set_volume(vol)
Esempio n. 8
0
    def set_option(self, option, value, save=True):
        """
            Set an option (in ``section/key`` syntax) to the specified value

            :param option: the full path to an option
            :type option: string
            :param value: the value the option should be assigned
            :type value: any
            :param save: If True, cause the settings to be written to file
        """
        value = self._val_to_str(value)
        splitvals = option.split('/')
        section, key = "/".join(splitvals[:-1]), splitvals[-1]

        try:
            self.set(section, key, value)
        except NoSectionError:
            self.add_section(section)
            self.set(section, key, value)

        self._dirty = True
        
        if save:
            self.delayed_save()

        section = section.replace('/', '_')

        event.log_event('option_set', self, option)
        event.log_event('%s_option_set' % section, self, option)
Esempio n. 9
0
    def get_cddb_info(self):
        try:
            disc = DiscID.open(self.device)
            self.info = DiscID.disc_id(disc)
            status, info = CDDB.query(self.info)
        except IOError:
            return

        if status in (210, 211):
            info = info[0]
            status = 200
        if status != 200:
            return

        (status, info) = CDDB.read(info["category"], info["disc_id"])

        title = info["DTITLE"].split(" / ")
        for i in range(self.info[1]):
            tr = self[i]
            tr.set_tag_raw("title", info["TTITLE" + str(i)].decode("iso-8859-15", "replace"))
            tr.set_tag_raw("album", title[1].decode("iso-8859-15", "replace"))
            tr.set_tag_raw("artist", title[0].decode("iso-8859-15", "replace"))
            tr.set_tag_raw("year", info["EXTD"].replace("YEAR: ", ""))
            tr.set_tag_raw("genre", info["DGENRE"])

        self.name = title[1].decode("iso-8859-15", "replace")
        event.log_event("cddb_info_retrieved", self, True)
Esempio n. 10
0
    def unregister_provider(self, servicename, provider, target=None):
        """
            Unregisters a provider.

            :param servicename: the name of the service
            :type servicename: string
            :param provider: the provider to be removed
            :type provider: object
            :param target: a specific target for the service [object]
            :type target: object
        """
        if servicename not in self.services:
            return
        try:
            service = self.services[servicename]
            if provider in service[target]:
                service[target].remove(provider)
                logger.debug(
                    "Provider %(provider)s unregistered from "
                    "service %(service)s with target %(target)s" % {
                        'provider' : provider.name,
                        'service' : servicename,
                        'target' : target
                    }
                )
                event.log_event("%s_provider_removed" % servicename, self, (provider, target))
                if not service[target]: #no values for target key then del it
                    del service[target]
        except KeyError:
            return
Esempio n. 11
0
    def play(self, track):
        """
            plays the specified track, overriding any currently playing track

            if the track cannot be played, playback stops completely
        """
        if track is None:
            self.stop()
            return False
        else:
            self.stop(fire=False)

        playing = self.is_playing()

        self._current = track

        uri = track.get_loc_for_io()
        logger.info("Playing %s" % uri)
        self.reset_playtime_stamp()

        self.playbin.set_property("uri", uri)
        if urlparse.urlsplit(uri)[0] == "cdda":
            self.notify_id = self.playbin.connect('notify::source',
                    self.__notify_source)

        self.playbin.set_state(gst.STATE_PLAYING)
        if not playing:
            event.log_event('playback_player_start', self, track)
        event.log_event('playback_track_start', self, track)

        return True
Esempio n. 12
0
 def update_field(self, name, *params):
     try:
         self[name] = getattr(self, '_'+name.replace('-', '_'))(*params)
     except:
         self[name] = ''
     if name in [f[0] for f in self.get_template_fields()]:
         event.log_event('field_refresh', self, (name, self[name]))
Esempio n. 13
0
    def remove_library(self, library):
        """
            Remove a library from the collection

            :param library: the library to remove
            :type library: :class:`Library`
        """
        for k, v in self.libraries.iteritems():
            if v == library:
                del self.libraries[k]
                break

        to_rem = []
        if not "://" in library.location:
            location = u"file://" + library.location
        else:
            location = library.location
        for tr in self.tracks:
            if tr.startswith(location):
                to_rem.append(self.tracks[tr]._track)
        self.remove_tracks(to_rem)

        self.serialize_libraries()
        self._dirty = True

        if self._frozen:
            self._libraries_dirty = True
        else:
            event.log_event('libraries_modified', self, None)
Esempio n. 14
0
    def seek(self, value):
        """
            seek to the given position in the current stream
        """
        self.streams[self._current_stream].seek(value)
        self._reset_crossfade_timer()

        event.log_event('playback_seeked', self, value)
Esempio n. 15
0
 def remove_device(self, device):
     try:
         if device.connected:
             device.disconnect()
         del self.devices[device.get_name()]
     except KeyError:
         pass
     event.log_event("device_removed", self, device)
Esempio n. 16
0
 def __set_connected(self, val):
     prior = self._connected
     self._connected = val
     if prior != val:
         if val:
             event.log_event("device_connected", self, self)
         else:
             event.log_event("device_disconnected", self, self)
Esempio n. 17
0
 def link_clicked(self, link):
     if link[0] == 'rate':
         self.track.set_rating(int(link[1]))
         self.refresh_rating()
         for field in ['rating', 'track-info']:
             if field in self.get_template_fields():
                 event.log_event('field_refresh', self, (field, str(self[field])))
         return True
Esempio n. 18
0
 def do_rating_changed(self, rating):
     """
         Updates the rating of the currently playing track
     """
     if player.PLAYER.current is not None:
         player.PLAYER.current.set_rating(rating)
         maximum = settings.get_option('rating/maximum', 5)
         event.log_event('rating_changed', self, rating / maximum * 100)
Esempio n. 19
0
    def SetRating(self, value):
        """
            Sets the current track's rating

            :param value: the new rating
            :type value: int
        """
        self.SetTrackAttr('__rating', value)
        event.log_event('rating_changed', self, value)
Esempio n. 20
0
 def __set_queue_has_tracks(self, value):
     if value != self.__queue_has_tracks_val:
         oldpos = self.current_position
         self.__queue_has_tracks_val = value
         event.log_event(
             "playlist_current_position_changed",
             self,
             (self.current_position, oldpos),
         )
Esempio n. 21
0
 def engine_notify_error(self, msg):
     '''
         Notification that some kind of error has occurred. If the error
         is not recoverable, the engine is expected to stop playback and
         reset itself to a state where playback can begin again.
         
         .. note:: Only to be called from engine
     '''
     event.log_event('playback_error', self, msg)
Esempio n. 22
0
    def seek(self, value):
        """
            Seek to a position in the currently playing stream

            :param value: the position in seconds
            :type value: int
        """
        if self._engine.seek(value):
            event.log_event('playback_seeked', self, value)
Esempio n. 23
0
 def _settle_state(self):
     self._settle_flag = 1
     if self._settle_trap > 10:
         self._settle_trap = 0
         self._settle_flag = 0
         logger.debug("Failed to settle state on %s."%self)
         gst.Bin.set_state(self, gst.STATE_NULL)
         event.log_event("stream_settled", self, None)
         return
     gobject.idle_add(self._settle_state_sub)
Esempio n. 24
0
 def pause(self):
     """
         pause playback. DOES NOT TOGGLE
     """
     if self.is_playing():
         self.pipe.set_state(gst.STATE_PAUSED)
         self._reset_crossfade_timer()
         event.log_event('playback_player_pause', self, self.current)
         return True
     return False
Esempio n. 25
0
 def pause(self):
     """
         pause playback. DOES NOT TOGGLE
     """
     if self.is_playing():
         self.update_playtime()
         self.playbin.set_state(gst.STATE_PAUSED)
         self.reset_playtime_stamp()
         event.log_event('playback_player_pause', self, self.current)
         return True
     return False
Esempio n. 26
0
    def add_device(self, device):
        # make sure we don't overwrite existing devices
        count = 3
        if device.get_name() in self.devices:
            device.name += " (2)"
        while device.get_name() in self.devices:
            device.name = device.name[:-4] + " (%s)" % count
            count += 1

        self.devices[device.get_name()] = device
        event.log_event("device_added", self, device)
Esempio n. 27
0
    def on_rating_changed(self, widget, rating):
        """
            Updates the rating of the selected tracks
        """
        tracks = self.get_selected_tracks()

        for track in tracks:
            track.set_rating(rating)

        maximum = settings.get_option('rating/maximum', 5)
        event.log_event('rating_changed', self, rating / maximum * 100)
Esempio n. 28
0
    def set_loc(self, loc):
        """
            Sets the location.

            :param loc: the location, as either a uri or a file path.
        """
        self.__unregister()
        gloc = Gio.File.new_for_commandline_arg(loc)
        self.__tags['__loc'] = gloc.get_uri()
        self.__register()
        event.log_event('track_tags_changed', self, '__loc')
Esempio n. 29
0
 def engine_notify_track_start(self, track):
     '''
         Called when a track has just entered the playing state
         
         :param track: Track that is being played now
         
         .. note:: Only to be called from engine
     '''
     
     self._reset_playtime_stamp()
     event.log_event('playback_track_start', self, track)
Esempio n. 30
0
    def disable(self, exaile):
        logger.debug('Disabling Preview Device')
        event.log_event('preview_device_disabling', self, None)
        self._destroy_gui_hooks()
        self._destroy_gui()
        self.player.destroy()

        self.player = None
        self.queue = None

        logger.debug('Preview Device Disabled')
Esempio n. 31
0
    def _handle_message(self, bus, message, reading_tag=False):

        if message.type == gst.MESSAGE_BUFFERING:
            percent = message.parse_buffering()
            if not percent < 100:
                logger.info('Buffering complete')
            if percent % 5 == 0:
                event.log_event('playback_buffering', self, percent)

        elif message.type == gst.MESSAGE_ELEMENT and \
                message.src == self._pipe and \
                message.structure.get_name() == 'playbin2-stream-changed' and \
                self._buffered_track is not None:
            self.queue.next(autoplay=False)
            self._next_track(self._buffered_track, already_playing=True)

        else:
            return False
        return True
Esempio n. 32
0
 def connect(self):
     try:
         self.bus = dbus.SystemBus()
         hal_obj = self.bus.get_object('org.freedesktop.Hal',
                                       '/org/freedesktop/Hal/Manager')
         self.hal = dbus.Interface(hal_obj, 'org.freedesktop.Hal.Manager')
         logger.debug("HAL Providers: %s" % repr(self.get_providers()))
         for p in self.get_providers():
             try:
                 self.on_provider_added(p)
             except:
                 logger.exception("Failed to load HAL devices for %s",
                                  p.name)
         self.setup_device_events()
         logger.debug("Connected to HAL")
         event.log_event("hal_connected", self, None)
     except:
         logger.warning("Failed to connect to HAL, " \
                 "autodetection of devices will be disabled.")
Esempio n. 33
0
 def connect(self):
     assert self._state == 'init'
     logger.debug("Connecting to %s", self.name)
     try:
         self.obj = self._connect()
         logger.info("Connected to %s", self.name)
         event.log_event("hal_connected", self, None)
     except Exception:
         logger.warning("Failed to connect to %s, " \
                 "autodetection of devices will be disabled.", self.name)
         common.log_exception()
         return False
         
     self._state = 'addremove'
     logger.debug("%s: state = addremove", self.name)
     self._add_all(self.obj)
     self._state = 'listening'
     logger.debug("%s: state = listening", self.name)
     return True
Esempio n. 34
0
    def add_tracks(self, tracks):
        """
            Like add(), but takes a list of :class:`xl.trax.Track`
        """
        locations = []
        now = time()
        for tr in tracks:
            if not tr.get_tag_raw('__date_added'):
                tr.set_tags(__date_added=now)
            location = tr.get_loc_for_io()
            # Don't add duplicates -- track URLs are unique
            if location in self.tracks:
                continue
            locations += [location]
            self.tracks[location] = TrackHolder(tr, self._key)
            self._key += 1

        if locations:
            event.log_event('tracks_added', self, locations)
            self._dirty = True
Esempio n. 35
0
    def set_cover(self, track, db_string, data=None):
        """
            Sets the cover for a track. This will overwrite any existing
            entry.

            :param track: The track to set the cover for
            :param db_string: the string identifying the source of the
                    cover, in "method:key" format.
            :param data: The raw cover data to store for the track.  Will
                    only be stored if the method has use_cache=True
        """
        name = db_string.split(":", 1)[0]
        method = self.methods.get(name)
        if method and method.use_cache and data:
            db_string = "cache:%s" % self.__cache.add(data)
        key = self._get_track_key(track)
        if key:
            self.db[key] = db_string
            self.timeout_save()
            event.log_event('cover_set', self, track)
Esempio n. 36
0
    def _set_direct(self, option, value):
        """
            Sets the option directly to the value,
            only for use in copying settings.

            :param option: the option path
            :type option: string
            :param value: the value to set
            :type value: any
        """
        splitvals = option.split('/')
        section, key = "/".join(splitvals[:-1]), splitvals[-1]

        try:
            self.set(section, key, value)
        except NoSectionError:
            self.add_section(section)
            self.set(section, key, value)

        event.log_event('option_set', self, option)
Esempio n. 37
0
    def _progress_update(self, type, library, count):
        """
        Called when a progress update should be emitted while scanning
        tracks
        """
        self._running_count = count
        count = count + self._running_total_count

        if self.file_count < 0:
            event.log_event('scan_progress_update', self, 0)
            return

        try:
            event.log_event(
                'scan_progress_update',
                self,
                count / self.file_count * 100,
            )
        except ZeroDivisionError:
            pass
Esempio n. 38
0
    def set_current_playlist(self, playlist):
        """
            Sets the playlist to be processed in the queue

            :param playlist: the playlist to process
            :type playlist: :class:`xl.playlist.Playlist`

            .. note:: The following :doc:`events </xl/event>` will be emitted by this method:

                * `queue_current_playlist_changed`: indicates that the queue playlist has been changed
        """
        if playlist is self.__current_playlist:
            return
        elif playlist is None:
            playlist = self

        if playlist is self:
            self.__queue_has_tracks = True

        self.__current_playlist = playlist
        event.log_event('queue_current_playlist_changed', self, playlist)
Esempio n. 39
0
    def engine_notify_track_end(self, track, done):
        """
        Called when a track has been stopped. Either:

        - stop() was called
        - play() was called and the prior track was stopped

        :param track: Must be the track that was just playing, and must
                      never be None

        :param done:  If True, no further tracks will be played

        .. note:: Only to be called from engine
        """

        self._update_playtime(track)
        event.log_event('playback_track_end', self, track)

        if done:
            self._cancel_delayed_start()
            event.log_event('playback_player_end', self, track)
Esempio n. 40
0
    def on_message(self, bus, message, reading_tag = False):
        """
            Called when a message is received from gstreamer
        """
        if message.type == gst.MESSAGE_TAG and self.tag_func:
            self.tag_func(message.parse_tag())
            if not self.current.get_tag_raw('__length'):
                try:
                    duration = float(self.playbin.query_duration(
                            gst.FORMAT_TIME, None)[0])/1000000000
                    if duration > 0:
                        self.current.set_tag_raw('__length', duration)
                except gst.QueryError:
                    logger.error("Couldn't query duration")
        elif message.type == gst.MESSAGE_EOS and not self.is_paused():
            self.eof_func()
        elif message.type == gst.MESSAGE_ERROR:
            logger.error("%s %s" %(message, dir(message)) )
            a = message.parse_error()[0]
            gobject.idle_add(self._on_playback_error, a.message)

            # TODO: merge this into stop() and make it engine-agnostic somehow
            curr = self.current
            self._current = None
            self.playbin.set_state(gst.STATE_NULL)
            self.setup_pipe()
            event.log_event("playback_track_end", self, curr)
            event.log_event("playback_player_end", self, curr)
        elif message.type == gst.MESSAGE_BUFFERING:
            percent = message.parse_buffering()
            if not percent < 100:
                logger.info('Buffering complete')
            if percent % 5 == 0:
                event.log_event('playback_buffering', self, percent)
        return True
Esempio n. 41
0
    def play(self, track, start_at=None, paused=False):
        """
        Starts the playback with the provided track
        or stops the playback it immediately if none

        :param track: the track to play or None
        :type track: :class:`xl.trax.Track`
        :param start_at: The offset to start playback at, in seconds
        :param paused: If True, start the track in 'paused' mode

        .. note:: The following :doc:`events </xl/event>` will be emitted by this method:

            * `playback_player_start`: indicates the start of playback overall
            * `playback_track_start`: indicates playback start of a track
        """
        if track is None:
            self.stop()
        else:
            if self.is_stopped():
                event.log_event('playback_player_start', self, track)

            play_args = self._get_play_params(track, start_at, paused, False)
            self._engine.play(*play_args)
            if play_args[2]:
                event.log_event('playback_player_pause', self, track)
                event.log_event("playback_toggle_pause", self, track)
Esempio n. 42
0
    def stop(self, _fire=True, **kwargs):
        """
            Stops the playback

            :param fire: Send the 'playback_player_end' event. Used by engines
                to avoid spurious playback_end events. Not public API.

            .. note:: The following :doc:`events </xl/event>` will be emitted by this method:

                * `playback_player_end`: indicates the end of playback overall
                * `playback_track_end`: indicates playback end of a track
        """
        self._cancel_delayed_start()
        self._cancel_stop_offset()
        
        if self.is_playing() or self.is_paused():
            prev_current = self._stop(**kwargs)

            if _fire:
                event.log_event('playback_player_end', self, prev_current)
            return True
        return False
Esempio n. 43
0
    def _stop(self, _onlyfire=False):
        """
            Stops playback.

            The following parameters are for internal use only and are
            not public API.

            :param onlyfire: Only send the _end event(s), don't actually
                         halt playback. This is used at the end of a playlist,
                         because the gapless mechanism will fire to tell us to
                         load the next track for buffering, but since there
                         isn't one if we actually halt the player the last few
                         moments of the prior track will be cut off.
        """
        self._buffered_track = None
        current = self.current
        if not _onlyfire:
            self._pipe.set_state(gst.STATE_NULL)
        self._update_playtime()
        self._current = None
        event.log_event('playback_track_end', self, current)
        return current
Esempio n. 44
0
    def unpause(self):
        """
        Resumes the playback if it is paused, does not toggle it

        :returns: True if paused, False otherwise

        .. note:: The following :doc:`events </xl/event>` will be emitted by this method:

            * `playback_player_resume`: indicates that the playback has been resumed
            * `playback_toggle_pause`: indicates that the playback has been paused or resumed
        """
        self._cancel_delayed_start()
        if self.is_paused():

            self._reset_playtime_stamp()
            self._engine.unpause()

            current = self.current
            event.log_event('playback_player_resume', self, current)
            event.log_event("playback_toggle_pause", self, current)
            return True
        return False
Esempio n. 45
0
    def next(self, autoplay=True, track=None):
        """
            Goes to the next track, either in the queue, or in the current
            playlist.  If a track is passed in, that track is played

            :param autoplay: play the track in addition to returning it
            :type autoplay: bool
            :param track: if passed, play this track
            :type track: :class:`xl.trax.Track`

            .. note:: The following :doc:`events </xl/event>` will be emitted by this method:

                * `playback_playlist_end`: indicates that the end of the queue has been reached
        """
        if track is None:
            if self.__queue_has_tracks:
                if not self.__remove_item_on_playback:
                    track = super().next()
                else:
                    try:
                        track = self.pop(0)
                    except IndexError:
                        pass

                # reached the end of the internal queue, don't repeat
                if track is None:
                    self.__queue_has_tracks = False

            if track is None and self.current_playlist is not self:
                track = self.current_playlist.next()

        if autoplay:
            self.player.play(track)

        if not track:
            event.log_event("playback_playlist_end", self,
                            self.current_playlist)
        return track
Esempio n. 46
0
 def unlink_stream(self, stream):
     try:
         current = stream.get_track()
         pad = stream.get_static_pad("src").get_peer()
         stream.unlink(self.adder)
         try:
             self.adder.release_request_pad(pad)
         except TypeError:
             pass
         gobject.idle_add(stream.set_state, gst.STATE_NULL)
         try:
             self.pipe.remove(stream)
         except gst.RemoveError:
             logger.debug("Failed to remove stream %s"%stream)
         if stream in self.streams:
             self.streams[self.streams.index(stream)] = None
         event.log_event("playback_track_end", self, current)
         return True
     except AttributeError:
         return True
     except:
         common.log_exception(log=logger)
         return False
Esempio n. 47
0
    def play(self, track, stop_last=True):
        """
            plays the specified track, overriding any currently playing track

            if the track cannot be played, playback stops completely
        """
        if track is None:
            self.stop()
            return False
        elif stop_last:
            self.stop(fire=False)
        else:
            self.stop(fire=False, onlyfire=True)

        playing = self.is_playing()

        if not playing:
            event.log_event_sync('playback_reconfigure_bins', self, None)

        self._current = track

        uri = track.get_loc_for_io()
        logger.info("Playing %s" % uri)
        self.reset_playtime_stamp()

        self.playbin.set_property("uri", uri)
        if urlparse.urlsplit(uri)[0] == "cdda":
            self.notify_id = self.playbin.connect('notify::source',
                    self.__notify_source)

        self.playbin.set_state(gst.STATE_PLAYING)
        if not playing:
            event.log_event('playback_player_start', self, track)
        event.log_event('playback_track_start', self, track)

        return True
Esempio n. 48
0
    def set_tag_raw(self, tag, values, notify_changed=True):
        """
            Set the raw value of a tag.

            :param tag: The name of the tag to set.
            :param values: The value or values to set the tag to.
            :param notify_changed: whether to send a signal to let other
                parts of Exaile know there has been an update. Only set
                this to False if you know that no other parts of Exaile
                need to be updated.
        """
        # handle values that aren't lists
        if not isinstance(values, list):
            if not tag.startswith("__"):  # internal tags dont have to be lists
                values = [values]

        # TODO: is this needed? why?
        # for lists, filter out empty values and convert to unicode
        if isinstance(values, list):
            values = [
                common.to_unicode(x, self.__tags.get('__encoding'))
                for x in values if x not in (None, '')
            ]

        # save some memory by not storing null values.
        if not values:
            try:
                del self.__tags[tag]
            except KeyError:
                pass
        else:
            self.__tags[tag] = values

        self._dirty = True
        if notify_changed:
            event.log_event("track_tags_changed", self, tag)
Esempio n. 49
0
    def register_provider(self, servicename, provider, target=None):
        """
            Registers a provider for a service. The provider object is used
            by consumers of the service.

            Services can be targeted for a specific use. For example, if you
            have a widget that uses a service 'foo', if your object can perform
            a service only for a specific type of widget, then target would be
            set to the widget type.

            If you had a service that could perform 'foo' for all widgets, then
            target would be set to None, and all widgets could use your service.

            It is intended that most services should set target to None, with
            some narrow exceptions.

            :param servicename: the name of the service [string]
            :type servicename: string
            :param provider: the object that is the provider [object]
            :type provider: object
            :param target: a specific target for the service [object]
            :type target: object
        """
        service = self.services.setdefault(servicename, {})
        providers = service.setdefault(target, [])
        if provider not in providers:
            providers.append(provider)
            logger.debug(
                "Provider %(provider)s registered for service %(service)s "
                "with target %(target)s" % {
                    'provider': provider.name,
                    'service': servicename,
                    'target': target
                })
            event.log_event("%s_provider_added" % servicename, self,
                            (provider, target))
Esempio n. 50
0
    def _next_track(self, track, already_playing=False):
        """
            internal api: advances the track to the next track
        """
        if track is None:
            self.stop()
            return False
        elif not already_playing:
            self.stop(_fire=False)
        else:
            self.stop(_fire=False, _onlyfire=True)

        playing = self.is_playing()

        if not playing:
            event.log_event('playback_reconfigure_bins', self, None)

        self._current = track

        uri = track.get_loc_for_io()
        logger.info("Playing %s" % common.sanitize_url(uri))
        self._reset_playtime_stamp()

        if not already_playing:
            self._pipe.set_property("uri", uri)
            if urlparse.urlsplit(uri)[0] == "cdda":
                self.notify_id = self._pipe.connect('notify::source',
                                                    self.__notify_source)

            self._pipe.set_state(gst.STATE_PLAYING)

        if not playing:
            event.log_event('playback_player_start', self, track)
        event.log_event('playback_track_start', self, track)

        if playing and self._should_delay_start():
            self._last_position = 0

        self._setup_startstop_offsets(track)

        return True
Esempio n. 51
0
 def _run():
     on_ui_thread[0] = False
     event.log_event('test', ucb, None)
Esempio n. 52
0
    def quit(self, restart=False):
        """
            Exits Exaile normally. Takes care of saving
            preferences, databases, etc.

            :param restart: Whether to directly restart
            :type restart: bool
        """
        if self.quitting:
            return
        self.quitting = True
        logger.info("Exaile is shutting down...")

        logger.info("Disabling plugins...")
        for k, plugin in self.plugins.enabled_plugins.iteritems():
            if hasattr(plugin, 'teardown'):
                try:
                    plugin.teardown(self)
                except Exception:
                    pass

        from xl import event
        # this event should be used by modules that dont need
        # to be saved in any particular order. modules that might be
        # touched by events triggered here should be added statically
        # below.
        event.log_event("quit_application", self, None)

        logger.info("Saving state...")
        self.plugins.save_enabled()

        if self.gui:
            self.gui.quit()

        from xl import covers
        covers.MANAGER.save()

        self.collection.save_to_location()

        # Save order of custom playlists
        self.playlists.save_order()
        self.stations.save_order()

        # save player, queue
        from xl import player
        player.QUEUE._save_player_state(
            os.path.join(xdg.get_data_dir(), 'player.state'))
        player.QUEUE.save_to_location(
            os.path.join(xdg.get_data_dir(), 'queue.state'))
        player.PLAYER.stop()

        from xl import settings
        settings.MANAGER.save()

        if restart:
            logger.info("Restarting...")
            logger_setup.stop_logging()
            python = sys.executable
            if sys.platform == 'win32':
                # Python Win32 bug: it does not quote individual command line
                # arguments. Here we do it ourselves and pass the whole thing
                # as one string.
                # See https://bugs.python.org/issue436259 (closed wontfix).
                import subprocess
                cmd = [python] + sys.argv
                cmd = subprocess.list2cmdline(cmd)
                os.execl(python, cmd)
            else:
                os.execl(python, python, *sys.argv)

        logger.info("Bye!")
        logger_setup.stop_logging()
        sys.exit(0)
Esempio n. 53
0
 def on_provider_removed(self, station):
     if station.name in self.stations:
         del self.stations[station.name]
         event.log_event('station_removed', self, station)
Esempio n. 54
0
 def on_provider_added(self, station):
     if not station.name in self.stations:
         self.stations[station.name] = station
         event.log_event('station_added', self, station)
Esempio n. 55
0
    def set_rating(self, rating):
        prev_rating = trax.Track.get_rating(self)
        trax.Track.set_rating(self, rating)

        event.log_event('doubanfm_track_rating_change', self,
                        (prev_rating, rating))
Esempio n. 56
0
    def play(self, track, user=True):
        if not track:
            return # we cant play nothing

        playing = self.is_playing()

        logger.debug("Attmepting to play \"%s\""%track)
        next = 1-self._current_stream

        if self.streams[next]:
            self.unlink_stream(self.streams[next])

        fading = False
        duration = 0

        if user:
            if settings.get_option("player/user_fade_enabled", False):
                fading = True
                duration = settings.get_option("player/user_fade", 1000)
            else:
                self.unlink_stream(self.streams[self._current_stream])
        else:
            if settings.get_option("player/crossfading", False):
                fading = True
                duration = settings.get_option(
                        "player/crossfade_duration", 3000)
            else:
                self.unlink_stream(self.streams[self._current_stream])

        self.streams[next] = AudioStream("Stream%s"%(next), caps=self.caps)
        self.streams[next].dec.connect("drained", self._on_drained,
                self.streams[next])

        if not self.link_stream(self.streams[next], track):
            return False

        if fading:
            self.streams[next].set_volume(0)

        self.pipe.set_state(gst.STATE_PLAYING)
        self.streams[next]._settle_flag = 1
        gobject.idle_add(self.streams[next].set_state, gst.STATE_PLAYING)
        gobject.idle_add(self._set_state, self.pipe, gst.STATE_PLAYING)

        if fading:
            timeout = int(float(duration)/float(100))
            if self.streams[next]:
                gobject.timeout_add(timeout, self._fade_stream,
                        self.streams[next], 1)
            if self.streams[self._current_stream]:
                gobject.timeout_add(timeout, self._fade_stream,
                        self.streams[self._current_stream], -1, True)
            if settings.get_option("player/crossfading", False):
                time = int(track.get_tag_raw("__length")*1000 - duration)
                gobject.timer_id = gobject.timeout_add(time,
                        self._start_crossfade)

        self._current_stream = next
        if not playing:
            event.log_event('playback_player_start', self, track)
        event.log_event('playback_track_start', self, track)

        return True
Esempio n. 57
0
    def on_message(self, bus, message):
        '''
            This is called on the main thread
        '''

        if message.type == Gst.MessageType.BUFFERING:
            percent = message.parse_buffering()
            if not percent < 100:
                self.logger.info('Buffering complete')
            if percent % 5 == 0:
                event.log_event('playback_buffering', self.engine.player,
                                percent)

        elif message.type == Gst.MessageType.TAG:
            """ Update track length and optionally metadata from gstreamer's parser.
                Useful for streams and files mutagen doesn't understand. """

            current = self.current_track

            if not current.is_local():
                gst_utils.parse_stream_tags(current, message.parse_tag())

            if current and not current.get_tag_raw('__length'):
                res, raw_duration = self.playbin.query_duration(
                    Gst.Format.TIME)
                if not res:
                    self.logger.error("Couldn't query duration")
                    raw_duration = 0
                duration = float(raw_duration) / Gst.SECOND
                if duration > 0:
                    current.set_tag_raw('__length', duration)

        elif (message.type == Gst.MessageType.EOS
              and not self.get_gst_state() == Gst.State.PAUSED):
            self.engine._eos_func(self)

        elif (message.type == Gst.MessageType.STREAM_START
              and message.src == self.playbin
              and self.buffered_track is not None):

            # This handles starting the next track during gapless transition
            buffered_track = self.buffered_track
            self.buffered_track = None
            play_args = self.engine.player.engine_autoadvance_notify_next(
                buffered_track) + (True, True)
            self.engine._next_track(*play_args)

        elif message.type == Gst.MessageType.STATE_CHANGED:

            # This idea from quodlibet: pulsesink will not notify us when
            # volume changes if the stream is paused, so do it when the
            # state changes.
            if message.src == self.audio_sink:
                self.playbin.notify("volume")

        elif message.type == Gst.MessageType.ERROR:
            self.__handle_error_message(message)

        elif message.type == Gst.MessageType.ELEMENT:
            if not missing_plugin.handle_message(message, self.engine):
                logger.debug(
                    "Unexpected element-specific GstMessage received from %s: %s",
                    message.src,
                    message,
                )

        elif message.type == Gst.MessageType.WARNING:
            # TODO there might be some useful warnings we ignore for now.
            gerror, debug_text = Gst.Message.parse_warning(message)
            logger.warn(
                "Unhandled GStreamer warning received:\n\tGError: %s\n\tDebug text: %s",
                gerror,
                debug_text,
            )

        else:
            # TODO there might be some useful messages we ignore for now.
            logger.debug("Unhandled GstMessage of type %s received: %s",
                         message.type, message)
Esempio n. 58
0
def __notify_user_on_error(message_text, engine):
    event.log_event('playback_error', engine.player, message_text)
Esempio n. 59
0
    def __init(self):
        """
            Initializes Exaile
        """
        # pylint: disable-msg=W0201
        logger.info("Loading Exaile %s on Python %s..." %
                    (__version__, platform.python_version()))

        logger.info("Loading settings...")
        try:
            from xl import settings
        except common.VersionError:
            logger.exception("Error loading settings")
            sys.exit(1)

        logger.debug("Settings loaded from %s" % settings.location)

        # display locale information if available
        try:
            import locale
            lc, enc = locale.getlocale()
            if enc is not None:
                logger.info("Using %s %s locale" % (lc, enc))
            else:
                logger.info("Using unknown locale")
        except Exception:
            pass

        splash = None

        if self.options.StartGui:
            if settings.get_option('gui/use_splash', True):
                from xlgui.widgets.info import Splash

                splash = Splash()
                splash.show()

        firstrun = settings.get_option("general/first_run", True)

        if not self.options.NoImport and \
                (firstrun or self.options.ForceImport):
            try:
                sys.path.insert(0, xdg.get_data_path("migrations"))
                import migration_200907100931 as migrator
                del sys.path[0]
                migrator.migrate(force=self.options.ForceImport)
                del migrator
            except Exception:
                logger.exception("Failed to migrate from 0.2.14")

        # Migrate old rating options
        from xl.migrations.settings import rating
        rating.migrate()

        # Migrate builtin OSD to plugin
        from xl.migrations.settings import osd
        osd.migrate()

        # Migrate engines
        from xl.migrations.settings import engine
        engine.migrate()

        # TODO: enable audio plugins separately from normal
        #       plugins? What about plugins that use the player?

        # Gstreamer doesn't initialize itself automatically, and fails
        # miserably when you try to inherit from something and GST hasn't
        # been initialized yet. So this is here.
        from gi.repository import Gst
        Gst.init(None)

        # Initialize plugin manager
        from xl import plugins
        self.plugins = plugins.PluginsManager(self)

        if not self.options.SafeMode:
            logger.info("Loading plugins...")
            self.plugins.load_enabled()
        else:
            logger.info("Safe mode enabled, not loading plugins.")

        # Initialize the collection
        logger.info("Loading collection...")
        from xl import collection
        try:
            self.collection = collection.Collection("Collection",
                                                    location=os.path.join(
                                                        xdg.get_data_dir(),
                                                        'music.db'))
        except common.VersionError:
            logger.exception("VersionError loading collection")
            sys.exit(1)

        from xl import event
        # Set up the player and playback queue
        from xl import player
        event.log_event("player_loaded", player.PLAYER, None)

        # Initalize playlist manager
        from xl import playlist
        self.playlists = playlist.PlaylistManager()
        self.smart_playlists = playlist.SmartPlaylistManager(
            'smart_playlists', collection=self.collection)
        if firstrun:
            self._add_default_playlists()
        event.log_event("playlists_loaded", self, None)

        # Initialize dynamic playlist support
        from xl import dynamic
        dynamic.MANAGER.collection = self.collection

        # Initalize device manager
        logger.info("Loading devices...")
        from xl import devices
        self.devices = devices.DeviceManager()
        event.log_event("device_manager_ready", self, None)

        # Initialize dynamic device discovery interface
        # -> if initialized and connected, then the object is not None

        self.udisks2 = None
        self.udisks = None
        self.hal = None

        if self.options.Hal:
            from xl import hal

            udisks2 = hal.UDisks2(self.devices)
            if udisks2.connect():
                self.udisks2 = udisks2
            else:
                udisks = hal.UDisks(self.devices)
                if udisks.connect():
                    self.udisks = udisks
                else:
                    self.hal = hal.HAL(self.devices)
                    self.hal.connect()
        else:
            self.hal = None

        # Radio Manager
        from xl import radio
        self.stations = playlist.PlaylistManager('radio_stations')
        self.radio = radio.RadioManager()

        self.gui = None
        # Setup GUI
        if self.options.StartGui:
            logger.info("Loading interface...")

            import xlgui
            self.gui = xlgui.Main(self)
            self.gui.main.window.show_all()
            event.log_event("gui_loaded", self, None)

            if splash is not None:
                splash.destroy()

        if firstrun:
            settings.set_option("general/first_run", False)

        self.loading = False
        Exaile._exaile = self
        event.log_event("exaile_loaded", self, None)

        restore = True

        if self.gui:
            # Find out if the user just passed in a list of songs
            # TODO: find a better place to put this

            songs = [
                Gio.File.new_for_path(arg).get_uri()
                for arg in self.options.locs
            ]
            if len(songs) > 0:
                restore = False
                self.gui.open_uri(songs[0], play=True)
                for arg in songs[1:]:
                    self.gui.open_uri(arg)

            # kick off autoscan of libraries
            # -> don't do it in command line mode, since that isn't expected
            self.gui.rescan_collection_with_progress(True)

        if restore:
            player.QUEUE._restore_player_state(
                os.path.join(xdg.get_data_dir(), 'player.state'))
Esempio n. 60
0
    def rescan(self,
               notify_interval: Optional[int] = None,
               force_update: bool = False):  # TODO: What return type?
        """
        Rescan the associated folder and add the contained files
        to the Collection
        """
        # TODO: use gio's cancellable support

        if self.collection is None:
            return True

        if self.scanning:
            return

        logger.info("Scanning library: %s", self.location)
        self.scanning = True
        libloc = Gio.File.new_for_uri(self.location)

        count = 0
        dirtracks = deque()
        compilations = deque()
        ccheck = {}
        for fil in common.walk(libloc):
            count += 1
            type = fil.query_info("standard::type",
                                  Gio.FileQueryInfoFlags.NONE,
                                  None).get_file_type()
            if type == Gio.FileType.DIRECTORY:
                if dirtracks:
                    for tr in dirtracks:
                        self._check_compilation(ccheck, compilations, tr)
                    for (basedir, album) in compilations:
                        base = basedir.replace('"', '\\"')
                        alb = album.replace('"', '\\"')
                        items = [
                            tr for tr in dirtracks
                            if tr.get_tag_raw('__basedir') == base and
                            # FIXME: this is ugly
                            alb in "".join(tr.get_tag_raw('album')
                                           or []).lower()
                        ]
                        for item in items:
                            item.set_tag_raw('__compilation', (basedir, album))
                dirtracks = deque()
                compilations = deque()
                ccheck = {}
            elif type == Gio.FileType.REGULAR:
                tr = self.update_track(fil, force_update=force_update)
                if not tr:
                    continue

                if dirtracks is not None:
                    dirtracks.append(tr)
                    # do this so that if we have, say, a 4000-song folder
                    # we dont get bogged down trying to keep track of them
                    # for compilation detection. Most albums have far fewer
                    # than 110 tracks anyway, so it is unlikely that this
                    # restriction will affect the heuristic's accuracy.
                    # 110 was chosen to accomodate "top 100"-style
                    # compilations.
                    if len(dirtracks) > 110:
                        logger.debug(
                            "Too many files, skipping "
                            "compilation detection heuristic for %s",
                            fil.get_uri(),
                        )
                        dirtracks = None

            if self.collection and self.collection._scan_stopped:
                self.scanning = False
                logger.info("Scan canceled")
                return

            # progress update
            if notify_interval is not None and count % notify_interval == 0:
                event.log_event('tracks_scanned', self, count)

        # final progress update
        if notify_interval is not None:
            event.log_event('tracks_scanned', self, count)

        removals = deque()
        for tr in self.collection.tracks.values():
            tr = tr._track
            loc = tr.get_loc_for_io()
            if not loc:
                continue
            gloc = Gio.File.new_for_uri(loc)
            try:
                if not gloc.has_prefix(libloc):
                    continue
            except UnicodeDecodeError:
                logger.exception("Error decoding file location")
                continue

            if not gloc.query_exists(None):
                removals.append(tr)

        for tr in removals:
            logger.debug("Removing %s", tr)
            self.collection.remove(tr)

        logger.info("Scan completed: %s", self.location)
        self.scanning = False