Changeset - 94039df5cdd9
[Not reviewed]
default
0 6 1
David McClosky - 15 years ago 2010-06-22 04:41:11
dmcc@bigasterisk.com
create Playlist class which is now used in wavecurve, musicPad, and ascoltami2.
Ignore-this: da54d0239906e81ad5d95741d22f5975
Several other refactorings. Also, totally untested.
7 files changed with 158 insertions and 72 deletions:
0 comments (0 inline, 0 general)
bin/ascoltami2
Show inline comments
 
@@ -3,33 +3,56 @@ import web, thread, gobject, sys, optpar
 
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)
bin/musicPad
Show inline comments
 
@@ -6,20 +6,18 @@ import sys, wave, logging, os
 
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')
 

	
bin/wavecurve
Show inline comments
 
@@ -3,7 +3,6 @@ import os, sys, optparse
 
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 @@ def createCurve(inpath, outpath, t):
 
    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)
light9/ascoltami/player.py
Show inline comments
 
@@ -9,7 +9,13 @@ import gst, gobject
 
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 @@ class Player(object):
 
            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 @@ class Player(object):
 
            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 @@ class Player(object):
 
        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 @@ class Player(object):
 
    def isPlaying(self):
 
        _, state, _ = self.pipeline.get_state()
 
        return state == gst.STATE_PLAYING
 
                  
 
                           
 
        
light9/ascoltami/playlist.py
Show inline comments
 
new file 100644
 
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)
light9/ascoltami/webapp.py
Show inline comments
 
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 root(object):
 

	
 
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 @@ class timeResource(object):
 
        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 timeResource(object):
 

	
 
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 songs(object):
 
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 @@ class seekPlayOrPause(object):
 
            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)
light9/showconfig.py
Show inline comments
 
@@ -41,6 +41,10 @@ def root():
 
            "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 @@ def findMpdHome():
 

	
 
    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 @@ def songFilenameFromURI(uri):
 
    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 @@ def prePostSong():
 
    graph = getGraph()
 
    return [graph.value(MUS['preSong'], L9['showPath']),
 
            graph.value(MUS['postSong'], L9['showPath'])]
 

	
0 comments (0 inline, 0 general)