Added a randomize over albums option
This commit is contained in:
parent
1c7e405a2a
commit
56108cea38
181
mpd/client.py
181
mpd/client.py
@ -9,6 +9,7 @@ and MPD_PORT scheme described in `mpc` man.
|
|||||||
You can pass an integer argument to the script to change the length of the
|
You can pass an integer argument to the script to change the length of the
|
||||||
generated playlist (default is to add 20 songs).
|
generated playlist (default is to add 20 songs).
|
||||||
"""
|
"""
|
||||||
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
@ -102,8 +103,60 @@ if "XDG_DATA_HOME" in os.environ:
|
|||||||
else:
|
else:
|
||||||
_BLISSIFY_DATA_HOME = os.path.expanduser("~/.local/share/blissify")
|
_BLISSIFY_DATA_HOME = os.path.expanduser("~/.local/share/blissify")
|
||||||
|
|
||||||
|
# Distance between two songs
|
||||||
|
def distance(x, y):
|
||||||
|
distance = math.sqrt(
|
||||||
|
(x["tempo"] - y["tempo"])**2 +
|
||||||
|
(x["amplitude"] - y["amplitude"])**2 +
|
||||||
|
(x["frequency"] - y["frequency"])**2 +
|
||||||
|
(x["attack"] - y["attack"])**2)
|
||||||
|
return distance
|
||||||
|
|
||||||
def main(queue_length):
|
# Distance between a set and a point
|
||||||
|
def distance_set(X, y):
|
||||||
|
temp_distance = distance(X[0], y);
|
||||||
|
for song in X:
|
||||||
|
a = distance(song, y);
|
||||||
|
temp_distance = a if a < temp_distance else temp_distance;
|
||||||
|
return temp_distance;
|
||||||
|
|
||||||
|
# Hausdorff distance between two sets
|
||||||
|
def hauff_distance_sets(X, Y):
|
||||||
|
temp_distance = distance_set(X, Y[0]);
|
||||||
|
for song in X:
|
||||||
|
a = distance_set(Y, song);
|
||||||
|
temp_distance = a if a > temp_distance else temp_distance;
|
||||||
|
|
||||||
|
for song in Y:
|
||||||
|
a = distance_set(X, song);
|
||||||
|
temp_distance = a if a > temp_distance else temp_distance;
|
||||||
|
return temp_distance
|
||||||
|
|
||||||
|
def mean_song(X):
|
||||||
|
count = 0;
|
||||||
|
result = { 'tempo': 0, 'amplitude': 0, 'frequency':0, 'attack':0 }
|
||||||
|
|
||||||
|
for song in X:
|
||||||
|
result["tempo"] += song["tempo"];
|
||||||
|
result["amplitude"] += song["amplitude"];
|
||||||
|
result["frequency"] += song["frequency"];
|
||||||
|
result["attack"] += song["attack"];
|
||||||
|
count = count + 1;
|
||||||
|
result["tempo"] /= count;
|
||||||
|
result["amplitude"] /= count;
|
||||||
|
result["frequency"] /= count;
|
||||||
|
result["attack"] /= count;
|
||||||
|
return result;
|
||||||
|
|
||||||
|
|
||||||
|
# Custom distance between two sets
|
||||||
|
def distance_sets(X, Y):
|
||||||
|
a = mean_song(X);
|
||||||
|
b = mean_song(Y);
|
||||||
|
|
||||||
|
return distance(a, b);
|
||||||
|
|
||||||
|
def main_album(queue_length):
|
||||||
# Get MPD connection settings
|
# Get MPD connection settings
|
||||||
try:
|
try:
|
||||||
mpd_host = os.environ["MPD_HOST"]
|
mpd_host = os.environ["MPD_HOST"]
|
||||||
@ -142,12 +195,110 @@ def main(queue_length):
|
|||||||
current_song = playlist[-1].replace("file: ", "").rstrip()
|
current_song = playlist[-1].replace("file: ", "").rstrip()
|
||||||
# If current playlist is empty
|
# If current playlist is empty
|
||||||
else:
|
else:
|
||||||
# Add a random song to start with
|
# Add a random song to start with TODO add a random album
|
||||||
all_songs = [x["file"] for x in client.listall() if "file" in x]
|
all_songs = [x["file"] for x in client.listall() if "file" in x]
|
||||||
current_song = random.choice(all_songs)
|
current_song = random.choice(all_songs)
|
||||||
client.add(current_song)
|
client.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
|
||||||
|
cur.execute("SELECT id, tempo, amplitude, frequency, attack, filename, album FROM songs WHERE filename=?", (current_song,))
|
||||||
|
current_song_coords = cur.fetchone()
|
||||||
|
if current_song_coords is None:
|
||||||
|
logging.error("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):
|
||||||
|
# No cache management
|
||||||
|
# Get all songs from the current album
|
||||||
|
album = current_song_coords["album"];
|
||||||
|
cur.execute("SELECT id, tempo, amplitude, frequency, attack, filename, album FROM songs WHERE album=?", (album,))
|
||||||
|
target_album_set = cur.fetchall();
|
||||||
|
|
||||||
|
# Get all other songs
|
||||||
|
cur.execute("SELECT id, tempo, amplitude, frequency, attack, filename, album FROM songs ORDER BY album")
|
||||||
|
tmp_song_data = cur.fetchone()
|
||||||
|
shortest_distance = -1
|
||||||
|
|
||||||
|
# Check the best suitable album
|
||||||
|
while tmp_song_data:
|
||||||
|
current_album_set = list()
|
||||||
|
current_album_set.append(tmp_song_data)
|
||||||
|
tmp_song_data = cur.fetchone()
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
# Get all songs from the current temporary album
|
||||||
|
while tmp_song_data:
|
||||||
|
if (current_album_set[i]["album"] == tmp_song_data["album"]):
|
||||||
|
current_album_set.append(tmp_song_data);
|
||||||
|
else:
|
||||||
|
break;
|
||||||
|
tmp_song_data = cur.fetchone()
|
||||||
|
i = i + 1
|
||||||
|
# Skip current album and already processed albums
|
||||||
|
if ( (current_album_set[0]["album"] != target_album_set[0]["album"]) and
|
||||||
|
not (("file: %s" % (current_album_set[0]["filename"],)) in client.playlist()) ):
|
||||||
|
tmp_distance = distance_sets(current_album_set, target_album_set)
|
||||||
|
if tmp_distance < shortest_distance or shortest_distance == -1:
|
||||||
|
shortest_distance = tmp_distance
|
||||||
|
closest_album = current_album_set;
|
||||||
|
|
||||||
|
logging.info("Closest album found is \"%s\". Distance is %f." % (closest_album[0]["album"], shortest_distance))
|
||||||
|
for song in closest_album:
|
||||||
|
client.add(song["filename"])
|
||||||
|
current_song_coords = closest_album[-1]
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
client.close()
|
||||||
|
client.disconnect()
|
||||||
|
|
||||||
|
def main_single(queue_length):
|
||||||
|
# Get MPD connection settings
|
||||||
|
try:
|
||||||
|
mpd_host = os.environ["MPD_HOST"]
|
||||||
|
try:
|
||||||
|
mpd_password, mpd_host = mpd_host.split("@")
|
||||||
|
except ValueError:
|
||||||
|
mpd_password = None
|
||||||
|
except KeyError:
|
||||||
|
mpd_host = "localhost"
|
||||||
|
mpd_password = None
|
||||||
|
try:
|
||||||
|
mpd_port = os.environ["MPD_PORT"]
|
||||||
|
except KeyError:
|
||||||
|
mpd_port = 6600
|
||||||
|
|
||||||
|
# Connect to MPD
|
||||||
|
client = PersistentMPDClient(host=mpd_host, port=mpd_port)
|
||||||
|
if mpd_password is not None:
|
||||||
|
client.password(mpd_password)
|
||||||
|
# Connect to db
|
||||||
|
db_path = os.path.join(_BLISSIFY_DATA_HOME, "db.sqlite3")
|
||||||
|
logging.debug("Using DB path: %s." % (db_path,))
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
conn.execute('pragma foreign_keys=ON')
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# Ensure random is not enabled
|
||||||
|
status = client.status()
|
||||||
|
if int(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
|
||||||
|
playlist = client.playlist()
|
||||||
|
if len(playlist) > 0:
|
||||||
|
current_song = playlist[-1].replace("file: ", "").rstrip()
|
||||||
|
# If current playlist is empty
|
||||||
|
else:
|
||||||
|
# Add a random song to start with
|
||||||
|
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
|
# Get current song coordinates
|
||||||
cur.execute("SELECT id, tempo, amplitude, frequency, attack, filename FROM songs WHERE filename=?", (current_song,))
|
cur.execute("SELECT id, tempo, amplitude, frequency, attack, filename FROM songs WHERE filename=?", (current_song,))
|
||||||
current_song_coords = cur.fetchone()
|
current_song_coords = cur.fetchone()
|
||||||
@ -274,10 +425,22 @@ def main(queue_length):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
queue_length = _QUEUE_LENGTH
|
parser = argparse.ArgumentParser()
|
||||||
if len(sys.argv) > 1:
|
parser.add_argument("--queue-length", help="The number of items to add to the MPD playlist.", type=int)
|
||||||
try:
|
group = parser.add_mutually_exclusive_group(required=True)
|
||||||
queue_length = int(sys.argv[1])
|
group.add_argument("--song-based", help="Make a playlist based on single songs.",
|
||||||
except ValueError:
|
action="store_true", default=False)
|
||||||
sys.exit("Usage: %s [PLAYLIST_LENGTH]" % (sys.argv[0],))
|
group.add_argument("--album-based", help="Make a playlist based on whole albums.",
|
||||||
main(queue_length)
|
action="store_true", default=False)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.queue_length:
|
||||||
|
queue_length = args.queue_length
|
||||||
|
else:
|
||||||
|
queue_length = _QUEUE_LENGTH
|
||||||
|
|
||||||
|
if args.song_based:
|
||||||
|
main_single(queue_length)
|
||||||
|
elif args.album_based:
|
||||||
|
main_album(queue_length)
|
||||||
|
|
||||||
|
@ -51,7 +51,8 @@ int _init_db(char *data_folder, char* db_path)
|
|||||||
amplitude REAL, \
|
amplitude REAL, \
|
||||||
frequency REAL, \
|
frequency REAL, \
|
||||||
attack REAL, \
|
attack REAL, \
|
||||||
filename TEXT UNIQUE)",
|
filename TEXT UNIQUE, \
|
||||||
|
album TEXT)",
|
||||||
NULL, NULL, NULL);
|
NULL, NULL, NULL);
|
||||||
if (SQLITE_OK != dberr) {
|
if (SQLITE_OK != dberr) {
|
||||||
fprintf(stderr, "Error creating db: %s.\n", sqlite3_errmsg(dbh));
|
fprintf(stderr, "Error creating db: %s.\n", sqlite3_errmsg(dbh));
|
||||||
@ -151,7 +152,7 @@ int _parse_music_helper(
|
|||||||
}
|
}
|
||||||
// Insert song analysis in database
|
// Insert song analysis in database
|
||||||
dberr = sqlite3_prepare_v2(dbh,
|
dberr = sqlite3_prepare_v2(dbh,
|
||||||
"INSERT INTO songs(tempo, amplitude, frequency, attack, filename) VALUES(?, ?, ?, ?, ?)",
|
"INSERT INTO songs(tempo, amplitude, frequency, attack, filename, album) VALUES(?, ?, ?, ?, ?, ?)",
|
||||||
-1, &res, 0);
|
-1, &res, 0);
|
||||||
if (SQLITE_OK != dberr) {
|
if (SQLITE_OK != dberr) {
|
||||||
fprintf(stderr, "Error while inserting data in db: %s\n\n", sqlite3_errmsg(dbh));
|
fprintf(stderr, "Error while inserting data in db: %s\n\n", sqlite3_errmsg(dbh));
|
||||||
@ -173,6 +174,7 @@ int _parse_music_helper(
|
|||||||
sqlite3_bind_double(res, 3, song_analysis.force_vector.frequency);
|
sqlite3_bind_double(res, 3, song_analysis.force_vector.frequency);
|
||||||
sqlite3_bind_double(res, 4, song_analysis.force_vector.attack);
|
sqlite3_bind_double(res, 4, song_analysis.force_vector.attack);
|
||||||
sqlite3_bind_text(res, 5, song_uri, strlen(song_uri), SQLITE_STATIC);
|
sqlite3_bind_text(res, 5, song_uri, strlen(song_uri), SQLITE_STATIC);
|
||||||
|
sqlite3_bind_text(res, 6, song_analysis.album, strlen(song_analysis.album), SQLITE_STATIC);
|
||||||
dberr = sqlite3_step(res);
|
dberr = sqlite3_step(res);
|
||||||
if (SQLITE_DONE != dberr) {
|
if (SQLITE_DONE != dberr) {
|
||||||
// Free song analysis
|
// Free song analysis
|
||||||
|
Loading…
Reference in New Issue
Block a user