Skip to content

Commit

Permalink
reverted back to mongo, this time async
Browse files Browse the repository at this point in the history
  • Loading branch information
fjebaker committed Nov 15, 2020
1 parent bbcc796 commit 4a3f5e6
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 85 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
53 changes: 11 additions & 42 deletions asteroid_player/__main__.py
Original file line number Diff line number Diff line change
@@ -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())
28 changes: 28 additions & 0 deletions asteroid_player/cycler.py
Original file line number Diff line number Diff line change
@@ -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()
48 changes: 25 additions & 23 deletions asteroid_player/database.py
Original file line number Diff line number Diff line change
@@ -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
21 changes: 6 additions & 15 deletions asteroid_player/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
4 changes: 2 additions & 2 deletions config.yml
Original file line number Diff line number Diff line change
@@ -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"
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 4a3f5e6

Please sign in to comment.