Skip to content

Commit

Permalink
Merge pull request #563 from RustyNova016/feat/playlist_converter
Browse files Browse the repository at this point in the history
feat: playlist exporter
  • Loading branch information
RustyNova016 authored Feb 26, 2025
2 parents 5f3459d + 00964f5 commit 5d6fe59
Show file tree
Hide file tree
Showing 16 changed files with 257 additions and 18 deletions.
11 changes: 5 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ tuillez = { path = "./tuillez" }
musicbrainz-db-lite = { path = "./musicbrainz_db_lite" }

# Musicbrainz dependencies
listenbrainz = "0.8.1"
#listenbrainz = "0.8.1"
listenbrainz = { branch = "alistral_version", git = "https://github.com/RustyNova016/listenbrainz-rs.git" }

derive_builder = "0.20.2"
inquire = "0.7.5"
Expand Down
4 changes: 3 additions & 1 deletion alistral_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ musicbrainz-db-lite = { path = "../musicbrainz_db_lite" }
interzic = { path = "../interzic" }
tuillez = { path = "../tuillez" }

#listenbrainz = "0.8.1"
listenbrainz = { branch = "alistral_version", git = "https://github.com/RustyNova016/listenbrainz-rs.git" }

itertools = "0.14.0"
serde = "1.0.218"
sqlx = { version = "0.8.3", features = ["runtime-tokio", "macros"] }
Expand All @@ -18,6 +21,5 @@ chrono = "0.4.39"
rust_decimal = "1.36.0"
rust_decimal_macros = "1.36.0"
futures = "0.3.31"
listenbrainz = "0.8.1"
tracing-indicatif = "0.3.9"
tracing = "0.1.41"
35 changes: 35 additions & 0 deletions docs/book/src/CommandLineHelp.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ This document contains the help content for the `alistral` command-line program.
* [`alistral mapping list-unmapped`](#alistral-mapping-list-unmapped)
* [`alistral musicbrainz`](#alistral-musicbrainz)
* [`alistral musicbrainz clippy`](#alistral-musicbrainz-clippy)
* [`alistral playlist`](#alistral-playlist)
* [`alistral playlist convert`](#alistral-playlist-convert)
* [`alistral radio`](#alistral-radio)
* [`alistral radio circles`](#alistral-radio-circles)
* [`alistral radio underrated`](#alistral-radio-underrated)
Expand Down Expand Up @@ -65,6 +67,7 @@ A CLI app containing a set of useful tools for Listenbrainz
* `lookup` — Get detailled information about an entity
* `mapping` — Commands for interacting with listen mappings
* `musicbrainz` — Commands for musicbrainz stuff
* `playlist` — Interact with playlists
* `radio` — Generate radio playlists for you
* `stats` — Shows top statistics for a specific target
* `unstable` — A CLI app containing a set of useful tools for Listenbrainz
Expand Down Expand Up @@ -584,6 +587,38 @@ Search for potential mistakes, missing data and style issues. This allows to qui



## `alistral playlist`

Interact with playlists

**Usage:** `alistral playlist <COMMAND>`

###### **Subcommands:**

* `convert` — Convert a playlist from one service to another



## `alistral playlist convert`

Convert a playlist from one service to another

**Usage:** `alistral playlist convert <SOURCE> <ID> <TARGET>`

###### **Arguments:**

* `<SOURCE>` — Get the playlist from which service?

Possible values: `listenbrainz`

* `<ID>` — The id of the playlist on the external service
* `<TARGET>` — Convert to this service

Possible values: `youtube`




## `alistral radio`

Generate radio playlists for you
Expand Down
9 changes: 9 additions & 0 deletions docs/book/src/playlist/playlist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Playlist

Those commands interact with playlists

## `convert`

Convert a playlist from one service to another

[Usage](../CommandLineHelp.md#alistral-playlist-convert)
15 changes: 12 additions & 3 deletions interzic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,29 @@ edition = "2021"
description = "Bunch of utilities to link musicbrainz data to different services"

[dependencies]

# --- MB ecosystem crates ---
#musicbrainz_rs = "0.9.0"
musicbrainz_rs = { branch = "develop", git = "https://github.com/RustyNova016/musicbrainz_rs.git", features = ["extras"]}

#TODO: Make musicbrainz-db-lite optional
musicbrainz-db-lite = { path = "../musicbrainz_db_lite" }
tuillez = { path = "../tuillez" }
#listenbrainz = "0.8.1"
listenbrainz = { branch = "alistral_version", git = "https://github.com/RustyNova016/listenbrainz-rs.git" }


# --- Service crates ---
google-youtube3 = "6.0.0"
musicbrainz_rs = "0.9.0"

# --- Other crates ---
regex = "1.11.1"
sqlx = "0.8.3"
thiserror = "2.0.0"
listenbrainz = "0.8.1"
governor = "0.8.0"
serde = "1.0.218"
serde_json = "1.0.139"
tracing-indicatif = "0.3.9"
tracing = "0.1.41"
futures = "0.3.31"
async-fn-stream = "0.2.2"
async-fn-stream = "0.2.2"
17 changes: 17 additions & 0 deletions interzic/src/models/playlist_stub.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::models::messy_recording::MessyRecording;
use crate::InterzicClient;

#[derive(Debug, Clone)]
pub struct PlaylistStub {
Expand All @@ -7,3 +8,19 @@ pub struct PlaylistStub {
pub recordings: Vec<MessyRecording>,
//TODO: #521 Allow setting playlist visibility
}

impl PlaylistStub {
pub async fn save_recordings(self, client: &InterzicClient) -> Result<Self, crate::Error> {
let mut saved_recordings = Vec::new();

for rec in self.recordings {
saved_recordings.push(rec.upsert(&client.database_client).await?);
}

Ok(Self {
title: self.title,
description: self.description,
recordings: saved_recordings,
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use crate::InterzicClient;

pub struct Listenbrainz;

pub mod playlists;

impl Listenbrainz {
pub async fn create_playlist(
client: &InterzicClient,
Expand Down Expand Up @@ -51,4 +53,14 @@ impl Listenbrainz {
},
}
}

pub fn import_playlist(
client: &InterzicClient,
playlist_id: &str,
) -> Result<PlaylistStub, crate::Error> {
Ok(client
.listenbrainz_client()?
.get_playlist(playlist_id)
.map(|res| PlaylistStub::from(res.playlist))?)
}
}
32 changes: 32 additions & 0 deletions interzic/src/models/services/listenbrainz/playlists.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use listenbrainz::raw::jspf;
use listenbrainz::raw::jspf::Track;
use musicbrainz_rs::utils::parse_mbid;

use crate::models::messy_recording::MessyRecording;
use crate::models::playlist_stub::PlaylistStub;

impl From<jspf::PlaylistInfo> for PlaylistStub {
fn from(value: jspf::PlaylistInfo) -> Self {
PlaylistStub {
title: value.title,
description: value.annotation.unwrap_or_default(),
recordings: value.track.into_iter().map(MessyRecording::from).collect(),
}
}
}

impl From<Track> for MessyRecording {
fn from(value: Track) -> Self {
Self {
id: 0,
artist_credits: value.creator,
title: value.title,
release: Some(value.album),
mbid: value
.identifier
.into_iter()
.filter_map(|s| parse_mbid(&s))
.next(),
}
}
}
8 changes: 4 additions & 4 deletions musicbrainz_db_lite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ rust-version = "1.82.0"
musicbrainz_db_lite_macros = { path = "./musicbrainz_db_lite_macros" }
musicbrainz_db_lite_schema = { path = "./musicbrainz_db_lite_schema" }

musicbrainz_rs_nova = { version = "0.9.0", package = "musicbrainz_rs" }
#musicbrainz_rs_nova = { branch = "develop", git = "https://github.com/RustyNova016/musicbrainz_rs.git", package = "musicbrainz_rs" }
#musicbrainz_rs_nova = { version = "0.9.0", package = "musicbrainz_rs" }
musicbrainz_rs_nova = { branch = "develop", git = "https://github.com/RustyNova016/musicbrainz_rs.git", package = "musicbrainz_rs" }
#musicbrainz_rs_nova = { path = "../musicbrainz_rs_nova" }

listenbrainz = "0.8.1"
#listenbrainz = { branch = "alistral_version", git = "https://github.com/RustyNova016/listenbrainz-rs.git" }
#listenbrainz = "0.8.1"
listenbrainz = { branch = "alistral_version", git = "https://github.com/RustyNova016/listenbrainz-rs.git" }

async-trait = "0.1.82"
chrono = "0.4.38"
Expand Down
6 changes: 6 additions & 0 deletions src/models/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use crate::tools::bumps::bump_command;
use crate::tools::bumps::bump_down_command;
use crate::tools::compatibility::compatibility_command;
use crate::tools::daily::daily_report;
use crate::tools::playlist::PlaylistCommand;
use crate::tools::stats::stats_command;

use super::config::Config;
Expand Down Expand Up @@ -156,6 +157,9 @@ pub enum Commands {
/// Commands for musicbrainz stuff
Musicbrainz(MusicbrainzCommand),

/// Interact with playlists
Playlist(PlaylistCommand),

/// Generate radio playlists for you
Radio(RadioCommand),

Expand Down Expand Up @@ -228,6 +232,8 @@ impl Commands {

Self::Musicbrainz(val) => val.run(conn).await,

Self::Playlist(val) => val.run(conn).await?,

Self::Bump(val) => bump_command(conn, val.clone()).await,

Self::BumpDown(val) => bump_down_command(conn, val.clone()).await,
Expand Down
1 change: 1 addition & 0 deletions src/tools/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod interzic;
pub mod listens;
pub mod lookup;
pub mod musicbrainz;
pub mod playlist;
pub mod radio;
pub mod stats;
pub mod unstable;
46 changes: 46 additions & 0 deletions src/tools/playlist/convert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use clap::Parser;
use interzic::models::services::listenbrainz::Listenbrainz;
use interzic::models::services::youtube::Youtube;
use tuillez::fatal_error::IntoFatal;

use crate::api::clients::ALISTRAL_CLIENT;
use crate::tools::playlist::PlaylistOrigin;
use crate::tools::playlist::PlaylistTarget;

#[derive(Parser, Debug, Clone)]
pub struct PlaylistConvertCommand {
/// Get the playlist from which service?
pub source: PlaylistOrigin,

/// The id of the playlist on the external service
pub id: String,

/// Convert to this service
pub target: PlaylistTarget,
}

impl PlaylistConvertCommand {
pub async fn run(&self, _conn: &mut sqlx::SqliteConnection) -> Result<(), crate::Error> {
let playlist = match self.source {
PlaylistOrigin::Listenbrainz => {
Listenbrainz::import_playlist(&ALISTRAL_CLIENT.interzic, &self.id)
.expect_fatal("Couldn't retrieve the playlist. Check for typos.")?
}
};

let playlist = playlist
.save_recordings(&ALISTRAL_CLIENT.interzic)
.await
.expect_fatal("Couldn't save the playlist's recording")?;

match self.target {
PlaylistTarget::Youtube => {
Youtube::create_playlist(&ALISTRAL_CLIENT.interzic, playlist)
.await
.expect_fatal("Couldn't send the playlist to youtube")?;
}
}

Ok(())
}
}
Loading

0 comments on commit 5d6fe59

Please sign in to comment.