changeset 617:94039df5cdd9

create Playlist class which is now used in wavecurve, musicPad, and ascoltami2. Ignore-this: da54d0239906e81ad5d95741d22f5975 Several other refactorings. Also, totally untested.
author David McClosky <dmcc@bigasterisk.com>
date Tue, 22 Jun 2010 04:41:11 +0000
parents f85304fdc975
children 6ccb5460585f
files bin/ascoltami2 bin/musicPad bin/wavecurve light9/ascoltami/player.py light9/ascoltami/playlist.py light9/ascoltami/webapp.py light9/showconfig.py
diffstat 7 files changed, 158 insertions(+), 72 deletions(-) [+]
line wrap: on
line diff
--- a/bin/ascoltami2	Tue Jun 22 02:27:30 2010 +0000
+++ b/bin/ascoltami2	Tue Jun 22 04:41:11 2010 +0000
@@ -3,33 +3,56 @@
 from rdflib import URIRef
 sys.path.append(".")
 from light9.ascoltami.player import Player
+from light9.ascoltami.playlist import Playlist, NoSuchSong
 from light9.ascoltami.webapp import makeApp
 from light9 import networking, showconfig
 
-logging.basicConfig()
-log = logging.getLogger()
-gobject.threads_init()
+class App:
+    def __init__(self, graph, show):
+        self.graph = graph
+        self.player = Player(onEOS=self.onEOS)
+        self.show = show
+        self.playlist = Playlist.fromShow(graph, show)
 
-parser = optparse.OptionParser()
-parser.add_option('--show',
-    help='show URI, like http://light9.bigasterisk.com/show/dance2008')
-parser.add_option("-v", "--verbose", action="store_true",
-                  help="logging.DEBUG")
-(options, args) = parser.parse_args()
+    def run(self, musicPort):
+        # the cherrypy server would wedge when vidref pounds on it; this
+        # one seems to run
+        thread.start_new(web.httpserver.runbasic,
+                         (makeWebApp(self).wsgifunc(),
+                          ('0.0.0.0', musicPort)))
 
-log.setLevel(logging.DEBUG if options.verbose else logging.INFO)
+        mainloop = gobject.MainLoop()
+        mainloop.run()
+
+    def onEOS(self, song):
+        self.player.pause()
+        self.player.seek(0)
 
-if not options.show:
-    raise ValueError("missing --show http://...")
-        
-graph = showconfig.getGraph()
-player = Player()
+        try:
+            nextSong = self.playlist.nextSong(song)
+        except NoSuchSong: # we're at the end of the playlist
+            return
+
+        self.player.setSong(nextSong, play=False)
+
+if __name__ == "__main__":
+    logging.basicConfig()
+    log = logging.getLogger()
+    gobject.threads_init()
 
-# the cherrypy server would wedge when vidref pounds on it; this
-# one seems to run
-thread.start_new(web.httpserver.runbasic,
-                 (makeApp(player, graph, URIRef(options.show)).wsgifunc(),
-                  ('0.0.0.0', networking.musicPort())))
+    parser = optparse.OptionParser()
+    parser.add_option('--show',
+        help='show URI, like http://light9.bigasterisk.com/show/dance2008')
+    parser.add_option("-v", "--verbose", action="store_true",
+                      help="logging.DEBUG")
+    (options, args) = parser.parse_args()
 
-mainloop = gobject.MainLoop()
-mainloop.run()
+    log.setLevel(logging.DEBUG if options.verbose else logging.INFO)
+
+    if not options.show:
+        raise ValueError("missing --show http://...")
+            
+    graph = showconfig.getGraph()
+    app = App(graph, URIRef(options.show))
+    musicPort = networking.musicPort()
+    app.run(musicPort)
--- a/bin/musicPad	Tue Jun 22 02:27:30 2010 +0000
+++ b/bin/musicPad	Tue Jun 22 04:41:11 2010 +0000
@@ -6,20 +6,18 @@
 sys.path.append(".")
 from light9 import networking, showconfig
 from light9.namespaces import L9
+from light9.ascoltami.playlist import Playlist
 logging.basicConfig(level=logging.INFO)
 log = logging.getLogger()
 
 introPad = 4
 postPad = 9 # 5 + autostop + 4
 
-graph = showconfig.getGraph()
+playlist = Playlist.fromShow(showconfig.getGraph(), showconfig.showUri())
 # instead of taking a show uri like it should, i just convert every
 # path i find in the graph (hoping that you only loaded statements for
 # the current show)
-for p in sorted(graph.objects(None, L9['showPath'])):
-    assert p.startswith("file://")
-    p = p[len("file://"):]
-    
+for p in playlist.allSongPaths():
     log.info("read %s", p)
     inputWave = wave.open(p, 'r')
 
--- a/bin/wavecurve	Tue Jun 22 02:27:30 2010 +0000
+++ b/bin/wavecurve	Tue Jun 22 04:41:11 2010 +0000
@@ -3,7 +3,6 @@
 import run_local
 from light9.wavepoints import simp
 
-
 def createCurve(inpath, outpath, t):
     print "reading %s, writing %s" % (inpath, outpath)
     points = simp(inpath.replace('.ogg', '.wav'), seconds_per_average=t)
@@ -11,33 +10,31 @@
     f = file(outpath, 'w')
     for time_val in points:
         print >>f, "%s %s" % time_val
-    
-
 
 parser = optparse.OptionParser(usage="""%prog inputSong.wav outputCurve
 
 You probably just want -a
 
 """)
-parser.add_option("-t",type="float",default=.01,
+parser.add_option("-t", type="float", default=.01,
                   help="seconds per sample (default .01, .07 is smooth)")
-parser.add_option("-a", action="store_true",
+parser.add_option("-a", "--all", action="store_true",
                   help="make standard curves for all songs")
 options,args = parser.parse_args()
 
-
-if options.a:
+if options.all:
     from light9 import showconfig
     from light9.namespaces import L9
     from rdflib import RDF
     graph = showconfig.getGraph()
-    for song in graph.subjects(RDF.type, L9['Song']):
-        inpath = showconfig.songOnDisk(song)
+
+    playlist = Playlist.fromShow(showconfig.getGraph(), showconfig.showUri())
+    for inpath in playlist.allSongPaths():
         for curveName, t in [('music', .01),
                              ('smooth_music', .07)]:
             outpath = showconfig.curvesDir() + "/%s-%s" % (
                 showconfig.songFilenameFromURI(song), curveName)
             createCurve(inpath, outpath, t)
 else:
-    inpath,outpath = args
+    inpath, outpath = args
     createCurve(inpath, outpath, options.t)
--- a/light9/ascoltami/player.py	Tue Jun 22 02:27:30 2010 +0000
+++ b/light9/ascoltami/player.py	Tue Jun 22 04:41:11 2010 +0000
@@ -9,7 +9,13 @@
 log = logging.getLogger()
 
 class Player(object):
-    def __init__(self, autoStopOffset=4):
+    def __init__(self, autoStopOffset=4, onEOS=None):
+        """autoStopOffset is the number of seconds before the end of
+        song before automatically stopping (which is really pausing).
+        onEOS is an optional function to be called when we reach the
+        end of a stream (for example, can be used to advance the song).
+        It is called with one argument which is the URI of the song that
+        just finished."""
         self.autoStopOffset = autoStopOffset
         self.playbin = self.pipeline = gst.parse_launch("playbin2")
         self.playStartTime = 0
@@ -45,12 +51,13 @@
             if self.lastWatchTime < self.autoStopTime < t:
                 log.info("autostop")
                 self.pause()
-            if self.isPlaying() and t >= self.duration() - .2:
-                # i don't expect to hit dur exactly with this
-                # polling. What would be better would be to watch for
-                # the EOS signal and react to that
-                self.pause()
-                self.seek(0)
+            if not self.onEOS:
+                if self.isPlaying() and t >= self.duration() - .2:
+                    # i don't expect to hit dur exactly with this
+                    # polling. What would be better would be to watch for
+                    # the EOS signal and react to that
+                    self.onEOS(self.getSong())
+
             self.lastWatchTime = t
         except:
             traceback.print_exc()
@@ -63,7 +70,7 @@
             t * gst.SECOND)
         self.playStartTime = time.time()
 
-    def setSong(self, songUri):
+    def setSong(self, songUri, play=True):
         """
         uri like file:///my/proj/light9/show/dance2010/music/07.wav
         """
@@ -72,8 +79,13 @@
         self.preload(songUri)
         self.pipeline.set_property("uri", songUri)
         # todo: don't have any error report yet if the uri can't be read
-        self.pipeline.set_state(gst.STATE_PLAYING)
-        self.playStartTime = time.time()
+        if play:
+            self.pipeline.set_state(gst.STATE_PLAYING)
+            self.playStartTime = time.time()
+
+    def getSong(self):
+        """Returns the URI of the current song."""
+        return self.playbin.get_property("uri")
 
     def preload(self, songUri):
         """
@@ -122,6 +134,3 @@
     def isPlaying(self):
         _, state, _ = self.pipeline.get_state()
         return state == gst.STATE_PLAYING
-                  
-                           
-        
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/light9/ascoltami/playlist.py	Tue Jun 22 04:41:11 2010 +0000
@@ -0,0 +1,45 @@
+from light9.showconfig import getSongsFromShow
+from light9.namespaces import L9
+
+class NoSuchSong(ValueError):
+    """Raised when a song is requested that doesn't exist (e.g. one
+    after the last song in the playlist)."""
+
+class Playlist(object):
+    def __init__(self, graph, playlistUri):
+        self.songs = list(graph.items(playlistUri))
+    def nextSong(self, currentSong):
+        """Returns the next song in the playlist or raises NoSuchSong if 
+        we are at the end of the playlist."""
+        try:
+            currentIndex = self.songs.index(currentSong)
+        except IndexError:
+            raise ValueError("%r is not in the current playlist (%r)." % \
+                (currentSong, self.playlistUri))
+
+        try:
+            nextSong = self.songs[currentIndex + 1]
+        except IndexError:
+            raise NoSuchSong("%r is the last item in the playlist." % \
+                             currentSong)
+
+        return nextSong
+    def allSongs(self):
+        """Returns a list of all song URIs in order."""
+        return self.songs
+    def allSongPaths(self):
+        """Returns a list of the filesystem paths to all songs in order."""
+        paths = []
+        for song in self.songs:
+            p = graph.value(song, L9['showPath'])
+            assert p.startswith("file://"), p
+            p = p[len("file://"):]
+            paths.append(p)
+        return paths
+
+    @classmethod
+    def fromShow(playlistClass, graph, show):
+        playlistUri = graph.value(show, L9['playList'])
+        if not playlistUri:
+            raise ValueError("%r has no l9:playList" % show)
+        return playlistClass(graph, playlistUri)
--- a/light9/ascoltami/webapp.py	Tue Jun 22 02:27:30 2010 +0000
+++ b/light9/ascoltami/webapp.py	Tue Jun 22 04:41:11 2010 +0000
@@ -1,11 +1,10 @@
 import web, jsonlib
 from twisted.python.util import sibpath
 from light9.namespaces import L9
+from light9.showconfig import getSongsFromShow
 from rdflib import URIRef
-player = None
-graph = None
-show = None
 
+app = None
 
 def songLocation(graph, songUri):
     loc = graph.value(songUri, L9['showPath'])
@@ -28,7 +27,10 @@
 
 class timeResource(object):
     def GET(self):
-        playingLocation = player.playbin.get_property("uri")
+        player = app.player
+        graph = app.graph
+
+        playingLocation = player.getSong()
         if playingLocation:
             song = songUri(graph, URIRef(playingLocation))
         else:
@@ -47,6 +49,7 @@
         with a pause/resume command too.
         """
         params = jsonlib.read(web.data(), use_float=True)
+        player = app.player
         if params.get('pause', False):
             player.pause()
         if params.get('resume', False):
@@ -57,12 +60,10 @@
 
 class songs(object):
     def GET(self):
-        playList = graph.value(show, L9['playList'])
-        if not playList:
-            raise ValueError("%r has no l9:playList" % show)
-        songs = list(graph.items(playList))
+        graph = app.graph
 
-        
+        songs = getSongsFromShow(graph, app.show)
+
         web.header("Content-type", "application/json")
         return jsonlib.write({"songs" : [
             {"uri" : s,
@@ -72,11 +73,15 @@
 class songResource(object):
     def POST(self):
         """post a uri of song to switch to (and start playing)"""
-        player.setSong(songLocation(graph, URIRef(web.data())))
+        graph = app.graph
+
+        app.player.setSong(songLocation(graph, URIRef(web.data())))
         return "ok"
     
 class seekPlayOrPause(object):
     def POST(self):
+        player = app.player
+
         data = jsonlib.read(web.data(), use_float=True)
         if player.isPlaying():
             player.pause()
@@ -84,16 +89,15 @@
             player.seek(data['t'])
             player.resume()
 
-def makeApp(thePlayer, theGraph, theShow):
-    global player, graph, show
-    player, graph, show = thePlayer, theGraph, theShow
+def makeWebApp(theApp):
+    global app
+    app = theApp
 
-    urls = ("/", "root",
-            "/time", "timeResource",
-            "/song", "songResource",
-            "/songs", "songs",
-            "/seekPlayOrPause", "seekPlayOrPause",
+    urls = (r"/", "root",
+            r"/time", "timeResource",
+            r"/song", "songResource",
+            r"/songs", "songs",
+            r"/seekPlayOrPause", "seekPlayOrPause",
             )
 
-    app = web.application(urls, globals(), autoreload=False)
-    return app
+    return web.application(urls, globals(), autoreload=False)
--- a/light9/showconfig.py	Tue Jun 22 02:27:30 2010 +0000
+++ b/light9/showconfig.py	Tue Jun 22 04:41:11 2010 +0000
@@ -41,6 +41,10 @@
             "LIGHT9_SHOW env variable has not been set to the show root")
     return r
 
+def showUri():
+    """Return the show URI associated with $LIGHT9_SHOW."""
+    return URIRef(file(path.join(root(), 'URI').read().strip()))
+
 def findMpdHome():
     """top of the music directory for the mpd on this system,
     including trailing slash"""
@@ -59,7 +63,6 @@
 
     raise ValueError("can't find music_directory in any mpd config file")
 
-
 def songInMpd(song):
     """
     get the mpd path (with correct encoding) from the song URI
@@ -98,6 +101,14 @@
     assert isinstance(uri, URIRef)
     return uri.split('/')[-1]
 
+def getSongsFromShow(graph, show):
+    playList = graph.value(show, L9['playList'])
+    if not playList:
+        raise ValueError("%r has no l9:playList" % show)
+    songs = list(graph.items(playList))
+
+    return songs
+
 def curvesDir():
     return path.join(root(),"curves")
 
@@ -117,4 +128,3 @@
     graph = getGraph()
     return [graph.value(MUS['preSong'], L9['showPath']),
             graph.value(MUS['postSong'], L9['showPath'])]
-