clGstreamer010.py

00001 #!/usr/bin/env python
00002 # -*- coding: utf-8 -*-
00003 
00004 # ----------------------------------------------------------------------------
00005 # pyjama - python jamendo audioplayer
00006 # Copyright (c) 2008 Daniel Nögel
00007 #
00008 # This program is free software: you can redistribute it and/or modify
00009 # it under the terms of the GNU General Public License as published by
00010 # the Free Software Foundation, either version 3 of the License, or
00011 # (at your option) any later version.
00012 #
00013 # This program is distributed in the hope that it will be useful,
00014 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00015 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016 # GNU General Public License for more details.
00017 # You should have received a copy of the GNU General Public License
00018 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
00019 # ----------------------------------------------------------------------------
00020 
00021 
00022 ## @package clGstreamer010
00023 # Pyjamas player module
00024 
00025 
00026 import pygtk
00027 pygtk.require('2.0')
00028 
00029 import sys, os
00030 
00031 import gobject
00032 
00033 import pygst
00034 pygst.require('0.10')
00035 import gst
00036 import gst.interfaces
00037 import gtk
00038 import random
00039 
00040 # Needed to prevent that the *same*
00041 # object appears to times in the playlist
00042 import copy
00043 
00044 # Time formatting
00045 from time import strftime, gmtime, time
00046 
00047 import functions
00048 
00049 ## Converts a given time from seconds to a more readable string
00050 # @param seconds as int
00051 # @return string
00052 def sec2time(seconds):
00053     try:
00054         time_string = strftime("%M:%S", gmtime(seconds))
00055     except ValueError:
00056         return "00:00"
00057     return time_string
00058 
00059 ## The new track object replaces all those
00060 # track-dicts
00061 class Track:
00062     def __init__(self):
00063         self.name = None
00064         self.duration = None
00065         self.numalbum = None
00066         self.id = None
00067         self.license = None
00068         self.id3genre = None    
00069 
00070         self.album_id = None
00071         self.album_name = None
00072 
00073         self.artist_name = None
00074         self.artist_id = None
00075 
00076         self.stream = None
00077         self.uid = None
00078 
00079         self.position_in_playlist = None
00080 
00081     ## This actually wraps my class into a dictionary :)
00082     # This methode was implemented for compability reasons
00083     # only and will be deleted some day.
00084     def __getitem__(self, item):
00085         print ""
00086         print "The track object now no longer is a dictionary"
00087         print "You can easily switch to the new track-object"
00088         print "by simply replacing track['%s'] through" % str(item)
00089         print "track.%s." % str(item)
00090         print ""
00091         print "Using a track object as a dictionary is deprecated!"
00092         return self.__dict__[item]
00093 
00094 
00095     def print_info(self):
00096         #return "Track: Artist: %s, Album: %s, Track: %s" % (self.artist_name, self.album_name, self.name)
00097         ret = ""
00098         for item in self.__dict__:
00099             ret += "%s: %s\n" % (item, str(self.__dict__[item]))
00100         return ret
00101 
00102     __call__ = print_info
00103     __str__ = print_info
00104 
00105 class TrackError(Exception):
00106     pass
00107 
00108 ## !!! WORKING !!!
00109 #gst-launch-0.10 uridecodebin uri=http://upload.wikimedia.org/wikipedia/commons/4/48/Frase_de_Neil_Armstrong.ogg ! volume volume=1.0 ! equalizer-10bands band0=12.0 band1=12.0 band2=12.0 ! pulsesink
00110 
00111 ## Pyjama's Player class based on gstreamer0.10
00112 class Player:
00113     def __init__(self, parent, sink="pulsesink"):
00114         parent.Events.add_event("player-status")
00115         parent.Events.add_event('end_of_playlist')
00116 
00117         ## Boolean - is the player playing?
00118         self.playing = False
00119 
00120         ## current buffer
00121         self.buffer = 0
00122 
00123         BIN_TO_USE = "new"
00124         if "oldbin" in sys.argv: BIN_TO_USE = "old"
00125 
00126         self.equalizer_available = False
00127         self.equalizer_bands = 10
00128 
00129         ## The gstreamer player object
00130         if BIN_TO_USE == "new":
00131             self.player = gst.element_factory_make("playbin", "player")
00132 
00133             #equalizer-nbands num-bands=15 band5
00134             pipe = "audioresample ! audioamplify amplification=1.0 ! equalizer-10bands ! %s" % sink
00135             pipe = pipe.split(" ")
00136             #~ pipe = "audioresample ! equalizer-10bands band0=12.0 band1=12.0 band2=12.0 ! pulsesink".split(" ")
00137             
00138             self.bin = gst.Bin('my-bin')
00139             audioconvert = gst.element_factory_make('audioconvert')
00140             self.bin.add(audioconvert)
00141             pad = audioconvert.get_pad("sink")
00142             ghostpad = gst.GhostPad("sink", pad)
00143             self.bin.add_pad(ghostpad)
00144             
00145             
00146             next_is_pipe_element=True
00147             pipeel=None
00148             els=[audioconvert]
00149             i=0
00150             while len(pipe)>i:
00151                 el=pipe[i]
00152                 i+=1
00153                 if el.strip()!="":
00154                     if el=="!":
00155                         next_is_pipe_element=True
00156                     else:
00157                         if next_is_pipe_element:
00158                             if parent.debug:
00159                                 print ("pipe element: %s"%el)
00160                             if el.startswith('equalizer'):
00161                                 pipeel = gst.element_factory_make(el)
00162                                 self.eq=pipeel
00163                                 self.bin.add(pipeel)
00164                                 els.append(pipeel)
00165                             elif el.startswith('audioamplify'):
00166                                 pipeel = gst.element_factory_make(el)
00167                                 self.amp=pipeel
00168                                 self.bin.add(pipeel)
00169                                 els.append(pipeel)
00170                             elif el.startswith('audioresample'):
00171                                 pipeel = gst.element_factory_make(el)
00172                                 self.resample=pipeel
00173                                 self.bin.add(pipeel)
00174                                 els.append(pipeel)
00175                             elif el.startswith('audio/x-raw-int'):
00176                                 caps = gst.Caps(el)
00177                                 filter = gst.element_factory_make("capsfilter", "filter")
00178                                 self.bin.add(filter)
00179                                 filter.set_property("caps", caps)
00180                                 els.append(filter)
00181                             else:
00182                                 pipeel = gst.element_factory_make(el)
00183                                 self.bin.add(pipeel)
00184                                 els.append(pipeel)
00185 
00186                             next_is_pipe_element=False
00187                         else:
00188                             data=el.split("=")
00189                             if parent.debug:
00190                                 print ("\tproperty %s = %s"%(data[0],data[1]))
00191                             try:
00192                                 if data[1].isdigit():
00193                                     val=int(data[1])
00194                                 else:
00195                                     val=float(data[1])
00196                             except:
00197                                 val=data[1]
00198                             pipeel.set_property(data[0],val)
00199                             
00200             
00201                     
00202             
00203             gst.element_link_many(*els)
00204             fakesink = gst.element_factory_make('fakesink', "my-fakesink")
00205             #self.player.set_property("video-sink", fakesink)
00206             self.player.set_property("audio-sink", self.bin)
00207             
00208             
00209             #~ bus = self.player.get_bus()
00210             #~ bus.add_signal_watch()
00211             #~ bus.connect('message', self.on_message)
00212             #~ self.player.set_state(gst.STATE_PLAYING)
00213 
00214             self.src = self.player
00215             self.volume = self.player
00216 
00217             self.equalizer_available = True
00218             
00219         elif BIN_TO_USE == "old":
00220             self.player = gst.element_factory_make("playbin", "player") # old playbin
00221             self.src = self.player
00222             self.volume = self.player
00223         else: ## not working pipe
00224             self.player = gst.Pipeline("pyjama_player")
00225             
00226             self.src = gst.element_factory_make("uridecodebin", "src")
00227             self.player.add(self.src)
00228 
00229             self.volume_bin = gst.element_factory_make("volume", "volume")
00230             self.player.add(self.volume_bin)
00231 
00232             self.equalizer_bin = gst.element_factory_make("equalizer-10bands", "equalizer")
00233             self.player.add(self.equalizer_bin)
00234 
00235             self.pulse_bin = gst.element_factory_make("pulsesink", "pulse") # alsasink
00236             self.player.add(self.pulse_bin)
00237 
00238         
00239 
00240         #
00241         # The following lines of code were meant to replace the playbin
00242         # for some reasons this didn't work for me
00243         #
00244 
00245         #        #gst-launch-0.10 souphttpsrc location=http://upload.wikimedia.org/wikipedia/commons/4/48/Frase_de_Neil_Armstrong.ogg ! decodebin ! audioconvert ! alsasink
00246 
00247         #        self.player = gst.Pipeline("pyjama_player")
00248 
00249         #        self.src = gst.element_factory_make("souphttpsrc", "src")
00250         #        self.player.add(self.src)
00251         #        self.decoder = gst.element_factory_make("decodebin", "decoder")
00252         #        self.player.add(self.decoder)
00253         #        self.converter = gst.element_factory_make("audioconvert", "convert")
00254         #        self.player.add(self.converter)
00255         #        self.sink = gst.element_factory_make("alsasink", "sink")
00256         #        self.player.add(self.sink)
00257 
00258 
00259         ## End of stream
00260         self.EOS = False
00261 
00262         bus = self.player.get_bus()
00263         bus.enable_sync_message_emission()
00264         bus.add_signal_watch()
00265         bus.connect('sync-message::element', self.on_sync_message)
00266         bus.connect('message', self.on_message)
00267 
00268 #        gobject.timeout_add(500, self.update)
00269 
00270 
00271         ## Reference to pyjama
00272         self.pyjama = parent
00273         ## Song being played
00274         self.cur_playing = None
00275         ## Song played before
00276         self.last_played = None
00277         ## Obsolete
00278         self.connection_tries = 0
00279 
00280         ## Dictionary - Number each artist was played
00281         self.artist_counter = {}
00282         ## Dictionary - Number each album was played
00283         self.album_counter = {}
00284         ## Dictionary - Number each track was played
00285         self.track_counter = {}
00286 
00287         ## List of Tracks
00288         self.playlist = []#{}
00289         ## List of ids of the tracks in the playlist
00290         self.playlist_track_uids = []
00291 
00292         ## Playback position in %
00293         self.percentage = None
00294         self.text = None
00295         ## Status of the player
00296         self.status = None 
00297         ## Current position of song in s
00298         self.cursec = None
00299         ## Duration of the song being played in s
00300         self.duration = None
00301         ## Seconds to go
00302         self.togo = None
00303         ## Name of the artist being played
00304         self.artist_name  = None
00305         ## Name of the track being played
00306         self.track_name = None
00307         ## Number of the track being played
00308         self.numalbum = None
00309 
00310         ## Load equalizer settings:
00311         if self.equalizer_available:
00312             bands = []
00313             for i in range(self.equalizer_bands):
00314                 val = self.pyjama.settings.get_value("SOUND", "band%i" % i, 0.0, float)
00315                 bands.append(val)
00316             self.set_equalizer(bands)
00317 
00318     ## Set pyjama's equalizer
00319     # @param self OP
00320     # @param bands A list with values between -24.0 and +12.0
00321     def set_equalizer(self, bands):
00322         i = 0
00323         for band in bands:
00324             self.eq.set_property("band%i" % i, float(band))
00325             i += 1
00326 
00327     ###################################################################
00328     #
00329     # adds a track-object to the playlist
00330     # RETURNS: n/A
00331     #
00332     def add2playlist(self, track):
00333         if isinstance(track, Track):
00334             for pl_track in self.playlist:
00335                 if track is pl_track:
00336                     if self.pyjama.verbose:
00337                         print ("Track #%i in playlist and the track you added are identical. Will now create a copy." % pl_track.position_in_playlist)
00338                     track = copy.deepcopy(track)
00339             count = len(self.playlist)
00340             # self.playlist[count] = track
00341             track.uid = random.random()
00342             track.position_in_playlist = count
00343             self.playlist.append(track)
00344             self.playlist_track_uids.append(track.uid)
00345         else:
00346             print "No valid track object was given"
00347             raise TrackError
00348 
00349     ###################################################################
00350     #
00351     # moves a track-object inside the playlist
00352     # RETURNS: n/A
00353     #
00354     def move_item(self, source, dest, before):
00355 #        print source, dest
00356 #      #  if source == dest or (dest == source-1 and not before) or (dest == source+1 and before): return
00357 #        if dest == 0 and before: dest = 1
00358 #        if dest == len(self.playlist) and not before: dest = len(self.playlist) 
00359         if dest > source:
00360             if before: 
00361                 before = -1
00362             else:
00363                 before = 0
00364         else:
00365             if before: 
00366                 before = 0
00367             else:
00368                 before = 1
00369         dest = dest + before
00370 
00371         old = self.playlist.pop(source)
00372         self.playlist.insert(dest, old)
00373         old.position_in_playlist = dest
00374         print ("new pos in playlist:", dest)
00375 
00376         old = self.playlist_track_uids.pop(source)
00377         self.playlist_track_uids.insert(dest, old)
00378 
00379         if self.pyjama.debug_extreme:
00380             for track in self.playlist:
00381                 print track.id
00382 
00383     ###################################################################
00384     #
00385     # remove a track from playlist
00386     # RETURNS: n/A
00387     #
00388     def remove(self, items):
00389         for item in items:
00390             track = self.playlist.pop(item)
00391             self.playlist_track_uids.remove(track.uid)
00392 #        for item in items:
00393 #            del self.playlist[item]
00394 #            self.playlist_track_uids = []
00395 #        pl = {}
00396 #        counter = 0
00397 #        for x in self.playlist:
00398 #            pl[counter] = self.playlist[x]
00399 #            self.playlist_track_uids.append(self.playlist[x]['uid'])
00400 #            counter += 1
00401 #        self.playlist = pl        
00402 
00403     ###################################################################
00404     #
00405     # guess what: clear playlist 
00406     # RETURNS: n/A
00407     #
00408     def clearplaylist(self):
00409         self.cur_playing = None
00410         self.last_played = None
00411         self.playlist = []#{}
00412         self.playlist_track_uids = []
00413         
00414     ###################################################################
00415     #
00416     # set volume via mocp
00417     # RETURNS: n/A
00418     #
00419     def set_volume(self, volume):
00420         #print volume
00421         volume = volume / 100.0
00422         #print volume
00423         self.volume.set_property('volume', volume)
00424 
00425 
00426     ###################################################################
00427     #
00428     # checks status of mocp
00429     # RETURNS: None if not playing, otherwise mocp status-infos
00430     #
00431     def check_status(self):
00432         if self.status == "paused":
00433             self.pyjama.Events.raise_event("player-status", "paused")
00434             return "paused"
00435         if self.status == "End":
00436             self.pyjama.Events.raise_event("player-status", "end")
00437             return None
00438         if self.cur_playing == None: 
00439             self.pyjama.Events.raise_event("player-status", "end")
00440             self.status =  "End"
00441         else:
00442             ret = self.get_state
00443 #            if self.pyjama.debug_extreme:
00444 #                print ret
00445 #            else:
00446             self.status = "Playing"
00447             position, duration = self.query_position() 
00448             self.cursec = position / (10**9)
00449             self.duration = int(self.cur_playing.duration)
00450             self.percentage =  (1.0 / self.duration) * self.cursec
00451             if self.percentage > 1: self.percentage = 0
00452             self.togo = self.cursec - self.duration
00453             self.artist_name = self.cur_playing.artist_name
00454             self.track_name = self.cur_playing.name
00455             self.numalbum = self.cur_playing.numalbum
00456             self.text = "%s:%s: %s / %s - %s" % (self.cur_playing.artist_name ,self.cur_playing.name, sec2time(self.cursec), sec2time(self.duration), sec2time(self.togo*-1))
00457             self.pyjama.Events.raise_event("player-status", "playing", self.cursec, self.duration)
00458 
00459 #                print self.togo
00460 #                if self.togo <= 0:
00461 #                    uid = self.cur_playing['uid']
00462 #                    if uid in self.playlist_track_uids:
00463 #                        pos = self.playlist_track_uids.index(uid)
00464 #                        if len(self.playlist)> pos+1:
00465 #                            self.play(self.playlist[pos+1], pos+1)        
00466         
00467 
00468     def on_eos(self):
00469         self.EOS = False
00470         #print "END OF STREAM"
00471         #self.playing = False        
00472         self.next()
00473         #self.player.seek(0L)
00474         #self.play_toggled()
00475         
00476     def on_sync_message(self, bus, message):
00477         if message.structure is None:
00478             return
00479         if message.structure.get_name() == 'prepare-xwindow-id':
00480             self.videowidget.set_sink(message.src)
00481             message.src.set_property('force-aspect-ratio', True)
00482             
00483     def on_message(self, bus, message):
00484         t = message.type
00485         if t == gst.MESSAGE_ERROR:
00486             err, debug = message.parse_error()
00487             print "GstError: %s" % err, debug
00488             print 99*"-"
00489             print self.cur_playing.stream
00490             print self.src.get_property('uri')
00491 
00492             if self.EOS:
00493                 self.on_eos()
00494             else:
00495                 self.next()
00496         elif t == gst.MESSAGE_EOS:
00497             self.EOS = True
00498             self.on_eos()
00499         elif t == gst.MESSAGE_BUFFERING:
00500             percentage = message.parse_buffering()
00501             self.buffer = percentage
00502 
00503     def set_location(self, location):
00504         #~ self.player.set_property('uri', location)
00505         self.src.set_property('uri', location)
00506 
00507     def query_position(self):
00508         "Returns a (position, duration) tuple"
00509         try:
00510             position, format = self.src.query_position(gst.FORMAT_TIME)
00511         except:
00512             #~ print ("nopos")
00513             position = gst.CLOCK_TIME_NONE
00514 
00515         try:
00516             duration, format = self.src.query_duration(gst.FORMAT_TIME)
00517         except:
00518             #~ print ("nodur")
00519             duration = gst.CLOCK_TIME_NONE
00520 
00521         return (position, duration)
00522 
00523 
00524     ## Seek inside a track
00525     # @param self Object Pointer
00526     # @param location Position to seek to in ns
00527     # @return 
00528     # - True if seeking was succesfull
00529     # - False if seeking failed
00530     def seek(self, location):
00531         gst.debug("seeking to %r" % location)
00532         event = gst.event_new_seek(1.0, gst.FORMAT_TIME,
00533             gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE,
00534             gst.SEEK_TYPE_SET, location,
00535             gst.SEEK_TYPE_NONE, 0)
00536 
00537         res = self.src.send_event(event)
00538         if res:
00539             gst.info("setting new stream time to 0")
00540             self.src.set_new_stream_time(0L)
00541             return True
00542         else:
00543             gst.error("seek to %r failed" % location)
00544             return False
00545 
00546 
00547     def pause(self):
00548         gst.info("pausing player")
00549         self.status = "paused"
00550         self.src.set_state(gst.STATE_PAUSED)
00551         self.playing = False
00552 
00553     ###################################################################
00554     #
00555     # ???
00556     # RETURNS: n/A
00557     #
00558     def increment_counter(self, array, key):
00559         if array.has_key(key):
00560             array[key] += 1
00561         else:
00562             array[key] = 1
00563 #        print array[key]
00564 
00565     ###################################################################
00566     #
00567     # play a track
00568     # RETURNS: n/A
00569     #            
00570     def play(self, track=None, activate_item=-1):
00571 #        self.player.set_state(gst.STATE_NULL)
00572 #        gst.info("stopped player")
00573         if self.status == "paused" and track == None:
00574             track = self.cur_playing
00575         self.increment_counter(self.artist_counter, track.artist_id)
00576         self.increment_counter(self.album_counter, track.album_id)
00577         self.increment_counter(self.track_counter, track.id)
00578         if track.stream == "query":
00579             track.stream = self.pyjama.jamendo.stream(track.id)
00580         if track.duration == 0:
00581             #GET FROM JAMENDO
00582             track.duration = 60*59
00583 
00584         self.last_played = self.cur_playing
00585         self.cur_playing = track
00586         
00587         if self.status != "paused":
00588             self.stop()
00589 
00590         try:
00591             self.set_location(track.stream)
00592         except Exception, inst:
00593             print ("error: %s" % inst)
00594         gst.info("playing player")
00595         self.src.set_state(gst.STATE_PLAYING)
00596         self.playing = True
00597         self.status = "Playing"
00598 
00599         if activate_item > -1:
00600             track.position_in_playlist = activate_item
00601 
00602         ev = self.pyjama.Events
00603         ev.raise_event('nowplaying', track)
00604 #        ev.raise_event('nowplaying', artist = track['artist_name'], album = track['album_name'], track = track['name'], icon = img)
00605         #self.pyjama.notification(_("Now playing"), "<b><i>%s</i></b>:\n%s" % (track['artist_name'], track['name']), icon = img, size =)
00606 
00607         if activate_item > -1:
00608             self.pyjama.setplaylist(activate_item)
00609 
00610 
00611     ###################################################################
00612     #
00613     # stop playing
00614     # RETURNS: n/A
00615     #        
00616     def stop(self):
00617         self.percentage = None
00618         self.text = None
00619         self.cursec = None
00620         self.duration = None
00621         self.togo = None
00622         self.artist_name  = None
00623         self.track_name = None
00624         self.numalbum = None
00625         self.status = "End"
00626         self.src.set_state(gst.STATE_NULL)
00627         gst.info("stopped player")
00628                 
00629 
00630 
00631     ###################################################################
00632     #
00633     # play next track in playlist
00634     # RETURNS: n/A
00635     #
00636     def next(self):
00637         if self.cur_playing is not None:
00638             uid = self.cur_playing.uid
00639             if uid in self.playlist_track_uids:
00640                 pos = self.playlist_track_uids.index(uid)
00641                 if len(self.playlist)> pos+1:
00642                     self.play(self.playlist[pos+1], pos+1)
00643                 else:
00644                     self.status = "End"
00645                     self.pyjama.Events.raise_event('end_of_playlist')
00646                 
00647     ###################################################################
00648     #
00649     # play previous track in playlist
00650     # RETURNS: n/A
00651     #                
00652     def prev(self):
00653         if self.cur_playing is not None:
00654             uid = self.cur_playing.uid
00655             if uid in self.playlist_track_uids:
00656                 pos = self.playlist_track_uids.index(uid)
00657                 if pos-1 >= 0:
00658                     self.play(self.playlist[pos-1], pos-1)                
00659 
00660     def get_state(self, timeout=1):
00661         print "getstate"
00662         return self.src.get_state(timeout=timeout)
00663 
00664     def is_playing(self):
00665         return self.playing

Generated on Thu Jun 4 19:08:24 2009 for Pyjama by  doxygen 1.5.8