You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

225 lines
7.0 KiB
Python

import glob
import urllib.request
import kode256
import requests
import gi
from gi.repository import TotemPlParser
from gsp import GstreamerPlayer
from tinytag import TinyTag
from bureau import Bureau, add_command, add_webview, add_api
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.
"""
self.stop()
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:
self.player.queue(url)
self.current_uri = urls[0]
@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
@add_api("add_album", "Add a music album.")
def add_album(self, data):
"""
add a local folder with music or audio
"""
try:
im_dir = data["album_dir"]
store = data["music_dir"]
except KeyError as e:
self.log.error("you must specify a directory to import")
# scan to find out the name(s) of artist/albums and track order
# create necessary folders and copy the files over
# create a playlist and add the files in the right order
# save the url
# print a nice card for perusal and playing
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: get rid of this stuff and use the playlist parser
# TODO: what to do with unwrapped links - raw mp3 or whateva - use vlc info?
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()
# 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()