Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get Rare Songs #691

Merged
merged 7 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions front/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,7 @@ export default class API {
genre?: Identifier;
artist?: Identifier;
versionsOf?: Identifier;
rare?: Identifier;
query?: string;
bsides?: Identifier;
random?: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,15 +158,9 @@ const InfiniteSongView = <
]}
disableSorting={props.disableSorting}
onChange={setOptions}
sortingKeys={SongSortingKeys.filter(
(key) => key !== "userPlayCount",
)}
sortingKeys={SongSortingKeys}
defaultSortingOrder={props.initialSortingOrder}
defaultSortingKey={
props.initialSortingField == "userPlayCount"
? "totalPlayCount"
: props.initialSortingField
}
defaultSortingKey={props.initialSortingField}
router={props.light == true ? undefined : router}
disableLayoutToggle
defaultLayout={"list"}
Expand Down
3 changes: 2 additions & 1 deletion front/src/i18n/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -237,5 +237,6 @@
"fr": "French",
"yes": "Yes",
"no": "No",
"featuredAlbums": "Featured Albums"
"featuredAlbums": "Featured Albums",
"rareSongs": "Rare Songs"
}
3 changes: 2 additions & 1 deletion front/src/i18n/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -226,5 +226,6 @@
"fr": "Français",
"yes": "Oui",
"no": "Non",
"featuredAlbums": "En Vedette"
"featuredAlbums": "En Vedette",
"rareSongs": "Pistes Inédites"
}
24 changes: 24 additions & 0 deletions front/src/pages/artists/[slugOrId]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ const topSongsQuery = (artistSlugOrId: string | number) =>
["artist", "featuring", "master", "illustration"],
);

const rareSongsQuery = (artistSlugOrId: string | number) =>
API.getSongs(
{ rare: artistSlugOrId },
{ sortBy: "totalPlayCount", order: "desc" },
["artist", "featuring", "master", "illustration"],
);

const artistQuery = (artistSlugOrId: string | number) =>
API.getArtist(artistSlugOrId, ["externalIds", "illustration"]);

Expand All @@ -88,6 +95,7 @@ const prepareSSR = (context: NextPageContext) => {
additionalProps: { artistIdentifier },
queries: [artistQuery(artistIdentifier)],
infiniteQueries: [
rareSongsQuery(artistIdentifier),
videosQuery(artistIdentifier),
topSongsQuery(artistIdentifier),
appearanceQuery(artistIdentifier),
Expand All @@ -110,6 +118,7 @@ const ArtistPage: Page<GetPropsTypesFrom<typeof prepareSSR>> = ({ props }) => {
}));
const videos = useInfiniteQuery(videosQuery, artistIdentifier);
const topSongs = useInfiniteQuery(topSongsQuery, artistIdentifier);
const rareSongs = useInfiniteQuery(rareSongsQuery, artistIdentifier);
const appearances = useInfiniteQuery(appearanceQuery, artistIdentifier);
const externalIdWithDescription = artist.data?.externalIds
.filter(({ provider }) => provider.name.toLowerCase() !== "discogs")
Expand Down Expand Up @@ -262,13 +271,28 @@ const ArtistPage: Page<GetPropsTypesFrom<typeof prepareSSR>> = ({ props }) => {
</Grid>
</Fragment>
))}
{(rareSongs.data?.pages?.at(0)?.items.length ?? 0) != 0 && (
<>
<SectionHeader heading={t("rareSongs")} />
<Grid item sx={{ overflowX: "clip", width: "100%" }}>
<SongGrid
parentArtistName={artist.data?.name}
songs={
rareSongs.data?.pages?.at(0)?.items ??
generateArray(songListSize)
}
/>
</Grid>
</>
)}
{[
{ label: "topVideos", items: musicVideos } as const,
{ label: "extras", items: extras } as const,
].map(
({ label, items }) =>
items.length != 0 && (
<Fragment key={`videos-${label}`}>
<Box sx={{ paddingBottom: sectionPadding }} />
<SectionHeader
heading={t(label)}
trailing={
Expand Down
9 changes: 7 additions & 2 deletions server/src/scanner/parser.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,11 @@ describe("Parser Service", () => {
parserService.getSongType("My Song (Original Version)"),
).toBe(SongType.Original);
});
it("Original Version (Original Mix)", () => {
expect(parserService.getSongType("My Song (Original Mix)")).toBe(
SongType.Original,
);
});
it("Original Version (Feat Group)", () => {
expect(parserService.getSongType("My Song (feat. A)")).toBe(
SongType.Original,
Expand Down Expand Up @@ -852,12 +857,12 @@ describe("Parser Service", () => {

it("Demo (Rough Mix)", () => {
expect(parserService.getSongType("Fever (Rough Mix)")).toBe(
SongType.Original,
SongType.Demo,
);
});
it("Demo (Rough Mix Edit)", () => {
expect(parserService.getSongType("Fever (Rough Mix Edit)")).toBe(
SongType.Original,
SongType.Demo,
);
});

Expand Down
3 changes: 3 additions & 0 deletions server/src/scanner/parser.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@ export default class ParserService {
return SongType.Clean;
}
if (jointExtensionWords.includes("rough mix")) {
return SongType.Demo;
}
if (jointExtensionWords == "original mix") {
return SongType.Original;
}
if (containsWord("mix") && containsWord("edit")) {
Expand Down
16 changes: 15 additions & 1 deletion server/src/song/song.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,13 @@ export class Selector {
@TransformIdentifier(ReleaseService)
bsides: ReleaseQueryParameters.WhereInput;

@IsOptional()
@ApiPropertyOptional({
description: "Filter songs that would be considered to be 'rare'",
})
@TransformIdentifier(ReleaseService)
rare: ArtistQueryParameters.WhereInput;

@IsOptional()
@ApiPropertyOptional({
description: "The Seed to Sort the items",
Expand Down Expand Up @@ -188,6 +195,13 @@ export class SongController {
include,
sort,
);
} else if (selector.rare) {
return this.songService.getRareSongsByArtist(
selector.rare,
paginationParameters,
include,
sort,
);
}
return this.songService.getMany(
selector,
Expand All @@ -212,7 +226,7 @@ export class SongController {
}

@ApiOperation({
summary: "Upate a song",
summary: "Update a song",
})
@Response({ handler: SongResponseBuilder })
@Post(":idOrSlug")
Expand Down
141 changes: 133 additions & 8 deletions server/src/song/song.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
getRandomIds,
sortItemsUsingOrderedIdList,
} from "src/repository/repository.utils";
import ArtistQueryParameters from "src/artist/models/artist.query-parameters";

@Injectable()
export default class SongService extends SearchableRepositoryService {
Expand Down Expand Up @@ -676,14 +677,138 @@ export default class SongService extends SearchableRepositoryService {
// We only want songs that have at least one audtio tracks
{ tracks: { some: { type: TrackType.Audio } } },
{
type: {
in: [
SongType.Original,
SongType.Acoustic,
SongType.Demo,
SongType.NonMusic,
],
},
OR: [
// We take original songs or extras
{
type: { in: ["Original", "NonMusic"] },
},
// Or songs that are only available as demos/acoustic versions
{
group: {
versions: {
every: {
type: { in: ["Demo", "Acoustic"] },
},
},
},
},
],
},
],
},
orderBy: sort ? this.formatSortingInput(sort) : undefined,
include: include ?? ({} as I),
...formatPaginationParameters(pagination),
});
}

async getRareSongsByArtist<I extends SongQueryParameters.RelationInclude>(
where: ArtistQueryParameters.WhereInput,
pagination?: PaginationParameters,
include?: I,
sort?: SongQueryParameters.SortingParameter,
) {
const artist = await this.artistService
.get(where, { albums: true })
.catch(() => null);

if (!artist || artist.albums.length == 0) {
// if the artist does not have albums, lets skip this
return [];
}
return this.prismaService.song.findMany({
where: {
// Take the tracks that have at least one audio track
tracks: {
some: { type: "Audio" },
},
AND: [
{
OR: [
{
type: { in: ["Original"] },
},
{
group: {
versions: {
every: {
type: { in: ["Demo", "Acoustic"] },
},
},
},
},
],
},
{
OR: [
{ artistId: artist.id },
{ featuring: { some: { id: artist.id } } },
],
},
{
OR: [
// Take songs that only appears on other artist's album
{
// In that case, we only want song with artist being the main one
artistId: artist.id,
tracks: {
every: {
release: {
album: {
artistId: { not: artist.id },
},
},
},
},
},
// Take all tracks that only appear on non-master albums
{
tracks: {
every: {
release: {
album: {
type: "StudioRecording",
},
masterOf: null,
},
},
},
},
// Take all tracks that appear only on singles AND non master albums
{
tracks: {
every: {
OR: [
{
release: {
album: {
type: "Single",
},
},
trackIndex: {
notIn: [0, 1],
},
},
{
release: {
album: {
type: "StudioRecording",
},
masterOf: null,
},
},
],
},
},
},
{
tracks: {
some: {
isBonus: true,
},
},
},
],
},
],
},
Expand Down
Loading