mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 03:47:23 +01:00
Implement program parameters
This commit is contained in:
parent
815dede141
commit
1788303bf3
6 changed files with 108 additions and 42 deletions
37
Cargo.lock
generated
37
Cargo.lock
generated
|
|
@ -1049,7 +1049,6 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"mpris-player",
|
"mpris-player",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rand",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
|
@ -1206,12 +1205,6 @@ version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ppv-lite86"
|
|
||||||
version = "0.2.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
|
|
@ -1273,36 +1266,6 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand"
|
|
||||||
version = "0.8.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"rand_chacha",
|
|
||||||
"rand_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_chacha"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
|
||||||
dependencies = [
|
|
||||||
"ppv-lite86",
|
|
||||||
"rand_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_core"
|
|
||||||
version = "0.6.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.10.2"
|
version = "1.10.2"
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ lazy_static = "1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mpris-player = "0.6"
|
mpris-player = "0.6"
|
||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
rand = "0.8"
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
</key>
|
</key>
|
||||||
<key name="program1" type="s">
|
<key name="program1" type="s">
|
||||||
<!-- Translators: Configuration for the default programs in JSON. Please only translate the values of "title" and "description". -->
|
<!-- Translators: Configuration for the default programs in JSON. Please only translate the values of "title" and "description". -->
|
||||||
<default l10n="messages">'{"title":"Just play some music","description":"Randomly select some music. Customize programs using the button in the top right.","design":"Program1","prefer_recently_added":0.0,"prefer_least_recently_played":0.0,"play_full_recordings":true}'</default>
|
<default l10n="messages">'{"title":"Just play some music","description":"Randomly select some music. Customize programs using the button in the top right.","design":"Program1","prefer_recently_added":0.0,"prefer_least_recently_played":0.1,"play_full_recordings":true}'</default>
|
||||||
<summary>Default settings for program 1</summary>
|
<summary>Default settings for program 1</summary>
|
||||||
</key>
|
</key>
|
||||||
<key name="program2" type="s">
|
<key name="program2" type="s">
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
</key>
|
</key>
|
||||||
<key name="program3" type="s">
|
<key name="program3" type="s">
|
||||||
<!-- Translators: Configuration for the default programs in JSON. Please only translate the values of "title" and "description". -->
|
<!-- Translators: Configuration for the default programs in JSON. Please only translate the values of "title" and "description". -->
|
||||||
<default l10n="messages">'{"title":"A long time ago","description":"Works that you haven\'t listend to for a long time.","design":"Program3","prefer_recently_added":-1.0,"prefer_least_recently_played":1.0,"play_full_recordings":false}'</default>
|
<default l10n="messages">'{"title":"A long time ago","description":"Works that you haven\'t listend to for a long time.","design":"Program3","prefer_recently_added":0.0,"prefer_least_recently_played":1.0,"play_full_recordings":false}'</default>
|
||||||
<summary>Default settings for program 3</summary>
|
<summary>Default settings for program 3</summary>
|
||||||
</key>
|
</key>
|
||||||
</schema>
|
</schema>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,13 @@ use std::{
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use diesel::{dsl::exists, prelude::*, QueryDsl, SqliteConnection};
|
use diesel::{
|
||||||
|
dsl::{exists, sql},
|
||||||
|
prelude::*,
|
||||||
|
sql_query,
|
||||||
|
sql_types::BigInt,
|
||||||
|
QueryDsl, SqliteConnection,
|
||||||
|
};
|
||||||
use gtk::{glib, glib::Properties, prelude::*, subclass::prelude::*};
|
use gtk::{glib, glib::Properties, prelude::*, subclass::prelude::*};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -414,7 +420,59 @@ impl MusicusLibrary {
|
||||||
query = query.filter(album_recordings::album_id.eq(album_id));
|
query = query.filter(album_recordings::album_id.eq(album_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement prefer_recently_added and prefer_least_recently_played.
|
if program.prefer_recently_added() > 0.0 {
|
||||||
|
let oldest_timestamp = sql_query(
|
||||||
|
"SELECT CAST(STRFTIME('%s', MIN(created_at)) AS INTEGER) AS value FROM recordings",
|
||||||
|
)
|
||||||
|
.get_result::<IntegerValue>(connection)?
|
||||||
|
.value;
|
||||||
|
|
||||||
|
let newest_timestamp = sql_query(
|
||||||
|
"SELECT CAST(STRFTIME('%s', MAX(created_at)) AS INTEGER) AS value FROM recordings",
|
||||||
|
)
|
||||||
|
.get_result::<IntegerValue>(connection)?
|
||||||
|
.value;
|
||||||
|
|
||||||
|
let range = newest_timestamp - oldest_timestamp;
|
||||||
|
|
||||||
|
if range >= 60 {
|
||||||
|
let proportion = program.prefer_recently_added().max(1.0) * 0.9;
|
||||||
|
let cutoff_timestamp =
|
||||||
|
oldest_timestamp + (proportion * range as f64).floor() as i64;
|
||||||
|
|
||||||
|
query = query.filter(
|
||||||
|
sql::<BigInt>("CAST(STRFTIME('%s', recordings.created_at) AS INTEGER)")
|
||||||
|
.ge(cutoff_timestamp)
|
||||||
|
.or(recordings::last_played_at.is_null()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if program.prefer_least_recently_played() > 0.0 {
|
||||||
|
let oldest_timestamp =
|
||||||
|
sql_query("SELECT CAST(STRFTIME('%s', MIN(last_played_at)) AS INTEGER) AS value FROM recordings")
|
||||||
|
.get_result::<IntegerValue>(connection)?
|
||||||
|
.value;
|
||||||
|
|
||||||
|
let newest_timestamp =
|
||||||
|
sql_query("SELECT CAST(STRFTIME('%s', MAX(last_played_at)) AS INTEGER) AS value FROM recordings")
|
||||||
|
.get_result::<IntegerValue>(connection)?
|
||||||
|
.value;
|
||||||
|
|
||||||
|
let range = newest_timestamp - oldest_timestamp;
|
||||||
|
|
||||||
|
if range >= 60 {
|
||||||
|
let proportion = 1.0 - program.prefer_least_recently_played().max(1.0) * 0.9;
|
||||||
|
let cutoff_timestamp =
|
||||||
|
oldest_timestamp + (proportion * range as f64).floor() as i64;
|
||||||
|
|
||||||
|
query = query.filter(
|
||||||
|
sql::<BigInt>("CAST(STRFTIME('%s', recordings.last_played_at) AS INTEGER)")
|
||||||
|
.le(cutoff_timestamp)
|
||||||
|
.or(recordings::last_played_at.is_null()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let row = query
|
let row = query
|
||||||
.order(random())
|
.order(random())
|
||||||
|
|
@ -424,6 +482,31 @@ impl MusicusLibrary {
|
||||||
Recording::from_table(row, &self.folder(), connection)
|
Recording::from_table(row, &self.folder(), connection)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn track_played(&self, track_id: &str) -> Result<()> {
|
||||||
|
let mut binding = self.imp().connection.borrow_mut();
|
||||||
|
let connection = &mut *binding.as_mut().unwrap();
|
||||||
|
|
||||||
|
let now = Local::now().naive_local();
|
||||||
|
|
||||||
|
diesel::update(recordings::table)
|
||||||
|
.filter(exists(
|
||||||
|
tracks::table.filter(
|
||||||
|
tracks::track_id
|
||||||
|
.eq(track_id)
|
||||||
|
.and(tracks::recording_id.eq(recordings::recording_id)),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.set(recordings::last_played_at.eq(now))
|
||||||
|
.execute(connection)?;
|
||||||
|
|
||||||
|
diesel::update(tracks::table)
|
||||||
|
.filter(tracks::track_id.eq(track_id))
|
||||||
|
.set(tracks::last_played_at.eq(now))
|
||||||
|
.execute(connection)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn search_persons(&self, search: &str) -> Result<Vec<Person>> {
|
pub fn search_persons(&self, search: &str) -> Result<Vec<Person>> {
|
||||||
let search = format!("%{}%", search);
|
let search = format!("%{}%", search);
|
||||||
let mut binding = self.imp().connection.borrow_mut();
|
let mut binding = self.imp().connection.borrow_mut();
|
||||||
|
|
@ -555,3 +638,9 @@ impl LibraryResults {
|
||||||
&& self.albums.is_empty()
|
&& self.albums.is_empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(QueryableByName)]
|
||||||
|
pub struct IntegerValue {
|
||||||
|
#[diesel(sql_type = diesel::sql_types::BigInt)]
|
||||||
|
pub value: i64,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,13 @@ mod imp {
|
||||||
|
|
||||||
self.current_index.set(index);
|
self.current_index.set(index);
|
||||||
item.set_is_playing(true);
|
item.set_is_playing(true);
|
||||||
|
|
||||||
|
self.library
|
||||||
|
.borrow()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.track_played(&item.track_id())
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -228,6 +235,7 @@ impl MusicusPlayer {
|
||||||
Some(&performances),
|
Some(&performances),
|
||||||
None,
|
None,
|
||||||
&tracks[0].path,
|
&tracks[0].path,
|
||||||
|
&tracks[0].track_id,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
let mut tracks = tracks.into_iter();
|
let mut tracks = tracks.into_iter();
|
||||||
|
|
@ -254,6 +262,7 @@ impl MusicusPlayer {
|
||||||
Some(&performances),
|
Some(&performances),
|
||||||
Some(&track_title(&first_track, 1)),
|
Some(&track_title(&first_track, 1)),
|
||||||
&first_track.path,
|
&first_track.path,
|
||||||
|
&first_track.track_id,
|
||||||
));
|
));
|
||||||
|
|
||||||
for (index, track) in tracks.enumerate() {
|
for (index, track) in tracks.enumerate() {
|
||||||
|
|
@ -264,6 +273,7 @@ impl MusicusPlayer {
|
||||||
// track number = track index + 1 (first track) + 1 (zero based)
|
// track number = track index + 1 (first track) + 1 (zero based)
|
||||||
Some(&track_title(&track, index + 2)),
|
Some(&track_title(&track, index + 2)),
|
||||||
&track.path,
|
&track.path,
|
||||||
|
&track.track_id,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,9 @@ mod imp {
|
||||||
|
|
||||||
#[property(get, construct_only)]
|
#[property(get, construct_only)]
|
||||||
pub path: OnceCell<PathBuf>,
|
pub path: OnceCell<PathBuf>,
|
||||||
|
|
||||||
|
#[property(get, construct_only)]
|
||||||
|
pub track_id: OnceCell<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
|
|
@ -50,6 +53,7 @@ impl PlaylistItem {
|
||||||
performers: Option<&str>,
|
performers: Option<&str>,
|
||||||
part_title: Option<&str>,
|
part_title: Option<&str>,
|
||||||
path: impl AsRef<Path>,
|
path: impl AsRef<Path>,
|
||||||
|
track_id: &str,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
glib::Object::builder()
|
glib::Object::builder()
|
||||||
.property("is-title", is_title)
|
.property("is-title", is_title)
|
||||||
|
|
@ -57,6 +61,7 @@ impl PlaylistItem {
|
||||||
.property("performers", performers)
|
.property("performers", performers)
|
||||||
.property("part-title", part_title)
|
.property("part-title", part_title)
|
||||||
.property("path", path.as_ref())
|
.property("path", path.as_ref())
|
||||||
|
.property("track-id", track_id)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue