Working MPD client using distance meetrics only

This commit is contained in:
Lucas Verney 2016-05-24 20:12:53 +02:00
parent 36357d4423
commit 40b7df9140
2 changed files with 50 additions and 35 deletions

0
client/build_cache.py Normal file → Executable file
View File

69
client/client.py Normal file → Executable file
View File

@ -2,13 +2,14 @@
import logging
import math
import os
import random
import sqlite3
import subprocess
import sys
logging.basicConfig(level=logging.INFO)
_QUEUE_LENGTH = 100
_QUEUE_LENGTH = 10
# TODO: Use cosine similarity as well
_DISTANCE_THRESHOLD = 4.0
@ -27,29 +28,34 @@ def main():
conn.execute('pragma foreign_keys=ON')
cur = conn.cursor()
# Take the last song from current playlist and iterate from it
current_song = subprocess.check_output(
["mpc", "current", '--format', '"%file%"'])
current_song = current_song.decode("utf-8").strip('" \r\n\t')
["mpc", "playlist", '--format', '"%file%"'])
current_song = current_song.decode("utf-8").strip().split("\n")[-1]
current_song = current_song.strip('"')
# If current playlist is empty
if current_song is "":
logging.warning("Currently played song could not be found.")
sys.exit(1)
# 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])
logging.info("Currently played song is %s." % (current_song,))
# Get current song coordinates
cur.execute("SELECT id, tempo, amplitude, frequency, attack, filename FROM songs WHERE filename=?", (current_song,))
current_song = cur.fetchone()
if current_song is None:
current_song_coords = cur.fetchone()
if current_song_coords is None:
logging.warning("Current song %s is not in db. You should update the db." %
(current_song["filename"],))
(current_song,))
sys.exit(1)
for i in range(_QUEUE_LENGTH):
# Append current song to the mpd queue to avoid duplicates
mpd_queue.append(current_song["filename"])
mpd_queue.append(current_song_coords["filename"])
# Get cached distances from db
cur.execute(
"SELECT id, filename, distance FROM (SELECT s2.id AS id, s2.filename AS filename, distances.distance AS distance 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, distances.distance as distance 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["filename"], current_song["filename"]))
"SELECT id, filename, distance, 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 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 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]
@ -63,15 +69,16 @@ def main():
cached_distances[0]["filename"]])
# Continue using latest pushed song as current song
logging.info("Using cached distance. Found %s. Distance is %f." %
(current_song["filename"], cached_distances[0]["distance"]))
current_song = cached_distances[0]
(cached_distances[0]["filename"],
cached_distances[0]["distance"]))
current_song_coords = cached_distances[0]
continue
# Get all other songs coordinates
closest_song = None
cur.execute("SELECT id, tempo, amplitude, frequency, attack, filename FROM songs")
for tmp_song_data in cur.fetchall():
if(tmp_song_data["filename"] == current_song["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 mpd_queue):
# Skip current song and already processed songs
@ -79,41 +86,49 @@ def main():
continue
# Compute distance
distance = math.sqrt(
(current_song["tempo"] - tmp_song_data["tempo"])**2 +
(current_song["amplitude"] - tmp_song_data["amplitude"])**2 +
(current_song["frequency"] - tmp_song_data["frequency"])**2 +
(current_song["attack"] - tmp_song_data["attack"])**2
(current_song_coords["tempo"] - tmp_song_data["tempo"])**2 +
(current_song_coords["amplitude"] - tmp_song_data["amplitude"])**2 +
(current_song_coords["frequency"] - tmp_song_data["frequency"])**2 +
(current_song_coords["attack"] - tmp_song_data["attack"])**2
)
logging.debug("Distance between %s and %s is %f." %
(current_song["filename"],
(current_song_coords["filename"],
tmp_song_data["filename"], distance))
# Store distance in db cache
try:
logging.debug("Storing distance in database.")
conn.execute(
"INSERT INTO distances(song1, song2, distance) VALUES(?, ?, ?)",
(current_song["id"], tmp_song_data["id"], distance))
(current_song_coords["id"], tmp_song_data["id"], distance))
conn.commit()
except sqlite3.IntegrityError:
logging.warning("Unable to insert distance in database.")
conn.rollback()
# If distance is ok, just add the song
# Update the closest song
if closest_song is None or distance < closest_song[1]:
closest_song = (tmp_song_data, distance)
# If distance is ok, break from the loop
if distance < _DISTANCE_THRESHOLD:
break
# If a close enough song is found
if distance < _DISTANCE_THRESHOLD:
# Push it on the queue
subprocess.check_call(["mpc", "add", tmp_song_data["filename"]])
# Continue using latest pushed song as current song
logging.info("Found a close song: %s. Distance is %f." %
(tmp_song_data["filename"], distance))
current_song = tmp_song_data
break
elif closest_song is None or distance < closest_song[1]:
closest_song = (tmp_song_data, distance)
current_song_coords = tmp_song_data
continue
# If no song found, take the closest one
else:
logging.info("No close enough song found. Using %s. Distance is %f." %
(closest_song[0]["filename"], closest_song[1]))
current_song = closest_song[0]
current_song_coords = closest_song[0]
subprocess.check_call(["mpc", "add", closest_song[0]["filename"]])
continue
conn.close()