1# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*- 2# 3# Copyright (C) 2010 Jonathan Matthew 4# 5# This program is free software; you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation; either version 2, or (at your option) 8# any later version. 9# 10# The Rhythmbox authors hereby grant permission for non-GPL compatible 11# GStreamer plugins to be used and distributed together with GStreamer 12# and Rhythmbox. This permission is above and beyond the permissions granted 13# by the GPL license by which Rhythmbox is covered. If you modify this code 14# you may extend this exception to your version of the code, but you are not 15# obligated to do so. If you do not wish to do so, delete this exception 16# statement from your version. 17# 18# This program is distributed in the hope that it will be useful, 19# but WITHOUT ANY WARRANTY; without even the implied warranty of 20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21# GNU General Public License for more details. 22# 23# You should have received a copy of the GNU General Public License 24# along with this program; if not, write to the Free Software 25# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 26# 27 28import rb 29import gi 30 31gi.require_version("Gst", "1.0") 32from gi.repository import RB 33from gi.repository import GObject, Gio, Gst 34 35import config 36 37import gettext 38gettext.install('rhythmbox', RB.locale_dir()) 39 40EPSILON = 0.001 41 42class ReplayGainPlayer(object): 43 def __init__(self, shell): 44 # make sure the replaygain elements are available 45 missing = [] 46 required = ("rgvolume", "rglimiter") 47 for e in required: 48 if Gst.ElementFactory.find(e) is None: 49 missing.append(e) 50 51 if len(missing) > 0: 52 msg = _("The GStreamer elements required for ReplayGain processing are not available. The missing elements are: %s") % ", ".join(missing) 53 RB.error_dialog(shell.props.window, _("ReplayGain GStreamer plugins not available"), msg) 54 raise Exception(msg) 55 56 self.shell_player = shell.props.shell_player 57 self.player = self.shell_player.props.player 58 self.settings = Gio.Settings.new("org.gnome.rhythmbox.plugins.replaygain") 59 60 self.settings.connect("changed::limiter", self.limiter_changed_cb) 61 62 self.previous_gain = [] 63 self.fallback_gain = 0.0 64 65 # we use different means to hook into the playback pipeline depending on 66 # the playback backend in use 67 if GObject.signal_lookup("get-stream-filters", self.player): 68 self.setup_xfade_mode() 69 self.deactivate_backend = self.deactivate_xfade_mode 70 else: 71 self.setup_playbin_mode() 72 self.deactivate_backend = self.deactivate_playbin_mode 73 74 75 76 def deactivate(self): 77 self.deactivate_backend() 78 self.player = None 79 self.shell_player = None 80 81 82 def set_rgvolume(self, rgvolume): 83 # set preamp level 84 preamp = self.settings['preamp'] 85 rgvolume.props.pre_amp = preamp 86 87 # set mode 88 # there may eventually be a 'guess' mode here that tries to figure out 89 # what to do based on the upcoming tracks 90 mode = self.settings['mode'] 91 if mode == config.REPLAYGAIN_MODE_ALBUM: 92 rgvolume.props.album_mode = 1 93 else: 94 rgvolume.props.album_mode = 0 95 96 # set calculated fallback gain 97 rgvolume.props.fallback_gain = self.fallback_gain 98 99 print("updated rgvolume settings: preamp %f, album-mode %s, fallback gain %f" % ( 100 rgvolume.props.pre_amp, str(rgvolume.props.album_mode), rgvolume.props.fallback_gain)) 101 102 103 def update_fallback_gain(self, rgvolume): 104 gain = rgvolume.props.target_gain - rgvolume.props.pre_amp 105 # filter out bogus notifications 106 if abs(gain - self.fallback_gain) < EPSILON: 107 print("ignoring gain %f (current fallback gain)" % gain) 108 return False 109 if abs(gain) < EPSILON: 110 print("ignoring zero gain (pretty unlikely)") 111 return False 112 113 # update the running average 114 if len(self.previous_gain) == config.AVERAGE_GAIN_SAMPLES: 115 self.previous_gain.pop(0) 116 self.previous_gain.append(gain) 117 self.fallback_gain = sum(self.previous_gain) / len(self.previous_gain) 118 print("got target gain %f; running average of previous gain values is %f" % (gain, self.fallback_gain)) 119 return True 120 121 122 123 ### playbin mode (rgvolume ! rglimiter as global filter) 124 125 def playbin_target_gain_cb(self, rgvolume, pspec): 126 self.update_fallback_gain(rgvolume) 127 128 def setup_playbin_mode(self): 129 print("using output filter for rgvolume and rglimiter") 130 self.rgfilter = Gst.Bin() 131 132 self.rgvolume = Gst.ElementFactory.make("rgvolume", None) 133 self.rgvolume.connect("notify::target-gain", self.playbin_target_gain_cb) 134 self.rgfilter.add(self.rgvolume) 135 136 self.rglimiter = Gst.ElementFactory.make("rglimiter", None) 137 self.rgfilter.add(self.rglimiter) 138 139 self.rgfilter.add_pad(Gst.GhostPad.new("sink", self.rgvolume.get_static_pad("sink"))) 140 self.rgfilter.add_pad(Gst.GhostPad.new("src", self.rglimiter.get_static_pad("src"))) 141 self.rgvolume.link(self.rglimiter) 142 143 self.player.add_filter(self.rgfilter) 144 145 def deactivate_playbin_mode(self): 146 self.player.remove_filter(self.rgfilter) 147 self.rgfilter = None 148 149 150 ### xfade mode (rgvolume as stream filter, rglimiter as global filter) 151 152 def xfade_target_gain_cb(self, rgvolume, pspec): 153 if self.update_fallback_gain(rgvolume) is True: 154 # we don't want any further notifications from this stream 155 rgvolume.disconnect_by_func(self.xfade_target_gain_cb) 156 157 def create_stream_filter_cb(self, player, uri): 158 print("creating rgvolume instance for stream %s" % uri) 159 rgvolume = Gst.ElementFactory.make("rgvolume", None) 160 rgvolume.connect("notify::target-gain", self.xfade_target_gain_cb) 161 self.set_rgvolume(rgvolume) 162 return [rgvolume] 163 164 def limiter_changed_cb(self, settings, key): 165 if self.rglimiter is not None: 166 limiter = settings['limiter'] 167 print("limiter setting is now %s" % str(limiter)) 168 self.rglimiter.props.enabled = limiter 169 170 def setup_xfade_mode(self): 171 print("using per-stream filter for rgvolume") 172 self.stream_filter_id = self.player.connect("get-stream-filters", self.create_stream_filter_cb) 173 174 # and add rglimiter as an output filter 175 self.rglimiter = Gst.ElementFactory.make("rglimiter", None) 176 self.player.add_filter(self.rglimiter) 177 178 def deactivate_xfade_mode(self): 179 self.player.disconnect(self.stream_filter_id) 180 self.stream_filter_id = None 181 self.player.remove_filter(self.rglimiter) 182 self.rglimiter = None 183