diff --git a/src/bin/musichoard-edit.rs b/src/bin/musichoard-edit.rs index 58fc8f8..7c180e7 100644 --- a/src/bin/musichoard-edit.rs +++ b/src/bin/musichoard-edit.rs @@ -44,6 +44,8 @@ enum ArtistCommand { Add(ArtistValue), #[structopt(about = "Remove an artist from the collection")] Remove(ArtistValue), + #[structopt(about = "Edit the artist's sort name")] + Sort(SortCommand), #[structopt(name = "musicbrainz", about = "Edit the MusicBrainz URL of an artist")] MusicBrainz(UrlCommand), #[structopt( @@ -57,12 +59,28 @@ enum ArtistCommand { Qobuz(UrlCommand), } +#[derive(StructOpt, Debug)] +enum SortCommand { + #[structopt(help = "Set the provided name as the artist's sort name")] + Set(ArtistSortValue), + #[structopt(help = "Clear the artist's sort name")] + Clear(ArtistValue), +} + #[derive(StructOpt, Debug)] struct ArtistValue { #[structopt(help = "The name of the artist")] artist: String, } +#[derive(StructOpt, Debug)] +struct ArtistSortValue { + #[structopt(help = "The name of the artist")] + artist: String, + #[structopt(help = "The sort name of the artist")] + sort: String, +} + #[derive(StructOpt, Debug)] enum UrlCommand { #[structopt(about = "Add the provided URL(s) without overwriting existing values")] @@ -137,6 +155,9 @@ impl ArtistCommand { ArtistCommand::Remove(artist_value) => { music_hoard.remove_artist(ArtistId::new(artist_value.artist)); } + ArtistCommand::Sort(sort_command) => { + sort_command.handle(music_hoard); + } ArtistCommand::MusicBrainz(url_command) => { single_url_command_dispatch!(url_command, music_hoard, musicbrainz) } @@ -153,6 +174,22 @@ impl ArtistCommand { } } +impl SortCommand { + fn handle(self, music_hoard: &mut MH) { + match self { + SortCommand::Set(artist_sort_value) => music_hoard + .set_artist_sort( + ArtistId::new(artist_sort_value.artist), + ArtistId::new(artist_sort_value.sort), + ) + .expect("faild to set artist sort name"), + SortCommand::Clear(artist_value) => music_hoard + .clear_artist_sort(ArtistId::new(artist_value.artist)) + .expect("failed to clear artist sort name"), + } + } +} + fn main() { let opt = Opt::from_args(); diff --git a/src/database/json/mod.rs b/src/database/json/mod.rs index 5acce3c..08b8441 100644 --- a/src/database/json/mod.rs +++ b/src/database/json/mod.rs @@ -225,9 +225,7 @@ mod tests { fn save_errors() { let mut object = HashMap::::new(); object.insert( - ArtistId { - name: String::from("artist"), - }, + ArtistId::new(String::from("artist")), String::from("string"), ); let serde_err = serde_json::to_string(&object); diff --git a/src/lib.rs b/src/lib.rs index 535fa40..6cf7fef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -380,6 +380,7 @@ impl Merge for ArtistProperties { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] pub struct Artist { pub id: ArtistId, + pub sort: Option, pub properties: ArtistProperties, pub albums: Vec, } @@ -433,11 +434,33 @@ impl Artist { pub fn new>(id: ID) -> Self { Artist { id: id.into(), + sort: None, properties: ArtistProperties::default(), albums: vec![], } } + pub fn new_with_sort, SORT: Into>(id: ID, sort: SORT) -> Self { + Artist { + id: id.into(), + sort: Some(sort.into()), + properties: ArtistProperties::default(), + albums: vec![], + } + } + + fn get_sort_key(&self) -> &ArtistId { + self.sort.as_ref().unwrap_or(&self.id) + } + + fn set_sort_key>(&mut self, sort: SORT) { + self.sort = Some(sort.into()); + } + + fn clear_sort_key(&mut self) { + _ = self.sort.take(); + } + fn add_unique_url, T: for<'a> TryFrom<&'a str, Error = Error> + Eq + Display>( container: &mut Option, url: S, @@ -552,7 +575,7 @@ impl PartialOrd for Artist { impl Ord for Artist { fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.id.cmp(&other.id) + self.get_sort_key().cmp(other.get_sort_key()) } } @@ -823,6 +846,21 @@ impl MusicHoard { } } + pub fn set_artist_sort, SORT: Into>( + &mut self, + artist_id: ID, + artist_sort: SORT, + ) -> Result<(), Error> { + self.get_artist_or_err(artist_id.as_ref())? + .set_sort_key(artist_sort); + Ok(()) + } + + pub fn clear_artist_sort>(&mut self, artist_id: ID) -> Result<(), Error> { + self.get_artist_or_err(artist_id.as_ref())?.clear_sort_key(); + Ok(()) + } + music_hoard_unique_url_dispatch!(musicbrainz); music_hoard_multi_url_dispatch!(musicbutler); @@ -845,7 +883,7 @@ impl MusicHoard { MergeSorted::new(primary.into_iter(), secondary.into_iter()).collect() } - fn items_to_artists(items: Vec) -> Vec { + fn items_to_artists(items: Vec) -> Result, Error> { let mut artists: Vec = vec![]; let mut album_ids = HashMap::>::new(); @@ -854,6 +892,8 @@ impl MusicHoard { name: item.album_artist, }; + let artist_sort = item.album_artist_sort.map(|s| ArtistId { name: s }); + let album_id = AlbumId { year: item.album_year, title: item.album_title, @@ -886,6 +926,21 @@ impl MusicHoard { artists.last_mut().unwrap() }; + if artist.sort.is_some() { + if artist_sort.is_some() && (artist.sort != artist_sort) { + return Err(Error::CollectionError(format!( + "multiple album_artist_sort found for artist '{}': '{}' != '{}'", + artist.id, + artist.sort.as_ref().unwrap(), + artist_sort.as_ref().unwrap() + ))); + } + } else { + if artist_sort.is_some() { + artist.sort = artist_sort; + } + } + if album_ids[&artist_id].contains(&album_id) { // Assume results are in some order which means they will likely be grouped by // album. Therefore, we look from the back since the last inserted album is most @@ -909,7 +964,7 @@ impl MusicHoard { } } - artists + Ok(artists) } fn get_artist(&mut self, artist_id: &ArtistId) -> Option<&mut Artist> { @@ -927,7 +982,7 @@ impl MusicHoard { /// Rescan the library and merge with the in-memory collection. pub fn rescan_library(&mut self) -> Result<(), Error> { let items = self.library.list(&Query::new())?; - let mut library_collection = Self::items_to_artists(items); + let mut library_collection = Self::items_to_artists(items)?; Self::sort(&mut library_collection); let collection = mem::take(&mut self.collection); @@ -1036,6 +1091,7 @@ mod tests { for track in album.tracks.iter() { items.push(Item { album_artist: artist.id.name.clone(), + album_artist_sort: artist.sort.as_ref().map(|s| s.name.clone()), album_year: album.id.year, album_title: album.id.title.clone(), track_number: track.id.number, diff --git a/src/library/beets/mod.rs b/src/library/beets/mod.rs index 9253016..699c630 100644 --- a/src/library/beets/mod.rs +++ b/src/library/beets/mod.rs @@ -22,6 +22,8 @@ const LIST_FORMAT_ARG: &str = concat!( "--format=", "$albumartist", list_format_separator!(), + "$albumartist_sort", + list_format_separator!(), "$year", list_format_separator!(), "$album", @@ -52,6 +54,7 @@ impl ToBeetsArg for Field { let negate = if include { "" } else { "^" }; match self { Field::AlbumArtist(ref s) => format!("{negate}albumartist:{s}"), + Field::AlbumArtistSort(ref s) => format!("{negate}albumartist_sort:{s}"), Field::AlbumYear(ref u) => format!("{negate}year:{u}"), Field::AlbumTitle(ref s) => format!("{negate}album:{s}"), Field::TrackNumber(ref u) => format!("{negate}track:{u}"), @@ -128,24 +131,30 @@ impl ILibraryPrivate for BeetsLibrary { } let album_artist = split[0].to_string(); - let album_year = split[1].parse::()?; - let album_title = split[2].to_string(); - let track_number = split[3].parse::()?; - let track_title = split[4].to_string(); - let track_artist = split[5] + let album_artist_sort = if !split[1].is_empty() { + Some(split[1].to_string()) + } else { + None + }; + let album_year = split[2].parse::()?; + let album_title = split[3].to_string(); + let track_number = split[4].parse::()?; + let track_title = split[5].to_string(); + let track_artist = split[6] .to_string() .split("; ") .map(|s| s.to_owned()) .collect(); - let track_format = match split[6].to_string().as_str() { + let track_format = match split[7].to_string().as_str() { TRACK_FORMAT_FLAC => Format::Flac, TRACK_FORMAT_MP3 => Format::Mp3, _ => return Err(Error::Invalid(line.to_string())), }; - let track_bitrate = split[7].trim_end_matches("kbps").parse::()?; + let track_bitrate = split[8].trim_end_matches("kbps").parse::()?; items.push(Item { album_artist, + album_artist_sort, album_year, album_title, track_number, @@ -170,10 +179,15 @@ mod tests { fn item_to_beets_string(item: &Item) -> String { format!( - "{album_artist}{sep}{album_year}{sep}{album_title}{sep}\ + "{album_artist}{sep}{album_artist_sort}{sep}\ + {album_year}{sep}{album_title}{sep}\ {track_number}{sep}{track_title}{sep}\ {track_artist}{sep}{track_format}{sep}{track_bitrate}kbps", album_artist = item.album_artist, + album_artist_sort = match item.album_artist_sort { + Some(ref album_artist_sort) => album_artist_sort, + None => "", + }, album_year = item.album_year, album_title = item.album_title, track_number = item.track_number, diff --git a/src/library/mod.rs b/src/library/mod.rs index bc476ed..53b53d3 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -30,6 +30,7 @@ impl ILibrary for NullLibrary { #[derive(Debug, PartialEq, Eq, Hash)] pub struct Item { pub album_artist: String, + pub album_artist_sort: Option, pub album_year: u32, pub album_title: String, pub track_number: u32, @@ -43,6 +44,7 @@ pub struct Item { #[derive(Debug, Hash, PartialEq, Eq)] pub enum Field { AlbumArtist(String), + AlbumArtistSort(String), AlbumYear(u32), AlbumTitle(String), TrackNumber(u32), diff --git a/src/testlib.rs b/src/testlib.rs index 0a4c306..f1de5d0 100644 --- a/src/testlib.rs +++ b/src/testlib.rs @@ -5,6 +5,7 @@ macro_rules! collection { id: ArtistId { name: "album_artist a".to_string(), }, + sort: None, properties: ArtistProperties { musicbrainz: Some(MusicBrainz::new( "https://musicbrainz.org/artist/00000000-0000-0000-0000-000000000000", @@ -100,6 +101,7 @@ macro_rules! collection { id: ArtistId { name: "album_artist b".to_string(), }, + sort: None, properties: ArtistProperties { musicbrainz: Some(MusicBrainz::new( "https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111", @@ -192,6 +194,7 @@ macro_rules! collection { id: ArtistId { name: "album_artist c".to_string(), }, + sort: None, properties: ArtistProperties { musicbrainz: Some(MusicBrainz::new( "https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111", diff --git a/tests/files/database/database.json b/tests/files/database/database.json index b633728..7647746 100644 --- a/tests/files/database/database.json +++ b/tests/files/database/database.json @@ -1 +1 @@ -[{"id":{"name":"Eluveitie"},"properties":{"musicbrainz":"https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38","musicbutler":["https://www.musicbutler.io/artist-page/269358403"],"bandcamp":[],"qobuz":"https://www.qobuz.com/nl-nl/interpreter/eluveitie/download-streaming-albums"},"albums":[{"id":{"year":2004,"title":"Vên [re‐recorded]"},"tracks":[{"id":{"number":1,"title":"Verja Urit an Bitus"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":961}},{"id":{"number":2,"title":"Uis Elveti"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1067}},{"id":{"number":3,"title":"Ôrô"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":933}},{"id":{"number":4,"title":"Lament"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1083}},{"id":{"number":5,"title":"Druid"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1073}},{"id":{"number":6,"title":"Jêzaïg"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1002}}]},{"id":{"year":2008,"title":"Slania"},"tracks":[{"id":{"number":1,"title":"Samon"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":953}},{"id":{"number":2,"title":"Primordial Breath"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1103}},{"id":{"number":3,"title":"Inis Mona"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1117}},{"id":{"number":4,"title":"Gray Sublime Archon"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1092}},{"id":{"number":5,"title":"Anagantios"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":923}},{"id":{"number":6,"title":"Bloodstained Ground"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1098}},{"id":{"number":7,"title":"The Somber Lay"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1068}},{"id":{"number":8,"title":"Slanias Song"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1098}},{"id":{"number":9,"title":"Giamonios"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":825}},{"id":{"number":10,"title":"Tarvos"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1115}},{"id":{"number":11,"title":"Calling the Rain"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1096}},{"id":{"number":12,"title":"Elembivos"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1059}}]}]},{"id":{"name":"Frontside"},"properties":{"musicbrainz":"https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490","musicbutler":["https://www.musicbutler.io/artist-page/826588800"],"bandcamp":[],"qobuz":"https://www.qobuz.com/nl-nl/interpreter/frontside/download-streaming-albums"},"albums":[{"id":{"year":2001,"title":"…nasze jest królestwo, potęga i chwała na wieki…"},"tracks":[{"id":{"number":1,"title":"Intro = Chaos"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":1024}},{"id":{"number":2,"title":"Modlitwa"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":1073}},{"id":{"number":3,"title":"Długa droga z piekła"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":1058}},{"id":{"number":4,"title":"Synowie ognia"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":1066}},{"id":{"number":5,"title":"1902"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":1074}},{"id":{"number":6,"title":"Krew za krew"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":1080}},{"id":{"number":7,"title":"Kulminacja"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":992}},{"id":{"number":8,"title":"Judasz"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":1018}},{"id":{"number":9,"title":"Więzy"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":1077}},{"id":{"number":10,"title":"Zagubione dusze"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":1033}},{"id":{"number":11,"title":"Linia życia"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":987}}]}]},{"id":{"name":"Heaven’s Basement"},"properties":{"musicbrainz":"https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc","musicbutler":["https://www.musicbutler.io/artist-page/291158685"],"bandcamp":[],"qobuz":"https://www.qobuz.com/nl-nl/interpreter/heaven-s-basement/download-streaming-albums"},"albums":[{"id":{"year":2011,"title":"Paper Plague"},"tracks":[{"id":{"number":0,"title":"Paper Plague"},"artist":["Heaven’s Basement"],"quality":{"format":"Mp3","bitrate":320}}]},{"id":{"year":2011,"title":"Unbreakable"},"tracks":[{"id":{"number":1,"title":"Unbreakable"},"artist":["Heaven’s Basement"],"quality":{"format":"Mp3","bitrate":208}},{"id":{"number":2,"title":"Guilt Trips and Sins"},"artist":["Heaven’s Basement"],"quality":{"format":"Mp3","bitrate":205}},{"id":{"number":3,"title":"The Long Goodbye"},"artist":["Heaven’s Basement"],"quality":{"format":"Mp3","bitrate":227}},{"id":{"number":4,"title":"Close Encounters"},"artist":["Heaven’s Basement"],"quality":{"format":"Mp3","bitrate":213}},{"id":{"number":5,"title":"Paranoia"},"artist":["Heaven’s Basement"],"quality":{"format":"Mp3","bitrate":218}},{"id":{"number":6,"title":"Let Me Out of Here"},"artist":["Heaven’s Basement"],"quality":{"format":"Mp3","bitrate":207}},{"id":{"number":7,"title":"Leeches"},"artist":["Heaven’s Basement"],"quality":{"format":"Mp3","bitrate":225}}]}]},{"id":{"name":"Metallica"},"properties":{"musicbrainz":"https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab","musicbutler":["https://www.musicbutler.io/artist-page/3996865"],"bandcamp":[],"qobuz":"https://www.qobuz.com/nl-nl/interpreter/metallica/download-streaming-albums"},"albums":[{"id":{"year":1984,"title":"Ride the Lightning"},"tracks":[{"id":{"number":1,"title":"Fight Fire with Fire"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":954}},{"id":{"number":2,"title":"Ride the Lightning"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":951}},{"id":{"number":3,"title":"For Whom the Bell Tolls"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":889}},{"id":{"number":4,"title":"Fade to Black"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":939}},{"id":{"number":5,"title":"Trapped under Ice"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":955}},{"id":{"number":6,"title":"Escape"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":941}},{"id":{"number":7,"title":"Creeping Death"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":958}},{"id":{"number":8,"title":"The Call of Ktulu"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":888}}]},{"id":{"year":1999,"title":"S&M"},"tracks":[{"id":{"number":1,"title":"The Ecstasy of Gold"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":875}},{"id":{"number":2,"title":"The Call of Ktulu"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1030}},{"id":{"number":3,"title":"Master of Puppets"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1082}},{"id":{"number":4,"title":"Of Wolf and Man"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1115}},{"id":{"number":5,"title":"The Thing That Should Not Be"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1029}},{"id":{"number":6,"title":"Fuel"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1057}},{"id":{"number":7,"title":"The Memory Remains"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1080}},{"id":{"number":8,"title":"No Leaf Clover"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1004}},{"id":{"number":9,"title":"Hero of the Day"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":962}},{"id":{"number":10,"title":"Devil’s Dance"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1076}},{"id":{"number":11,"title":"Bleeding Me"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":993}},{"id":{"number":12,"title":"Nothing Else Matters"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":875}},{"id":{"number":13,"title":"Until It Sleeps"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1038}},{"id":{"number":14,"title":"For Whom the Bell Tolls"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1072}},{"id":{"number":15,"title":"−Human"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1029}},{"id":{"number":16,"title":"Wherever I May Roam"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1035}},{"id":{"number":17,"title":"Outlaw Torn"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1042}},{"id":{"number":18,"title":"Sad but True"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1082}},{"id":{"number":19,"title":"One"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1017}},{"id":{"number":20,"title":"Enter Sandman"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":993}},{"id":{"number":21,"title":"Battery"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":967}}]}]},{"id":{"name":"Аркона"},"properties":{"musicbrainz":"https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212","musicbutler":["https://www.musicbutler.io/artist-page/283448581"],"bandcamp":["https://arkonamoscow.bandcamp.com/"],"qobuz":"https://www.qobuz.com/nl-nl/interpreter/arkona/download-streaming-albums"},"albums":[{"id":{"year":2011,"title":"Slovo"},"tracks":[{"id":{"number":1,"title":"Az’"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":992}},{"id":{"number":2,"title":"Arkaim"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":1061}},{"id":{"number":3,"title":"Bol’no mne"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":1004}},{"id":{"number":4,"title":"Leshiy"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":1077}},{"id":{"number":5,"title":"Zakliatie"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":1041}},{"id":{"number":6,"title":"Predok"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":756}},{"id":{"number":7,"title":"Nikogda"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":1059}},{"id":{"number":8,"title":"Tam za tumanami"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":1023}},{"id":{"number":9,"title":"Potomok"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":838}},{"id":{"number":10,"title":"Slovo"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":1028}},{"id":{"number":11,"title":"Odna"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":991}},{"id":{"number":12,"title":"Vo moiom sadochke…"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":919}},{"id":{"number":13,"title":"Stenka na stenku"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":1039}},{"id":{"number":14,"title":"Zimushka"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":974}}]}]}] \ No newline at end of file +[{"id":{"name":"Аркона"},"sort":{"name":"Arkona"},"properties":{"musicbrainz":"https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212","musicbutler":["https://www.musicbutler.io/artist-page/283448581"],"bandcamp":["https://arkonamoscow.bandcamp.com/"],"qobuz":"https://www.qobuz.com/nl-nl/interpreter/arkona/download-streaming-albums"},"albums":[{"id":{"year":2011,"title":"Slovo"},"tracks":[{"id":{"number":1,"title":"Az’"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":992}},{"id":{"number":2,"title":"Arkaim"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":1061}},{"id":{"number":3,"title":"Bol’no mne"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":1004}},{"id":{"number":4,"title":"Leshiy"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":1077}},{"id":{"number":5,"title":"Zakliatie"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":1041}},{"id":{"number":6,"title":"Predok"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":756}},{"id":{"number":7,"title":"Nikogda"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":1059}},{"id":{"number":8,"title":"Tam za tumanami"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":1023}},{"id":{"number":9,"title":"Potomok"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":838}},{"id":{"number":10,"title":"Slovo"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":1028}},{"id":{"number":11,"title":"Odna"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":991}},{"id":{"number":12,"title":"Vo moiom sadochke…"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":919}},{"id":{"number":13,"title":"Stenka na stenku"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":1039}},{"id":{"number":14,"title":"Zimushka"},"artist":["Аркона"],"quality":{"format":"Flac","bitrate":974}}]}]},{"id":{"name":"Eluveitie"},"sort":null,"properties":{"musicbrainz":"https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38","musicbutler":["https://www.musicbutler.io/artist-page/269358403"],"bandcamp":[],"qobuz":"https://www.qobuz.com/nl-nl/interpreter/eluveitie/download-streaming-albums"},"albums":[{"id":{"year":2004,"title":"Vên [re‐recorded]"},"tracks":[{"id":{"number":1,"title":"Verja Urit an Bitus"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":961}},{"id":{"number":2,"title":"Uis Elveti"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1067}},{"id":{"number":3,"title":"Ôrô"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":933}},{"id":{"number":4,"title":"Lament"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1083}},{"id":{"number":5,"title":"Druid"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1073}},{"id":{"number":6,"title":"Jêzaïg"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1002}}]},{"id":{"year":2008,"title":"Slania"},"tracks":[{"id":{"number":1,"title":"Samon"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":953}},{"id":{"number":2,"title":"Primordial Breath"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1103}},{"id":{"number":3,"title":"Inis Mona"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1117}},{"id":{"number":4,"title":"Gray Sublime Archon"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1092}},{"id":{"number":5,"title":"Anagantios"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":923}},{"id":{"number":6,"title":"Bloodstained Ground"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1098}},{"id":{"number":7,"title":"The Somber Lay"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1068}},{"id":{"number":8,"title":"Slanias Song"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1098}},{"id":{"number":9,"title":"Giamonios"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":825}},{"id":{"number":10,"title":"Tarvos"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1115}},{"id":{"number":11,"title":"Calling the Rain"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1096}},{"id":{"number":12,"title":"Elembivos"},"artist":["Eluveitie"],"quality":{"format":"Flac","bitrate":1059}}]}]},{"id":{"name":"Frontside"},"sort":null,"properties":{"musicbrainz":"https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490","musicbutler":["https://www.musicbutler.io/artist-page/826588800"],"bandcamp":[],"qobuz":"https://www.qobuz.com/nl-nl/interpreter/frontside/download-streaming-albums"},"albums":[{"id":{"year":2001,"title":"…nasze jest królestwo, potęga i chwała na wieki…"},"tracks":[{"id":{"number":1,"title":"Intro = Chaos"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":1024}},{"id":{"number":2,"title":"Modlitwa"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":1073}},{"id":{"number":3,"title":"Długa droga z piekła"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":1058}},{"id":{"number":4,"title":"Synowie ognia"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":1066}},{"id":{"number":5,"title":"1902"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":1074}},{"id":{"number":6,"title":"Krew za krew"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":1080}},{"id":{"number":7,"title":"Kulminacja"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":992}},{"id":{"number":8,"title":"Judasz"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":1018}},{"id":{"number":9,"title":"Więzy"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":1077}},{"id":{"number":10,"title":"Zagubione dusze"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":1033}},{"id":{"number":11,"title":"Linia życia"},"artist":["Frontside"],"quality":{"format":"Flac","bitrate":987}}]}]},{"id":{"name":"Heaven’s Basement"},"sort":null,"properties":{"musicbrainz":"https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc","musicbutler":["https://www.musicbutler.io/artist-page/291158685"],"bandcamp":[],"qobuz":"https://www.qobuz.com/nl-nl/interpreter/heaven-s-basement/download-streaming-albums"},"albums":[{"id":{"year":2011,"title":"Paper Plague"},"tracks":[{"id":{"number":0,"title":"Paper Plague"},"artist":["Heaven’s Basement"],"quality":{"format":"Mp3","bitrate":320}}]},{"id":{"year":2011,"title":"Unbreakable"},"tracks":[{"id":{"number":1,"title":"Unbreakable"},"artist":["Heaven’s Basement"],"quality":{"format":"Mp3","bitrate":208}},{"id":{"number":2,"title":"Guilt Trips and Sins"},"artist":["Heaven’s Basement"],"quality":{"format":"Mp3","bitrate":205}},{"id":{"number":3,"title":"The Long Goodbye"},"artist":["Heaven’s Basement"],"quality":{"format":"Mp3","bitrate":227}},{"id":{"number":4,"title":"Close Encounters"},"artist":["Heaven’s Basement"],"quality":{"format":"Mp3","bitrate":213}},{"id":{"number":5,"title":"Paranoia"},"artist":["Heaven’s Basement"],"quality":{"format":"Mp3","bitrate":218}},{"id":{"number":6,"title":"Let Me Out of Here"},"artist":["Heaven’s Basement"],"quality":{"format":"Mp3","bitrate":207}},{"id":{"number":7,"title":"Leeches"},"artist":["Heaven’s Basement"],"quality":{"format":"Mp3","bitrate":225}}]}]},{"id":{"name":"Metallica"},"sort":null,"properties":{"musicbrainz":"https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab","musicbutler":["https://www.musicbutler.io/artist-page/3996865"],"bandcamp":[],"qobuz":"https://www.qobuz.com/nl-nl/interpreter/metallica/download-streaming-albums"},"albums":[{"id":{"year":1984,"title":"Ride the Lightning"},"tracks":[{"id":{"number":1,"title":"Fight Fire with Fire"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":954}},{"id":{"number":2,"title":"Ride the Lightning"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":951}},{"id":{"number":3,"title":"For Whom the Bell Tolls"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":889}},{"id":{"number":4,"title":"Fade to Black"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":939}},{"id":{"number":5,"title":"Trapped under Ice"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":955}},{"id":{"number":6,"title":"Escape"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":941}},{"id":{"number":7,"title":"Creeping Death"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":958}},{"id":{"number":8,"title":"The Call of Ktulu"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":888}}]},{"id":{"year":1999,"title":"S&M"},"tracks":[{"id":{"number":1,"title":"The Ecstasy of Gold"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":875}},{"id":{"number":2,"title":"The Call of Ktulu"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1030}},{"id":{"number":3,"title":"Master of Puppets"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1082}},{"id":{"number":4,"title":"Of Wolf and Man"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1115}},{"id":{"number":5,"title":"The Thing That Should Not Be"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1029}},{"id":{"number":6,"title":"Fuel"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1057}},{"id":{"number":7,"title":"The Memory Remains"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1080}},{"id":{"number":8,"title":"No Leaf Clover"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1004}},{"id":{"number":9,"title":"Hero of the Day"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":962}},{"id":{"number":10,"title":"Devil’s Dance"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1076}},{"id":{"number":11,"title":"Bleeding Me"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":993}},{"id":{"number":12,"title":"Nothing Else Matters"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":875}},{"id":{"number":13,"title":"Until It Sleeps"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1038}},{"id":{"number":14,"title":"For Whom the Bell Tolls"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1072}},{"id":{"number":15,"title":"−Human"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1029}},{"id":{"number":16,"title":"Wherever I May Roam"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1035}},{"id":{"number":17,"title":"Outlaw Torn"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1042}},{"id":{"number":18,"title":"Sad but True"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1082}},{"id":{"number":19,"title":"One"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":1017}},{"id":{"number":20,"title":"Enter Sandman"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":993}},{"id":{"number":21,"title":"Battery"},"artist":["Metallica"],"quality":{"format":"Flac","bitrate":967}}]}]}] \ No newline at end of file diff --git a/tests/library/beets.rs b/tests/library/beets.rs index 96d1889..e216b75 100644 --- a/tests/library/beets.rs +++ b/tests/library/beets.rs @@ -41,6 +41,7 @@ fn artist_to_items(artist: &Artist) -> Vec { for track in album.tracks.iter() { items.push(Item { album_artist: artist.id.name.clone(), + album_artist_sort: artist.sort.as_ref().map(|s| s.name.clone()), album_year: album.id.year, album_title: album.id.title.clone(), track_number: track.id.number, diff --git a/tests/testlib.rs b/tests/testlib.rs index 83de7c9..1a1036f 100644 --- a/tests/testlib.rs +++ b/tests/testlib.rs @@ -10,6 +10,7 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { id: ArtistId { name: String::from("Eluveitie"), }, + sort: None, properties: ArtistProperties { musicbrainz: Some(MusicBrainz::new( "https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38", @@ -243,6 +244,7 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { id: ArtistId { name: String::from("Frontside"), }, + sort: None, properties: ArtistProperties { musicbrainz: Some(MusicBrainz::new( "https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490", @@ -389,6 +391,7 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { id: ArtistId { name: String::from("Heaven’s Basement"), }, + sort: None, properties: ArtistProperties { musicbrainz: Some(MusicBrainz::new( "https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc", @@ -509,6 +512,7 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { id: ArtistId { name: String::from("Metallica"), }, + sort: None, properties: ArtistProperties { musicbrainz: Some(MusicBrainz::new( "https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab", @@ -863,6 +867,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { id: ArtistId { name: String::from("Аркона"), }, + sort: Some(ArtistId{ + name: String::from("Arkona") + }), properties: ArtistProperties { musicbrainz: Some(MusicBrainz::new( "https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212",