Example #1
0
    def __init__(self, player, library):
        super(SeekBar, self).__init__()

        self._elapsed_label = TimeLabel()
        self._remaining_label = TimeLabel()
        scale = Gtk.Scale(orientation=Gtk.Orientation.HORIZONTAL)
        scale.set_adjustment(Gtk.Adjustment.new(0, 0, 0, 3, 15, 0))
        scale.set_draw_value(False)
        self._scale = scale

        self.pack_start(Align(self._elapsed_label, border=6), False, True, 0)
        self.pack_start(scale, True, True, 0)
        self.pack_start(Align(self._remaining_label, border=6), False, True, 0)
        for child in self.get_children():
            child.show_all()

        self._id = self._scale.connect("value-changed", self._on_user_changed, player)
        self._scale.connect("value-changed", self._on_scale_value_changed, player)

        self._tracker = TimeTracker(player)
        self._tracker.connect("tick", self._on_tick, player)

        connect_destroy(player, "seek", self._on_player_seek)
        connect_destroy(player, "song-started", self._on_song_started)
        connect_destroy(player, "notify::seekable", self._on_seekable_changed)

        connect_destroy(library, "changed", self._on_song_changed, player)

        self.connect("destroy", self._on_destroy)

        with self._inhibit():
            self._update(player)
        self._tracker.tick()
Example #2
0
    def __init__(self, player, library):
        super(WaveformSeekBar, self).__init__()

        self._player = player
        self._rms_vals = []

        self._elapsed_label = TimeLabel()
        self._remaining_label = TimeLabel()
        self._waveform_scale = WaveformScale()

        self.pack_start(Align(self._elapsed_label, border=6), False, True, 0)
        self.pack_start(self._waveform_scale, True, True, 0)
        self.pack_start(Align(self._remaining_label, border=6), False, True, 0)

        for child in self.get_children():
            child.show_all()

        self._tracker = TimeTracker(player)
        self._tracker.connect('tick', self._on_tick, player)

        connect_destroy(player, 'seek', self._on_player_seek)
        connect_destroy(player, 'song-started', self._on_song_started)
        connect_destroy(player, 'song-ended', self._on_song_ended)
        connect_destroy(player, 'notify::seekable', self._on_seekable_changed)
        connect_destroy(library, 'changed', self._on_song_changed, player)

        self.connect('destroy', self._on_destroy)
        self._update(player)
        self._tracker.tick()

        if player.info:
            self._create_waveform(player.info, CONFIG.data_size)
Example #3
0
    def __init__(self, player, library):
        super(WaveformSeekBar, self).__init__()

        self._player = player
        self._rms_vals = []
        self._hovering = False

        self._elapsed_label = TimeLabel()
        self._remaining_label = TimeLabel()
        self._waveform_scale = WaveformScale(player)

        self.pack_start(Align(self._elapsed_label, border=6), False, True, 0)
        self.pack_start(self._waveform_scale, True, True, 0)
        self.pack_start(Align(self._remaining_label, border=6), False, True, 0)

        for child in self.get_children():
            child.show_all()
        self.set_time_label_visibility(CONFIG.show_time_labels)

        self._waveform_scale.connect('size-allocate',
                                     self._update_redraw_interval)
        self._waveform_scale.connect('motion-notify-event',
                                     self._on_mouse_hover)
        self._waveform_scale.connect('leave-notify-event',
                                     self._on_mouse_leave)

        self._label_tracker = TimeTracker(player)
        self._label_tracker.connect('tick', self._on_tick_label, player)

        self._redraw_tracker = TimeTracker(player)
        self._redraw_tracker.connect('tick', self._on_tick_waveform, player)

        connect_destroy(player, 'seek', self._on_player_seek)
        connect_destroy(player, 'song-started', self._on_song_started)
        connect_destroy(player, 'song-ended', self._on_song_ended)
        connect_destroy(player, 'notify::seekable', self._on_seekable_changed)
        connect_destroy(library, 'changed', self._on_song_changed, player)

        self.connect('destroy', self._on_destroy)
        self._update(player)

        if player.info:
            self._create_waveform(player.info, CONFIG.max_data_points)
Example #4
0
    def __init__(self, player, library):
        hbox = gtk.HBox(spacing=3)
        l = gtk.Label("0:00")
        hbox.pack_start(l)
        hbox.pack_start(
            gtk.Arrow(gtk.ARROW_RIGHT, gtk.SHADOW_NONE), expand=False)
        super(SeekBar, self).__init__(hbox)

        self.scale.connect('button-press-event', self.__seek_lock)
        self.scale.connect('button-release-event', self.__seek_unlock, player)
        self.scale.connect('key-press-event', self.__seek_lock)
        self.scale.connect('key-release-event', self.__seek_unlock, player)
        self.connect('scroll-event', self.__scroll, player)
        self.scale.connect('value-changed', self.__update_time, l)

        m = gtk.Menu()
        c = ConfigCheckMenuItem(
            _("Display remaining time"), "player", "time_remaining")
        c.set_active(config.getboolean("player", "time_remaining"))
        c.connect_object('toggled', self.scale.emit, 'value-changed')
        self.__remaining = c
        m.append(c)
        m.append(gtk.SeparatorMenuItem())
        i = qltk.MenuItem(_("_Edit Bookmarks..."), gtk.STOCK_EDIT)
        i.connect_object(
            'activate', bookmarks.EditBookmarks, self, library, player)
        m.append(i)
        m.show_all()
        self.child.connect_object(
            'button-press-event', self.__check_menu, m, player)
        self.connect_object('popup-menu', self.__popup_menu, m, player,
                self.child.child)

        timer = TimeTracker(player)
        timer.connect_object('tick', self.__check_time, player)

        player.connect('song-started', self.__song_changed, l, m)
        player.connect('seek', self.__seeked)
Example #5
0
class WaveformSeekBar(Gtk.Box):
    """A widget containing labels and the seekbar."""

    def __init__(self, player, library):
        super(WaveformSeekBar, self).__init__()

        self._player = player
        self._rms_vals = []
        self._hovering = False

        self._elapsed_label = TimeLabel()
        self._remaining_label = TimeLabel()
        self._waveform_scale = WaveformScale(player)

        self.pack_start(Align(self._elapsed_label, border=6), False, True, 0)
        self.pack_start(self._waveform_scale, True, True, 0)
        self.pack_start(Align(self._remaining_label, border=6), False, True, 0)

        for child in self.get_children():
            child.show_all()

        self._waveform_scale.connect('size-allocate',
                                     self._update_redraw_interval)
        self._waveform_scale.connect('motion-notify-event',
                                     self._on_mouse_hover)
        self._waveform_scale.connect('leave-notify-event',
                                     self._on_mouse_leave)

        self._label_tracker = TimeTracker(player)
        self._label_tracker.connect('tick', self._on_tick_label, player)

        self._redraw_tracker = TimeTracker(player)
        self._redraw_tracker.connect('tick', self._on_tick_waveform, player)

        connect_destroy(player, 'seek', self._on_player_seek)
        connect_destroy(player, 'song-started', self._on_song_started)
        connect_destroy(player, 'song-ended', self._on_song_ended)
        connect_destroy(player, 'notify::seekable', self._on_seekable_changed)
        connect_destroy(library, 'changed', self._on_song_changed, player)

        self.connect('destroy', self._on_destroy)
        self._update(player)

        if player.info:
            self._create_waveform(player.info, CONFIG.max_data_points)

    def _create_waveform(self, song, points):
        # Close any existing pipeline to avoid leaks
        self._clean_pipeline()

        if not song.is_file:
            return

        command_template = """
        uridecodebin name=uridec
        ! audioconvert
        ! level name=audiolevel interval={} post-messages=true
        ! fakesink sync=false"""
        interval = int(song("~#length") * 1E9 / points)
        print_d("Computing data for each %.3f seconds" % (interval / 1E9))

        command = command_template.format(interval)
        pipeline = Gst.parse_launch(command)
        pipeline.get_by_name("uridec").set_property("uri", song("~uri"))

        bus = pipeline.get_bus()
        self._bus_id = bus.connect("message", self._on_bus_message)
        bus.add_signal_watch()

        pipeline.set_state(Gst.State.PLAYING)

        self._pipeline = pipeline
        self._new_rms_vals = []

    def _on_bus_message(self, bus, message):
        if message.type == Gst.MessageType.ERROR:
            error, debug = message.parse_error()
            print_d("Error received from element {name}: {error}".format(
                name=message.src.get_name(), error=error))
            print_d("Debugging information: {}".format(debug))
        elif message.type == Gst.MessageType.ELEMENT:
            structure = message.get_structure()
            if structure.get_name() == "level":
                rms_db = structure.get_value("rms")
                if rms_db:
                    # Calculate average of all channels (usually 2)
                    rms_db_avg = sum(rms_db) / len(rms_db)
                    # Normalize dB value to value between 0 and 1
                    rms = pow(10, (rms_db_avg / 20))
                    self._new_rms_vals.append(rms)
            else:
                print_w("Got unexpected message of type {}"
                        .format(message.type))
        elif message.type == Gst.MessageType.EOS:
            self._clean_pipeline()

            # Update the waveform with the new data
            self._rms_vals = self._new_rms_vals
            self._waveform_scale.reset(self._rms_vals)
            self._waveform_scale.set_placeholder(False)
            self._update_redraw_interval()

            # Clear temporary reference to the waveform data
            del self._new_rms_vals

    def _clean_pipeline(self):
        if hasattr(self, "_pipeline") and self._pipeline:
            self._pipeline.set_state(Gst.State.NULL)
            if self._bus_id:
                bus = self._pipeline.get_bus()
                bus.remove_signal_watch()
                bus.disconnect(self._bus_id)
                self._bus_id = None
            if self._pipeline:
                self._pipeline = None

    def _update_redraw_interval(self, *args):
        if self._player.info and self.is_visible():
            # Must be recomputed when size is changed
            interval = self._waveform_scale.compute_redraw_interval()
            self._redraw_tracker.set_interval(interval)

    def _on_destroy(self, *args):
        self._clean_pipeline()
        self._label_tracker.destroy()
        self._redraw_tracker.destroy()

    def _on_tick_label(self, tracker, player):
        self._update_label(player)

    def _on_tick_waveform(self, tracker, player):
        self._update_waveform(player)

    def _on_seekable_changed(self, player, *args):
        self._update_label(player)

    def _on_player_seek(self, player, song, ms):
        self._update(player)

    def _on_song_changed(self, library, songs, player):
        if not player.info:
            return
        # Check that the currently playing song has changed
        if player.info in songs:
            # Trigger a re-computation of the waveform
            self._create_waveform(player.info, CONFIG.max_data_points)
            self._resize_labels(player.info)
            # Only update the label if some tag value changed
            self._update_label(player)

    def _on_song_started(self, player, song):
        if player.info:
            # Trigger a re-computation of the waveform
            self._create_waveform(player.info, CONFIG.max_data_points)
            self._resize_labels(player.info)

        self._waveform_scale.set_placeholder(True)
        self._update(player, True)

    def _on_song_ended(self, player, song, ended):
        self._update(player)

    def _update(self, player, full_redraw=False):
        self._update_label(player)
        self._update_waveform(player, full_redraw)

    def _update_label(self, player):
        if player.info:
            if self._hovering:
                # Show the position pointed by the mouse
                position = self._waveform_scale.get_mouse_position()
            else:
                # Show the position of the player (converted in seconds)
                position = player.get_position() / 1000.0
            length = player.info("~#length")
            remaining = length - position

            self._elapsed_label.set_time(position)
            self._remaining_label.set_time(remaining)

            self._elapsed_label.set_disabled(not player.seekable)
            self._remaining_label.set_disabled(not player.seekable)
            self.set_sensitive(player.seekable)
        else:
            self._remaining_label.set_disabled(True)
            self._elapsed_label.set_disabled(True)
            self.set_sensitive(False)

    def _update_waveform(self, player, full_redraw=False):
        if player.info:
            # Position in ms, length in seconds
            position = player.get_position() / 1000.0
            length = player.info("~#length")

            if length != 0:
                self._waveform_scale.set_position(position / length)
            else:
                print_d("Length reported as zero for %s" % player.info)
                self._waveform_scale.set_position(0)

            if position == 0 or full_redraw:
                self._waveform_scale.queue_draw()
            else:
                (x, y, w, h) = self._waveform_scale.compute_redraw_area()
                self._waveform_scale.queue_draw_area(x, y, w, h)
        else:
            self._waveform_scale.set_placeholder(True)
            self._waveform_scale.queue_draw()

    def _on_mouse_hover(self, _, event):
        def clamp(a, x, b):
            '''Return x if a <= x <= b, else the a or b nearest to x.'''
            return min(max(x, a), b)

        width = self._waveform_scale.get_allocation().width
        self._waveform_scale.set_mouse_x_position(clamp(0, event.x, width))

        if self._hovering:
            (x, y, w, h) = self._waveform_scale.compute_hover_redraw_area()
            self._waveform_scale.queue_draw_area(x, y, w, h)
        else:
            self._waveform_scale.queue_draw()

        self._update_label(self._player)
        self._hovering = True

    def _on_mouse_leave(self, _, event):
        self._waveform_scale.set_mouse_x_position(-1)
        self._waveform_scale.queue_draw()

        self._hovering = False
        self._update_label(self._player)

    def _resize_labels(self, song):
        """Resize the labels to make sure there is enough space to display the
        length of the songs.

        This prevents the waveform from changing size when the position changes
        from 9:59 to 10:00 for example."""
        length = util.format_time_display(song("~#length"))

        # Get the width needed to display the length of the song (the text
        # displayed in the labels will always be shorter than that)
        layout = self._remaining_label.get_layout()
        layout.set_text(length, -1)
        width, height = layout.get_pixel_size()

        # Set it as the minimum width of the labels to prevent them from
        # changing width
        self._remaining_label.set_size_request(width, -1)
        self._elapsed_label.set_size_request(width, -1)
Example #6
0
class WaveformSeekBar(Gtk.Box):
    """A widget containing labels and the seekbar."""
    def __init__(self, player, library):
        super().__init__()

        self._player = player
        self._rms_vals = []
        self._hovering = False

        self._elapsed_label = TimeLabel()
        self._remaining_label = TimeLabel()
        self._waveform_scale = WaveformScale(player)

        self.pack_start(Align(self._elapsed_label, border=6), False, True, 0)
        self.pack_start(self._waveform_scale, True, True, 0)
        self.pack_start(Align(self._remaining_label, border=6), False, True, 0)

        for child in self.get_children():
            child.show_all()
        self.set_time_label_visibility(CONFIG.show_time_labels)

        self._waveform_scale.connect('size-allocate',
                                     self._update_redraw_interval)
        self._waveform_scale.connect('motion-notify-event',
                                     self._on_mouse_hover)
        self._waveform_scale.connect('leave-notify-event',
                                     self._on_mouse_leave)

        self._label_tracker = TimeTracker(player)
        self._label_tracker.connect('tick', self._on_tick_label, player)

        self._redraw_tracker = TimeTracker(player)
        self._redraw_tracker.connect('tick', self._on_tick_waveform, player)

        connect_destroy(player, 'seek', self._on_player_seek)
        connect_destroy(player, 'song-started', self._on_song_started)
        connect_destroy(player, 'song-ended', self._on_song_ended)
        connect_destroy(player, 'notify::seekable', self._on_seekable_changed)
        connect_destroy(library, 'changed', self._on_song_changed, player)

        self.connect('destroy', self._on_destroy)
        self._update(player)

        if player.info:
            self._create_waveform(player.info, CONFIG.max_data_points)

    def set_time_label_visibility(self, is_visible):
        self._time_labels_visible = is_visible
        if is_visible:
            self._elapsed_label.show()
            self._remaining_label.show()
        else:
            self._elapsed_label.hide()
            self._remaining_label.hide()

    def _create_waveform(self, song, points):
        # Close any existing pipeline to avoid leaks
        self._clean_pipeline()

        if not song.is_file:
            return

        command_template = """
        uridecodebin name=uridec
        ! audioconvert
        ! level name=audiolevel interval={} post-messages=true
        ! fakesink sync=false"""
        interval = int(song("~#length") * 1E9 / points)
        if not interval:
            return
        print_d("Computing data for each %.3f seconds" % (interval / 1E9))

        command = command_template.format(interval)
        pipeline = Gst.parse_launch(command)
        pipeline.get_by_name("uridec").set_property("uri",
                                                    uri2gsturi(song("~uri")))

        bus = pipeline.get_bus()
        self._bus_id = bus.connect("message", self._on_bus_message, points)
        bus.add_signal_watch()

        pipeline.set_state(Gst.State.PLAYING)

        self._pipeline = pipeline
        self._new_rms_vals = []

    def _on_bus_message(self, bus, message, points):
        force_stop = False
        if message.type == Gst.MessageType.ERROR:
            error, debug = message.parse_error()
            print_d("Error received from element {name}: {error}".format(
                name=message.src.get_name(), error=error))
            print_d("Debugging information: {}".format(debug))
        elif message.type == Gst.MessageType.ELEMENT:
            structure = message.get_structure()
            if structure.get_name() == "level":
                rms_db = structure.get_value("rms")
                if rms_db:
                    # Calculate average of all channels (usually 2)
                    rms_db_avg = sum(rms_db) / len(rms_db)
                    # Normalize dB value to value between 0 and 1
                    rms = pow(10, (rms_db_avg / 20))
                    self._new_rms_vals.append(rms)
                    if len(self._new_rms_vals) >= points:
                        # The audio might be much longer than we anticipated
                        # and we would get way too many events due to the too
                        # short interval set.
                        force_stop = True
            else:
                print_w("Got unexpected message of type {}".format(
                    message.type))

        if message.type == Gst.MessageType.EOS or force_stop:
            self._clean_pipeline()

            # Update the waveform with the new data
            self._rms_vals = self._new_rms_vals
            self._waveform_scale.reset(self._rms_vals)
            self._update_redraw_interval()

            # Clear temporary reference to the waveform data
            del self._new_rms_vals

    def _clean_pipeline(self):
        if hasattr(self, "_pipeline") and self._pipeline:
            self._pipeline.set_state(Gst.State.NULL)
            if self._bus_id:
                bus = self._pipeline.get_bus()
                bus.remove_signal_watch()
                bus.disconnect(self._bus_id)
                self._bus_id = None
            if self._pipeline:
                self._pipeline = None

    def _update_redraw_interval(self, *args):
        if self._player.info and self.is_visible():
            # Must be recomputed when size is changed
            interval = self._waveform_scale.compute_redraw_interval()
            self._redraw_tracker.set_interval(interval)

    def _on_destroy(self, *args):
        self._clean_pipeline()
        self._label_tracker.destroy()
        self._redraw_tracker.destroy()

    def _on_tick_label(self, tracker, player):
        self._update_label(player)

    def _on_tick_waveform(self, tracker, player):
        self._update_waveform(player)

    def _on_seekable_changed(self, player, *args):
        self._update_label(player)

    def _on_player_seek(self, player, song, ms):
        self._update(player)

    def _on_song_changed(self, library, songs, player):
        if not player.info:
            return
        # Check that the currently playing song has changed
        if player.info in songs:
            # Trigger a re-computation of the waveform
            self._create_waveform(player.info, CONFIG.max_data_points)
            self._resize_labels(player.info)
            # Only update the label if some tag value changed
            self._update_label(player)

    def _on_song_started(self, player, song):
        if player.info:
            # Trigger a re-computation of the waveform
            self._create_waveform(player.info, CONFIG.max_data_points)
            self._resize_labels(player.info)

        self._rms_vals.clear()
        self._update(player, True)

    def _on_song_ended(self, player, song, ended):
        self._update(player)

    def _update(self, player, full_redraw=False):
        self._update_label(player)
        self._update_waveform(player, full_redraw)

    def _update_label(self, player):
        if not self._time_labels_visible:
            self.set_sensitive(player.info is not None and player.seekable)
            return

        if player.info:
            if self._hovering:
                # Show the position pointed by the mouse
                position = self._waveform_scale.get_mouse_position()
            else:
                # Show the position of the player (converted in seconds)
                position = player.get_position() / 1000.0
            length = player.info("~#length")
            remaining = length - position

            self._elapsed_label.set_time(position)
            self._remaining_label.set_time(remaining)

            self._elapsed_label.set_disabled(not player.seekable)
            self._remaining_label.set_disabled(not player.seekable)
            self.set_sensitive(player.seekable)
        else:
            self._remaining_label.set_disabled(True)
            self._elapsed_label.set_disabled(True)
            self.set_sensitive(False)

    def _update_waveform(self, player, full_redraw=False):
        if player.info:
            # Position in ms, length in seconds
            position = player.get_position() / 1000.0
            length = player.info("~#length")

            if length != 0:
                self._waveform_scale.set_position(position / length)
            else:
                print_d("Length reported as zero for %s" % player.info)
                self._waveform_scale.set_position(0)

            if position == 0 or full_redraw:
                self._waveform_scale.queue_draw()
            else:
                (x, y, w, h) = self._waveform_scale.compute_redraw_area()
                self._waveform_scale.queue_draw_area(x, y, w, h)
        else:
            self._rms_vals.clear()
            self._waveform_scale.queue_draw()

    def _on_mouse_hover(self, _, event):
        def clamp(a, x, b):
            """Return x if a <= x <= b, else the a or b nearest to x."""
            return min(max(x, a), b)

        width = self._waveform_scale.get_allocation().width
        self._waveform_scale.set_mouse_x_position(clamp(0, event.x, width))

        if self._hovering:
            (x, y, w, h) = self._waveform_scale.compute_hover_redraw_area()
            self._waveform_scale.queue_draw_area(x, y, w, h)
        else:
            self._waveform_scale.queue_draw()

        self._update_label(self._player)
        self._hovering = True

    def _on_mouse_leave(self, _, event):
        self._waveform_scale.set_mouse_x_position(-1)
        self._waveform_scale.queue_draw()

        self._hovering = False
        self._update_label(self._player)

    def _resize_labels(self, song):
        """Resize the labels to make sure there is enough space to display the
        length of the songs.

        This prevents the waveform from changing size when the position changes
        from 9:59 to 10:00 for example."""
        length = util.format_time_display(song("~#length"))

        # Get the width needed to display the length of the song (the text
        # displayed in the labels will always be shorter than that)
        layout = self._remaining_label.get_layout()
        layout.set_text(length, -1)
        width, height = layout.get_pixel_size()

        # Set it as the minimum width of the labels to prevent them from
        # changing width
        self._remaining_label.set_size_request(width, -1)
        self._elapsed_label.set_size_request(width, -1)
Example #7
0
class WaveformSeekBar(Gtk.Box):
    """A widget containing labels and the seekbar."""
    def __init__(self, player, library):
        super(WaveformSeekBar, self).__init__()

        self._player = player
        self._rms_vals = []

        self._elapsed_label = TimeLabel()
        self._remaining_label = TimeLabel()
        self._waveform_scale = WaveformScale(player)

        self.pack_start(Align(self._elapsed_label, border=6), False, True, 0)
        self.pack_start(self._waveform_scale, True, True, 0)
        self.pack_start(Align(self._remaining_label, border=6), False, True, 0)

        for child in self.get_children():
            child.show_all()

        self._waveform_scale.connect('size-allocate',
                                     self._update_redraw_interval)

        self._label_tracker = TimeTracker(player)
        self._label_tracker.connect('tick', self._on_tick_label, player)

        self._redraw_tracker = TimeTracker(player)
        self._redraw_tracker.connect('tick', self._on_tick_waveform, player)

        connect_destroy(player, 'seek', self._on_player_seek)
        connect_destroy(player, 'song-started', self._on_song_started)
        connect_destroy(player, 'song-ended', self._on_song_ended)
        connect_destroy(player, 'notify::seekable', self._on_seekable_changed)
        connect_destroy(library, 'changed', self._on_song_changed, player)

        self.connect('destroy', self._on_destroy)
        self._update(player)

        if player.info:
            self._create_waveform(player.info, CONFIG.max_data_points)

    def _create_waveform(self, song, points):
        # Close any existing pipelines to avoid warnings
        if hasattr(self, "_pipeline") and self._pipeline:
            self._pipeline.set_state(Gst.State.NULL)
            self._clean_pipeline()

        command_template = """
        filesrc name=fs
        ! decodebin ! audioconvert
        ! level name=audiolevel interval={} post-messages=true
        ! fakesink sync=false"""
        interval = int(song("~#length") * 1E9 / points)
        print_d("Computing data for each %.3f seconds" % (interval / 1E9))

        command = command_template.format(interval)
        pipeline = Gst.parse_launch(command)
        pipeline.get_by_name("fs").set_property("location", song("~filename"))

        bus = pipeline.get_bus()
        self._bus_id = bus.connect("message", self._on_bus_message)
        bus.add_signal_watch()

        pipeline.set_state(Gst.State.PLAYING)

        self._pipeline = pipeline
        self._new_rms_vals = []

    def _on_bus_message(self, bus, message):
        if message.type == Gst.MessageType.ERROR:
            error, debug = message.parse_error()
            print_d("Error received from element {name}: {error}".format(
                name=message.src.get_name(), error=error))
            print_d("Debugging information: {}".format(debug))
        elif message.type == Gst.MessageType.ELEMENT:
            structure = message.get_structure()
            if structure.get_name() == "level":
                rms_db = structure.get_value("rms")
                # Calculate average of all channels (usually 2)
                rms_db_avg = sum(rms_db) / len(rms_db)
                # Normalize dB value to value between 0 and 1
                rms = pow(10, (rms_db_avg / 20))
                self._new_rms_vals.append(rms)
            else:
                print_w("Got unexpected message of type {}".format(
                    message.type))
        elif message.type == Gst.MessageType.EOS:
            self._pipeline.set_state(Gst.State.NULL)
            self._clean_pipeline()

            # Only update the waveform if it has changed
            if self._player.info and self._rms_vals != self._new_rms_vals:
                self._rms_vals = self._new_rms_vals
                self._waveform_scale.reset(self._rms_vals)
                self._waveform_scale.set_placeholder(False)
                self._update_redraw_interval()
            else:
                del self._new_rms_vals

    def _clean_pipeline(self):
        if self._bus_id:
            bus = self._pipeline.get_bus()
            bus.remove_signal_watch()
            bus.disconnect(self._bus_id)
            self._bus_id = None
        if self._pipeline:
            self._pipeline = None

    def _update_redraw_interval(self, *args):
        if self._player.info:
            # Must be recomputed when size is changed
            interval = self._waveform_scale.compute_redraw_interval()
            self._redraw_tracker.set_interval(interval)

    def _on_destroy(self, *args):
        self._label_tracker.destroy()
        self._redraw_tracker.destroy()

    def _on_tick_label(self, tracker, player):
        self._update_label(player)

    def _on_tick_waveform(self, tracker, player):
        self._update_waveform(player)

    def _on_seekable_changed(self, player, *args):
        self._update_label(player)

    def _on_player_seek(self, player, song, ms):
        self._update(player)

    def _on_song_changed(self, library, songs, player):
        # Check that the currently playing song has changed
        if player.info and player.info in songs:
            # Trigger a re-computation of the waveform
            self._create_waveform(player.info, CONFIG.max_data_points)
            # Only update the label if some tag value changed
            self._update_label(player)

    def _on_song_started(self, player, song):
        if player.info:
            # Trigger a re-computation of the waveform
            self._create_waveform(player.info, CONFIG.max_data_points)

        self._waveform_scale.set_placeholder(True)
        self._update(player, True)

    def _on_song_ended(self, player, song, ended):
        self._update(player)

    def _update(self, player, full_redraw=False):
        self._update_label(player)
        self._update_waveform(player, full_redraw)

    def _update_label(self, player):
        if player.info:
            # Position in ms, length in seconds
            position = player.get_position() / 1000.0
            length = player.info("~#length")
            remaining = length - position

            self._elapsed_label.set_time(position)
            self._remaining_label.set_time(remaining)

            self._elapsed_label.set_disabled(not player.seekable)
            self._remaining_label.set_disabled(not player.seekable)
            self.set_sensitive(player.seekable)
        else:
            self._remaining_label.set_disabled(True)
            self._elapsed_label.set_disabled(True)
            self.set_sensitive(False)

    def _update_waveform(self, player, full_redraw=False):
        if player.info:
            # Position in ms, length in seconds
            position = player.get_position() / 1000.0
            length = player.info("~#length")

            if length != 0:
                self._waveform_scale.set_position(position / length)
            else:
                print_d("Length reported as zero for %s" % player.info)
                self._waveform_scale.set_position(0)

            if position == 0 or full_redraw:
                self._waveform_scale.queue_draw()
            else:
                (x, y, w, h) = self._waveform_scale.compute_redraw_area()
                self._waveform_scale.queue_draw_area(x, y, w, h)
        else:
            self._waveform_scale.set_placeholder(True)
            self._waveform_scale.queue_draw()
Example #8
0
class WaveformSeekBar(Gtk.Box):
    """A widget containing labels and the seekbar."""

    def __init__(self, player, library):
        super(WaveformSeekBar, self).__init__()

        self._player = player
        self._rms_vals = []

        self._elapsed_label = TimeLabel()
        self._remaining_label = TimeLabel()
        self._waveform_scale = WaveformScale(player)

        self.pack_start(Align(self._elapsed_label, border=6), False, True, 0)
        self.pack_start(self._waveform_scale, True, True, 0)
        self.pack_start(Align(self._remaining_label, border=6), False, True, 0)

        for child in self.get_children():
            child.show_all()

        self._waveform_scale.connect('size-allocate',
                                     self._update_redraw_interval)

        self._label_tracker = TimeTracker(player)
        self._label_tracker.connect('tick', self._on_tick_label, player)

        self._redraw_tracker = TimeTracker(player)
        self._redraw_tracker.connect('tick', self._on_tick_waveform, player)

        connect_destroy(player, 'seek', self._on_player_seek)
        connect_destroy(player, 'song-started', self._on_song_started)
        connect_destroy(player, 'song-ended', self._on_song_ended)
        connect_destroy(player, 'notify::seekable', self._on_seekable_changed)
        connect_destroy(library, 'changed', self._on_song_changed, player)

        self.connect('destroy', self._on_destroy)
        self._update(player)

        if player.info:
            self._create_waveform(player.info, CONFIG.max_data_points)

    def _create_waveform(self, song, points):
        # Close any existing pipeline to avoid leaks
        self._clean_pipeline()

        command_template = """
        filesrc name=fs
        ! decodebin ! audioconvert
        ! level name=audiolevel interval={} post-messages=true
        ! fakesink sync=false"""
        interval = int(song("~#length") * 1E9 / points)
        print_d("Computing data for each %.3f seconds" % (interval / 1E9))

        command = command_template.format(interval)
        pipeline = Gst.parse_launch(command)
        pipeline.get_by_name("fs").set_property("location", song("~filename"))

        bus = pipeline.get_bus()
        self._bus_id = bus.connect("message", self._on_bus_message)
        bus.add_signal_watch()

        pipeline.set_state(Gst.State.PLAYING)

        self._pipeline = pipeline
        self._new_rms_vals = []

    def _on_bus_message(self, bus, message):
        if message.type == Gst.MessageType.ERROR:
            error, debug = message.parse_error()
            print_d("Error received from element {name}: {error}".format(
                name=message.src.get_name(), error=error))
            print_d("Debugging information: {}".format(debug))
        elif message.type == Gst.MessageType.ELEMENT:
            structure = message.get_structure()
            if structure.get_name() == "level":
                rms_db = structure.get_value("rms")
                # Calculate average of all channels (usually 2)
                rms_db_avg = sum(rms_db) / len(rms_db)
                # Normalize dB value to value between 0 and 1
                rms = pow(10, (rms_db_avg / 20))
                self._new_rms_vals.append(rms)
            else:
                print_w("Got unexpected message of type {}"
                        .format(message.type))
        elif message.type == Gst.MessageType.EOS:
            self._clean_pipeline()

            # Update the waveform with the new data
            self._rms_vals = self._new_rms_vals
            self._waveform_scale.reset(self._rms_vals)
            self._waveform_scale.set_placeholder(False)
            self._update_redraw_interval()

            # Clear temporary reference to the waveform data
            del self._new_rms_vals

    def _clean_pipeline(self):
        if hasattr(self, "_pipeline") and self._pipeline:
            self._pipeline.set_state(Gst.State.NULL)
            if self._bus_id:
                bus = self._pipeline.get_bus()
                bus.remove_signal_watch()
                bus.disconnect(self._bus_id)
                self._bus_id = None
            if self._pipeline:
                self._pipeline = None

    def _update_redraw_interval(self, *args):
        if self._player.info and self.is_visible():
            # Must be recomputed when size is changed
            interval = self._waveform_scale.compute_redraw_interval()
            self._redraw_tracker.set_interval(interval)

    def _on_destroy(self, *args):
        self._clean_pipeline()
        self._label_tracker.destroy()
        self._redraw_tracker.destroy()

    def _on_tick_label(self, tracker, player):
        self._update_label(player)

    def _on_tick_waveform(self, tracker, player):
        self._update_waveform(player)

    def _on_seekable_changed(self, player, *args):
        self._update_label(player)

    def _on_player_seek(self, player, song, ms):
        self._update(player)

    def _on_song_changed(self, library, songs, player):
        if not player.info:
            return
        if not player.info.is_file:
            print_d("%s is not a local file, skipping waveform calculation."
                    % player.info("~filename"))
            return
        # Check that the currently playing song has changed
        if player.info in songs:
            # Trigger a re-computation of the waveform
            self._create_waveform(player.info, CONFIG.max_data_points)
            # Only update the label if some tag value changed
            self._update_label(player)

    def _on_song_started(self, player, song):
        if player.info and player.info.is_file:
            # Trigger a re-computation of the waveform
            self._create_waveform(player.info, CONFIG.max_data_points)

        self._waveform_scale.set_placeholder(True)
        self._update(player, True)

    def _on_song_ended(self, player, song, ended):
        self._update(player)

    def _update(self, player, full_redraw=False):
        self._update_label(player)
        self._update_waveform(player, full_redraw)

    def _update_label(self, player):
        if player.info:
            # Position in ms, length in seconds
            position = player.get_position() / 1000.0
            length = player.info("~#length")
            remaining = length - position

            self._elapsed_label.set_time(position)
            self._remaining_label.set_time(remaining)

            self._elapsed_label.set_disabled(not player.seekable)
            self._remaining_label.set_disabled(not player.seekable)
            self.set_sensitive(player.seekable)
        else:
            self._remaining_label.set_disabled(True)
            self._elapsed_label.set_disabled(True)
            self.set_sensitive(False)

    def _update_waveform(self, player, full_redraw=False):
        if player.info:
            # Position in ms, length in seconds
            position = player.get_position() / 1000.0
            length = player.info("~#length")

            if length != 0:
                self._waveform_scale.set_position(position / length)
            else:
                print_d("Length reported as zero for %s" % player.info)
                self._waveform_scale.set_position(0)

            if position == 0 or full_redraw:
                self._waveform_scale.queue_draw()
            else:
                (x, y, w, h) = self._waveform_scale.compute_redraw_area()
                self._waveform_scale.queue_draw_area(x, y, w, h)
        else:
            self._waveform_scale.set_placeholder(True)
            self._waveform_scale.queue_draw()
Example #9
0
class SeekBar(Gtk.Box):
    def __init__(self, player, library):
        super(SeekBar, self).__init__()

        self._elapsed_label = TimeLabel()
        self._remaining_label = TimeLabel()
        scale = Gtk.Scale(orientation=Gtk.Orientation.HORIZONTAL)
        scale.set_adjustment(Gtk.Adjustment.new(0, 0, 0, 3, 15, 0))
        scale.set_draw_value(False)
        self._scale = scale

        self.pack_start(Align(self._elapsed_label, border=6), False, True, 0)
        self.pack_start(scale, True, True, 0)
        self.pack_start(Align(self._remaining_label, border=6), False, True, 0)
        for child in self.get_children():
            child.show_all()

        self._id = self._scale.connect("value-changed", self._on_user_changed, player)
        self._scale.connect("value-changed", self._on_scale_value_changed, player)

        self._tracker = TimeTracker(player)
        self._tracker.connect("tick", self._on_tick, player)

        connect_destroy(player, "seek", self._on_player_seek)
        connect_destroy(player, "song-started", self._on_song_started)
        connect_destroy(player, "notify::seekable", self._on_seekable_changed)

        connect_destroy(library, "changed", self._on_song_changed, player)

        self.connect("destroy", self._on_destroy)

        with self._inhibit():
            self._update(player)
        self._tracker.tick()

    def _on_destroy(self, *args):
        self._tracker.destroy()

    @contextlib.contextmanager
    def _inhibit(self):
        with GObject.signal_handler_block(self._scale, self._id):
            yield

    def _on_user_changed(self, scale, player):
        if player.seekable:
            player.seek(scale.get_value() * 1000)

    def _on_scale_value_changed(self, scale, player):
        self._update(player)

    def _on_tick(self, tracker, player):
        position = player.get_position() // 1000
        with self._inhibit():
            self._scale.set_value(position)

    def _on_seekable_changed(self, player, *args):
        with self._inhibit():
            self._update(player)

    def _on_song_changed(self, library, songs, player):
        if player.info in songs:
            with self._inhibit():
                self._update(player)

    def _on_player_seek(self, player, song, ms):
        with self._inhibit():
            self._scale.set_value(ms // 1000)
            self._update(player)

    def _on_song_started(self, player, song):
        with self._inhibit():
            self._scale.set_value(0)
            self._update(player)

    def _update(self, player):
        if player.info:
            self._scale.set_range(0, player.info("~#length"))
        else:
            self._scale.set_range(0, 1)

        if not player.seekable:
            self._scale.set_value(0)

        value = self._scale.get_value()
        max_ = self._scale.get_adjustment().get_upper()
        remaining = value - max_
        self._elapsed_label.set_time(value)
        self._remaining_label.set_time(remaining)
        self._remaining_label.set_disabled(not player.seekable)
        self._elapsed_label.set_disabled(not player.seekable)

        self.set_sensitive(player.seekable)
Example #10
0
 def enabled(self):
     self._seekpoint_A, self._seekpoint_B = self._get_seekpoints()
     self._tracker = TimeTracker(app.player)
     self._tracker.connect('tick', self._on_tick)
Example #11
0
class SeekPointsPlugin(EventPlugin, PluginConfigMixin):
    """The plugin class."""

    PLUGIN_ID = "Seekpoints"
    PLUGIN_NAME = _("Seekpoint Bookmarks")
    PLUGIN_ICON = Icons.GO_JUMP
    PLUGIN_CONFIG_SECTION = __name__
    PLUGIN_DESC = _(
        "Store Seekpoints A and/or B for tracks. "
        "Skip to time A and stop after time B when track is played.\n"
        "Note that changing the names of the points below does not "
        "update the actual bookmark names, it only changes which "
        "bookmark names the plugin looks for when deciding whether to seek.")

    CFG_SEEKPOINT_A_TEXT = "A"
    CFG_SEEKPOINT_B_TEXT = "B"
    DEFAULT_A_TEXT = "A"
    DEFAULT_B_TEXT = "B"

    def enabled(self):
        self._seekpoint_A, self._seekpoint_B = self._get_seekpoints()
        self._tracker = TimeTracker(app.player)
        self._tracker.connect('tick', self._on_tick)

    def disabled(self):
        self._tracker.destroy()

    def plugin_on_song_started(self, song):
        """Seeks to point A if it exists, and also fetches the bookmarks of the
           current track that matches the seekpoint names set in config.
        """
        self._seekpoint_A, self._seekpoint_B = self._get_seekpoints()
        if not self._seekpoint_A:
            return
        self._seek(self._seekpoint_A)

    def _on_tick(self, tracker):
        """Checks whether the current position is past point B if it exists,
           and if it is -- seek to the end of the track.
        """
        if not self._seekpoint_B:
            return

        time = app.player.get_position() // 1000
        if self._seekpoint_B <= time:
            self._seek(app.player.info("~#length"))

    def _get_seekpoints(self):
        """Reads seekpoint-names from config, which are compared to the
           bookmark-names of the current track to get timestamps (if any).
        """
        if not app.player.song:
            return None, None

        marks = []
        if has_bookmark(app.player.song):
            marks = app.player.song.bookmarks

        seekpoint_A = None
        seekpoint_B = None
        seekpoint_A_name = self.config_get(self.CFG_SEEKPOINT_A_TEXT,
                                           self.DEFAULT_A_TEXT)
        seekpoint_B_name = self.config_get(self.CFG_SEEKPOINT_B_TEXT,
                                           self.DEFAULT_B_TEXT)
        for time, mark in marks:
            if mark == seekpoint_A_name:
                seekpoint_A = time
            elif mark == seekpoint_B_name:
                seekpoint_B = time

        # if seekpoints are not properly ordered (or identical), the track
        # will likely endlessly seek when looping tracks, so discard B
        # (maybe raise an exception for the plugin list?).
        if (seekpoint_A is not None) and (seekpoint_B is not None):
            if seekpoint_A >= seekpoint_B:
                return seekpoint_A, None

        return seekpoint_A, seekpoint_B

    def _seek(self, seconds):
        app.player.seek(seconds * 1000)

    def PluginPreferences(self, parent):
        vb = Gtk.VBox(spacing=12)

        # Bookmark name to use for point A
        hb = Gtk.HBox(spacing=6)
        entry = UndoEntry()
        entry.set_text(
            self.config_get(self.CFG_SEEKPOINT_A_TEXT, self.DEFAULT_A_TEXT))
        entry.connect('changed', self.config_entry_changed,
                      self.CFG_SEEKPOINT_A_TEXT)
        lbl = Gtk.Label(label=_("Bookmark name for point A"))
        entry.set_tooltip_markup(
            _("Bookmark name to check for when "
              "a track is started, and if found the player seeks to that "
              "timestamp"))
        lbl.set_mnemonic_widget(entry)
        hb.pack_start(lbl, False, True, 0)
        hb.pack_start(entry, True, True, 0)
        vb.pack_start(hb, True, True, 0)

        # Bookmark name to use for point B
        hb = Gtk.HBox(spacing=6)
        entry = UndoEntry()
        entry.set_text(
            self.config_get(self.CFG_SEEKPOINT_B_TEXT, self.DEFAULT_B_TEXT))
        entry.connect('changed', self.config_entry_changed,
                      self.CFG_SEEKPOINT_B_TEXT)
        lbl = Gtk.Label(label=_("Bookmark name for point B"))
        entry.set_tooltip_markup(
            _("Bookmark name to use each tick during "
              "play of a track if it exist. If the current position exceeds "
              "the timestamp, seek to the end of the track."))
        lbl.set_mnemonic_widget(entry)
        hb.pack_start(lbl, False, True, 0)
        hb.pack_start(entry, True, True, 0)
        vb.pack_start(hb, True, True, 0)

        return vb
Example #12
0
class SeekBar(Gtk.Box):

    def __init__(self, player, library):
        super(SeekBar, self).__init__()

        self._elapsed_label = TimeLabel()
        self._remaining_label = TimeLabel()
        scale = Gtk.Scale(orientation=Gtk.Orientation.HORIZONTAL)
        scale.set_adjustment(Gtk.Adjustment.new(0, 0, 0, 3, -15, 0))
        scale.set_draw_value(False)
        self._scale = scale

        self.pack_start(Align(self._elapsed_label, border=6), False, True, 0)
        self.pack_start(scale, True, True, 0)
        self.pack_start(Align(self._remaining_label, border=6), False, True, 0)
        for child in self.get_children():
            child.show_all()

        self._id = self._scale.connect(
            'value-changed', self._on_user_changed, player)
        self._scale.connect(
            'value-changed', self._on_scale_value_changed, player)

        self._tracker = TimeTracker(player)
        self._tracker.connect('tick', self._on_tick, player)

        connect_destroy(player, 'seek', self._on_player_seek)
        connect_destroy(player, 'song-started', self._on_song_started)
        connect_destroy(player, "notify::seekable", self._on_seekable_changed)

        connect_destroy(
            library, "changed", self._on_song_changed, player)

        self.connect("destroy", self._on_destroy)

        with self._inhibit():
            self._update(player)
        self._tracker.tick()

    def _on_destroy(self, *args):
        self._tracker.destroy()

    @contextlib.contextmanager
    def _inhibit(self):
        with GObject.signal_handler_block(self._scale, self._id):
            yield

    def _on_user_changed(self, scale, player):
        if player.seekable:
            player.seek(scale.get_value() * 1000)

    def _on_scale_value_changed(self, scale, player):
        self._update(player)

    def _on_tick(self, tracker, player):
        position = player.get_position() // 1000
        with self._inhibit():
            self._scale.set_value(position)

    def _on_seekable_changed(self, player, *args):
        with self._inhibit():
            self._update(player)

    def _on_song_changed(self, library, songs, player):
        if player.info in songs:
            with self._inhibit():
                self._update(player)

    def _on_player_seek(self, player, song, ms):
        with self._inhibit():
            self._scale.set_value(ms // 1000)
            self._update(player)

    def _on_song_started(self, player, song):
        with self._inhibit():
            self._scale.set_value(0)
            self._update(player)

    def _update(self, player):
        if player.info:
            self._scale.set_range(0, player.info("~#length"))
        else:
            self._scale.set_range(0, 1)

        if not player.seekable:
            self._scale.set_value(0)

        value = self._scale.get_value()
        max_ = self._scale.get_adjustment().get_upper()
        remaining = value - max_
        self._elapsed_label.set_time(value)
        self._remaining_label.set_time(remaining)
        self._remaining_label.set_disabled(not player.seekable)
        self._elapsed_label.set_disabled(not player.seekable)

        self.set_sensitive(player.seekable)
Example #13
0
class WaveformSeekBar(Gtk.Box):
    """A widget containing labels and the seekbar."""

    def __init__(self, player, library):
        super(WaveformSeekBar, self).__init__()

        self._player = player
        self._rms_vals = []

        self._elapsed_label = TimeLabel()
        self._remaining_label = TimeLabel()
        self._waveform_scale = WaveformScale()

        self.pack_start(Align(self._elapsed_label, border=6), False, True, 0)
        self.pack_start(self._waveform_scale, True, True, 0)
        self.pack_start(Align(self._remaining_label, border=6), False, True, 0)

        for child in self.get_children():
            child.show_all()

        self._tracker = TimeTracker(player)
        self._tracker.connect('tick', self._on_tick, player)

        connect_destroy(player, 'seek', self._on_player_seek)
        connect_destroy(player, 'song-started', self._on_song_started)
        connect_destroy(player, 'song-ended', self._on_song_ended)
        connect_destroy(player, 'notify::seekable', self._on_seekable_changed)
        connect_destroy(library, 'changed', self._on_song_changed, player)

        self.connect('destroy', self._on_destroy)
        self._update(player)
        self._tracker.tick()

        if player.info:
            self._create_waveform(player.info, CONFIG.data_size)

    def _create_waveform(self, song, points):
        # Close any existing pipelines to avoid warnings
        if hasattr(self, "_pipeline") and self._pipeline:
            self._pipeline.set_state(Gst.State.NULL)

        command_template = """
        filesrc name=fs
        ! decodebin ! audioconvert
        ! level name=audiolevel interval={} post-messages=true
        ! fakesink sync=false"""
        interval = int(song("~#length") * 1E9 / points)
        print_d("Computing data for each %.3f seconds" % (interval / 1E9))

        command = command_template.format(interval)
        pipeline = Gst.parse_launch(command)
        pipeline.get_by_name("fs").set_property("location", song("~filename"))

        bus = pipeline.get_bus()
        self._bus_id = bus.connect("message", self._on_bus_message)
        bus.add_signal_watch()

        pipeline.set_state(Gst.State.PLAYING)

        self._pipeline = pipeline
        self._rms_vals = []

    def _on_bus_message(self, bus, message):
        if message.type == Gst.MessageType.ERROR:
            error, debug = message.parse_error()
            print_d("Error received from element {name}: {error}".format(
                name=message.src.get_name(), error=error))
            print_d("Debugging information: {}".format(debug))
        elif message.type == Gst.MessageType.ELEMENT:
            structure = message.get_structure()
            if structure.get_name() == "level":
                rms_db = structure.get_value("rms")
                # Calculate average of all channels (usually 2)
                rms_db_avg = sum(rms_db) / len(rms_db)
                # Normalize dB value to value between 0 and 1
                rms = pow(10, (rms_db_avg / 20))
                self._rms_vals.append(rms)
            else:
                print_w("Got unexpected message of type {}"
                        .format(message.type))
        elif message.type == Gst.MessageType.EOS:
            self._pipeline.set_state(Gst.State.NULL)

            if self._player.info:
                self._waveform_scale.reset(self._rms_vals, self._player)
                self._waveform_scale.set_placeholder(False)

    def _on_destroy(self, *args):
        self._tracker.destroy()

    def _on_tick(self, tracker, player):
        self._update(player)

    def _on_seekable_changed(self, player, *args):
        self._update(player)

    def _on_player_seek(self, player, song, ms):
        self._update(player)

    def _on_song_changed(self, library, songs, player):
        if player.info:
            self._create_waveform(player.info, CONFIG.data_size)

        self._waveform_scale.set_placeholder(True)
        self._update(player)

    def _on_song_started(self, player, song):
        self._update(player)

    def _on_song_ended(self, player, song, ended):
        self._update(player)

    def _update(self, player):
        if player.info:
            # Position in ms, length in seconds
            position = player.get_position() / 1000.0
            length = player.info("~#length")
            remaining = length - position

            if length != 0:
                self._waveform_scale.set_position(position / length)
            else:
                print_d("Length reported as zero for %s" % player.info)
                self._waveform_scale.set_position(0)

            self._elapsed_label.set_time(position)
            self._remaining_label.set_time(remaining)
            self._remaining_label.set_disabled(not player.seekable)
            self._elapsed_label.set_disabled(not player.seekable)

            self.set_sensitive(player.seekable)
        else:
            self._waveform_scale.set_placeholder(True)
            self._remaining_label.set_disabled(True)
            self._elapsed_label.set_disabled(True)

            self.set_sensitive(player.seekable)

        self._waveform_scale.queue_draw()
Example #14
0
 def enabled(self):
     self._seekpoint_A, self._seekpoint_B = self._get_seekpoints()
     self._tracker = TimeTracker(app.player)
     self._tracker.connect('tick', self._on_tick)
Example #15
0
class SeekPointsPlugin(EventPlugin, PluginConfigMixin):
    """The plugin class."""

    PLUGIN_ID = "Seekpoints"
    PLUGIN_NAME = _("Seekpoint Bookmarks")
    PLUGIN_ICON = Icons.GO_JUMP
    PLUGIN_CONFIG_SECTION = __name__
    PLUGIN_DESC = _(
       "Store Seekpoints A and/or B for tracks. "
       "Skip to time A and stop after time B when track is played.\n"
       "Note that changing the names of the points below does not "
       "update the actual bookmark names, it only changes which "
       "bookmark names the plugin looks for when deciding whether to seek.")

    CFG_SEEKPOINT_A_TEXT = "A"
    CFG_SEEKPOINT_B_TEXT = "B"
    DEFAULT_A_TEXT = "A"
    DEFAULT_B_TEXT = "B"

    def enabled(self):
        self._seekpoint_A, self._seekpoint_B = self._get_seekpoints()
        self._tracker = TimeTracker(app.player)
        self._tracker.connect('tick', self._on_tick)

    def disabled(self):
        self._tracker.destroy()

    def plugin_on_song_started(self, song):
        """Seeks to point A if it exists, and also fetches the bookmarks of the
           current track that matches the seekpoint names set in config.
        """
        self._seekpoint_A, self._seekpoint_B = self._get_seekpoints()
        if not self._seekpoint_A:
            return
        self._seek(self._seekpoint_A)

    def _on_tick(self, tracker):
        """Checks whether the current position is past point B if it exists,
           and if it is -- seek to the end of the track.
        """
        if not self._seekpoint_B:
            return

        time = app.player.get_position() // 1000
        if self._seekpoint_B <= time:
            self._seek(app.player.info("~#length"))

    def _get_seekpoints(self):
        """Reads seekpoint-names from config, which are compared to the
           bookmark-names of the current track to get timestamps (if any).
        """
        if not app.player.song:
            return None, None

        marks = []
        if has_bookmark(app.player.song):
            marks = app.player.song.bookmarks

        seekpoint_A = None
        seekpoint_B = None
        seekpoint_A_name = self.config_get(self.CFG_SEEKPOINT_A_TEXT,
                                           self.DEFAULT_A_TEXT)
        seekpoint_B_name = self.config_get(self.CFG_SEEKPOINT_B_TEXT,
                                           self.DEFAULT_B_TEXT)
        for time, mark in marks:
            if mark == seekpoint_A_name:
                seekpoint_A = time
            elif mark == seekpoint_B_name:
                seekpoint_B = time

        # if seekpoints are not properly ordered (or identical), the track
        # will likely endlessly seek when looping tracks, so discard B
        # (maybe raise an exception for the plugin list?).
        if (seekpoint_A is not None) and (seekpoint_B is not None):
            if seekpoint_A >= seekpoint_B:
                return seekpoint_A, None

        return seekpoint_A, seekpoint_B

    def _seek(self, seconds):
        app.player.seek(seconds * 1000)

    def PluginPreferences(self, parent):
        vb = Gtk.VBox(spacing=12)

        # Bookmark name to use for point A
        hb = Gtk.HBox(spacing=6)
        entry = UndoEntry()
        entry.set_text(self.config_get(self.CFG_SEEKPOINT_A_TEXT,
                                       self.DEFAULT_A_TEXT))
        entry.connect('changed', self.config_entry_changed,
                      self.CFG_SEEKPOINT_A_TEXT)
        lbl = Gtk.Label(label=_("Bookmark name for point A"))
        entry.set_tooltip_markup(_("Bookmark name to check for when "
            "a track is started, and if found the player seeks to that "
            "timestamp"))
        lbl.set_mnemonic_widget(entry)
        hb.pack_start(lbl, False, True, 0)
        hb.pack_start(entry, True, True, 0)
        vb.pack_start(hb, True, True, 0)

        # Bookmark name to use for point B
        hb = Gtk.HBox(spacing=6)
        entry = UndoEntry()
        entry.set_text(self.config_get(self.CFG_SEEKPOINT_B_TEXT,
                                       self.DEFAULT_B_TEXT))
        entry.connect('changed', self.config_entry_changed,
                      self.CFG_SEEKPOINT_B_TEXT)
        lbl = Gtk.Label(label=_("Bookmark name for point B"))
        entry.set_tooltip_markup(_("Bookmark name to use each tick during "
            "play of a track if it exist. If the current position exceeds "
            "the timestamp, seek to the end of the track."))
        lbl.set_mnemonic_widget(entry)
        hb.pack_start(lbl, False, True, 0)
        hb.pack_start(entry, True, True, 0)
        vb.pack_start(hb, True, True, 0)

        return vb
Example #16
0
class MatrixOrbitalLCD(EventPlugin):

    PLUGIN_ID = "MatrixOrbitalLCD"
    PLUGIN_NAME = "Matrix Orbital LCD"
    PLUGIN_DESC = _("Print info to a Matrix Orbital MX2/MX3 LCD.")
    PLUGIN_ICON = Icons.UTILITIES_TERMINAL
    PLUGIN_VERSION = "1.0"

    def align_text2bytes(self, text,
        h_align=Alignment.CENTER, v_align=Alignment.TOP):

        if (h_align == Alignment.LEFT):
            index = 1
        elif (h_align == Alignment.RIGHT):
            index = max(CONFIG.lcd_width - len(text) + 1, 1)
        elif (h_align == Alignment.CENTER):
            index = floor((int(CONFIG.lcd_width) - len(text)) / 2) + 1

        alignment = b"\xFEG" + bytes(chr(index), "utf8")

        if v_align == Alignment.TOP:
            alignment += b"\x01"
        elif v_align == Alignment.BOTTOM:
            alignment += b"\x02"

        return alignment + bytes(text, "utf8")

    def reset_lcd(self):

        # Empty LCD screen (X) and send cursor home (H).
        self.write_bytes(b"\xFEX\xFEH")

    def write_bytes(self, text):

        self._dev.write(text)

    def write_header_with_text(self, text=""):

        self.reset_lcd()
        self._write_header()
        self.write_bytes(
            self.align_text2bytes(text, Alignment.CENTER, Alignment.BOTTOM))

    def _write_header(self, header="Quod Libet"):

        self.write_bytes(self.align_text2bytes(header))

    def PluginPreferences(self, parent):

        def _path_changed(entry):
            CONFIG.lcd_dev = entry.get_text()

        def _width_changed(entry):
            try:
                CONFIG.lcd_width = int(entry.get_text())
            except:
                CONFIG.lcd_width = 20

        def _interval_changed(entry):
            try:
                CONFIG.lcd_interval = int(entry.get_text())
            except:
                CONFIG.lcd_interval = 150

        label = Gtk.Label(label=_("LCD serial device path:"))
        hbox = Gtk.HBox()
        hbox.pack_start(label, False, True, 6)

        entry = Gtk.Entry()
        entry.set_text(CONFIG.lcd_dev)
        entry.connect("changed", _path_changed)
        hbox.pack_start(entry, True, True, 6)

        vbox = Gtk.VBox()
        vbox.pack_start(hbox, False, True, 6)

        label = Gtk.Label(label=_("LCD device width (characters):"))
        hbox = Gtk.HBox()
        hbox.pack_start(label, False, True, 6)

        entry = Gtk.Entry()
        entry.set_text(CONFIG.lcd_width)
        entry.connect("changed", _width_changed)
        hbox.pack_start(entry, True, True, 6)

        vbox.pack_start(hbox, True, True, 0)

        label = Gtk.Label(label=_("LCD update interval (ms):"))
        hbox = Gtk.HBox()
        hbox.pack_start(label, False, True, 6)

        entry = Gtk.Entry()
        entry.set_text(CONFIG.lcd_interval)
        entry.connect("changed", _interval_changed)
        hbox.pack_start(entry, True, True, 6)

        vbox.pack_start(hbox, True, True, 6)
        return vbox

    def _failed_initialization(self):

        if not hasattr(self, "_dev"):
            print_e("Matrix Orbital LCD plugin not initialized correctly.")
            return True
        return False

    def enabled(self):

        if system() != "Linux":
            print_e(self.PLUGIN_NAME + " plugin requires a Linux environment.")
            return

        try:
            self._dev = open(CONFIG.lcd_dev, 'wb', buffering=0)
        except:
            print_e("Matrix Orbital LCD device not found at " + CONFIG.lcd_dev)
            return

        self.reset_lcd()

        # Ready horizontal bars.
        self.write_bytes(b"\xFEh")
        # Turn backlight on.
        self.write_bytes(b"\xFEB\x00")

        self._write_header()

        self._npld = NowPlayingLCDData(self)
        self._tracker = TimeTracker(app.player)
        self._tracker.connect('tick', self._npld.on_tracker_tick)
        self._tracker.set_interval(int(CONFIG.lcd_interval))

    def disabled(self):

        if self._failed_initialization():
            return

        self._tracker.destroy()
        self.reset_lcd()

        # Turn backlight off.
        self.write_bytes(b"\xFEF")

    def plugin_on_seek(self, song, msec):

        if self._failed_initialization():
            return

        self.reset_lcd()
        self._npld.prevent_update(1)

        self._write_header(_("Seeking..."))
        percent_played = int(round(msec / float(song.get("~#length", 0)) / 10))

        # Draw a horizontal bar graph starting at column 1 on row 2
        # to the right (0), with a length of 0-100 pixels.
        self.write_bytes(b"\xFE\x7C\x01\x02\x00" +
            bytes(chr(percent_played), "utf8"))

    def plugin_on_song_started(self, song):

        if self._failed_initialization() or song is None:
            return

        self._npld.reset()
        self._npld.set_basic_info(song("artist"), song("title"))
        self._npld.set_disc_info(song("album"),
            song.get("discnumber"), song.get("tracknumber"))
        self.reset_lcd()

    def plugin_on_song_ended(self, song, stopped):

        if self._failed_initialization():
            return

        self._npld.reset()
        self.write_header_with_text(_("* not playing *"))

    def plugin_on_paused(self):

        if self._failed_initialization():
            return

        # No need to set pause for trackers manually.
        self.write_header_with_text(_("* paused *"))

    def plugin_on_unpaused(self):

        if self._failed_initialization():
            return

        self._npld.set_forced_update()