Linking Album->Artist so that artist name shows on albumLists

This commit is contained in:
simojenki
2021-04-21 14:35:21 +10:00
parent 9f90f54067
commit a06ae2e18e
8 changed files with 151 additions and 76 deletions

View File

@@ -50,6 +50,9 @@ export type AlbumSummary = {
name: string; name: string;
year: string | undefined; year: string | undefined;
genre: Genre | undefined; genre: Genre | undefined;
artistName: string;
artistId: string;
}; };
export type Album = AlbumSummary & {}; export type Album = AlbumSummary & {};
@@ -111,6 +114,8 @@ export const albumToAlbumSummary = (it: Album): AlbumSummary => ({
name: it.name, name: it.name,
year: it.year, year: it.year,
genre: it.genre, genre: it.genre,
artistName: it.artistName,
artistId: it.artistId,
}); });
export type StreamingHeader = "content-type" | "content-length" | "content-range" | "accept-ranges"; export type StreamingHeader = "content-type" | "content-length" | "content-range" | "accept-ranges";

View File

@@ -69,6 +69,8 @@ export type album = {
_genre: string | undefined; _genre: string | undefined;
_year: string | undefined; _year: string | undefined;
_coverArt: string | undefined; _coverArt: string | undefined;
_artist: string;
_artistId: string;
}; };
export type artistSummary = { export type artistSummary = {
@@ -215,6 +217,8 @@ const asAlbum = (album: album) => ({
name: album._name, name: album._name,
year: album._year, year: album._year,
genre: maybeAsGenre(album._genre), genre: maybeAsGenre(album._genre),
artistId: album._artistId,
artistName: album._artist
}); });
export const asGenre = (genreName: string) => ({ export const asGenre = (genreName: string) => ({
@@ -361,6 +365,8 @@ export class Navidrome implements MusicService {
name: album._name, name: album._name,
year: album._year, year: album._year,
genre: maybeAsGenre(album._genre), genre: maybeAsGenre(album._genre),
artistId: album._artistId,
artistName: album._artist
})); }));
getArtist = ( getArtist = (
@@ -379,6 +385,8 @@ export class Navidrome implements MusicService {
name: album._name, name: album._name,
year: album._year, year: album._year,
genre: maybeAsGenre(album._genre), genre: maybeAsGenre(album._genre),
artistId: it._id,
artistName: it._name,
})), })),
})); }));
@@ -422,6 +430,8 @@ export class Navidrome implements MusicService {
name: album._name, name: album._name,
year: album._year, year: album._year,
genre: maybeAsGenre(album._genre), genre: maybeAsGenre(album._genre),
artistId: album._artistId,
artistName: album._artist
})); }));
search3 = (credentials: Credentials, q: any) => search3 = (credentials: Credentials, q: any) =>

View File

@@ -188,30 +188,15 @@ class SonosSoap {
} }
} }
export type ContainerType = "container" | "search" | "albumList";
export type Container = { export type Container = {
itemType: "container" | "search"; itemType: ContainerType;
id: string; id: string;
title: string; title: string;
displayType: string | undefined
}; };
const container = ({
id,
title,
}: {
id: string;
title: string;
}): Container => ({
itemType: "container",
id,
title,
});
const search = ({ id, title }: { id: string; title: string }): Container => ({
itemType: "search",
id,
title,
});
const genre = (genre: Genre) => ({ const genre = (genre: Genre) => ({
itemType: "container", itemType: "container",
id: `genre:${genre.id}`, id: `genre:${genre.id}`,
@@ -239,6 +224,8 @@ export const album = (
) => ({ ) => ({
itemType: "album", itemType: "album",
id: `album:${album.id}`, id: `album:${album.id}`,
artist: album.artistName,
artistId: album.artistId,
title: album.name, title: album.name,
albumArtURI: defaultAlbumArtURI(webAddress, accessToken, album), albumArtURI: defaultAlbumArtURI(webAddress, accessToken, album),
canPlay: true, canPlay: true,
@@ -262,10 +249,10 @@ export const track = (
albumArtURI: defaultAlbumArtURI(webAddress, accessToken, track.album), albumArtURI: defaultAlbumArtURI(webAddress, accessToken, track.album),
artist: track.artist.name, artist: track.artist.name,
artistId: track.artist.id, artistId: track.artist.id,
duration: `${track.duration}`, duration: track.duration,
genre: track.album.genre?.name, genre: track.album.genre?.name,
genreId: track.album.genre?.id, genreId: track.album.genre?.id,
trackNumber: `${track.number}`, trackNumber: track.number,
}, },
}); });
@@ -490,23 +477,46 @@ function bindSmapiSoapServiceToExpress(
case "root": case "root":
return getMetadataResult({ return getMetadataResult({
mediaCollection: [ mediaCollection: [
container({ id: "artists", title: "Artists" }), {
container({ id: "albums", title: "Albums" }), itemType: "container",
container({ id: "genres", title: "Genres" }), id: "artists",
container({ id: "randomAlbums", title: "Random" }), title: "Artists",
container({ id: "starredAlbums", title: "Starred" }), },
container({ {
itemType: "albumList",
id: "albums",
title: "Albums",
},
{
itemType: "container",
id: "genres",
title: "Genres",
},
{
itemType: "albumList",
id: "randomAlbums",
title: "Random",
},
{
itemType: "albumList",
id: "starredAlbums",
title: "Starred",
},
{
itemType: "albumList",
id: "recentlyAdded", id: "recentlyAdded",
title: "Recently Added", title: "Recently Added",
}), },
container({ {
itemType: "albumList",
id: "recentlyPlayed", id: "recentlyPlayed",
title: "Recently Played", title: "Recently Played",
}), },
container({ {
itemType: "albumList",
id: "mostPlayed", id: "mostPlayed",
title: "Most Played", title: "Most Played",
}), },
], ],
index: 0, index: 0,
total: 8, total: 8,
@@ -514,9 +524,9 @@ function bindSmapiSoapServiceToExpress(
case "search": case "search":
return getMetadataResult({ return getMetadataResult({
mediaCollection: [ mediaCollection: [
search({ id: "artists", title: "Artists" }), { itemType: "search", id: "artists", title: "Artists" },
search({ id: "albums", title: "Albums" }), { itemType: "search", id: "albums", title: "Albums" },
search({ id: "tracks", title: "Tracks" }), { itemType: "search", id: "tracks", title: "Tracks" },
], ],
index: 0, index: 0,
total: 3, total: 3,

View File

@@ -7,7 +7,7 @@ import logger from "./logger";
import { SOAP_PATH, STRINGS_ROUTE, PRESENTATION_MAP_ROUTE } from "./smapi"; import { SOAP_PATH, STRINGS_ROUTE, PRESENTATION_MAP_ROUTE } from "./smapi";
import qs from "querystring" import qs from "querystring"
export const PRESENTATION_AND_STRINGS_VERSION = "12"; export const PRESENTATION_AND_STRINGS_VERSION = "15";
export type Capability = export type Capability =
| "search" | "search"

View File

@@ -4,6 +4,7 @@ import { Credentials } from "../src/smapi";
import { Service, Device } from "../src/sonos"; import { Service, Device } from "../src/sonos";
import { Album, Artist, Track, albumToAlbumSummary, artistToArtistSummary } from "../src/music_service"; import { Album, Artist, Track, albumToAlbumSummary, artistToArtistSummary } from "../src/music_service";
import randomString from "../src/random_string";
const randomInt = (max: number) => Math.floor(Math.random() * Math.floor(max)); const randomInt = (max: number) => Math.floor(Math.random() * Math.floor(max));
const randomIpAddress = () => `127.0.${randomInt(255)}.${randomInt(255)}`; const randomIpAddress = () => `127.0.${randomInt(255)}.${randomInt(255)}`;
@@ -70,7 +71,7 @@ export function someCredentials(token: string): Credentials {
export function anArtist(fields: Partial<Artist> = {}): Artist { export function anArtist(fields: Partial<Artist> = {}): Artist {
const id = uuid(); const id = uuid();
return { const artist = {
id, id,
name: `Artist ${id}`, name: `Artist ${id}`,
albums: [anAlbum(), anAlbum(), anAlbum()], albums: [anAlbum(), anAlbum(), anAlbum()],
@@ -85,6 +86,11 @@ export function anArtist(fields: Partial<Artist> = {}): Artist {
], ],
...fields, ...fields,
}; };
artist.albums.forEach(album => {
album.artistId = artist.id;
album.artistName = artist.name;
})
return artist;
} }
export const HIP_HOP = { id: "genre_hip_hop", name: "Hip-Hop" }; export const HIP_HOP = { id: "genre_hip_hop", name: "Hip-Hop" };
@@ -123,25 +129,33 @@ export function anAlbum(fields: Partial<Album> = {}): Album {
name: `Album ${id}`, name: `Album ${id}`,
genre: randomGenre(), genre: randomGenre(),
year: `19${randomInt(99)}`, year: `19${randomInt(99)}`,
artistId: `Artist ${uuid()}`,
artistName: `Artist ${randomString()}`,
...fields, ...fields,
}; };
} }
export const BLONDIE_ID = uuid();
export const BLONDIE_NAME = "Blondie";
export const BLONDIE: Artist = { export const BLONDIE: Artist = {
id: uuid(), id: BLONDIE_ID,
name: "Blondie", name: BLONDIE_NAME,
albums: [ albums: [
{ {
id: uuid(), id: uuid(),
name: "Blondie", name: "Blondie",
year: "1976", year: "1976",
genre: NEW_WAVE, genre: NEW_WAVE,
artistId: BLONDIE_ID,
artistName: BLONDIE_NAME
}, },
{ {
id: uuid(), id: uuid(),
name: "Parallel Lines", name: "Parallel Lines",
year: "1978", year: "1978",
genre: POP_ROCK, genre: POP_ROCK,
artistId: BLONDIE_ID,
artistName: BLONDIE_NAME
}, },
], ],
image: { image: {
@@ -152,13 +166,15 @@ export const BLONDIE: Artist = {
similarArtists: [], similarArtists: [],
}; };
export const BOB_MARLEY_ID = uuid();
export const BOB_MARLEY_NAME = "Bob Marley";
export const BOB_MARLEY: Artist = { export const BOB_MARLEY: Artist = {
id: uuid(), id: BOB_MARLEY_ID,
name: "Bob Marley", name: BOB_MARLEY_NAME,
albums: [ albums: [
{ id: uuid(), name: "Burin'", year: "1973", genre: REGGAE }, { id: uuid(), name: "Burin'", year: "1973", genre: REGGAE, artistId: BOB_MARLEY_ID, artistName: BOB_MARLEY_NAME },
{ id: uuid(), name: "Exodus", year: "1977", genre: REGGAE }, { id: uuid(), name: "Exodus", year: "1977", genre: REGGAE, artistId: BOB_MARLEY_ID, artistName: BOB_MARLEY_NAME },
{ id: uuid(), name: "Kaya", year: "1978", genre: SKA }, { id: uuid(), name: "Kaya", year: "1978", genre: SKA, artistId: BOB_MARLEY_ID, artistName: BOB_MARLEY_NAME },
], ],
image: { image: {
small: "http://localhost/BOB_MARLEY/sml", small: "http://localhost/BOB_MARLEY/sml",
@@ -168,9 +184,11 @@ export const BOB_MARLEY: Artist = {
similarArtists: [], similarArtists: [],
}; };
export const MADONNA_ID = uuid();
export const MADONNA_NAME = "Madonna";
export const MADONNA: Artist = { export const MADONNA: Artist = {
id: uuid(), id: MADONNA_ID,
name: "Madonna", name: MADONNA_NAME,
albums: [], albums: [],
image: { image: {
small: "http://localhost/MADONNA/sml", small: "http://localhost/MADONNA/sml",
@@ -180,21 +198,27 @@ export const MADONNA: Artist = {
similarArtists: [], similarArtists: [],
}; };
export const METALLICA_ID = uuid();
export const METALLICA_NAME = "Metallica";
export const METALLICA: Artist = { export const METALLICA: Artist = {
id: uuid(), id: METALLICA_ID,
name: "Metallica", name: METALLICA_NAME,
albums: [ albums: [
{ {
id: uuid(), id: uuid(),
name: "Ride the Lightening", name: "Ride the Lightening",
year: "1984", year: "1984",
genre: METAL, genre: METAL,
artistId: METALLICA_ID,
artistName: METALLICA_NAME,
}, },
{ {
id: uuid(), id: uuid(),
name: "Master of Puppets", name: "Master of Puppets",
year: "1986", year: "1986",
genre: METAL, genre: METAL,
artistId: METALLICA_ID,
artistName: METALLICA_NAME,
}, },
], ],
image: { image: {

View File

@@ -1374,26 +1374,24 @@ describe("Navidrome", () => {
const tripHop = asGenre("Trip-Hop"); const tripHop = asGenre("Trip-Hop");
const album = anAlbum({ id: "album1", name: "Burnin", genre: hipHop }); const album = anAlbum({ id: "album1", name: "Burnin", genre: hipHop });
const albumSummary = albumToAlbumSummary(album);
const artist = anArtist({ const artist = anArtist({
id: "artist1", id: "artist1",
name: "Bob Marley", name: "Bob Marley",
albums: [album], albums: [album],
}); });
const artistSummary = artistToArtistSummary(artist);
const tracks = [ const tracks = [
aTrack({ artist: artistSummary, album: albumSummary, genre: hipHop }), aTrack({ artist: artistToArtistSummary(artist), album: albumToAlbumSummary(album), genre: hipHop }),
aTrack({ artist: artistSummary, album: albumSummary, genre: hipHop }), aTrack({ artist: artistToArtistSummary(artist), album: albumToAlbumSummary(album), genre: hipHop }),
aTrack({ aTrack({
artist: artistSummary, artist: artistToArtistSummary(artist),
album: albumSummary, album: albumToAlbumSummary(album),
genre: tripHop, genre: tripHop,
}), }),
aTrack({ aTrack({
artist: artistSummary, artist: artistToArtistSummary(artist),
album: albumSummary, album: albumToAlbumSummary(album),
genre: tripHop, genre: tripHop,
}), }),
]; ];
@@ -1433,19 +1431,17 @@ describe("Navidrome", () => {
name: "Burnin", name: "Burnin",
genre: flipFlop, genre: flipFlop,
}); });
const albumSummary = albumToAlbumSummary(album);
const artist = anArtist({ const artist = anArtist({
id: "artist1", id: "artist1",
name: "Bob Marley", name: "Bob Marley",
albums: [album], albums: [album],
}); });
const artistSummary = artistToArtistSummary(artist);
const tracks = [ const tracks = [
aTrack({ aTrack({
artist: artistSummary, artist: artistToArtistSummary(artist),
album: albumSummary, album: albumToAlbumSummary(album),
genre: flipFlop, genre: flipFlop,
}), }),
]; ];
@@ -1520,18 +1516,16 @@ describe("Navidrome", () => {
const pop = asGenre("Pop"); const pop = asGenre("Pop");
const album = anAlbum({ id: "album1", name: "Burnin", genre: pop }); const album = anAlbum({ id: "album1", name: "Burnin", genre: pop });
const albumSummary = albumToAlbumSummary(album);
const artist = anArtist({ const artist = anArtist({
id: "artist1", id: "artist1",
name: "Bob Marley", name: "Bob Marley",
albums: [album], albums: [album],
}); });
const artistSummary = artistToArtistSummary(artist);
const track = aTrack({ const track = aTrack({
artist: artistSummary, artist: artistToArtistSummary(artist),
album: albumSummary, album: albumToAlbumSummary(album),
genre: pop, genre: pop,
}); });
@@ -2730,11 +2724,11 @@ describe("Navidrome", () => {
describe("searchAlbums", () => { describe("searchAlbums", () => {
describe("when there is 1 search results", () => { describe("when there is 1 search results", () => {
it("should return true", async () => { it("should return true", async () => {
const artist = anArtist({ name: "#1" });
const album = anAlbum({ const album = anAlbum({
name: "foo woo", name: "foo woo",
genre: { id: "pop", name: "pop" }, genre: { id: "pop", name: "pop" },
}); });
const artist = anArtist({ name: "#1", albums:[album] });
mockGET mockGET
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK))) .mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
@@ -2765,17 +2759,17 @@ describe("Navidrome", () => {
describe("when there are many search results", () => { describe("when there are many search results", () => {
it("should return true", async () => { it("should return true", async () => {
const artist1 = anArtist({ name: "artist1" });
const album1 = anAlbum({ const album1 = anAlbum({
name: "album1", name: "album1",
genre: { id: "pop", name: "pop" }, genre: { id: "pop", name: "pop" },
}); });
const artist1 = anArtist({ name: "artist1", albums: [album1] });
const artist2 = anArtist({ name: "artist2" });
const album2 = anAlbum({ const album2 = anAlbum({
name: "album2", name: "album2",
genre: { id: "pop", name: "pop" }, genre: { id: "pop", name: "pop" },
}); });
const artist2 = anArtist({ name: "artist2", albums: [album2] });
mockGET mockGET
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK))) .mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))

View File

@@ -241,6 +241,8 @@ describe("album", () => {
title: someAlbum.name, title: someAlbum.name,
albumArtURI: defaultAlbumArtURI(webAddress, accessToken, someAlbum), albumArtURI: defaultAlbumArtURI(webAddress, accessToken, someAlbum),
canPlay: true, canPlay: true,
artist: someAlbum.artistName,
artistId: someAlbum.artistId
}); });
}); });
}); });
@@ -633,7 +635,7 @@ describe("api", () => {
musicLibrary.searchTracks.mockResolvedValue([track1, track2]); musicLibrary.searchTracks.mockResolvedValue([track1, track2]);
}); });
it.only("should return the tracks", async () => { it("should return the tracks", async () => {
const term = "whoopie"; const term = "whoopie";
const result = await ws.searchAsync({ const result = await ws.searchAsync({
@@ -642,7 +644,15 @@ describe("api", () => {
}); });
expect(result[0]).toEqual( expect(result[0]).toEqual(
searchResult({ searchResult({
mediaCollection: tracks.map((it) => track(rootUrl, accessToken, it)), mediaCollection: tracks.map((it) => {
const t = track(rootUrl, accessToken, it) as any;
t.trackMetadata = {
...t.trackMetadata,
duration: `${t.trackMetadata.duration}`,
trackNumber: `${t.trackMetadata.trackNumber}`,
}
return t;
}),
index: 0, index: 0,
total: 2, total: 2,
}) })
@@ -722,30 +732,30 @@ describe("api", () => {
getMetadataResult({ getMetadataResult({
mediaCollection: [ mediaCollection: [
{ itemType: "container", id: "artists", title: "Artists" }, { itemType: "container", id: "artists", title: "Artists" },
{ itemType: "container", id: "albums", title: "Albums" }, { itemType: "albumList", id: "albums", title: "Albums" },
{ itemType: "container", id: "genres", title: "Genres" }, { itemType: "container", id: "genres", title: "Genres" },
{ {
itemType: "container", itemType: "albumList",
id: "randomAlbums", id: "randomAlbums",
title: "Random", title: "Random",
}, },
{ {
itemType: "container", itemType: "albumList",
id: "starredAlbums", id: "starredAlbums",
title: "Starred", title: "Starred",
}, },
{ {
itemType: "container", itemType: "albumList",
id: "recentlyAdded", id: "recentlyAdded",
title: "Recently Added", title: "Recently Added",
}, },
{ {
itemType: "container", itemType: "albumList",
id: "recentlyPlayed", id: "recentlyPlayed",
title: "Recently Played", title: "Recently Played",
}, },
{ {
itemType: "container", itemType: "albumList",
id: "mostPlayed", id: "mostPlayed",
title: "Most Played", title: "Most Played",
}, },
@@ -853,6 +863,8 @@ describe("api", () => {
title: it.name, title: it.name,
albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it),
canPlay: true, canPlay: true,
artistId: it.artistId,
artist: it.artistName
})), })),
index: 0, index: 0,
total: artistWithManyAlbums.albums.length, total: artistWithManyAlbums.albums.length,
@@ -884,6 +896,8 @@ describe("api", () => {
title: it.name, title: it.name,
albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it),
canPlay: true, canPlay: true,
artistId: it.artistId,
artist: it.artistName
})), })),
index: 2, index: 2,
total: artistWithManyAlbums.albums.length, total: artistWithManyAlbums.albums.length,
@@ -1137,6 +1151,8 @@ describe("api", () => {
title: it.name, title: it.name,
albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it),
canPlay: true, canPlay: true,
artistId: it.artistId,
artist: it.artistName
})), })),
index: 0, index: 0,
total: 6, total: 6,
@@ -1180,6 +1196,8 @@ describe("api", () => {
title: it.name, title: it.name,
albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it),
canPlay: true, canPlay: true,
artistId: it.artistId,
artist: it.artistName
})), })),
index: 0, index: 0,
total: 6, total: 6,
@@ -1223,6 +1241,8 @@ describe("api", () => {
title: it.name, title: it.name,
albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it),
canPlay: true, canPlay: true,
artistId: it.artistId,
artist: it.artistName
})), })),
index: 0, index: 0,
total: 6, total: 6,
@@ -1266,6 +1286,8 @@ describe("api", () => {
title: it.name, title: it.name,
albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it),
canPlay: true, canPlay: true,
artistId: it.artistId,
artist: it.artistName
})), })),
index: 0, index: 0,
total: 6, total: 6,
@@ -1309,6 +1331,8 @@ describe("api", () => {
title: it.name, title: it.name,
albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it),
canPlay: true, canPlay: true,
artistId: it.artistId,
artist: it.artistName
})), })),
index: 0, index: 0,
total: 6, total: 6,
@@ -1350,6 +1374,8 @@ describe("api", () => {
title: it.name, title: it.name,
albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it),
canPlay: true, canPlay: true,
artistId: it.artistId,
artist: it.artistName
})), })),
index: 0, index: 0,
total: 6, total: 6,
@@ -1391,6 +1417,8 @@ describe("api", () => {
title: it.name, title: it.name,
albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it),
canPlay: true, canPlay: true,
artistId: it.artistId,
artist: it.artistName
})), })),
index: 2, index: 2,
total: 6, total: 6,
@@ -1430,6 +1458,8 @@ describe("api", () => {
title: it.name, title: it.name,
albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it),
canPlay: true, canPlay: true,
artistId: it.artistId,
artist: it.artistName
})), })),
index: 0, index: 0,
total: 4, total: 4,
@@ -1472,6 +1502,8 @@ describe("api", () => {
title: it.name, title: it.name,
albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it), albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it),
canPlay: true, canPlay: true,
artistId: it.artistId,
artist: it.artistName
})), })),
index: 0, index: 0,
total: 4, total: 4,