mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 20:40:15 +02:00
305 lines
8.0 KiB
TypeScript
305 lines
8.0 KiB
TypeScript
import { prisma } from '@lib/prisma';
|
|
import {
|
|
ExternalSource,
|
|
ExternalType,
|
|
ImageType,
|
|
Prisma,
|
|
Server,
|
|
ServerFolder,
|
|
} from '@prisma/client';
|
|
import uniqBy from 'lodash/uniqBy';
|
|
import { uniqueArray } from '../../utils/unique-array';
|
|
import {
|
|
JFAlbum,
|
|
JFAlbumArtist,
|
|
JFExternalType,
|
|
JFImageType,
|
|
JFSong,
|
|
} from './jellyfin.types';
|
|
|
|
const insertGenres = async (items: JFSong[] | JFAlbum[] | JFAlbumArtist[]) => {
|
|
const genresCreateMany = items
|
|
.flatMap((item) => item.GenreItems)
|
|
.map((genre) => ({ name: genre.Name }));
|
|
|
|
await prisma.genre.createMany({
|
|
data: genresCreateMany,
|
|
skipDuplicates: true,
|
|
});
|
|
};
|
|
|
|
const insertArtists = async (
|
|
server: Server,
|
|
serverFolder: ServerFolder,
|
|
items: JFSong[] | JFAlbum[]
|
|
) => {
|
|
const artistItems = uniqBy(
|
|
items.flatMap((item) => item.ArtistItems),
|
|
'Id'
|
|
);
|
|
|
|
const createMany = artistItems.map((artist) => ({
|
|
name: artist.Name,
|
|
remoteId: artist.Id,
|
|
serverId: server.id,
|
|
sortName: artist.Name,
|
|
}));
|
|
|
|
await prisma.artist.createMany({
|
|
data: createMany,
|
|
skipDuplicates: true,
|
|
});
|
|
|
|
for (const artist of artistItems) {
|
|
await prisma.artist.update({
|
|
data: { serverFolders: { connect: { id: serverFolder.id } } },
|
|
where: {
|
|
uniqueArtistId: {
|
|
remoteId: artist.Id,
|
|
serverId: server.id,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
};
|
|
|
|
const insertImages = async (items: JFSong[] | JFAlbum[] | JFAlbumArtist[]) => {
|
|
const imageItems = uniqBy(
|
|
items.flatMap((item) => item.ImageTags),
|
|
'Id'
|
|
);
|
|
|
|
const createMany: Prisma.ImageCreateManyInput[] = [];
|
|
|
|
for (const image of imageItems) {
|
|
if (image.Logo) {
|
|
createMany.push({
|
|
remoteUrl: image.Logo,
|
|
type: ImageType.LOGO,
|
|
});
|
|
}
|
|
if (image.Primary) {
|
|
createMany.push({
|
|
remoteUrl: image.Primary,
|
|
type: ImageType.PRIMARY,
|
|
});
|
|
}
|
|
}
|
|
|
|
await prisma.image.createMany({
|
|
data: createMany,
|
|
skipDuplicates: true,
|
|
});
|
|
};
|
|
|
|
const insertExternals = async (
|
|
items: JFSong[] | JFAlbum[] | JFAlbumArtist[]
|
|
) => {
|
|
const externalItems = uniqBy(
|
|
items.flatMap((item) => item.ExternalUrls),
|
|
'Url'
|
|
);
|
|
const createMany: Prisma.ExternalCreateManyInput[] = [];
|
|
|
|
for (const external of externalItems) {
|
|
if (
|
|
external.Name === JFExternalType.MUSICBRAINZ ||
|
|
external.Name === JFExternalType.THEAUDIODB
|
|
) {
|
|
const source =
|
|
external.Name === JFExternalType.MUSICBRAINZ
|
|
? ExternalSource.MUSICBRAINZ
|
|
: ExternalSource.THEAUDIODB;
|
|
|
|
const value = external.Url.split('/').pop() || '';
|
|
|
|
createMany.push({ source, type: ExternalType.ID, value });
|
|
}
|
|
}
|
|
|
|
await prisma.external.createMany({
|
|
data: createMany,
|
|
skipDuplicates: true,
|
|
});
|
|
};
|
|
|
|
const insertSongGroup = async (
|
|
server: Server,
|
|
serverFolder: ServerFolder,
|
|
songs: JFSong[],
|
|
remoteAlbumId: string
|
|
) => {
|
|
const remoteAlbumArtist =
|
|
songs[0].AlbumArtists.length > 0 ? songs[0].AlbumArtists[0] : undefined;
|
|
|
|
let albumArtist = remoteAlbumArtist?.Id
|
|
? await prisma.albumArtist.findUnique({
|
|
where: {
|
|
uniqueAlbumArtistId: {
|
|
remoteId: remoteAlbumArtist.Id,
|
|
serverId: server.id,
|
|
},
|
|
},
|
|
})
|
|
: undefined;
|
|
|
|
// If Jellyfin returns an invalid album artist, we'll just use the first matching one
|
|
if (remoteAlbumArtist && !albumArtist) {
|
|
albumArtist = await prisma.albumArtist.findFirst({
|
|
where: {
|
|
name: remoteAlbumArtist?.Name,
|
|
serverId: server.id,
|
|
},
|
|
});
|
|
}
|
|
|
|
const albumArtistId = albumArtist ? albumArtist.id : undefined;
|
|
|
|
const songsUpsert: Prisma.SongUpsertWithWhereUniqueWithoutAlbumInput[] =
|
|
songs.map((song) => {
|
|
const genresConnect = song.Genres.map((genre) => ({ name: genre }));
|
|
|
|
const artistsConnect = song.ArtistItems.map((artist) => ({
|
|
uniqueArtistId: {
|
|
remoteId: artist.Id,
|
|
serverId: server.id,
|
|
},
|
|
}));
|
|
|
|
const externalsConnect = song.ExternalUrls.map((external) => ({
|
|
uniqueExternalId: {
|
|
source:
|
|
external.Name === JFExternalType.MUSICBRAINZ
|
|
? ExternalSource.MUSICBRAINZ
|
|
: ExternalSource.THEAUDIODB,
|
|
value: external.Url.split('/').pop() || '',
|
|
},
|
|
}));
|
|
|
|
const imagesConnectOrCreate = [];
|
|
for (const [key, value] of Object.entries(song.ImageTags)) {
|
|
if (key === JFImageType.PRIMARY) {
|
|
imagesConnectOrCreate.push({
|
|
create: {
|
|
remoteUrl: value,
|
|
type: ImageType.PRIMARY,
|
|
},
|
|
where: {
|
|
uniqueImageId: { remoteUrl: value, type: ImageType.PRIMARY },
|
|
},
|
|
});
|
|
}
|
|
if (key === JFImageType.LOGO) {
|
|
imagesConnectOrCreate.push({
|
|
create: {
|
|
remoteUrl: value,
|
|
type: ImageType.LOGO,
|
|
},
|
|
where: {
|
|
uniqueImageId: { remoteUrl: value, type: ImageType.LOGO },
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
const pathSplit = song.MediaSources[0].Path.split('/');
|
|
const parentPath = pathSplit.slice(0, pathSplit.length - 1).join('/');
|
|
|
|
return {
|
|
create: {
|
|
albumArtistId,
|
|
artists: { connect: artistsConnect },
|
|
bitRate: Math.floor(song.MediaSources[0].Bitrate / 1e3),
|
|
container: song.MediaSources[0].Container,
|
|
deleted: false,
|
|
discNumber: song.ParentIndexNumber,
|
|
duration: Math.floor(song.MediaSources[0].RunTimeTicks / 1e7),
|
|
externals: { connect: externalsConnect },
|
|
folders: {
|
|
connect: {
|
|
uniqueFolderId: { path: parentPath, serverId: server.id },
|
|
},
|
|
},
|
|
genres: { connect: genresConnect },
|
|
images: { connectOrCreate: imagesConnectOrCreate },
|
|
name: song.Name,
|
|
releaseDate: song.PremiereDate,
|
|
releaseYear: song.ProductionYear,
|
|
remoteCreatedAt: song.DateCreated,
|
|
remoteId: song.Id,
|
|
serverFolders: { connect: { id: serverFolder.id } },
|
|
serverId: server.id,
|
|
size: song.MediaSources[0].Size,
|
|
sortName: song.Name,
|
|
trackNumber: song.IndexNumber,
|
|
},
|
|
update: {
|
|
albumArtistId,
|
|
artists: { connect: artistsConnect },
|
|
bitRate: Math.floor(song.MediaSources[0].Bitrate / 1e3),
|
|
container: song.MediaSources[0].Container,
|
|
deleted: false,
|
|
discNumber: song.ParentIndexNumber,
|
|
duration: Math.floor(song.MediaSources[0].RunTimeTicks / 1e7),
|
|
externals: { connect: externalsConnect },
|
|
folders: {
|
|
connect: {
|
|
uniqueFolderId: { path: parentPath, serverId: server.id },
|
|
},
|
|
},
|
|
genres: { connect: genresConnect },
|
|
images: { connectOrCreate: imagesConnectOrCreate },
|
|
name: song.Name,
|
|
releaseDate: song.PremiereDate,
|
|
releaseYear: song.ProductionYear,
|
|
remoteCreatedAt: song.DateCreated,
|
|
remoteId: song.Id,
|
|
serverFolders: { connect: { id: serverFolder.id } },
|
|
serverId: server.id,
|
|
size: song.MediaSources[0].Size,
|
|
sortName: song.Name,
|
|
trackNumber: song.IndexNumber,
|
|
},
|
|
where: {
|
|
uniqueSongId: {
|
|
remoteId: song.Id,
|
|
serverId: server.id,
|
|
},
|
|
},
|
|
};
|
|
});
|
|
|
|
const uniqueArtistIds = songs
|
|
.flatMap((song) => song.ArtistItems.flatMap((artist) => artist.Id))
|
|
.filter(uniqueArray);
|
|
|
|
const artistsConnect = uniqueArtistIds.map((artistId) => ({
|
|
uniqueArtistId: {
|
|
remoteId: artistId,
|
|
serverId: server.id,
|
|
},
|
|
}));
|
|
|
|
await prisma.album.update({
|
|
data: {
|
|
artists: { connect: artistsConnect },
|
|
deleted: false,
|
|
songs: { upsert: songsUpsert },
|
|
},
|
|
where: {
|
|
uniqueAlbumId: {
|
|
remoteId: remoteAlbumId,
|
|
serverId: server.id,
|
|
},
|
|
},
|
|
});
|
|
};
|
|
|
|
export const jellyfinUtils = {
|
|
insertArtists,
|
|
insertExternals,
|
|
insertGenres,
|
|
insertImages,
|
|
insertSongGroup,
|
|
};
|