mirror of
https://github.com/owntone/owntone-server.git
synced 2025-02-03 09:56:00 -05:00
[db/conf] Add automatic calculation of ratings based on played/skipped
If activated in the configuration file, rating is updated after playing or skipping a song. The calculation is based on how it is done in the mpdstats plugin for beets (https://beets.readthedocs.io/en/latest/plugins/mpdstats.html).
This commit is contained in:
parent
197fc6402e
commit
f84e67ed5e
@ -178,6 +178,17 @@ library {
|
||||
# there is data to be read. To exclude specific pipes from watching,
|
||||
# consider using the above _ignore options.
|
||||
# pipe_autostart = true
|
||||
|
||||
# Enable automatic rating updates
|
||||
# If enabled, rating is automatically updated after a song has either been
|
||||
# played or skipped (only skipping to the next song is taken into account).
|
||||
# The calculation is taken from the beets plugin "mpdstats" (see
|
||||
# https://beets.readthedocs.io/en/latest/plugins/mpdstats.html).
|
||||
# It consist of calculating a stable rating based only on the play- and
|
||||
# skipcount and a rolling rating based on the current rating and the action
|
||||
# (played or skipped). Both results are combined with a mix-factor of 0.75:
|
||||
# new rating = 0.75 * stable rating + 0.25 * rolling rating)
|
||||
# rating_updates = false
|
||||
}
|
||||
|
||||
# Local audio output
|
||||
|
@ -100,6 +100,7 @@ static cfg_opt_t sec_library[] =
|
||||
CFG_STR_LIST("no_decode", NULL, CFGF_NONE),
|
||||
CFG_STR_LIST("force_decode", NULL, CFGF_NONE),
|
||||
CFG_BOOL("pipe_autostart", cfg_true, CFGF_NONE),
|
||||
CFG_BOOL("rating_updates", cfg_false, CFGF_NONE),
|
||||
CFG_END()
|
||||
};
|
||||
|
||||
|
39
src/db.c
39
src/db.c
@ -411,6 +411,7 @@ db_data_kind_label(enum data_kind data_kind)
|
||||
struct rng_ctx shuffle_rng;
|
||||
|
||||
static char *db_path;
|
||||
static bool db_rating_updates;
|
||||
static __thread sqlite3 *hdl;
|
||||
|
||||
|
||||
@ -2333,9 +2334,31 @@ void
|
||||
db_file_inc_playcount(int id)
|
||||
{
|
||||
#define Q_TMPL "UPDATE files SET play_count = play_count + 1, time_played = %" PRIi64 ", seek = 0 WHERE id = %d;"
|
||||
/*
|
||||
* Rating calculation is taken from from the beets plugin "mpdstats" (see https://beets.readthedocs.io/en/latest/plugins/mpdstats.html)
|
||||
* and adapted to the forked-daapd rating rage (0 to 100).
|
||||
*
|
||||
* Rating consist of the stable rating and a rolling rating.
|
||||
* The stable rating is calculated based on the number was played and skipped:
|
||||
* stable rating = (play_count + 1.0) / (play_count + skip_count + 2.0) * 100
|
||||
* The rolling rating is calculated based on the current action (played or skipped):
|
||||
* rolling rating for played = rating + ((100.0 - rating) / 2.0)
|
||||
* rolling rating for skipped = rating - (rating / 2.0)
|
||||
*
|
||||
* The new rating is a mix of stable and rolling rating (factor 0.75):
|
||||
* new rating = stable rating * 0.75 + rolling rating * 0.25
|
||||
*/
|
||||
#define Q_TMPL_WITH_RATING \
|
||||
"UPDATE files "\
|
||||
" SET play_count = play_count + 1, time_played = %" PRIi64 ", seek = 0, "\
|
||||
" rating = CAST(((play_count + 1.0) / (play_count + skip_count + 2.0) * 100 * 0.75) + ((rating + ((100.0 - rating) / 2.0)) * 0.25) AS INT)" \
|
||||
" WHERE id = %d;"
|
||||
char *query;
|
||||
|
||||
query = sqlite3_mprintf(Q_TMPL, (int64_t)time(NULL), id);
|
||||
if (db_rating_updates)
|
||||
query = sqlite3_mprintf(Q_TMPL_WITH_RATING, (int64_t)time(NULL), id);
|
||||
else
|
||||
query = sqlite3_mprintf(Q_TMPL, (int64_t)time(NULL), id);
|
||||
if (!query)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
||||
@ -2345,15 +2368,25 @@ db_file_inc_playcount(int id)
|
||||
|
||||
db_query_run(query, 1, 0);
|
||||
#undef Q_TMPL
|
||||
#undef Q_TMPL_WITH_RATING
|
||||
}
|
||||
|
||||
void
|
||||
db_file_inc_skipcount(int id)
|
||||
{
|
||||
#define Q_TMPL "UPDATE files SET skip_count = skip_count + 1, time_skipped = %" PRIi64 " WHERE id = %d;"
|
||||
// see db_file_inc_playcount for a description of how the rating is calculated
|
||||
#define Q_TMPL_WITH_RATING \
|
||||
"UPDATE files "\
|
||||
" SET skip_count = skip_count + 1, time_skipped = %" PRIi64 ", seek = 0, "\
|
||||
" rating = CAST(((play_count + 1.0) / (play_count + skip_count + 2.0) * 100 * 0.75) + ((rating - (rating / 2.0)) * 0.25) AS INT)" \
|
||||
" WHERE id = %d;"
|
||||
char *query;
|
||||
|
||||
query = sqlite3_mprintf(Q_TMPL, (int64_t)time(NULL), id);
|
||||
if (db_rating_updates)
|
||||
query = sqlite3_mprintf(Q_TMPL_WITH_RATING, (int64_t)time(NULL), id);
|
||||
else
|
||||
query = sqlite3_mprintf(Q_TMPL, (int64_t)time(NULL), id);
|
||||
if (!query)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
||||
@ -2363,6 +2396,7 @@ db_file_inc_skipcount(int id)
|
||||
|
||||
db_query_run(query, 1, 0);
|
||||
#undef Q_TMPL
|
||||
#undef Q_TMPL_WITH_RATING
|
||||
}
|
||||
|
||||
void
|
||||
@ -6841,6 +6875,7 @@ db_init(void)
|
||||
}
|
||||
|
||||
db_path = cfg_getstr(cfg_getsec(cfg, "general"), "db_path");
|
||||
db_rating_updates = cfg_getbool(cfg_getsec(cfg, "library"), "rating_updates");
|
||||
|
||||
DPRINTF(E_LOG, L_DB, "Configured to use database file '%s'\n", db_path);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user