Files
feishin/server/queue/jellyfin/jellyfin.utils.ts
T
2022-10-25 16:52:45 -07:00

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,
};