Use Python-mpd2 instead of mpc calls
This commit is contained in:
parent
bdf1f64577
commit
3356ff711e
@ -1,4 +1,14 @@
|
|||||||
#!/usr/bin/env python3
|
#!/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 logging
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
@ -7,10 +17,11 @@ import sqlite3
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
from mpd import MPDClient
|
||||||
# TODO: Replace mpc calls by libmpd2?
|
|
||||||
|
|
||||||
_QUEUE_LENGTH = 10
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
_QUEUE_LENGTH = 20
|
||||||
_DISTANCE_THRESHOLD = 4.0
|
_DISTANCE_THRESHOLD = 4.0
|
||||||
_SIMILARITY_THRESHOLD = 0.95
|
_SIMILARITY_THRESHOLD = 0.95
|
||||||
|
|
||||||
@ -20,8 +31,25 @@ else:
|
|||||||
_MPDBLISS_DATA_HOME = os.path.expanduser("~/.local/share/mpdbliss")
|
_MPDBLISS_DATA_HOME = os.path.expanduser("~/.local/share/mpdbliss")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main(queue_length):
|
||||||
mpd_queue = []
|
# 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")
|
db_path = os.path.join(_MPDBLISS_DATA_HOME, "db.sqlite3")
|
||||||
logging.debug("Using DB path: %s." % (db_path,))
|
logging.debug("Using DB path: %s." % (db_path,))
|
||||||
conn = sqlite3.connect(db_path)
|
conn = sqlite3.connect(db_path)
|
||||||
@ -30,25 +58,20 @@ def main():
|
|||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
|
||||||
# Ensure random is not enabled
|
# Ensure random is not enabled
|
||||||
status = subprocess.check_output(["mpc", "status"]).decode("utf-8")
|
status = client.status()
|
||||||
random = [x.split(":")[1].strip() == "on"
|
if status["random"] != 0:
|
||||||
for x in status.split("\n")[-2].split(" ")
|
|
||||||
if x.startswith("random")][0]
|
|
||||||
if random:
|
|
||||||
logging.warning("Random mode is enabled. Are you sure you want it?")
|
logging.warning("Random mode is enabled. Are you sure you want it?")
|
||||||
|
|
||||||
# Take the last song from current playlist and iterate from it
|
# Take the last song from current playlist and iterate from it
|
||||||
current_song = subprocess.check_output(
|
playlist = client.playlist()
|
||||||
["mpc", "playlist", '--format', '"%file%"'])
|
if len(playlist) > 0:
|
||||||
current_song = current_song.decode("utf-8").strip().split("\n")[-1]
|
current_song = playlist[-1].replace("file:", "").strip()
|
||||||
current_song = current_song.strip('"')
|
|
||||||
# If current playlist is empty
|
# If current playlist is empty
|
||||||
if current_song is "":
|
else:
|
||||||
# Add a random song to start with
|
# Add a random song to start with
|
||||||
all_songs = subprocess.check_output(["mpc", "listall"]).decode("utf-8")
|
all_songs = [x["file"] for x in client.listall() if "file" in x]
|
||||||
all_songs = all_songs.strip().split("\n")
|
current_song = random.choice(all_songs)
|
||||||
current_song = random.choice(all_songs).strip("'")
|
client.add(current_song)
|
||||||
subprocess.check_call(["mpc", "add", current_song])
|
|
||||||
logging.info("Currently played song is %s." % (current_song,))
|
logging.info("Currently played song is %s." % (current_song,))
|
||||||
|
|
||||||
# Get current song coordinates
|
# Get current song coordinates
|
||||||
@ -57,17 +80,18 @@ def main():
|
|||||||
if current_song_coords is None:
|
if current_song_coords is None:
|
||||||
logging.warning("Current song %s is not in db. You should update the db." %
|
logging.warning("Current song %s is not in db. You should update the db." %
|
||||||
(current_song,))
|
(current_song,))
|
||||||
|
client.close()
|
||||||
|
client.disconnect()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
for i in range(_QUEUE_LENGTH):
|
for i in range(queue_length):
|
||||||
mpd_queue.append(current_song_coords["filename"])
|
|
||||||
# Get cached distances from db
|
# Get cached distances from db
|
||||||
cur.execute(
|
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",
|
"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"]))
|
(current_song_coords["filename"], current_song_coords["filename"]))
|
||||||
cached_distances = [row
|
cached_distances = [row
|
||||||
for row in cur.fetchall()
|
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]
|
cached_distances_songs = [i["filename"] for i in cached_distances]
|
||||||
|
|
||||||
# If distance to closest song is ok, just add the song
|
# 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
|
if(cached_distances[0]["distance"] < _DISTANCE_THRESHOLD and
|
||||||
cached_distances[0]["similarity"] > _SIMILARITY_THRESHOLD):
|
cached_distances[0]["similarity"] > _SIMILARITY_THRESHOLD):
|
||||||
# Push it on the queue
|
# Push it on the queue
|
||||||
subprocess.check_call(["mpc", "add",
|
client.add(cached_distances[0]["filename"])
|
||||||
cached_distances[0]["filename"]])
|
|
||||||
# Continue using latest pushed song as current song
|
# Continue using latest pushed song as current song
|
||||||
logging.info("Using cached distance. Found %s. Distance is (%f, %f)." %
|
logging.info("Using cached distance. Found %s. Distance is (%f, %f)." %
|
||||||
(cached_distances[0]["filename"],
|
(cached_distances[0]["filename"],
|
||||||
@ -91,7 +114,7 @@ def main():
|
|||||||
for tmp_song_data in cur.fetchall():
|
for tmp_song_data in cur.fetchall():
|
||||||
if(tmp_song_data["filename"] == current_song_coords["filename"] or
|
if(tmp_song_data["filename"] == current_song_coords["filename"] or
|
||||||
tmp_song_data["filename"] in cached_distances_songs 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
|
# Skip current song and already processed songs
|
||||||
logging.debug("Skipping %s." % (tmp_song_data["filename"]))
|
logging.debug("Skipping %s." % (tmp_song_data["filename"]))
|
||||||
continue
|
continue
|
||||||
@ -127,7 +150,7 @@ def main():
|
|||||||
try:
|
try:
|
||||||
logging.debug("Storing distance in database.")
|
logging.debug("Storing distance in database.")
|
||||||
conn.execute(
|
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,
|
(current_song_coords["id"], tmp_song_data["id"], distance,
|
||||||
similarity))
|
similarity))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
@ -137,8 +160,7 @@ def main():
|
|||||||
|
|
||||||
# Update the closest song
|
# Update the closest song
|
||||||
# TODO: Find a better heuristic?
|
# TODO: Find a better heuristic?
|
||||||
if closest_song is None or (distance < closest_song[1] and
|
if closest_song is None or distance < closest_song[1]:
|
||||||
similarity > closest_song[2]):
|
|
||||||
closest_song = (tmp_song_data, distance, similarity)
|
closest_song = (tmp_song_data, distance, similarity)
|
||||||
|
|
||||||
# If distance is ok, break from the loop
|
# If distance is ok, break from the loop
|
||||||
@ -150,7 +172,7 @@ def main():
|
|||||||
if(distance < _DISTANCE_THRESHOLD and
|
if(distance < _DISTANCE_THRESHOLD and
|
||||||
similarity > _SIMILARITY_THRESHOLD):
|
similarity > _SIMILARITY_THRESHOLD):
|
||||||
# Push it on the queue
|
# 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
|
# Continue using latest pushed song as current song
|
||||||
logging.info("Found a close song: %s. Distance is (%f, %f)." %
|
logging.info("Found a close song: %s. Distance is (%f, %f)." %
|
||||||
(tmp_song_data["filename"], distance, similarity))
|
(tmp_song_data["filename"], distance, similarity))
|
||||||
@ -162,10 +184,18 @@ def main():
|
|||||||
(closest_song[0]["filename"], closest_song[1],
|
(closest_song[0]["filename"], closest_song[1],
|
||||||
closest_song[2]))
|
closest_song[2]))
|
||||||
current_song_coords = closest_song[0]
|
current_song_coords = closest_song[0]
|
||||||
subprocess.check_call(["mpc", "add", closest_song[0]["filename"]])
|
client.add(closest_song[0]["filename"])
|
||||||
continue
|
continue
|
||||||
conn.close()
|
conn.close()
|
||||||
|
client.close()
|
||||||
|
client.disconnect()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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)
|
||||||
|
Loading…
Reference in New Issue
Block a user