Artist schema iteration

This commit is contained in:
Antoine Gersant 2024-09-08 13:46:47 -07:00
parent ae876915b4
commit e65cee366d
3 changed files with 68 additions and 91 deletions
src
app/index
server/dto

View file

@ -28,12 +28,8 @@ pub struct ArtistHeader {
#[derive(Debug, Default, PartialEq, Eq)]
pub struct Artist {
pub name: String,
pub albums_as_performer: Vec<Album>,
pub albums_as_additional_performer: Vec<Album>, // Albums where this artist shows up as `artist` without being `album_artist`
pub albums_as_composer: Vec<Album>,
pub albums_as_lyricist: Vec<Album>,
pub num_songs_by_genre: HashMap<String, u32>,
pub header: ArtistHeader,
pub albums: Vec<Album>,
}
#[derive(Debug, Default, PartialEq, Eq)]
@ -88,37 +84,18 @@ impl Collection {
}
pub fn get_artist(&self, strings: &RodeoReader, artist_key: ArtistKey) -> Option<Artist> {
self.artists.get(&artist_key).map(|a| {
let sort_albums = |a: &Album, b: &Album| (&a.year, &a.name).cmp(&(&b.year, &b.name));
let list_albums = |keys: &HashSet<AlbumKey>| {
let mut albums = keys
self.artists.get(&artist_key).map(|artist| {
let header = make_artist_header(artist, strings);
let albums = {
let mut albums = artist
.all_albums
.iter()
.filter_map(|key| self.get_album(strings, key.clone()))
.collect::<Vec<_>>();
albums.sort_by(sort_albums);
albums.sort_by(|a, b| (&a.year, &a.name).cmp(&(&b.year, &b.name)));
albums
};
let name = strings.resolve(&a.name).to_owned();
let albums_as_performer = list_albums(&a.albums_as_performer);
let albums_as_additional_performer = list_albums(&a.albums_as_additional_performer);
let albums_as_composer = list_albums(&a.albums_as_composer);
let albums_as_lyricist = list_albums(&a.albums_as_lyricist);
let num_songs_by_genre = a
.num_songs_by_genre
.iter()
.map(|(genre, num)| (strings.resolve(genre).to_string(), *num))
.collect();
Artist {
name,
albums_as_performer,
albums_as_additional_performer,
albums_as_composer,
albums_as_lyricist,
num_songs_by_genre,
}
Artist { header, albums }
})
}
@ -288,6 +265,9 @@ impl Builder {
for name in all_artists {
let artist = self.get_or_create_artist(name);
artist.num_songs += 1;
if let Some(album_key) = &album_key {
artist.all_albums.insert(album_key.clone());
}
for genre in &song.genres {
*artist
.num_songs_by_genre
@ -304,6 +284,7 @@ impl Builder {
.entry(artist_key)
.or_insert_with(|| storage::Artist {
name,
all_albums: HashSet::new(),
albums_as_performer: HashSet::new(),
albums_as_additional_performer: HashSet::new(),
albums_as_composer: HashSet::new(),
@ -550,10 +531,7 @@ mod test {
artists: Vec<String>,
composers: Vec<String>,
lyricists: Vec<String>,
expect_performer: bool,
expect_additional_performer: bool,
expect_composer: bool,
expect_lyricist: bool,
expect_listed: bool,
}
let test_cases = vec![
@ -563,43 +541,47 @@ mod test {
artists: vec![artist_name.to_string()],
composers: vec![artist_name.to_string()],
lyricists: vec![artist_name.to_string()],
expect_performer: true,
expect_composer: true,
expect_lyricist: true,
expect_listed: true,
..Default::default()
},
// Only tagged as artist
TestCase {
artists: vec![artist_name.to_string()],
expect_performer: true,
expect_listed: true,
..Default::default()
},
// Only tagged as artist w/ distinct album artist
TestCase {
album_artists: vec![other_artist_name.to_string()],
artists: vec![artist_name.to_string()],
expect_additional_performer: true,
expect_listed: true,
..Default::default()
},
// Tagged as artist and within album artists
TestCase {
album_artists: vec![artist_name.to_string(), other_artist_name.to_string()],
artists: vec![artist_name.to_string()],
expect_performer: true,
expect_listed: true,
..Default::default()
},
// Only tagged as composer
TestCase {
artists: vec![other_artist_name.to_string()],
composers: vec![artist_name.to_string()],
expect_composer: true,
expect_listed: true,
..Default::default()
},
// Only tagged as lyricist
TestCase {
artists: vec![other_artist_name.to_string()],
lyricists: vec![artist_name.to_string()],
expect_lyricist: true,
expect_listed: true,
..Default::default()
},
// Not tagged as lyricist
TestCase {
artists: vec![other_artist_name.to_string()],
expect_listed: false,
..Default::default()
},
];
@ -615,38 +597,12 @@ mod test {
..Default::default()
}]));
let artist_key = ArtistKey {
name: strings.get(artist_name),
};
let artist = collection.get_artist(&strings, artist_key).unwrap();
let artists = collection.get_artists(&strings);
let names = |a: &Vec<Album>| a.iter().map(|a| a.name.to_owned()).collect::<Vec<_>>();
if test.expect_performer {
assert_eq!(names(&artist.albums_as_performer), vec![album_name]);
if test.expect_listed {
assert!(artists.iter().any(|a| a.name == UniCase::new(artist_name)));
} else {
assert!(names(&artist.albums_as_performer).is_empty());
}
if test.expect_additional_performer {
assert_eq!(
names(&artist.albums_as_additional_performer),
vec![album_name]
);
} else {
assert!(names(&artist.albums_as_additional_performer).is_empty());
}
if test.expect_composer {
assert_eq!(names(&artist.albums_as_composer), vec![album_name]);
} else {
assert!(names(&artist.albums_as_composer).is_empty());
}
if test.expect_lyricist {
assert_eq!(names(&artist.albums_as_lyricist), vec![album_name]);
} else {
assert!(names(&artist.albums_as_lyricist).is_empty());
assert!(artists.iter().all(|a| a.name != UniCase::new(artist_name)));
}
}
}
@ -689,7 +645,7 @@ mod test {
let names = artist
.unwrap()
.albums_as_performer
.albums
.into_iter()
.map(|a| a.name)
.collect::<Vec<_>>();

View file

@ -19,6 +19,7 @@ pub enum File {
#[derive(Serialize, Deserialize)]
pub struct Artist {
pub name: Spur,
pub all_albums: HashSet<AlbumKey>,
pub albums_as_performer: HashSet<AlbumKey>,
pub albums_as_additional_performer: HashSet<AlbumKey>,
pub albums_as_composer: HashSet<AlbumKey>,

View file

@ -344,24 +344,44 @@ impl From<index::ArtistHeader> for ArtistHeader {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Artist {
pub name: String,
pub albums_as_performer: Vec<AlbumHeader>,
pub albums_as_additional_performer: Vec<AlbumHeader>,
pub albums_as_composer: Vec<AlbumHeader>,
pub albums_as_lyricist: Vec<AlbumHeader>,
pub num_songs_by_genre: HashMap<String, u32>,
#[serde(flatten)]
pub header: ArtistHeader,
pub albums: Vec<ArtistAlbum>,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ArtistAlbum {
#[serde(flatten)]
pub album: AlbumHeader,
pub contributions: Vec<Contribution>,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Contribution {
pub performer: bool,
pub composer: bool,
pub lyricist: bool,
}
impl From<index::Artist> for Artist {
fn from(a: index::Artist) -> Self {
let convert_albums = |a: Vec<index::Album>| a.into_iter().map(|a| a.into()).collect();
fn from(artist: index::Artist) -> Self {
let artist_name = artist.header.name.clone();
let convert_album = |album: index::Album| ArtistAlbum {
contributions: album
.songs
.iter()
.map(|song| Contribution {
performer: song.artists.contains(&artist_name)
|| song.album_artists.contains(&artist_name),
composer: song.composers.contains(&artist_name),
lyricist: song.lyricists.contains(&artist_name),
})
.collect(),
album: AlbumHeader::from(album),
};
Self {
name: a.name,
albums_as_performer: convert_albums(a.albums_as_performer),
albums_as_additional_performer: convert_albums(a.albums_as_additional_performer),
albums_as_composer: convert_albums(a.albums_as_composer),
albums_as_lyricist: convert_albums(a.albums_as_lyricist),
num_songs_by_genre: a.num_songs_by_genre,
header: ArtistHeader::from(artist.header),
albums: artist.albums.into_iter().map(convert_album).collect(),
}
}
}
@ -372,7 +392,7 @@ pub struct AlbumHeader {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub artwork: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub artists: Vec<String>,
pub main_artists: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub year: Option<i64>,
}
@ -382,7 +402,7 @@ impl From<index::Album> for AlbumHeader {
Self {
name: a.name,
artwork: a.artwork.map(|a| a.to_string_lossy().to_string()),
artists: a.artists,
main_artists: a.artists,
year: a.year,
}
}