- Spotify playlist, library, album and artist browsing with in-app playback - Song, playlist and album downloads via spotdl with YouTube and Bandcamp fallbacks - Electron desktop app (castLabs Widevine) with a permanent sidebar layout - Linux and Windows packaging scripts
42 lines
1022 B
TypeScript
42 lines
1022 B
TypeScript
export type LibraryIdentityInput = {
|
|
isrc?: string;
|
|
title?: string;
|
|
artist?: string;
|
|
album?: string;
|
|
};
|
|
|
|
function normalize(value?: string) {
|
|
return (value || "")
|
|
.toLowerCase()
|
|
.normalize("NFKD")
|
|
.replace(/[\u0300-\u036f]/g, "")
|
|
.replace(/[^a-z0-9]+/g, " ")
|
|
.trim();
|
|
}
|
|
|
|
export function makeLibraryIdentity(input: LibraryIdentityInput) {
|
|
if (input.isrc?.trim()) return `isrc:${input.isrc.trim().toUpperCase()}`;
|
|
return [
|
|
"meta",
|
|
normalize(input.artist),
|
|
normalize(input.title),
|
|
normalize(input.album),
|
|
].join(":");
|
|
}
|
|
|
|
export function findDuplicateIdentities(items: LibraryIdentityInput[]) {
|
|
const seen = new Map<string, number>();
|
|
const duplicates = new Set<number>();
|
|
items.forEach((item, index) => {
|
|
const identity = makeLibraryIdentity(item);
|
|
const first = seen.get(identity);
|
|
if (typeof first === "number") {
|
|
duplicates.add(first);
|
|
duplicates.add(index);
|
|
} else {
|
|
seen.set(identity, index);
|
|
}
|
|
});
|
|
return duplicates;
|
|
}
|