Compare commits
3 Commits
a102fecc2e
...
master
Author | SHA1 | Date | |
---|---|---|---|
bca4cd4eb1 | |||
eb30bc11d9 | |||
b0f9764fcf |
16
.idea/workspace.xml
generated
16
.idea/workspace.xml
generated
@ -16,11 +16,8 @@
|
|||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/Cargo.lock" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.lock" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/Cargo.lock" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.lock" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.toml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.toml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/etc/playlistd.toml" beforeDir="false" afterPath="$PROJECT_DIR$/etc/playlistd/playlistd.toml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/daemon/update_playlists.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/daemon/update_playlists.rs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/daemon/create_playlists.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/daemon/create_playlists.rs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/listenbrainz.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/listenbrainz.rs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/main.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/main.rs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/models.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/models.rs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/systemd/playlistd.service" beforeDir="false" afterPath="$PROJECT_DIR$/systemd/playlistd.service" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@ -165,7 +162,14 @@
|
|||||||
<workItem from="1713385358427" duration="2705000" />
|
<workItem from="1713385358427" duration="2705000" />
|
||||||
<workItem from="1713465993328" duration="1750000" />
|
<workItem from="1713465993328" duration="1750000" />
|
||||||
<workItem from="1713469457285" duration="6588000" />
|
<workItem from="1713469457285" duration="6588000" />
|
||||||
<workItem from="1713609379678" duration="6626000" />
|
<workItem from="1713609379678" duration="7216000" />
|
||||||
|
<workItem from="1713639881551" duration="577000" />
|
||||||
|
<workItem from="1716043707662" duration="542000" />
|
||||||
|
<workItem from="1716046070333" duration="1484000" />
|
||||||
|
<workItem from="1716640392358" duration="367000" />
|
||||||
|
<workItem from="1716642009543" duration="1546000" />
|
||||||
|
<workItem from="1716650511971" duration="14000" />
|
||||||
|
<workItem from="1723834879628" duration="4009000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="Structure">
|
<task id="LOCAL-00001" summary="Structure">
|
||||||
<created>1692008860369</created>
|
<created>1692008860369</created>
|
||||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1456,7 +1456,7 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "playlistd"
|
name = "playlistd"
|
||||||
version = "0.2.2"
|
version = "0.2.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"config",
|
"config",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "playlistd"
|
name = "playlistd"
|
||||||
version = "0.2.2"
|
version = "0.2.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Jude Southworth (judesouthworth@pm.me)"]
|
authors = ["Jude Southworth (judesouthworth@pm.me)"]
|
||||||
license = "AGPL-3.0 only"
|
license = "AGPL-3.0 only"
|
||||||
|
19
README.md
19
README.md
@ -20,19 +20,28 @@ For deploying, these can be configured in `/etc/playlistd.toml`.
|
|||||||
|
|
||||||
### Container build (Ubuntu 20.04)
|
### Container build (Ubuntu 20.04)
|
||||||
|
|
||||||
`podman run --rm --network=host -v "$PWD":/mnt -w /mnt -e "DATABASE_URL=postgres://jude@127.0.0.1/navidrome-playlists" playlistd cargo deb`
|
`podman run --rm --network=host -v "$PWD":/mnt -w /mnt -e "DATABASE_URL=postgres://jude@127.0.0.1/playlistd" playlistd cargo deb`
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
||||||
|
Each of these will work best when your music library is well organised with Musicbrainz metadata.
|
||||||
|
|
||||||
### Tracked playlist
|
### Tracked playlist
|
||||||
|
|
||||||
A tracked playlist is a single Subsonic playlist that tracks data from Listenbrainz. For example,
|
A tracked playlist is a single Subsonic playlist that tracks data from Listenbrainz. For example,
|
||||||
a playlist that contains your top 15 songs from the past week.
|
a playlist that contains your top 15 songs from the past week.
|
||||||
|
|
||||||
These will work best when your music library is well organised with Musicbrainz metadata.
|
|
||||||
|
|
||||||
You can create a tracked playlist by inserting a row into the `tracked_playlist` table.
|
You can create a tracked playlist by inserting a row into the `tracked_playlist` table.
|
||||||
|
|
||||||
### Static playlist
|
### Tracked playlist rule
|
||||||
|
|
||||||
TODO, not yet implemented
|
A tracked playlist rule is a time-based instruction to create a new tracked playlist. For example,
|
||||||
|
a playlist every year that contains your top songs.
|
||||||
|
|
||||||
|
You can create a tracked playlist rule by inserting a row into the `create_tracked_playlist` table.
|
||||||
|
For a yearly top songs playlist, run the following:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
INSERT INTO create_tracked_playlist (id, name_template, playlist_size, tracking_user, tracking_type)
|
||||||
|
VALUES (gen_random_uuid(), '%Y - Top 100', 100, 'jellywx', 'this_year');
|
||||||
|
```
|
||||||
|
@ -2,7 +2,7 @@ use crate::listenbrainz::StatsRange;
|
|||||||
use crate::models::TrackedPlaylist;
|
use crate::models::TrackedPlaylist;
|
||||||
use crate::subsonic::Subsonic;
|
use crate::subsonic::Subsonic;
|
||||||
use crate::{listenbrainz, CONFIG};
|
use crate::{listenbrainz, CONFIG};
|
||||||
use log::error;
|
use log::{error, warn};
|
||||||
use sqlx::postgres::PgPool;
|
use sqlx::postgres::PgPool;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@ -44,8 +44,9 @@ pub async fn update_playlists_daemon(
|
|||||||
match records_res {
|
match records_res {
|
||||||
Ok(records) => {
|
Ok(records) => {
|
||||||
for record in records {
|
for record in records {
|
||||||
// todo handle me
|
if let Err(e) = update_playlist(media_client.clone(), record).await {
|
||||||
update_playlist(media_client.clone(), record).await.unwrap();
|
warn!("Could not update playlist: {:?}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,11 +75,16 @@ async fn update_playlist(
|
|||||||
|
|
||||||
let mut tracks = vec![];
|
let mut tracks = vec![];
|
||||||
for recording in recordings {
|
for recording in recordings {
|
||||||
|
if recording.artists.clone().map_or(true, |o| o.is_empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let search_results = media_client
|
let search_results = media_client
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.song_search(format!(
|
.song_search(format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
recording.track_name, recording.artists[0].artist_credit_name
|
recording.track_name,
|
||||||
|
recording.artists.unwrap_or(vec![])[0].artist_credit_name
|
||||||
))
|
))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -90,6 +96,16 @@ async fn update_playlist(
|
|||||||
})
|
})
|
||||||
.cloned();
|
.cloned();
|
||||||
|
|
||||||
|
match filtered {
|
||||||
|
Some(track) => {
|
||||||
|
tracks.push(track.id);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let filtered = search_results
|
||||||
|
.iter()
|
||||||
|
.find(|s| alpha_compare(&s.title, &recording.track_name))
|
||||||
|
.cloned();
|
||||||
|
|
||||||
match filtered {
|
match filtered {
|
||||||
Some(track) => {
|
Some(track) => {
|
||||||
tracks.push(track.id);
|
tracks.push(track.id);
|
||||||
@ -102,6 +118,8 @@ async fn update_playlist(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let playlist = media_client
|
let playlist = media_client
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -71,18 +71,10 @@ struct RecordingsResponse {
|
|||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct RecordingsPayload {
|
pub struct RecordingsPayload {
|
||||||
count: u64,
|
|
||||||
from_ts: u64,
|
|
||||||
to_ts: u64,
|
|
||||||
last_updated: u64,
|
|
||||||
total_recording_count: u64,
|
|
||||||
user_id: String,
|
|
||||||
offset: u64,
|
|
||||||
range: String,
|
|
||||||
pub recordings: Vec<RecordingsEntry>,
|
pub recordings: Vec<RecordingsEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub struct Artist {
|
pub struct Artist {
|
||||||
pub artist_credit_name: String,
|
pub artist_credit_name: String,
|
||||||
}
|
}
|
||||||
@ -92,9 +84,7 @@ pub struct RecordingsEntry {
|
|||||||
pub track_name: String,
|
pub track_name: String,
|
||||||
pub release_name: String,
|
pub release_name: String,
|
||||||
pub artist_name: String,
|
pub artist_name: String,
|
||||||
pub artists: Vec<Artist>,
|
pub artists: Option<Vec<Artist>>,
|
||||||
pub recording_mbid: String,
|
|
||||||
pub release_mbid: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn recordings(
|
pub async fn recordings(
|
||||||
|
Reference in New Issue
Block a user