From 4a3f5e6d7728a3e30885727f811239dd06086108 Mon Sep 17 00:00:00 2001 From: dustpancake Date: Sun, 15 Nov 2020 12:58:12 +0100 Subject: [PATCH] reverted back to mongo, this time async --- README.md | 10 +++++-- asteroid_player/__main__.py | 53 ++++++++----------------------------- asteroid_player/cycler.py | 28 ++++++++++++++++++++ asteroid_player/database.py | 48 +++++++++++++++++---------------- asteroid_player/player.py | 21 +++++---------- config.yml | 4 +-- requirements.txt | 3 ++- 7 files changed, 82 insertions(+), 85 deletions(-) create mode 100644 asteroid_player/cycler.py diff --git a/README.md b/README.md index 84c1e4e..8c6e2d6 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,18 @@ Sample configuration file: ```yml -database: "../asteroid-flask/test.db" +database: "mongodb://localhost:27017" logging: level: INFO format: "%(asctime)s %(levelname)s: %(message)s" datefmt: "%d/%m/%Y %H:%M:%S" -musicfiles: "../asteroid-flask/musicfiles" +musicfiles: "/path/to/musicfiles" ``` + +Uses [playsound](https://github.com/TaylorSMarks/playsound), which may require additional unlisted dependencies on different operating systems. + +- OSX + +You'll also need [`pyobjc`](https://github.com/ronaldoussoren/pyobjc) diff --git a/asteroid_player/__main__.py b/asteroid_player/__main__.py index 9e0b6b6..332ca4b 100644 --- a/asteroid_player/__main__.py +++ b/asteroid_player/__main__.py @@ -1,52 +1,21 @@ import yaml +import asyncio +import logging + with open("./config.yml", "r") as ymlfile: - cfg = yaml.load( - ymlfile, - Loader=yaml.SafeLoader - ) + cfg = yaml.load(ymlfile, Loader=yaml.SafeLoader) + -import logging logcfg = cfg["logging"] logging.basicConfig( - format=logcfg["format"], + format=logcfg["format"], datefmt=logcfg["datefmt"], - level=logging.__getattribute__( - logcfg["level"].upper() - ) + level=logging.__getattribute__(logcfg["level"].upper()), ) -import asyncio - -from player import play_song -from database import Database - - -class Cycler: - """ main async class handling encapsulating song fetching and - playback """ - - def __init__(self): - self.db = Database(cfg['database']) - self.musicfiles = cfg['musicfiles'] - - async def cycle(self): - """ get song and play """ - next_song = await self.db.next_song() - logging.info(f"Next song {next_song}") - try: - await play_song(self.musicfiles, next_song) - except Exception as e: - loggin.error(f"Playback error: {e}") - return - - async def cycle_forever(self): - """ main loop """ - while True: - await self.cycle() - - -if __name__ == '__main__': - cylcer = Cycler() - asyncio.run(cylcer.cycle_forever()) +if __name__ == "__main__": + from cycler import Cycler + cycler = Cycler(cfg) + asyncio.run(cycler.cycle_forever()) diff --git a/asteroid_player/cycler.py b/asteroid_player/cycler.py new file mode 100644 index 0000000..6da10c1 --- /dev/null +++ b/asteroid_player/cycler.py @@ -0,0 +1,28 @@ +from player import play_song +from database import Database +import logging + + +class Cycler: + """main async class handling encapsulating song fetching and + playback""" + + def __init__(self, cfg): + self.db = Database(cfg["database"]) + self.musicfiles = cfg["musicfiles"] + + async def cycle(self): + """ get song and play """ + next_song = await self.db.next_song() + logging.info(f"Next song {next_song}") + try: + await play_song(self.musicfiles, next_song) + except Exception as e: + logging.error(f"Playback error: {e}") + return + + async def cycle_forever(self): + """ main loop """ + await self.db.connect() + while True: + await self.cycle() diff --git a/asteroid_player/database.py b/asteroid_player/database.py index f86d9be..d78040b 100644 --- a/asteroid_player/database.py +++ b/asteroid_player/database.py @@ -1,44 +1,46 @@ import asyncio -import sqlite3 import logging +import motor.motor_asyncio class Database: - def __init__(self, db): - self.conn = sqlite3.connect(db) + self._db = db + + async def connect(self): + client = motor.motor_asyncio.AsyncIOMotorClient(self._db) + self.collection = client.dev.get_collection("queue") async def next_song(self): """ gets next song, removes it from queue and adds to history """ - file, duration, _id = await self._fetch_from_queue() - - # remove from queue - self.conn.execute("DELETE FROM queue WHERE id=?", (_id,)) + queue_item = await self._fetch_from_queue() + song = queue_item["song"] + logging.info(f"Fetched item {song}") - # add to history, after changing id -> song_id to avoid conflicts - # song['song_id'] = song.pop('_id') - # self.db.history.insert_one(song) - - self.conn.commit() - return {"file":file, "duration":duration} + # remove it from queue + result = await self.collection.update_one( + {"name": "queue"}, {"$pull": {"songs": {"song._id": song["_id"]}}} + ) + if result.modified_count > 0: + logging.info("Removed song from queue.") + else: + logging.warning("Failed to remove song from queue.") + return {"file": song["file"], "duration": song["duration"]} async def _fetch_from_queue(self): """ get next item from queue, else waits until one available """ while True: await asyncio.sleep(1) - item = self._query_database() + item = await self._query_database() if item is not None: return item else: logging.info("No song in queue.") - def _query_database(self): - query = self.conn.execute( - ( - "SELECT music.file, music.duration, queue.id FROM queue " - "JOIN music ON queue.song_id = music.id " - "ORDER BY queue.votes DESC" - ) - ) - return query.fetchone() + async def _query_database(self): + queue = await self.collection.find_one({"name": "queue"}) + if queue != None and len(queue["songs"]) > 0: + return sorted(queue["songs"], key=lambda i: i["votes"], reverse=True)[0] + else: + return None diff --git a/asteroid_player/player.py b/asteroid_player/player.py index 10a8ff7..455cde0 100644 --- a/asteroid_player/player.py +++ b/asteroid_player/player.py @@ -2,21 +2,12 @@ import asyncio import os.path -try: - import vlc -except: - # for testing purposes - logging.warning("vlc could not be imported -- using MagicMock") - import unittest.mock as mock - vlc = mock.MagicMock() +from playsound import playsound + async def play_song(root, song): """ use vlc to play song; return once done """ - pl = vlc.MediaPlayer( - os.path.join(root, song['file']) - ) - pl.play() - logging.warning(pl.get_state()) - await asyncio.sleep(song['duration']) - return - + logging.info("Playing {}".format(song["file"])) + playsound(os.path.join(root, song["file"])) + logging.info("Playback finished.") + await asyncio.sleep(song["duration"]) diff --git a/config.yml b/config.yml index 08065f0..1b0303e 100644 --- a/config.yml +++ b/config.yml @@ -1,8 +1,8 @@ -database: "../asteroid-flask/test.db" +database: "mongodb://localhost:27017" logging: level: INFO format: "%(asctime)s %(levelname)s: %(message)s" datefmt: "%d/%m/%Y %H:%M:%S" -musicfiles: "../asteroid-flask/musicfiles" +musicfiles: "../asteroid-api" diff --git a/requirements.txt b/requirements.txt index 192b4be..94ea257 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ +motor==2.3.0 pymongo==3.11.0 -python-vlc==3.0.11115 PyYAML==5.3.1 six==1.15.0 +playsound==1.2.2