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
 
#!/usr/bin/python
 
import web, thread, gobject, sys, optparse, logging
 
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
 
@@ -3,26 +3,24 @@
 
rewrite all the songs with silence at the start and end
 
"""
 
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')
 

	
 
    outputDir = os.path.join(os.path.dirname(p), "pad")
 
    try:
 
        os.makedirs(outputDir)
bin/wavecurve
Show inline comments
 
#!/usr/bin/env python
 
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)
 

	
 
    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
 
@@ -6,13 +6,19 @@ alternate to the mpd music player, for a
 
from __future__ import division
 
import time, logging, traceback
 
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
 
        self.lastWatchTime = 0
 
        self.autoStopTime = 0
 

	
 
@@ -42,41 +48,47 @@ class Player(object):
 
                return True
 
            log.debug("watch %s < %s < %s",
 
                      self.lastWatchTime, self.autoStopTime, t)
 
            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()
 
        return True
 

	
 
    def seek(self, t):
 
        assert self.playbin.seek_simple(
 
            gst.FORMAT_TIME,
 
            gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE | gst.SEEK_FLAG_SKIP,
 
            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
 
        """
 
        log.info("set song to %r" % songUri)
 
        self.pipeline.set_state(gst.STATE_READY)
 
        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):
 
        """
 
        to avoid disk seek stutters, which happened sometimes (in 2007) with the
 
        non-gst version of this program, we read the whole file to get
 
        more OS caching.
 
@@ -119,9 +131,6 @@ class Player(object):
 
        # working out well. I'd get pauses at other times that were
 
        # hard to remove.
 

	
 
    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'])
 
    if loc is None:
 
        raise ValueError("no showPath for %r" % songUri)
 
    return loc
 
@@ -25,13 +24,16 @@ class root(object):
 
        # todo: use a template; embed the show name and the intro/post
 
        # times into the page
 
        return open(sibpath(__file__, "index.html")).read()
 

	
 
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:
 
            song = None
 
        return jsonlib.write({
 
            "song" : song,
 
@@ -44,56 +46,58 @@ class timeResource(object):
 
        """
 
        post a json object with {pause: true} or {resume: true} if you
 
        want those actions. Use {t: <seconds>} to seek, optionally
 
        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):
 
            player.resume()
 
        if 't' in params:
 
            player.seek(params['t'])
 
        return "ok"
 

	
 
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,
 
             "path" : graph.value(s, L9['showPath']),
 
             "label" : graph.label(s)} for s in songs]})
 

	
 
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()
 
        else:
 
            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
 
@@ -38,12 +38,16 @@ def root():
 
    r = getenv("LIGHT9_SHOW")
 
    if r is None:
 
        raise OSError(
 
            "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"""
 
    
 
    mpdHome = None
 
    for mpdConfFilename in ["/my/dl/modified/mpd/src/mpdconf-testing",
 
@@ -56,13 +60,12 @@ def findMpdHome():
 
            if line.startswith("music_directory"):
 
                mpdHome = line.split()[1].strip('"')
 
                return mpdHome.rstrip(path.sep) + path.sep  
 

	
 
    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
 

	
 
    mpd only works off its own musicroot, which for me is
 
    /my/music. song is a file in musicDir; this function returns a
 
@@ -95,12 +98,20 @@ def songFilenameFromURI(uri):
 

	
 
    everything that uses this should be deprecated for real URIs
 
    everywhere"""
 
    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")
 

	
 
def songFilename(song):
 
    return path.join(root(), "music", "%s.wav" % song)
 

	
 
@@ -114,7 +125,6 @@ def subsDir():
 
    return path.join(root(),'subs')
 

	
 
def prePostSong():
 
    graph = getGraph()
 
    return [graph.value(MUS['preSong'], L9['showPath']),
 
            graph.value(MUS['postSong'], L9['showPath'])]
 

	
0 comments (0 inline, 0 general)