|
|
|
import urllib.request
|
|
|
|
|
|
|
|
import kode256
|
|
|
|
import requests
|
|
|
|
import gi
|
|
|
|
|
|
|
|
from gi.repository import TotemPlParser
|
|
|
|
from gsp import GstreamerPlayer
|
|
|
|
|
|
|
|
from bureau import Bureau, add_command, add_webview
|
|
|
|
|
|
|
|
|
|
|
|
class Audio(Bureau):
|
|
|
|
"""
|
|
|
|
The Audio Services department provides for playback and recording
|
|
|
|
of sound within the office environment.
|
|
|
|
"""
|
|
|
|
|
|
|
|
name = "Audio Services Dept."
|
|
|
|
prefix = "AU"
|
|
|
|
version = 0
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
Bureau.__init__(self)
|
|
|
|
|
|
|
|
self.urldb = self.open_db("urldb")
|
|
|
|
self.player = GstreamerPlayer(None)
|
|
|
|
self.current_uri = None
|
|
|
|
|
|
|
|
@add_command("p", "Play an album, track or a live stream.")
|
|
|
|
def play(self, data):
|
|
|
|
"""
|
|
|
|
Initiates playback of a media reference. This could be a song or album
|
|
|
|
stored on the local office or remote URLs for live playback. Currently,
|
|
|
|
only supports line-out signals on the default DAC.
|
|
|
|
"""
|
|
|
|
shortcode, _ = data.split(".")
|
|
|
|
self.log.debug("looking up shortcode " + shortcode)
|
|
|
|
pl_url = self.urldb.get(shortcode)
|
|
|
|
self.log.debug(" playing url " + pl_url)
|
|
|
|
pl_file, _ = urllib.request.urlretrieve(pl_url)
|
|
|
|
pl_file= "file://" + pl_file
|
|
|
|
|
|
|
|
parser = TotemPlParser.Parser()
|
|
|
|
urls = []
|
|
|
|
def parsercb(parser, uri, metadata, urls):
|
|
|
|
#TODO: would be nice to use the metadata when adding the station
|
|
|
|
# and for now-playing
|
|
|
|
urls.append(uri)
|
|
|
|
parser.connect("entry-parsed", parsercb, urls)
|
|
|
|
parser.parse(pl_file, False)
|
|
|
|
|
|
|
|
for url in urls:
|
|
|
|
print("stream uri", url)
|
|
|
|
self.player.queue(url)
|
|
|
|
self.current_uri = url
|
|
|
|
|
|
|
|
@add_command("stop", "Halt audio playback.")
|
|
|
|
def stop(self):
|
|
|
|
"""
|
|
|
|
Stops all audio currently playing audio output.
|
|
|
|
"""
|
|
|
|
self.player.stop()
|
|
|
|
|
|
|
|
@add_command("resu", "Resume playback.")
|
|
|
|
def resume(self):
|
|
|
|
"""
|
|
|
|
Resume playback of paused audio.
|
|
|
|
"""
|
|
|
|
if self.current_uri:
|
|
|
|
self.player.queue(self.current_uri)
|
|
|
|
|
|
|
|
@add_command("next", "Play the next song.")
|
|
|
|
def play_next(self):
|
|
|
|
"""
|
|
|
|
Skip to the next song in the playlist or album.
|
|
|
|
"""
|
|
|
|
#subprocess.call(["mocp", "-f"])
|
|
|
|
# TODO
|
|
|
|
self.print_small("SORRY! playlist->next is not yet implemented.")
|
|
|
|
|
|
|
|
@add_command("prev", "Play the previous song.")
|
|
|
|
def play_prev(self):
|
|
|
|
"""
|
|
|
|
Skip to the previous song in the playlist or album.
|
|
|
|
"""
|
|
|
|
#subprocess.call(["mocp", "-r"])
|
|
|
|
# TODO
|
|
|
|
self.print_small("SORRY! playlist->prev is not yet implemented.")
|
|
|
|
|
|
|
|
@add_command("nowp", "Now Playing")
|
|
|
|
def now_playing(self):
|
|
|
|
"""
|
|
|
|
Prints the currently playing song or stream on the small printer.
|
|
|
|
"""
|
|
|
|
out = "Now Playing: "
|
|
|
|
if self.player.title:
|
|
|
|
out += self.player.title + "\n"
|
|
|
|
if self.player.artist:
|
|
|
|
out += "by " + self.player.artist + "\n"
|
|
|
|
if self.player.album:
|
|
|
|
out += "from the album '" + self.player.album + "'\n"
|
|
|
|
|
|
|
|
# TODO: add fields for "organization" and "location" for gstreamer-player
|
|
|
|
# so we can get the station ID stuff
|
|
|
|
|
|
|
|
self.log.debug("info output:" + out)
|
|
|
|
self.print_small(out)
|
|
|
|
|
|
|
|
@add_webview("radio", "radio")
|
|
|
|
def radio_webview(self, data=None):
|
|
|
|
"""
|
|
|
|
Edit internet radio stations.
|
|
|
|
"""
|
|
|
|
#TODO: allow deletes
|
|
|
|
#TODO: print out any new station
|
|
|
|
#TODO: use some kind of nicer templates?
|
|
|
|
ret = """
|
|
|
|
<html><head><title>screenless office - internet radio admin</title></head>
|
|
|
|
<body>
|
|
|
|
<h1>Audio Department</h1>
|
|
|
|
"""
|
|
|
|
if data:
|
|
|
|
if data["addurl"]:
|
|
|
|
self.save_url(data["addurl"])
|
|
|
|
ret += "<div>New station " + data["addurl"] + " saved!</div>\n"
|
|
|
|
|
|
|
|
ret += "<h2>Radio Stations<h2>\n <ul>\n"
|
|
|
|
with self.urldb.env.begin(db=self.urldb.db) as txn:
|
|
|
|
for sh_code, url in txn.cursor():
|
|
|
|
ret += "<li>" + url.decode("utf-8") + "</li>\n"
|
|
|
|
ret += "</ul>\n"
|
|
|
|
|
|
|
|
ret += """
|
|
|
|
<form action='/AU/radio' method='POST'>
|
|
|
|
<ul>
|
|
|
|
<li>
|
|
|
|
<label for="addurl">Add Radio Station URL:</label>
|
|
|
|
<input type="text" id="addurl" name="addurl" />
|
|
|
|
</li>
|
|
|
|
<li class="button">
|
|
|
|
<button type="submit">Save</button>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</form>
|
|
|
|
</body></html>"""
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def save_url(self, url):
|
|
|
|
"""
|
|
|
|
saves an url for a local file or network audio stream.
|
|
|
|
"""
|
|
|
|
# TODO: might be nice to save some station info: title, perhaps a logo, etc.
|
|
|
|
code = self.urldb.store_and_get_shortcode(url)
|
|
|
|
self.print_url(code)
|
|
|
|
print("saved url with shortcode: ", code)
|
|
|
|
|
|
|
|
def print_url(self, shortcode):
|
|
|
|
url = self.urldb.get(shortcode)
|
|
|
|
# download the url
|
|
|
|
headers = {'User-Agent': 'Mozilla/5.0'}
|
|
|
|
try:
|
|
|
|
resp = requests.get(url, timeout=20.0, headers=headers)
|
|
|
|
except requests.ReadTimeout:
|
|
|
|
self.log.warning("Timeout reading url %s", url)
|
|
|
|
self.print_small("Error: timed out reading " + url)
|
|
|
|
return
|
|
|
|
except requests.ConnectionError as e:
|
|
|
|
self.log.warning("Error reading url %s", url)
|
|
|
|
self.print_small("Error: connect error on " + url)
|
|
|
|
return
|
|
|
|
|
|
|
|
# TODO: support #EXTINF: extended attributes - logo or #EXTIMG or #PLAYLIST
|
|
|
|
# TODO: support XSPF?? does anyone use this?
|
|
|
|
# TODO: what to do with unwrapped links - raw mp3 or whateva - use vlc info?
|
|
|
|
# if line startswith #EXTINF: title = first comma to EOL
|
|
|
|
# if line startswith Title then title = first '=' to EOL
|
|
|
|
title = ""
|
|
|
|
for line in resp.text.splitlines():
|
|
|
|
if line.startswith("#EXTINF:"):
|
|
|
|
# this is m3u playlist - title is from first comma to EOL
|
|
|
|
title = line[(line.find(',') + 1):].strip()
|
|
|
|
elif line.startswith("Title"):
|
|
|
|
# this looks like a pls playlist - title is from first '=' to EOL
|
|
|
|
title = line[(line.find('=') + 1):].strip()
|
|
|
|
|
|
|
|
# TODO: create barcode
|
|
|
|
# small print title, url, barcode
|
|
|
|
prn = self._get_small_printer()
|
|
|
|
|
|
|
|
prn.textln("RADIO STATION:")
|
|
|
|
prn.textln(title)
|
|
|
|
prn.textln(url)
|
|
|
|
|
|
|
|
prn.soft_barcode("code128", "AUp." + shortcode)
|
|
|
|
prn.print_and_feed()
|
|
|
|
#TODO: cut
|
|
|
|
prn.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
au = Audio()
|
|
|
|
au.run()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|