Large refactor
* Fix strings overflows and associated segfaults * Add some client code in Python
This commit is contained in:
parent
54cf7074e8
commit
d5a1855b1d
10
README.md
10
README.md
@ -57,3 +57,13 @@ database and `mpdbliss` database in sync.
|
||||
|
||||
Check the `client.sh` script for an example client script to build smooth MPD
|
||||
playlists.
|
||||
out any flag, `mpdbliss` will listen for MPD IDLE protocol, and trigger
|
||||
an update of the database whenever the MPD database is modified.
|
||||
|
||||
Typical usage would be to run a `--rescan` first, and then either do periodic
|
||||
`--update` or let it run listening at MPD IDLE protocol to maintain MPD
|
||||
database and `mpdbliss` database in sync.
|
||||
|
||||
|
||||
Check the `client/client.py` script for an example client script to build smooth MPD
|
||||
playlists.
|
||||
|
24
client.sh
24
client.sh
@ -1,24 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
QUEUE_LENGTH=100
|
||||
|
||||
if [ -z "$XDG_DATA_HOME" ]; then
|
||||
mpdbliss_data_home="$HOME/.local/share/mpdbliss"
|
||||
else
|
||||
mpdbliss_data_home="$XDG_DATA_HOME/mpdbliss"
|
||||
fi
|
||||
|
||||
current_song=`mpc current --format "%file%"`
|
||||
current_song="bad/_Compilations/8 Mile_ Music From and Inspired by the Motion Picture/01 - Lose Yourself.mp3"
|
||||
for i in {1..$QUEUE_LENGTH}; do
|
||||
# Find closest song
|
||||
closest_song=`sqlite3 "$mpdbliss_data_home/db.sqlite3" "SELECT filename FROM (SELECT 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='$current_song' UNION SELECT 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=\"$current_song\") ORDER BY distance ASC LIMIT 1"`
|
||||
if [ ! -z "$closest_song" ]; then
|
||||
# Push it on the queue
|
||||
mpc add "$closest_song" 2>&1 > /dev/null
|
||||
# Continue using latest pushed song as current song
|
||||
current_song="$closest_song"
|
||||
# Note: if song could not be found by mpd, it is just not added to the
|
||||
# queue and skipped
|
||||
fi
|
||||
done
|
73
client/build_cache.py
Normal file
73
client/build_cache.py
Normal file
@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
if "XDG_DATA_HOME" in os.environ:
|
||||
_MPDBLISS_DATA_HOME = os.path.expandvars("$XDG_DATA_HOME/mpdbliss")
|
||||
else:
|
||||
_MPDBLISS_DATA_HOME = os.path.expanduser("~/.local/share/mpdbliss")
|
||||
|
||||
|
||||
def main():
|
||||
db_path = os.path.join(_MPDBLISS_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()
|
||||
|
||||
# Get cached distances from db
|
||||
cur.execute("SELECT song1, song2, distance FROM distances")
|
||||
cached_distances = cur.fetchall()
|
||||
|
||||
# Get all songs
|
||||
cur.execute("SELECT id, tempo, amplitude, frequency, attack, filename FROM songs")
|
||||
all_songs = cur.fetchall()
|
||||
|
||||
for i in range(len(all_songs)):
|
||||
for j in range(i + 1, len(all_songs)):
|
||||
song1 = all_songs[i]
|
||||
song2 = all_songs[j]
|
||||
is_cached = len([i for i in cached_distances
|
||||
if(i["song1"] == song1["id"] and
|
||||
i["song2"] == song2["id"]) or
|
||||
(i["song1"] == song2["id"] and
|
||||
i["song2"] == song1["id"])]) > 0
|
||||
if is_cached:
|
||||
# Pass pair if cached value is already there
|
||||
continue
|
||||
# Compute distance
|
||||
distance = math.sqrt(
|
||||
(song1["tempo"] - song2["tempo"])**2 +
|
||||
(song1["amplitude"] - song2["amplitude"])**2 +
|
||||
(song1["frequency"] - song2["frequency"])**2 +
|
||||
(song1["attack"] - song2["attack"])**2
|
||||
)
|
||||
logging.debug("Distance between %s and %s is %f." %
|
||||
(song1["filename"], song2["filename"], distance))
|
||||
# Store distance in db cache
|
||||
try:
|
||||
logging.debug("Storing distance in database.")
|
||||
conn.execute(
|
||||
"INSERT INTO distances(song1, song2, distance) VALUES(?, ?, ?)",
|
||||
(song1["id"], song2["id"], distance))
|
||||
conn.commit()
|
||||
# Update cached_distances list
|
||||
cached_distances.append({
|
||||
"song1": song1["id"],
|
||||
"song2": song2["id"],
|
||||
"distance": distance
|
||||
})
|
||||
except sqlite3.IntegrityError:
|
||||
logging.warning("Unable to insert distance in database.")
|
||||
conn.rollback()
|
||||
# Close connection
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
121
client/client.py
Normal file
121
client/client.py
Normal file
@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env python3
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
_QUEUE_LENGTH = 100
|
||||
# TODO: Use cosine similarity as well
|
||||
_DISTANCE_THRESHOLD = 4.0
|
||||
|
||||
if "XDG_DATA_HOME" in os.environ:
|
||||
_MPDBLISS_DATA_HOME = os.path.expandvars("$XDG_DATA_HOME/mpdbliss")
|
||||
else:
|
||||
_MPDBLISS_DATA_HOME = os.path.expanduser("~/.local/share/mpdbliss")
|
||||
|
||||
|
||||
def main():
|
||||
mpd_queue = []
|
||||
db_path = os.path.join(_MPDBLISS_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()
|
||||
|
||||
current_song = subprocess.check_output(
|
||||
["mpc", "current", '--format', '"%file%"'])
|
||||
current_song = current_song.decode("utf-8").strip('" \r\n\t')
|
||||
if current_song is "":
|
||||
logging.warning("Currently played song could not be found.")
|
||||
sys.exit(1)
|
||||
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:
|
||||
logging.warning("Current song %s is not in db. You should update the db." %
|
||||
(current_song["filename"],))
|
||||
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"])
|
||||
# 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"]))
|
||||
cached_distances = [row
|
||||
for row in cur.fetchall()
|
||||
if row["filename"] not in mpd_queue]
|
||||
cached_distances_songs = [i["filename"] for i in cached_distances]
|
||||
|
||||
# If distance to closest song is ok, just add the song
|
||||
if len(cached_distances) > 0:
|
||||
if cached_distances[0]["distance"] < _DISTANCE_THRESHOLD:
|
||||
# Push it on the queue
|
||||
subprocess.check_call(["mpc", "add",
|
||||
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]
|
||||
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
|
||||
tmp_song_data["filename"] in cached_distances_songs or
|
||||
tmp_song_data["filename"] in mpd_queue):
|
||||
# Skip current song and already processed songs
|
||||
logging.debug("Skipping %s." % (tmp_song_data["filename"]))
|
||||
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
|
||||
)
|
||||
logging.debug("Distance between %s and %s is %f." %
|
||||
(current_song["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))
|
||||
conn.commit()
|
||||
except sqlite3.IntegrityError:
|
||||
logging.warning("Unable to insert distance in database.")
|
||||
conn.rollback()
|
||||
|
||||
# If distance is ok, just add the song
|
||||
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)
|
||||
# If no song found, take the closest one
|
||||
logging.info("No close enough song found. Using %s. Distance is %f." %
|
||||
(closest_song[0]["filename"], closest_song[1]))
|
||||
current_song = closest_song[0]
|
||||
subprocess.check_call(["mpc", "add", closest_song[0]["filename"]])
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
6
include/constants.h
Normal file
6
include/constants.h
Normal file
@ -0,0 +1,6 @@
|
||||
#ifndef CONSTANTS_H
|
||||
#define CONSTANTS_H
|
||||
|
||||
#define DEFAULT_STRING_LENGTH 10000
|
||||
|
||||
#endif // CONSTANTS_H
|
@ -1,8 +1,6 @@
|
||||
#ifndef UTILITIES_H
|
||||
#define UTILITIES_H
|
||||
|
||||
#define DEFAULT_STRING_LENGTH 1024
|
||||
|
||||
|
||||
/**
|
||||
* Strip the trailing slash from a string.
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include "analysis.h"
|
||||
#include "constants.h"
|
||||
#include "utilities.h"
|
||||
|
||||
// TODO: Handle deletions from db
|
||||
@ -17,8 +18,8 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
// Get data directory, init db file
|
||||
char mpdbliss_data_folder[DEFAULT_STRING_LENGTH] = "";
|
||||
char mpdbliss_data_db[DEFAULT_STRING_LENGTH] = "";
|
||||
char mpdbliss_data_folder[DEFAULT_STRING_LENGTH + 1] = "";
|
||||
char mpdbliss_data_db[DEFAULT_STRING_LENGTH + 1] = "";
|
||||
if (0 != _init_db(mpdbliss_data_folder, mpdbliss_data_db)) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include "analysis.h"
|
||||
#include "cmdline.h"
|
||||
#include "constants.h"
|
||||
#include "utilities.h"
|
||||
|
||||
// TODO: Handle deletions from db
|
||||
@ -186,20 +187,20 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
// Handle mpd_root argument
|
||||
char mpd_base_path[DEFAULT_STRING_LENGTH] = "";
|
||||
char mpd_base_path[DEFAULT_STRING_LENGTH + 1] = "";
|
||||
strncat(mpd_base_path, args_info.mpd_root_arg, DEFAULT_STRING_LENGTH);
|
||||
strip_trailing_slash(mpd_base_path);
|
||||
strncat(mpd_base_path, "/", DEFAULT_STRING_LENGTH - strlen(mpd_base_path));
|
||||
|
||||
// Get data directory, init db file
|
||||
char mpdbliss_data_folder[DEFAULT_STRING_LENGTH] = "";
|
||||
char mpdbliss_data_db[DEFAULT_STRING_LENGTH] = "";
|
||||
char mpdbliss_data_folder[DEFAULT_STRING_LENGTH + 1] = "";
|
||||
char mpdbliss_data_db[DEFAULT_STRING_LENGTH + 1] = "";
|
||||
if (0 != _init_db(mpdbliss_data_folder, mpdbliss_data_db)) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Set data file path
|
||||
char mpdbliss_data_file[DEFAULT_STRING_LENGTH] = "";
|
||||
char mpdbliss_data_file[DEFAULT_STRING_LENGTH + 1] = "";
|
||||
strncat(mpdbliss_data_file, mpdbliss_data_folder, DEFAULT_STRING_LENGTH);
|
||||
strncat(mpdbliss_data_file, "/latest_mtime.txt", DEFAULT_STRING_LENGTH - strlen(mpdbliss_data_file));
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include <bliss.h>
|
||||
|
||||
#include "constants.h"
|
||||
#include "utilities.h"
|
||||
|
||||
|
||||
@ -21,7 +22,7 @@ int _init_db(char *data_folder, char* db_path)
|
||||
strncat(data_folder, "/.local/share/mpdbliss", DEFAULT_STRING_LENGTH - strlen(data_folder));
|
||||
}
|
||||
else {
|
||||
strncpy(data_folder, xdg_data_home_env, DEFAULT_STRING_LENGTH);
|
||||
strncat(data_folder, xdg_data_home_env, DEFAULT_STRING_LENGTH);
|
||||
strip_trailing_slash(data_folder);
|
||||
strncat(data_folder, "/mpdbliss", DEFAULT_STRING_LENGTH - strlen(data_folder));
|
||||
}
|
||||
@ -92,9 +93,9 @@ int _parse_music_helper(
|
||||
|
||||
// Compute full uri
|
||||
printf("\nAdding new song to db: %s\n", song_uri);
|
||||
char song_full_uri[DEFAULT_STRING_LENGTH] = "";
|
||||
char song_full_uri[DEFAULT_STRING_LENGTH + 1] = "";
|
||||
strncat(song_full_uri, base_path, DEFAULT_STRING_LENGTH);
|
||||
strncat(song_full_uri, song_uri, DEFAULT_STRING_LENGTH);
|
||||
strncat(song_full_uri, song_uri, DEFAULT_STRING_LENGTH - strlen(song_full_uri));
|
||||
|
||||
// Pass it to bliss
|
||||
struct bl_song song_analysis;
|
||||
@ -156,54 +157,7 @@ int _parse_music_helper(
|
||||
sqlite3_bind_double(res, 4, song_analysis.force_vector.attack);
|
||||
sqlite3_bind_text(res, 5, song_uri, strlen(song_uri), SQLITE_STATIC);
|
||||
sqlite3_step(res);
|
||||
sqlite3_finalize(res);
|
||||
int last_id = sqlite3_last_insert_rowid(dbh);
|
||||
// Insert updated distances
|
||||
dberr = sqlite3_prepare_v2(dbh, "SELECT id, tempo, amplitude, frequency, attack FROM songs", -1, &res, 0);
|
||||
if (SQLITE_OK != dberr) {
|
||||
fprintf(stderr, "Error while inserting data in db: %s\n\n", sqlite3_errmsg(dbh));
|
||||
// Free song analysis
|
||||
bl_free_song(&song_analysis);
|
||||
sqlite3_exec(dbh, "ROLLBACK", NULL, NULL, NULL);
|
||||
// Store error in db
|
||||
sqlite3_prepare_v2(dbh,
|
||||
"INSERT INTO errors(filename) VALUES(?)",
|
||||
-1, &res, 0);
|
||||
sqlite3_bind_text(res, 1, song_uri, strlen(song_uri), SQLITE_STATIC);
|
||||
sqlite3_step(res);
|
||||
sqlite3_finalize(res);
|
||||
// Pass file
|
||||
return 1;
|
||||
}
|
||||
int dberr2 = SQLITE_OK;
|
||||
while (sqlite3_step(res) == SQLITE_ROW) {
|
||||
int id = sqlite3_column_int(res, 0);
|
||||
if (id == last_id) {
|
||||
// Skip last inserted item
|
||||
continue;
|
||||
}
|
||||
struct force_vector_s song_db;
|
||||
song_db.tempo = sqlite3_column_double(res, 1);
|
||||
song_db.amplitude = sqlite3_column_double(res, 2);
|
||||
song_db.frequency = sqlite3_column_double(res, 3);
|
||||
song_db.attack = sqlite3_column_double(res, 4);
|
||||
float distance = bl_distance(song_analysis.force_vector, song_db);
|
||||
|
||||
sqlite3_stmt *res2;
|
||||
dberr2 = sqlite3_prepare_v2(dbh,
|
||||
"INSERT INTO distances(song1, song2, distance) VALUES(?, ?, ?)",
|
||||
-1, &res2, 0);
|
||||
if (SQLITE_OK != dberr2) {
|
||||
fprintf(stderr, "Error while inserting data in db: %s\n\n", sqlite3_errmsg(dbh));
|
||||
break;
|
||||
}
|
||||
sqlite3_bind_int(res2, 1, last_id);
|
||||
sqlite3_bind_int(res2, 2, id);
|
||||
sqlite3_bind_double(res2, 3, distance);
|
||||
sqlite3_step(res2);
|
||||
sqlite3_finalize(res2);
|
||||
}
|
||||
if (SQLITE_OK != dberr2) {
|
||||
// Free song analysis
|
||||
bl_free_song(&song_analysis);
|
||||
sqlite3_exec(dbh, "ROLLBACK", NULL, NULL, NULL);
|
||||
|
Loading…
Reference in New Issue
Block a user