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()
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)
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)
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()