Use Python-mpd2 instead of mpc calls

This commit is contained in:
Lucas Verney 2016-05-25 19:15:16 +02:00
parent bdf1f64577
commit 3356ff711e
1 changed files with 61 additions and 31 deletions

View File

@ -1,4 +1,14 @@
#!/usr/bin/env python3
"""
This is a client for MPD to generate a random playlist starting from the last
song of the current playlist and iterating using values computed using Bliss.
MPD connection settings are taken from environment variables, following MPD_HOST
and MPD_PORT scheme described in `mpc` man.
You can pass an integer argument to the script to change the length of the
generated playlist (default is to add 20 songs).
"""
import logging
import math
import os
@ -7,10 +17,11 @@ import sqlite3
import subprocess
import sys
logging.basicConfig(level=logging.INFO)
# TODO: Replace mpc calls by libmpd2?
from mpd import MPDClient
_QUEUE_LENGTH = 10
logging.basicConfig(level=logging.INFO)
_QUEUE_LENGTH = 20
_DISTANCE_THRESHOLD = 4.0
_SIMILARITY_THRESHOLD = 0.95
@ -20,8 +31,25 @@ else:
_MPDBLISS_DATA_HOME = os.path.expanduser("~/.local/share/mpdbliss")
def main():
mpd_queue = []
def main(queue_length):
# Get MPD connection settings
try:
mpd_host = os.environ["MPD_HOST"]
mpd_password, mpd_host = mpd_host.split("@")
except KeyError:
mpd_host = "localhost"
mpd_password = None
try:
mpd_port = os.environ["MPD_PORT"]
except KeyError:
mpd_port = 6600
# Connect to MPD²
client = MPDClient()
client.connect(mpd_host, mpd_port)
if mpd_password is not None:
client.password(mpd_password)
# Connect to db
db_path = os.path.join(_MPDBLISS_DATA_HOME, "db.sqlite3")
logging.debug("Using DB path: %s." % (db_path,))
conn = sqlite3.connect(db_path)
@ -30,25 +58,20 @@ def main():
cur = conn.cursor()
# Ensure random is not enabled
status = subprocess.check_output(["mpc", "status"]).decode("utf-8")
random = [x.split(":")[1].strip() == "on"
for x in status.split("\n")[-2].split(" ")
if x.startswith("random")][0]
if random:
status = client.status()
if status["random"] != 0:
logging.warning("Random mode is enabled. Are you sure you want it?")
# Take the last song from current playlist and iterate from it
current_song = subprocess.check_output(
["mpc", "playlist", '--format', '"%file%"'])
current_song = current_song.decode("utf-8").strip().split("\n")[-1]
current_song = current_song.strip('"')
playlist = client.playlist()
if len(playlist) > 0:
current_song = playlist[-1].replace("file:", "").strip()
# If current playlist is empty
if current_song is "":
else:
# Add a random song to start with
all_songs = subprocess.check_output(["mpc", "listall"]).decode("utf-8")
all_songs = all_songs.strip().split("\n")
current_song = random.choice(all_songs).strip("'")
subprocess.check_call(["mpc", "add", current_song])
all_songs = [x["file"] for x in client.listall() if "file" in x]
current_song = random.choice(all_songs)
client.add(current_song)
logging.info("Currently played song is %s." % (current_song,))
# Get current song coordinates
@ -57,17 +80,18 @@ def main():
if current_song_coords is None:
logging.warning("Current song %s is not in db. You should update the db." %
(current_song,))
client.close()
client.disconnect()
sys.exit(1)
for i in range(_QUEUE_LENGTH):
mpd_queue.append(current_song_coords["filename"])
for i in range(queue_length):
# Get cached distances from db
cur.execute(
"SELECT id, filename, distance, similarity, tempo, amplitude, frequency, attack FROM (SELECT s2.id AS id, s2.filename AS filename, s2.tempo AS tempo, s2.amplitude AS amplitude, s2.frequency AS frequency, s2.attack AS attack, distances.distance AS distance, distances.similarity AS similarity FROM distances INNER JOIN songs AS s1 ON s1.id=distances.song1 INNER JOIN songs AS s2 on s2.id=distances.song2 WHERE s1.filename=? UNION SELECT s1.id as id, s1.filename AS filename, s1.tempo AS tempo, s1.amplitude AS amplitude, s1.frequency AS frequency, s1.attack AS attack, distances.distance as distance, distances.similarity AS similarity FROM distances INNER JOIN songs AS s1 ON s1.id=distances.song1 INNER JOIN songs AS s2 on s2.id=distances.song2 WHERE s2.filename=?) ORDER BY distance ASC",
(current_song_coords["filename"], current_song_coords["filename"]))
cached_distances = [row
for row in cur.fetchall()
if row["filename"] not in mpd_queue]
if ("file: %s" % (row["filename"],)) not in client.playlist()]
cached_distances_songs = [i["filename"] for i in cached_distances]
# If distance to closest song is ok, just add the song
@ -75,8 +99,7 @@ def main():
if(cached_distances[0]["distance"] < _DISTANCE_THRESHOLD and
cached_distances[0]["similarity"] > _SIMILARITY_THRESHOLD):
# Push it on the queue
subprocess.check_call(["mpc", "add",
cached_distances[0]["filename"]])
client.add(cached_distances[0]["filename"])
# Continue using latest pushed song as current song
logging.info("Using cached distance. Found %s. Distance is (%f, %f)." %
(cached_distances[0]["filename"],
@ -91,7 +114,7 @@ def main():
for tmp_song_data in cur.fetchall():
if(tmp_song_data["filename"] == current_song_coords["filename"] or
tmp_song_data["filename"] in cached_distances_songs or
tmp_song_data["filename"] in mpd_queue):
("file: %s" % (tmp_song_data["filename"],)) in client.playlist()):
# Skip current song and already processed songs
logging.debug("Skipping %s." % (tmp_song_data["filename"]))
continue
@ -127,7 +150,7 @@ def main():
try:
logging.debug("Storing distance in database.")
conn.execute(
"INSERT INTO distances(song1, song2, distance, similarity) VALUES(?, ?, ?)",
"INSERT INTO distances(song1, song2, distance, similarity) VALUES(?, ?, ?, ?)",
(current_song_coords["id"], tmp_song_data["id"], distance,
similarity))
conn.commit()
@ -137,8 +160,7 @@ def main():
# Update the closest song
# TODO: Find a better heuristic?
if closest_song is None or (distance < closest_song[1] and
similarity > closest_song[2]):
if closest_song is None or distance < closest_song[1]:
closest_song = (tmp_song_data, distance, similarity)
# If distance is ok, break from the loop
@ -150,7 +172,7 @@ def main():
if(distance < _DISTANCE_THRESHOLD and
similarity > _SIMILARITY_THRESHOLD):
# Push it on the queue
subprocess.check_call(["mpc", "add", tmp_song_data["filename"]])
client.add(tmp_song_data["filename"])
# Continue using latest pushed song as current song
logging.info("Found a close song: %s. Distance is (%f, %f)." %
(tmp_song_data["filename"], distance, similarity))
@ -162,10 +184,18 @@ def main():
(closest_song[0]["filename"], closest_song[1],
closest_song[2]))
current_song_coords = closest_song[0]
subprocess.check_call(["mpc", "add", closest_song[0]["filename"]])
client.add(closest_song[0]["filename"])
continue
conn.close()
client.close()
client.disconnect()
if __name__ == "__main__":
main()
queue_length = _QUEUE_LENGTH
if len(sys.argv) > 1:
try:
queue_length = int(sys.argv[1])
except ValueError:
sys.exit("Usage: %s [PLAYLIST_LENGTH]" % (sys.argv[0],))
main(queue_length)