[rhythmbox] replaygain: new plugin using GStreamer's ReplayGain elements



commit a36e080618bc5919c0e1b0789ecb3b4f625fed31
Author: Jonathan Matthew <jonathan d14n org>
Date:   Sun Feb 7 17:01:35 2010 +1000

    replaygain: new plugin using GStreamer's ReplayGain elements
    
    This plugin uses the rgvolume and rglimiter elements to process
    ReplayGain tags and adjust the output volume accordingly.
    
    It provides configuration options for selecting the gain mode ('radio'
    or 'album'), setting the pre-amp level, and enabling or disabling
    compression in order to prevent clipping.
    
    It determines the fallback gain (used for tracks that don't provide
    ReplayGain tags) by calculating the mean of the last 10 non-fallback
    gain levels.

 configure.ac                               |    2 +
 data/rhythmbox.schemas                     |   58 +++++++
 plugins/Makefile.am                        |    1 +
 plugins/replaygain/Makefile.am             |   17 ++
 plugins/replaygain/replaygain-prefs.ui     |  163 ++++++++++++++++++++
 plugins/replaygain/replaygain.rb-plugin.in |    9 +
 plugins/replaygain/replaygain/Makefile.am  |    6 +
 plugins/replaygain/replaygain/__init__.py  |   56 +++++++
 plugins/replaygain/replaygain/config.py    |  102 +++++++++++++
 plugins/replaygain/replaygain/player.py    |  228 ++++++++++++++++++++++++++++
 10 files changed, 642 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 0ed81d5..f834ab9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -870,6 +870,8 @@ plugins/mmkeys/Makefile
 plugins/context/Makefile
 plugins/context/context/Makefile
 plugins/sendto/Makefile
+plugins/replaygain/Makefile
+plugins/replaygain/replaygain/Makefile
 bindings/Makefile
 bindings/python/Makefile
 bindings/vala/Makefile
diff --git a/data/rhythmbox.schemas b/data/rhythmbox.schemas
index f8bb356..e9d22fd 100644
--- a/data/rhythmbox.schemas
+++ b/data/rhythmbox.schemas
@@ -1556,5 +1556,63 @@
 	<long>True if the send tracks plugin is hidden.</long>
 	</locale>
       </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/plugins/replaygain/active</key>
+        <applyto>/apps/rhythmbox/plugins/replaygain/active</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>FALSE</default>
+        <locale name="C">
+        <short>True if the ReplayGain plugin is enabled.</short>
+	<long>True if the ReplayGain plugin is enabled.</long>
+	</locale>
+      </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/plugins/replaygain/hidden</key>
+        <applyto>/apps/rhythmbox/plugins/replaygain/hidden</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>FALSE</default>
+        <locale name="C">
+        <short>True if the ReplayGain plugin is hidden.</short>
+	<long>True if the ReplayGain plugin is hidden.</long>
+	</locale>
+      </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/plugins/replaygain/mode</key>
+        <applyto>/apps/rhythmbox/plugins/replaygain/mode</applyto>
+        <owner>rhythmbox</owner>
+        <type>int</type>
+        <default>0</default>
+        <locale name="C">
+        <short>ReplayGain adjustment mode.</short>
+	<long>
+          If 0, use 'radio' mode, where all tracks should have equal loudness.
+          If 1, use 'album' mode, where all tracks should have their ideal loudness.
+        </long>
+	</locale>
+      </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/plugins/replaygain/preamp</key>
+        <applyto>/apps/rhythmbox/plugins/replaygain/preamp</applyto>
+        <owner>rhythmbox</owner>
+        <type>float</type>
+        <default>0.0</default>
+        <locale name="C">
+        <short>ReplayGain pre-amp gain.</short>
+	<long>ReplayGain pre-amp gain, applied before the ReplayGain adjustment.</long>
+	</locale>
+      </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/plugins/replaygain/limiter</key>
+        <applyto>/apps/rhythmbox/plugins/replaygain/limiter</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>TRUE</default>
+        <locale name="C">
+        <short>Apply compression to prevent clipping due to ReplayGain adjustment</short>
+        <long>Apply compression to prevent clipping due to ReplayGain adjustment</long>
+	</locale>
+      </schema>
     </schemalist>
 </gconfschemafile>
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index f971fb3..dd7732e 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -25,6 +25,7 @@ SUBDIRS += 						\
 	im-status					\
 	context						\
 	sendto						\
+	replaygain					\
 	rb
 endif
 
diff --git a/plugins/replaygain/Makefile.am b/plugins/replaygain/Makefile.am
new file mode 100644
index 0000000..8194d8b
--- /dev/null
+++ b/plugins/replaygain/Makefile.am
@@ -0,0 +1,17 @@
+SUBDIRS = replaygain
+
+plugindir = $(PLUGINDIR)/replaygain
+
+%.rb-plugin: %.rb-plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+plugin_DATA = $(plugin_in_files:.rb-plugin.in=.rb-plugin)
+
+
+plugin_in_files = replaygain.rb-plugin.in
+
+gtkbuilder_DATA =			\
+		replaygain-prefs.ui
+
+gtkbuilderdir = $(plugindir)
+EXTRA_DIST = $(plugin_in_files) $(gtkbuilder_DATA)
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA)
diff --git a/plugins/replaygain/replaygain-prefs.ui b/plugins/replaygain/replaygain-prefs.ui
new file mode 100644
index 0000000..0223b2b
--- /dev/null
+++ b/plugins/replaygain/replaygain-prefs.ui
@@ -0,0 +1,163 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.14"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkListStore" id="replaygainmodemodel">
+    <columns>
+      <!-- column-name name -->
+      <column type="gchararray"/>
+    </columns>
+    <data>
+      <row>
+        <col id="0" translatable="yes">Radio (equal loudness for all tracks)</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes">Album (ideal loudness for all tracks)</col>
+      </row>
+    </data>
+  </object>
+  <object class="GtkAdjustment" id="preampadjustment">
+    <property name="lower">-15</property>
+    <property name="upper">25</property>
+    <property name="step_increment">0.10000000000000001</property>
+    <property name="page_increment">5</property>
+    <property name="page_size">10</property>
+  </object>
+  <object class="GtkVBox" id="replaygain-prefs">
+    <property name="visible">True</property>
+    <property name="orientation">vertical</property>
+    <property name="spacing">18</property>
+    <child>
+      <object class="GtkLabel" id="headerlabel">
+        <property name="visible">True</property>
+        <property name="xalign">0</property>
+        <property name="label" translatable="yes">ReplayGain preferences</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkHBox" id="hbox2">
+        <property name="visible">True</property>
+        <child>
+          <object class="GtkLabel" id="label4">
+            <property name="visible">True</property>
+            <property name="xpad">8</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkTable" id="table1">
+            <property name="visible">True</property>
+            <property name="n_rows">3</property>
+            <property name="n_columns">2</property>
+            <property name="column_spacing">12</property>
+            <property name="row_spacing">6</property>
+            <child>
+              <object class="GtkLabel" id="label3">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">ReplayGain _mode:</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="x_options">GTK_FILL</property>
+                <property name="y_options">GTK_FILL</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkComboBox" id="replaygainmode">
+                <property name="visible">True</property>
+                <property name="model">replaygainmodemodel</property>
+                <child>
+                  <object class="GtkCellRendererText" id="cellrenderertext2"/>
+                  <attributes>
+                    <attribute name="text">0</attribute>
+                  </attributes>
+                </child>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">2</property>
+                <property name="x_options">GTK_FILL</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="preamplabel">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">_Pre-amp:</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="top_attach">1</property>
+                <property name="bottom_attach">2</property>
+                <property name="x_options">GTK_FILL</property>
+                <property name="y_options">GTK_FILL</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkHScale" id="preamp">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="adjustment">preampadjustment</property>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">2</property>
+                <property name="top_attach">1</property>
+                <property name="bottom_attach">2</property>
+                <property name="y_options">GTK_FILL</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkCheckButton" id="limiter">
+                <property name="label" translatable="yes">_Apply compression to prevent clipping</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="use_underline">True</property>
+                <property name="xalign">0</property>
+                <property name="draw_indicator">True</property>
+              </object>
+              <packing>
+                <property name="right_attach">2</property>
+                <property name="top_attach">2</property>
+                <property name="bottom_attach">3</property>
+                <property name="x_options">GTK_FILL</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLinkButton" id="linkbutton">
+        <property name="label" translatable="yes">Learn more about ReplayGain at replaygain.org</property>
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="receives_default">True</property>
+        <property name="has_tooltip">True</property>
+        <property name="relief">none</property>
+        <property name="uri">http://replaygain.org/</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="position">2</property>
+      </packing>
+    </child>
+  </object>
+</interface>
diff --git a/plugins/replaygain/replaygain.rb-plugin.in b/plugins/replaygain/replaygain.rb-plugin.in
new file mode 100644
index 0000000..3ed2279
--- /dev/null
+++ b/plugins/replaygain/replaygain.rb-plugin.in
@@ -0,0 +1,9 @@
+[RB Plugin]
+Loader=python
+Module=replaygain
+IAge=1
+_Name=ReplayGain
+_Description=Use ReplayGain to provide a consistent playback volume
+Authors=Jonathan Matthew
+Copyright=Copyright © 2010 Jonathan Matthew
+Website=http://www.rhythmbox.org/
diff --git a/plugins/replaygain/replaygain/Makefile.am b/plugins/replaygain/replaygain/Makefile.am
new file mode 100644
index 0000000..7f3904b
--- /dev/null
+++ b/plugins/replaygain/replaygain/Makefile.am
@@ -0,0 +1,6 @@
+
+plugindir = $(PLUGINDIR)/replaygain
+plugin_PYTHON =				\
+	config.py			\
+	player.py			\
+	__init__.py
diff --git a/plugins/replaygain/replaygain/__init__.py b/plugins/replaygain/replaygain/__init__.py
new file mode 100644
index 0000000..32563f8
--- /dev/null
+++ b/plugins/replaygain/replaygain/__init__.py
@@ -0,0 +1,56 @@
+# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
+#
+# Copyright (C) 2010 Jonathan Matthew
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# The Rhythmbox authors hereby grant permission for non-GPL compatible
+# GStreamer plugins to be used and distributed together with GStreamer
+# and Rhythmbox. This permission is above and beyond the permissions granted
+# by the GPL license by which Rhythmbox is covered. If you modify this code
+# you may extend this exception to your version of the code, but you are not
+# obligated to do so. If you do not wish to do so, delete this exception
+# statement from your version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+#
+
+import rb
+
+from config import ReplayGainConfigDialog
+from player import ReplayGainPlayer
+
+class ReplayGainPlugin(rb.Plugin):
+
+	def __init__ (self):
+		rb.Plugin.__init__ (self)
+		self.config_dialog = None
+
+	def activate (self, shell):
+		self.player = ReplayGainPlayer(shell)
+
+	def deactivate (self, shell):
+		self.config_dialog = None
+		self.player.deactivate()
+		self.player = None
+
+	def create_configure_dialog(self, dialog=None):
+		if self.config_dialog is None:
+			self.config_dialog = ReplayGainConfigDialog(self)
+			self.config_dialog.connect('response', self.config_dialog_response_cb)
+
+		self.config_dialog.present()
+		return self.config_dialog
+
+	def config_dialog_response_cb(self, dialog, response):
+		dialog.hide()
diff --git a/plugins/replaygain/replaygain/config.py b/plugins/replaygain/replaygain/config.py
new file mode 100644
index 0000000..adab6aa
--- /dev/null
+++ b/plugins/replaygain/replaygain/config.py
@@ -0,0 +1,102 @@
+# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
+#
+# Copyright (C) 2010 Jonathan Matthew
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# The Rhythmbox authors hereby grant permission for non-GPL compatible
+# GStreamer plugins to be used and distributed together with GStreamer
+# and Rhythmbox. This permission is above and beyond the permissions granted
+# by the GPL license by which Rhythmbox is covered. If you modify this code
+# you may extend this exception to your version of the code, but you are not
+# obligated to do so. If you do not wish to do so, delete this exception
+# statement from your version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+#
+
+import gobject
+import gtk
+import gconf
+import rhythmdb, rb
+
+GCONF_DIR = '/apps/rhythmbox/plugins/replaygain'
+
+GCONF_KEYS = {
+	'mode': GCONF_DIR + '/mode',
+	'preamp': GCONF_DIR + '/preamp',
+	'limiter': GCONF_DIR + '/limiter'
+}
+
+# modes
+REPLAYGAIN_MODE_RADIO = 0
+REPLAYGAIN_MODE_ALBUM = 1
+
+# number of samples to keep for calculating the average gain
+# to apply for tracks that aren't tagged
+AVERAGE_GAIN_SAMPLES = 10
+
+class ReplayGainConfigDialog(gtk.Dialog):
+	def __init__(self, plugin):
+		gtk.Dialog.__init__(self)
+		self.set_border_width(12)
+
+		self.gconf = gconf.client_get_default()
+
+		ui_file = plugin.find_file("replaygain-prefs.ui")
+		self.builder = gtk.Builder()
+		self.builder.add_from_file(ui_file)
+
+		content = self.builder.get_object("replaygain-prefs")
+		self.get_content_area().add(content)
+
+		self.add_action_widget(gtk.Button(stock=gtk.STOCK_CLOSE), 0)
+		self.show_all()
+
+		label = self.builder.get_object("headerlabel")
+		label.set_markup("<b>%s</b>" % label.get_text())
+		label.set_use_markup(True)
+
+		combo = self.builder.get_object("replaygainmode")
+		combo.set_active(self.gconf.get_int(GCONF_KEYS['mode']))
+		combo.connect("changed", self.mode_changed_cb)
+
+		preamp = self.builder.get_object("preamp")
+		preamp.set_value(self.gconf.get_float(GCONF_KEYS['preamp']))
+		preamp.connect("value-changed", self.preamp_changed_cb)
+
+		preamp.add_mark(-15.0, gtk.POS_BOTTOM, _("-15.0 dB"))
+		preamp.add_mark(0.0, gtk.POS_BOTTOM, _("0.0 dB"))
+		preamp.add_mark(15.0, gtk.POS_BOTTOM, _("15.0 dB"))
+
+		limiter = self.builder.get_object("limiter")
+		limiter.set_active(self.gconf.get_bool(GCONF_KEYS['limiter']))
+		limiter.connect("toggled", self.limiter_changed_cb)
+
+
+	def mode_changed_cb(self, combo):
+		v = combo.get_active()
+		print "replaygain mode changed to %d" % v
+		self.gconf.set_int(GCONF_KEYS['mode'], v)
+
+	def preamp_changed_cb(self, preamp):
+		v = preamp.get_value()
+		print "preamp gain changed to %f" % v
+		self.gconf.set_float(GCONF_KEYS['preamp'], v)
+
+	def limiter_changed_cb(self, limiter):
+		v = limiter.get_active()
+		print "limiter changed to %d" % v
+		self.gconf.set_bool(GCONF_KEYS['limiter'], v)
+
+gobject.type_register(ReplayGainConfigDialog)
diff --git a/plugins/replaygain/replaygain/player.py b/plugins/replaygain/replaygain/player.py
new file mode 100644
index 0000000..8e1564f
--- /dev/null
+++ b/plugins/replaygain/replaygain/player.py
@@ -0,0 +1,228 @@
+# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
+#
+# Copyright (C) 2010 Jonathan Matthew
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# The Rhythmbox authors hereby grant permission for non-GPL compatible
+# GStreamer plugins to be used and distributed together with GStreamer
+# and Rhythmbox. This permission is above and beyond the permissions granted
+# by the GPL license by which Rhythmbox is covered. If you modify this code
+# you may extend this exception to your version of the code, but you are not
+# obligated to do so. If you do not wish to do so, delete this exception
+# statement from your version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+#
+
+import gtk
+import gconf
+import gst
+import gobject
+import rhythmdb, rb
+
+import config
+
+EPSILON = 0.001
+
+class ReplayGainPlayer(object):
+	def __init__(self, shell):
+		# make sure the replaygain elements are available
+		missing = []
+		required = ("rgvolume", "rglimiter")
+		for e in required:
+			if gst.element_factory_find(e) is None:
+				missing.append(e)
+
+		if len(missing) > 0:
+			msg = _("The GStreamer elements required for ReplayGain processing are not available. The missing elements are: %s") % ", ".join(missing)
+			rb.error_dialog(shell.props.window, _("ReplayGain GStreamer plugins not available"), msg)
+			raise Exception(msg)
+
+		self.shell_player = shell.props.shell_player
+		self.player = self.shell_player.props.player
+		self.gconf = gconf.client_get_default()
+
+		self.gconf.add_dir(config.GCONF_DIR, preload=False)
+		self.gconf.notify_add(config.GCONF_KEYS['limiter'], self.limiter_changed_cb)
+
+		self.previous_gain = []
+		self.fallback_gain = 0.0
+		self.resetting_rgvolume = False
+
+		# we use different means to hook into the playback pipeline depending on
+		# the playback backend in use
+		if gobject.signal_lookup("get-stream-filters", self.player):
+			self.setup_xfade_mode()
+			self.deactivate_backend = self.deactivate_xfade_mode
+		else:
+			self.setup_playbin2_mode()
+			self.deactivate_backend = self.deactivate_playbin2_mode
+
+
+
+	def deactivate(self):
+		self.deactivate_backend()
+		self.player = None
+		self.shell_player = None
+
+
+	def set_rgvolume(self, rgvolume):
+		# set preamp level
+		preamp = self.gconf.get_float(config.GCONF_KEYS['preamp'])
+		rgvolume.props.pre_amp = preamp
+
+		# set mode
+		# there may eventually be a 'guess' mode here that tries to figure out
+		# what to do based on the upcoming tracks
+		mode = self.gconf.get_int(config.GCONF_KEYS['mode'])
+		if mode == config.REPLAYGAIN_MODE_ALBUM:
+			rgvolume.props.album_mode = 1
+		else:
+			rgvolume.props.album_mode = 0
+
+		# set calculated fallback gain
+		rgvolume.props.fallback_gain = self.fallback_gain
+
+		print "updated rgvolume settings: preamp %f, album-mode %s, fallback gain %f" % (
+			rgvolume.props.pre_amp, str(rgvolume.props.album_mode), rgvolume.props.fallback_gain)
+
+
+	def update_fallback_gain(self, rgvolume):
+		gain = rgvolume.props.target_gain - rgvolume.props.pre_amp
+		# filter out bogus notifications
+		if abs(gain - self.fallback_gain) < EPSILON:
+			print "ignoring gain %f (current fallback gain)" % gain
+			return False
+		if abs(gain) < EPSILON:
+			print "ignoring zero gain (pretty unlikely)"
+			return False
+
+		# update the running average
+		if len(self.previous_gain) == config.AVERAGE_GAIN_SAMPLES:
+			self.previous_gain.pop(0)
+		self.previous_gain.append(gain)
+		self.fallback_gain = sum(self.previous_gain) / len(self.previous_gain)
+		print "got target gain %f; running average of previous gain values is %f" % (gain, self.fallback_gain)
+		return True
+
+
+
+	### playbin2 mode (rgvolume ! rglimiter as global filter)
+
+	def playbin2_uri_notify_cb(self, playbin, pspec):
+		self.got_replaygain = False
+
+	def playbin2_notify_cb(self, player, pspec):
+		playbin = player.props.playbin
+		playbin.connect("notify::uri", self.playbin2_uri_notify_cb)
+
+
+	def playbin2_target_gain_cb(self, rgvolume, pspec):
+		#if self.resetting_rgvolume is True:
+		#	return
+
+		if self.update_fallback_gain(rgvolume) == True:
+			self.got_replaygain = True
+		# do something clever probably
+
+	def rgvolume_reset_done(self, pad, blocked, rgvolume):
+		print "rgvolume reset done"
+		self.set_rgvolume(rgvolume)
+
+	def rgvolume_blocked_cb(self, pad, blocked, rgvolume):
+		print "bouncing rgvolume state to reset tags"
+		# somehow need to decide whether we've already got a gain value for the new track
+		#self.resetting_rgvolume = True
+		rgvolume.set_state(gst.STATE_READY)
+		rgvolume.set_state(gst.STATE_PLAYING)
+		#self.resetting_rgvolume = False
+		pad.set_blocked_async(False, self.rgvolume_reset_done, rgvolume)
+
+	def playing_entry_changed(self, player, entry):
+		if entry is None:
+			return
+
+		if self.got_replaygain is False:
+			print "blocking rgvolume to reset it"
+			pad = self.rgvolume.get_static_pad("sink").get_peer()
+			pad.set_blocked_async(True, self.rgvolume_blocked_cb, self.rgvolume)
+		else:
+			print "no need to reset rgvolume"
+
+	def setup_playbin2_mode(self):
+		print "using output filter for rgvolume and rglimiter"
+		self.rgvolume = gst.element_factory_make("rgvolume")
+		self.rgvolume.connect("notify::target-gain", self.playbin2_target_gain_cb)
+		self.rglimiter = gst.element_factory_make("rglimiter")
+
+		# on track changes, we need to reset the rgvolume state, otherwise it
+		# carries over the tags from the previous track
+		self.pec_id = self.shell_player.connect('playing-song-changed', self.playing_entry_changed)
+
+		# watch playbin2's uri property to see when a new track is opened
+		playbin = self.player.props.playbin
+		if playbin is None:
+			self.player.connect("notify::playbin", self.playbin2_notify_cb)
+		else:
+			playbin.connect("notify::uri", self.playbin2_uri_notify_cb)
+
+		self.rgfilter = gst.Bin()
+		self.rgfilter.add(self.rgvolume, self.rglimiter)
+		self.rgvolume.link(self.rglimiter)
+		self.rgfilter.add_pad(gst.GhostPad("sink", self.rgvolume.get_static_pad("sink")))
+		self.rgfilter.add_pad(gst.GhostPad("src", self.rglimiter.get_static_pad("src")))
+		self.player.add_filter(self.rgfilter)
+
+	def deactivate_playbin2_mode(self):
+		self.player.remove_filter(self.rgfilter)
+		self.rgfilter = None
+
+		self.shell_player.disconnect(self.pec_id)
+		self.pec_id = None
+
+
+
+	### xfade mode (rgvolume as stream filter, rglimiter as global filter)
+
+	def xfade_target_gain_cb(self, rgvolume, pspec):
+		if self.update_fallback_gain(rgvolume) is  True:
+			# we don't want any further notifications from this stream
+			rgvolume.disconnect_by_func(self.xfade_target_gain_cb)
+
+	def create_stream_filter_cb(self, player, uri):
+		print "creating rgvolume instance for stream %s" % uri
+		rgvolume = gst.element_factory_make("rgvolume")
+		rgvolume.connect("notify::target-gain", self.xfade_target_gain_cb)
+		self.set_rgvolume(rgvolume)
+		return [rgvolume]
+
+	def limiter_changed_cb(self, client, id, entry, d):
+		if self.rglimiter is not None:
+			limiter = self.gconf.get_bool(config.GCONF_KEYS['limiter'])
+			print "limiter setting is now %s" % str(limiter)
+			self.rglimiter.props.enabled = limiter
+
+	def setup_xfade_mode(self):
+		print "using per-stream filter for rgvolume"
+		self.stream_filter_id = self.player.connect("get-stream-filters", self.create_stream_filter_cb)
+
+		# and add rglimiter as an output filter
+		self.rglimiter = gst.element_factory_make("rglimiter")
+		self.player.add_filter(self.rglimiter)
+
+	def deactivate_xfade_mode(self):
+		self.player.disconnect(self.stream_filter_id)
+		self.stream_filter_id = None
+		self.player.remove_filter(self.rglimiter)
+		self.rglimiter = None



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]