Exemplo n.º 1
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)
Exemplo n.º 2
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()
Exemplo n.º 3
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()
Exemplo n.º 4
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)
Exemplo n.º 5
0
 def test_time_label(self):
     l = TimeLabel()
     l.set_time(42)
     time_text = l.get_text()
     l.set_disabled(True)
     disabled_text = l.get_text()
     self.assertNotEqual(time_text, disabled_text)
     l.set_disabled(False)
     self.assertEqual(l.get_text(), time_text)
Exemplo n.º 6
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)
Exemplo n.º 7
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)
Exemplo n.º 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()

        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 location="{}"
        ! decodebin
        ! 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))

        filename = song("~filename").replace('"', '\\"')
        command = command_template.format(filename, interval)
        pipeline = Gst.parse_launch(command)

        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()
Exemplo n.º 9
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()
Exemplo n.º 10
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()
Exemplo n.º 11
0
    def __init__(self, player, library):
        Gtk.Box.__init__(self)
        self.elapsed_label = TimeLabel()
        self.elapsed_label.set_name('elapsed-label')
        self.remaining_label = TimeLabel()
        self.remaining_label.set_name('remaining-label')
        self.remaining_label.set_width_chars(6)
        self.scale = Gtk.Scale(orientation=Gtk.Orientation.HORIZONTAL)
        self.scale.set_name('seek-bar')
        self.scale.set_adjustment(Gtk.Adjustment.new(0, 0, 0, 30, 5, 0))
        self.scale.set_digits(3)
        self.scale.set_show_fill_level(False)
        self.scale.set_draw_value(False)
        self._timer_modes = ['both', 'elapsed', 'remaining']
        self.c_timer_mode = __name__ + '_timer_mode'

        self.__timer_mode = 'both'
        self.__pressed_lmb = False
        self.__source_id = None

        self.elapsed_button = Gtk.Button()

        self.box = Gtk.Box(spacing=3)
        self.elapsed_button.add(self.box)
        self.elapsed_button.set_relief(Gtk.ReliefStyle.NONE)

        self.pack_start(self.scale, True, True, 0)

        try:
            self.__timer_mode = config.get('plugins', self.c_timer_mode)
        except NoOptionError:
            config.set('plugins', self.c_timer_mode, 'both')
        else:
            if self.__timer_mode not in self._timer_modes:
                self.__timer_mode = 'both'

        self._set_timer_mode(self.__timer_mode)

        self.elapsed_label.show()
        self.remaining_label.show()
        self.box.show()
        self.elapsed_button.show()
        self.scale.show()

        self.elapsed_button.connect('clicked', self._on_timer_clicked)
        self.scale.connect('button-release-event',
                           self._on_button_release, player)
        self.__id_button_press_event = self.scale.connect(
            'button-press-event', self._on_button_press, player)
        self.__id_change_value = self.scale.connect(
            'change-value', self._on_scale_value_change_request, player)

        self.__id_value_changed = self.scale.connect(
            'value-changed', self._on_scale_value_changed, player)
        GObject.signal_handler_block(self.scale, self.__id_value_changed)

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

        connect_destroy(player, 'seek', self._on_seek)
        connect_destroy(player, 'song-started', self._on_song_start)
        connect_destroy(player, "notify::seekable", self._update)

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

        self._update(player)
Exemplo n.º 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)
Exemplo n.º 13
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)
Exemplo n.º 14
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()
Exemplo n.º 15
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._on_size_change)
        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)

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

        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)

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

    def _create_waveform(self, song):
        if not song.is_file or not self._waveform_scale.get_realized():
            return

        # Close any existing pipeline to avoid leaks
        self._clean_pipeline()

        command_template = """
        uridecodebin name=uridec
        ! audioconvert
        ! level name=audiolevel interval={} post-messages=true
        ! fakesink sync=false"""
        interval = int(song("~#length") * 1E9 / self._waveform_scale.width)
        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 _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_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 _on_size_change(self, widget, allocation):
        self._update()

        if self._player.info:
            self._create_waveform(self._player.info)

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

    def _on_tick_label(self, tracker):
        if self.is_visible():
            self._update_label()

    def _on_tick_waveform(self, tracker):
        if self.is_visible():
            self._update_waveform()

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

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

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

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

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

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

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

    def _update_label(self):
        if self._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 self._player (converted in seconds)
                position = self._player.get_position() / 1000.0
            length = self._player.info("~#length")
            remaining = length - position

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

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

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

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

            if (position == 0 or full_redraw
                    or self._waveform_scale.is_placeholder()):
                # if the placeholder flag is set this means that the source is
                # most probably not a local file and has thus no waveform
                # In that case we want a 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._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()

    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)

    def update_colors(self):
        self._waveform_scale.update_colors()
Exemplo n.º 16
0
 def test_time_label(self):
     l = TimeLabel()
     l.set_time(42)
     time_text = l.get_text()
     l.set_disabled(True)
     disabled_text = l.get_text()
     self.assertNotEqual(time_text, disabled_text)
     l.set_disabled(False)
     self.assertEqual(l.get_text(), time_text)
Exemplo n.º 17
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)
Exemplo n.º 18
0
class SeekBar(Gtk.Box):

    def __init__(self, player, library):
        Gtk.Box.__init__(self)
        self.elapsed_label = TimeLabel()
        self.elapsed_label.set_name('elapsed-label')
        self.remaining_label = TimeLabel()
        self.remaining_label.set_name('remaining-label')
        self.remaining_label.set_width_chars(6)
        self.scale = Gtk.Scale(orientation=Gtk.Orientation.HORIZONTAL)
        self.scale.set_name('seek-bar')
        self.scale.set_adjustment(Gtk.Adjustment.new(0, 0, 0, 30, 5, 0))
        self.scale.set_digits(3)
        self.scale.set_show_fill_level(False)
        self.scale.set_draw_value(False)
        self._timer_modes = ['both', 'elapsed', 'remaining']
        self.c_timer_mode = __name__ + '_timer_mode'

        self.__timer_mode = 'both'
        self.__pressed_lmb = False
        self.__source_id = None

        self.elapsed_button = Gtk.Button()

        self.box = Gtk.Box(spacing=3)
        self.elapsed_button.add(self.box)
        self.elapsed_button.set_relief(Gtk.ReliefStyle.NONE)

        self.pack_start(self.scale, True, True, 0)

        try:
            self.__timer_mode = config.get('plugins', self.c_timer_mode)
        except NoOptionError:
            config.set('plugins', self.c_timer_mode, 'both')
        else:
            if self.__timer_mode not in self._timer_modes:
                self.__timer_mode = 'both'

        self._set_timer_mode(self.__timer_mode)

        self.elapsed_label.show()
        self.remaining_label.show()
        self.box.show()
        self.elapsed_button.show()
        self.scale.show()

        self.elapsed_button.connect('clicked', self._on_timer_clicked)
        self.scale.connect('button-release-event',
                           self._on_button_release, player)
        self.__id_button_press_event = self.scale.connect(
            'button-press-event', self._on_button_press, player)
        self.__id_change_value = self.scale.connect(
            'change-value', self._on_scale_value_change_request, player)

        self.__id_value_changed = self.scale.connect(
            'value-changed', self._on_scale_value_changed, player)
        GObject.signal_handler_block(self.scale, self.__id_value_changed)

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

        connect_destroy(player, 'seek', self._on_seek)
        connect_destroy(player, 'song-started', self._on_song_start)
        connect_destroy(player, "notify::seekable", self._update)

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

        self._update(player)

    def _set_timer_mode(self, mode='both'):

        widgets = find_widgets(self, TimeLabel)
        for elem in widgets:
            name = elem.get_name()
            if name == 'elapsed-label' or name == 'remaining-label':
                self.remove(elem)

        widgets = find_widgets(self.box, TimeLabel)
        for elem in widgets:
            name = elem.get_name()
            if name == 'elapsed-label' or name == 'remaining-label':
                self.box.remove(elem)

        if mode == 'both':
            self.box.pack_start(self.elapsed_label, True, True, 0)
            self.pack_start(self.remaining_label, False, True, 0)
        elif mode == 'remaining':
            self.box.pack_start(self.remaining_label, True, True, 0)
        elif mode == 'elapsed':
            self.box.pack_start(self.elapsed_label, True, True, 0)

        self.__timer_mode = mode
        config.set('plugins', self.c_timer_mode, self.__timer_mode)

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

    def _on_timer_clicked(self, button):

        i = self._timer_modes.index(self.__timer_mode)
        self._set_timer_mode(self._timer_modes[i-1])

    def _on_seek(self, player, song, ms):
        self._update_labels(player, ms)
        self._update_scale(player, ms)

    def _on_button_press(self, scale, event, player):
        GObject.signal_handler_block(self.scale, self.__id_button_press_event)
        self.__pressed_lmb = True
        GObject.signal_handler_unblock(self.scale, self.__id_value_changed)
        GObject.signal_handler_block(self.scale, self.__id_change_value)
        self.__pressed_lmb_scale_value = scale.get_value()

    def _on_button_release(self, scale, event, player):
        GObject.signal_handler_block(self.scale, self.__id_value_changed)
        value = scale.get_value()
        if player.seekable and self.__pressed_lmb_scale_value != value:
            player.seek(value * 1000)
        GObject.signal_handler_unblock(self.scale, self.__id_change_value)
        self.__pressed_lmb = False
        GObject.signal_handler_unblock(
            self.scale, self.__id_button_press_event)

    def _on_scale_value_changed(self, scale, player):
        elapsed = scale.get_value()
        remaining = elapsed - player.info("~#length")
        self.elapsed_label.set_time(elapsed)
        self.remaining_label.set_time(remaining)

    def _on_scale_value_change_request(self, scale, scroll, value, player):
        self.__pressed_lmb = True
        if self.__source_id is not None:
            GLib.source_remove(self.__source_id)
        self.__source_id = GLib.timeout_add(
            200, self.__scroll_timeout, value, player)

    def __scroll_timeout(self, value, player):
        if player.seekable:
            player.seek(value * 1000)
        self.__pressed_lmb = False
        self.__source_id = None

    def _on_tick(self, tracker, player):
        if not self.__pressed_lmb:
            self._update_labels(player)
            self._update_scale(player)

    def _update_labels(self, player, ms=None):
        if ms is not None:
            elapsed = ms // 1000
        else:
            elapsed = player.get_position() // 1000
        remaining = elapsed - player.info("~#length")
        self.elapsed_label.set_time(elapsed)
        self.remaining_label.set_time(remaining)

    def _update_scale(self, player, ms=None):
        if ms is not None:
            pval = ms / 1000
        else:
            pval = player.get_position() / 1000
        sval = self.scale.get_value()
        if (abs(pval - sval) > 0.001):
            self.scale.set_value(pval)

    def _on_song_start(self, player, *args):
        self._update(player, song_start=True)

    def _update(self, player, *args, song_start=False):
        if player.info:
            self.scale.set_range(0, player.info("~#length"))
        else:
            self.scale.set_range(0, 1)

        if player.seekable:
            self.elapsed_label.set_disabled(False)
            self.remaining_label.set_disabled(False)
            self.set_sensitive(True)
            if song_start:
                self._update_labels(player, 0)
                self._update_scale(player, 0)
            else:
                self._update_labels(player)
                self._update_scale(player)
        else:
            self.scale.set_value(0)
            self.elapsed_label.set_disabled(True)
            self.remaining_label.set_disabled(True)
            self.set_sensitive(False)