~ubuntu-branches/ubuntu/karmic/quodlibet/karmic

« back to all changes in this revision

Viewing changes to quodlibet/player/gstbe.py

  • Committer: Bazaar Package Importer
  • Author(s): Luca Falavigna
  • Date: 2009-01-30 23:55:34 UTC
  • mfrom: (1.1.12 upstream)
  • Revision ID: james.westby@ubuntu.com-20090130235534-l4e72ulw0vqfo17w
Tags: 2.0-1ubuntu1
* Merge from Debian experimental (LP: #276856), remaining Ubuntu changes:
  + debian/patches/40-use-music-profile.patch:
    - Use the "Music and Movies" pipeline per default.
* Refresh the above patch for new upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2004 Joe Wreschnig, Michael Urman
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License version 2 as
 
5
# published by the Free Software Foundation
 
6
#
 
7
# $Id: gstbe.py 4330 2008-09-14 03:19:26Z piman $
 
8
 
 
9
import gobject
 
10
 
 
11
import pygst
 
12
pygst.require("0.10")
 
13
 
 
14
import gst
 
15
 
 
16
from quodlibet import config
 
17
from quodlibet import const
 
18
 
 
19
from quodlibet.util import fver
 
20
from quodlibet.player import error as PlayerError
 
21
from quodlibet.player._base import BasePlayer
 
22
 
 
23
def GStreamerSink(pipeline):
 
24
    """Try to create a GStreamer pipeline:
 
25
    * Try making the pipeline (defaulting to gconfaudiosink).
 
26
    * If it fails, fall back to autoaudiosink.
 
27
    * If that fails, complain loudly."""
 
28
 
 
29
    if pipeline == "gconf": pipeline = "gconfaudiosink"
 
30
    try: pipe = gst.parse_launch(pipeline)
 
31
    except gobject.GError, err:
 
32
        if pipeline != "autoaudiosink":
 
33
            try: pipe = gst.parse_launch("autoaudiosink")
 
34
            except gobject.GError: pipe = None
 
35
            else: pipeline = "autoaudiosink"
 
36
        else: pipe = None
 
37
    if pipe: return pipe, pipeline
 
38
    else:
 
39
        raise PlayerError(
 
40
            _("Unable to create audio output"),
 
41
            _("The audio output pipeline %r could not be created. Check "
 
42
              "your GStreamer settings in ~/.quodlibet/config.") % pipeline)
 
43
 
 
44
class GStreamerPlayer(BasePlayer):
 
45
    __gproperties__ = BasePlayer._gproperties_
 
46
    __gsignals__ = BasePlayer._gsignals_
 
47
 
 
48
    def __init__(self, sinkname, librarian=None):
 
49
        super(GStreamerPlayer, self).__init__()
 
50
        device, sinkname = GStreamerSink(sinkname)
 
51
        self.name = sinkname
 
52
        self.version_info = "GStreamer: %s / PyGSt: %s" % (
 
53
            fver(gst.version()), fver(gst.pygst_version))
 
54
        self.bin = gst.element_factory_make('playbin')
 
55
        self.bin.set_property('video-sink', None)
 
56
        self.bin.set_property('audio-sink', device)
 
57
        bus = self.bin.get_bus()
 
58
        bus.add_signal_watch()
 
59
        bus.connect('message', self.__message, librarian)
 
60
        self.connect_object('destroy', self.bin.set_state, gst.STATE_NULL)
 
61
        self.paused = True
 
62
 
 
63
    def __message(self, bus, message, librarian):
 
64
        if message.type == gst.MESSAGE_EOS:
 
65
            self._source.next_ended()
 
66
            self._end(False)
 
67
        elif message.type == gst.MESSAGE_TAG:
 
68
            self.__tag(message.parse_tag(), librarian)
 
69
        elif message.type == gst.MESSAGE_ERROR:
 
70
            err, debug = message.parse_error()
 
71
            err = str(err).decode(const.ENCODING, 'replace')
 
72
            self.error(err, True)
 
73
        return True
 
74
 
 
75
    def stop(self):
 
76
        # On GStreamer, we can release the device when stopped.
 
77
        if not self.paused:
 
78
            self._paused = True
 
79
            if self.song:
 
80
                self.emit('paused')
 
81
                self.bin.set_state(gst.STATE_NULL)
 
82
        self.seek(0)
 
83
 
 
84
    def do_set_property(self, property, v):
 
85
        if property.name == 'volume':
 
86
            self._volume = v
 
87
            if self.song is None:
 
88
                self.bin.set_property('volume', v)
 
89
            else:
 
90
                if config.getboolean("player", "replaygain"):
 
91
                    profiles = filter(None, self.replaygain_profiles)[0]
 
92
                    v = max(0.0, min(4.0, v * self.song.replay_gain(profiles)))
 
93
                self.bin.set_property('volume', v)
 
94
        else:
 
95
            raise AttributeError
 
96
 
 
97
    def get_position(self):
 
98
        """Return the current playback position in milliseconds,
 
99
        or 0 if no song is playing."""
 
100
        if self.bin.get_property('uri'):
 
101
            try: p = self.bin.query_position(gst.FORMAT_TIME)[0]
 
102
            except gst.QueryError: p = 0
 
103
            p //= gst.MSECOND
 
104
            return p
 
105
        else: return 0
 
106
        
 
107
    def _set_paused(self, paused):
 
108
        if paused != self._paused:
 
109
            self._paused = paused
 
110
            if self.song:
 
111
                self.emit((paused and 'paused') or 'unpaused')
 
112
                if self._paused:
 
113
                   if not self.song.is_file:
 
114
                       self.bin.set_state(gst.STATE_NULL)
 
115
                   else: self.bin.set_state(gst.STATE_PAUSED)
 
116
                else: self.bin.set_state(gst.STATE_PLAYING)
 
117
            elif paused is True:
 
118
                # Something wants us to pause between songs, or when
 
119
                # we've got no song playing (probably StopAfterMenu).
 
120
                self.emit('paused')
 
121
    def _get_paused(self): return self._paused
 
122
    paused = property(_get_paused, _set_paused)
 
123
 
 
124
    def error(self, message, lock):
 
125
        self.bin.set_property('uri', '')
 
126
        self.bin.set_state(gst.STATE_NULL)
 
127
        self.emit('error', self.song, message, lock)
 
128
        if not self.paused:
 
129
            self.next()
 
130
 
 
131
    def seek(self, pos):
 
132
        """Seek to a position in the song, in milliseconds."""
 
133
        if self.bin.get_property('uri'):
 
134
            pos = max(0, int(pos))
 
135
            if pos >= self._length:
 
136
                self.paused = True
 
137
                pos = self._length
 
138
 
 
139
            gst_time = pos * gst.MSECOND
 
140
            event = gst.event_new_seek(
 
141
                1.0, gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH,
 
142
                gst.SEEK_TYPE_SET, gst_time, gst.SEEK_TYPE_NONE, 0)
 
143
            if self.bin.send_event(event):
 
144
                self.emit('seek', self.song, pos)
 
145
 
 
146
    def _end(self, stopped):
 
147
        # We need to set self.song to None before calling our signal
 
148
        # handlers. Otherwise, if they try to end the song they're given
 
149
        # (e.g. by removing it), then we get in an infinite loop.
 
150
        song = self.song
 
151
        self.song = self.info = None
 
152
        self.emit('song-ended', song, stopped)
 
153
 
 
154
        # Then, set up the next song.
 
155
        self.song = self.info = self._source.current
 
156
        self.emit('song-started', self.song)
 
157
 
 
158
        if self.song is not None:
 
159
            # Changing the URI in a playbin requires "resetting" it.
 
160
            if not self.bin.set_state(gst.STATE_NULL): return
 
161
            self.bin.set_property('uri', self.song("~uri"))
 
162
            self._length = self.song["~#length"] * 1000
 
163
            if self._paused: self.bin.set_state(gst.STATE_PAUSED)
 
164
            else: self.bin.set_state(gst.STATE_PLAYING)
 
165
        else:
 
166
            
 
167
            self.paused = True
 
168
            self.bin.set_state(gst.STATE_NULL)
 
169
            self.bin.set_property('uri', '')
 
170
 
 
171
    def __tag(self, tags, librarian):
 
172
        if self.song and self.song.multisong:
 
173
            self._fill_stream(tags, librarian)
 
174
        elif self.song and self.song.fill_metadata:
 
175
            pass
 
176
 
 
177
    def _fill_stream(self, tags, librarian):
 
178
        changed = False
 
179
        started = False
 
180
        if self.info is self.song:
 
181
            self.info = type(self.song)(self.song["~filename"])
 
182
            self.info.multisong = False
 
183
 
 
184
        for k in tags.keys():
 
185
            value = str(tags[k]).strip()
 
186
            if not value: continue
 
187
            if k == "bitrate":
 
188
                try: bitrate = int(value)
 
189
                except (ValueError, TypeError): pass
 
190
                else:
 
191
                    if bitrate != self.song.get("~#bitrate"):
 
192
                        changed = True
 
193
                        self.song["~#bitrate"] = bitrate
 
194
                        self.info["~#bitrate"] = bitrate
 
195
            elif k == "duration":
 
196
                try: length = int(long(value) / gst.SECOND)
 
197
                except (ValueError, TypeError): pass
 
198
                else:
 
199
                    if length != self.song.get("~#length"):
 
200
                        changed = True
 
201
                        self.info["~#length"] = length
 
202
            elif k in ["emphasis", "mode", "layer"]:
 
203
                continue
 
204
            elif isinstance(value, basestring):
 
205
                value = unicode(value, errors='replace')
 
206
                k = {"track-number": "tracknumber",
 
207
                     "location": "website"}.get(k, k)
 
208
                if self.info.get(k) == value:
 
209
                    continue
 
210
                elif k == "title":
 
211
                    self.info[k] = value
 
212
                    started = True
 
213
                else:
 
214
                    self.song[k] = self.info[k] = value
 
215
                changed = True
 
216
 
 
217
        if started:
 
218
            self.emit('song-started', self.info)
 
219
        elif changed and librarian is not None:
 
220
            librarian.changed([self.song])
 
221
 
 
222
def can_play_uri(uri):
 
223
    return gst.element_make_from_uri(gst.URI_SRC, uri, '') is not None
 
224
 
 
225
def init(librarian):
 
226
    pipeline = config.get("player", "gst_pipeline") or "gconfaudiosink"
 
227
    gst.debug_set_default_threshold(gst.LEVEL_ERROR)
 
228
    if gst.element_make_from_uri(
 
229
        gst.URI_SRC,
 
230
        "file:///fake/path/for/gst", ""):
 
231
        return GStreamerPlayer(pipeline, librarian)
 
232
    else:
 
233
        raise PlayerError(
 
234
            _("Unable to open input files"),
 
235
            _("GStreamer has no element to handle reading files. Check "
 
236
              "your GStreamer installation settings."))