Repository: IgnisDa/ryot (language: rust)

Issue:
[FEATURE REQUEST] - Music tracker
Hi there,
It would be cool to have also a music tracker, so that you can see which albums you've already listened to and see when new albums get released :D


Discussion / hints:
Thats a great idea. What sources can Ryot get the music data from? For example TMDB for movies, IGDB for video games etc.
Not sure if Last.fm, Deezer, MusicBrainz or Discogs can have valid APIs for the job and how easy they are to be integrated 🤔
Ah alright. I will keep this in mind. Another possible solution is adding integration with Spotify, Youtube music etc and using those.
> Thats a great idea. What sources can Ryot get the music data from? For example TMDB for movies, IGDB for video games etc.

Check out MusicBrainz, they've got a pretty robust and easy to use API
You could review [Maloja](https://github.com/krateng/maloja), as its basically a selfhosted last.fm. It doesn't do any scrobbling, but [Multi-Scrobbler](https://github.com/FoxxMD/multi-scrobbler) acts as an 'API Linker' to pull data from *x*, *y*. or *z* API, and push the music scrobbles to *x*, *y*, or *z* platform. 

I could see this project evolving into a 'plugin' style system where anyone could create an integration to scrobble data out of, and into here. 
@DaFlowah Looking into this. What exactly would a music integration look like? What would you track?
> @DaFlowah Looking into this. What exactly would a music integration look like? What would you track?

Not sure about anyone else preferences, but to me it would be nice to be able to track what I've already listened to (songs, albums, EPs, etc) and, most importantly, I'd like to be able to see when the bands I like release something new.
Note to self: Audiobookshelf has an [integration for MusicBrainz](https://github.com/advplyr/audiobookshelf/blob/master/server/providers/MusicBrainz.js) that I can shamelessly port to rust.
Hello. I plan to work on music integration for Ryot next. In relation to #203, I am a bit divided. Should I allow the search for just a single release, and then put an album (or EP) in a media group?

@DaFlowah @chunned @HStep20 I would appreciate your feedback here.
> Should I allow the search for just a single release, and then put an album (or EP) in a media group?

That sounds good to me. I would like to be able to search for a specific release, or for an artist (media group containing all that artist's releases). 
Artist support will be a long way off since the people management code for Ryot needs a complete overhaul.  I'm talking only about music here.
I'm kinda confused what the purpose of the media grouping for music would be then. Are you talking about grouping various editions of a single release, i.e. the way MusicBrainz uses release groups?
A group will be an album or an EP. And the music tracker itself will allow you to look up a single track.
Hi @IgnisDa :D
So, if I look for a song (i.e. Bohemian Rhapsody), it will pop up along with its album (A Night at the Opera)?
If so, it would be cool for me 😎
When you look up bohemian Rhapsody, only that would show up. When you open the details, you'll see something like "Belongs to A night at the Opera". You can click on that to view the entire album (this will happen when #203 is implemented).
> When you look up bohemian Rhapsody, only that would show up. When you open the details, you'll see something like "Belongs to A night at the Opera". You can click on that to view the entire album (this will happen when #203 is implemented).

Sounds good to me 😎
It would be nice if we could also add ratings to the songs
That should be possible, though you should be aware that I don't plan to work on music tracking RN. The quality of data from the various sources was awful, and I was discouraged.
Note to self: look into Discogs API.
> Note to self: look into Discogs API.

I've had pleasantly surprising results with the Discogs API in my own music tracker project. Often it's a better source of data than MusicBrainz, definitely a good idea to check it out. 
Can you share a link to that project?
Sure, but be warned it's quite a bit different from Ryot (and also still very rough around the edges) - [Databass](https://github.com/chunned/databass)

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 86649e2615..68775f6ef9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -66,6 +66,21 @@ version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
 
+[[package]]
+name = "alloc-no-stdlib"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
+
+[[package]]
+name = "alloc-stdlib"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
+dependencies = [
+ "alloc-no-stdlib",
+]
+
 [[package]]
 name = "allocator-api2"
 version = "0.2.20"
@@ -325,6 +340,20 @@ dependencies = [
  "nom",
 ]
 
+[[package]]
+name = "async-compression"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522"
+dependencies = [
+ "brotli",
+ "flate2",
+ "futures-core",
+ "memchr",
+ "pin-project-lite",
+ "tokio",
+]
+
 [[package]]
 name = "async-graphql"
 version = "7.0.11"
@@ -982,6 +1011,21 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "bit-set"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
 [[package]]
 name = "bitflags"
 version = "1.3.2"
@@ -1070,6 +1114,27 @@ dependencies = [
  "syn 2.0.90",
 ]
 
+[[package]]
+name = "brotli"
+version = "7.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+ "brotli-decompressor",
+]
+
+[[package]]
+name = "brotli-decompressor"
+version = "4.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
 [[package]]
 name = "bumpalo"
 version = "3.16.0"
@@ -1423,6 +1488,15 @@ dependencies = [
  "unicode-segmentation",
 ]
 
+[[package]]
+name = "copy_dir"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "543d1dd138ef086e2ff05e3a48cf9da045da2033d16f8538fd76b86cd49b2ca3"
+dependencies = [
+ "walkdir",
+]
+
 [[package]]
 name = "core-foundation"
 version = "0.9.4"
@@ -2257,6 +2331,17 @@ dependencies = [
  "tracing",
 ]
 
+[[package]]
+name = "fancy-regex"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
+dependencies = [
+ "bit-set",
+ "regex-automata 0.4.9",
+ "regex-syntax 0.8.5",
+]
+
 [[package]]
 name = "fast_chemail"
 version = "0.9.6"
@@ -3580,6 +3665,16 @@ version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
 
+[[package]]
+name = "libquickjs-dtp-sys"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b869d7bd9cfc01d9ee1b29a7556990897268da33765d750be3f51379e850fb95"
+dependencies = [
+ "cc",
+ "copy_dir",
+]
+
 [[package]]
 name = "libredox"
 version = "0.1.3"
@@ -3829,7 +3924,6 @@ dependencies = [
  "dependent-models",
  "dependent-utils",
  "enums",
- "env-utils",
  "futures",
  "itertools 0.13.0",
  "markdown",
@@ -3844,13 +3938,10 @@ dependencies = [
  "sea-query",
  "serde",
  "serde_json",
- "serde_with 3.11.0",
- "slug",
  "supporting-service",
  "tokio",
  "tracing",
  "traits",
- "unkey",
  "user-models",
  "uuid",
 ]
@@ -4594,7 +4685,6 @@ dependencies = [
  "enums",
  "graphql_client",
  "hashbag",
- "isolang",
  "itertools 0.13.0",
  "media-models",
  "paginate",
@@ -4603,6 +4693,7 @@ dependencies = [
  "rust_decimal",
  "rust_decimal_macros",
  "rust_iso3166",
+ "rustypipe",
  "scraper",
  "sea-orm",
  "serde",
@@ -4643,6 +4734,16 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "quick-js-dtp"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29d07297b897136123d4bb9c63249b587aafbd1a74dcc7f86848c41e506655ab"
+dependencies = [
+ "libquickjs-dtp-sys",
+ "once_cell",
+]
+
 [[package]]
 name = "quote"
 version = "1.0.37"
@@ -4893,6 +4994,7 @@ version = "0.12.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f"
 dependencies = [
+ "async-compression",
  "base64 0.22.1",
  "bytes",
  "encoding_rs",
@@ -4934,6 +5036,16 @@ dependencies = [
  "windows-registry",
 ]
 
+[[package]]
+name = "ress"
+version = "0.11.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d77111a94ef694bb2fe9867a93e21007e2151943c9a48b2ed72734bde41875a"
+dependencies = [
+ "log",
+ "unicode-xid",
+]
+
 [[package]]
 name = "rfc6979"
 version = "0.3.1"
@@ -5189,12 +5301,48 @@ version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
 
+[[package]]
+name = "rustypipe"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97dba9b1e6f02758c5dff06157082cdd55e8c6c96ef9bac3bea0b191cc864da7"
+dependencies = [
+ "base64 0.22.1",
+ "fancy-regex",
+ "once_cell",
+ "phf",
+ "quick-js-dtp",
+ "rand 0.8.5",
+ "regex",
+ "reqwest 0.12.9",
+ "ress",
+ "serde",
+ "serde_json",
+ "serde_plain",
+ "serde_with 3.11.0",
+ "thiserror 2.0.3",
+ "time",
+ "tokio",
+ "tracing",
+ "url",
+ "urlencoding",
+]
+
 [[package]]
 name = "ryu"
 version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
 
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
 [[package]]
 name = "schannel"
 version = "0.1.27"
@@ -6255,13 +6403,26 @@ dependencies = [
  "async-graphql",
  "background",
  "cache-service",
+ "chrono",
  "chrono-tz",
  "common-models",
+ "common-utils",
  "config",
+ "database-models",
  "dependent-models",
+ "enums",
+ "env-utils",
  "file-storage-service",
+ "isolang",
+ "itertools 0.13.0",
  "openidconnect",
+ "rustypipe",
  "sea-orm",
+ "serde",
+ "serde_json",
+ "serde_with 3.11.0",
+ "tracing",
+ "unkey",
 ]
 
 [[package]]
@@ -6890,6 +7051,12 @@ version = "0.1.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
 
+[[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
 [[package]]
 name = "unicode_categories"
 version = "0.1.1"
@@ -6941,6 +7108,12 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "urlencoding"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
+
 [[package]]
 name = "user-models"
 version = "0.1.0"
@@ -7064,6 +7237,16 @@ version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64"
 
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
 [[package]]
 name = "want"
 version = "0.3.1"
@@ -7227,6 +7410,15 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 
+[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
 [[package]]
 name = "winapi-x86_64-pc-windows-gnu"
 version = "0.4.0"
diff --git a/Cargo.toml b/Cargo.toml
index 60dfb076a5..1d1f086bb4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -106,6 +106,7 @@ regex = "=1.11.1"
 rust_decimal = "=1.36.0"
 rust_decimal_macros = "=1.36.0"
 rust_iso3166 = "=0.1.13"
+rustypipe = "0.7.2"
 schematic = { version = "=0.17.7", features = [
     "config",
     "env",
diff --git a/apps/backend/src/job.rs b/apps/backend/src/job.rs
index 528b7dd3c0..2becad99a9 100644
--- a/apps/backend/src/job.rs
+++ b/apps/backend/src/job.rs
@@ -24,13 +24,8 @@ pub async fn run_background_jobs(
 pub async fn run_frequent_jobs(
     _information: ScheduledJob,
     fitness_service: Data<Arc<FitnessService>>,
-    misc_service: Data<Arc<MiscellaneousService>>,
     integration_service: Data<Arc<IntegrationService>>,
 ) -> Result<(), Error> {
-    misc_service
-        .perform_server_key_validation()
-        .await
-        .trace_ok();
     integration_service
         .yank_integrations_data()
         .await
@@ -153,9 +148,6 @@ pub async fn perform_application_job(
                 .await
                 .is_ok()
         }
-        ApplicationJob::PerformServerKeyValidation => {
-            misc_service.perform_server_key_validation().await.is_ok()
-        }
         ApplicationJob::HandleEntityAddedToCollectionEvent(collection_to_entity_id) => {
             integration_service
                 .handle_entity_added_to_collection_event(collection_to_entity_id)
diff --git a/apps/backend/src/main.rs b/apps/backend/src/main.rs
index be68b012ac..2802d2c735 100644
--- a/apps/backend/src/main.rs
+++ b/apps/backend/src/main.rs
@@ -121,7 +121,6 @@ async fn main() -> Result<()> {
 
     join_all(
         [
-            ApplicationJob::PerformServerKeyValidation,
             ApplicationJob::SyncIntegrationsData,
             ApplicationJob::UpdateExerciseLibrary,
         ]
@@ -190,7 +189,6 @@ async fn main() -> Result<()> {
     let miscellaneous_service_1 = app_services.miscellaneous_service.clone();
     let miscellaneous_service_2 = app_services.miscellaneous_service.clone();
     let miscellaneous_service_3 = app_services.miscellaneous_service.clone();
-    let miscellaneous_service_4 = app_services.miscellaneous_service.clone();
 
     let monitor = Monitor::<TokioExecutor>::new()
         .register_with_count(
@@ -218,7 +216,6 @@ async fn main() -> Result<()> {
                 .layer(ApalisTraceLayer::new())
                 .data(integration_service_1.clone())
                 .data(fitness_service_2.clone())
-                .data(miscellaneous_service_4.clone())
                 .build_fn(run_frequent_jobs),
         )
         // application jobs
diff --git a/apps/frontend/app/lib/generals.ts b/apps/frontend/app/lib/generals.ts
index 703de1a364..91904f1dda 100644
--- a/apps/frontend/app/lib/generals.ts
+++ b/apps/frontend/app/lib/generals.ts
@@ -24,6 +24,7 @@ import {
 	IconDeviceTvOld,
 	IconHeadphones,
 	IconMicrophone,
+	IconMusic,
 } from "@tabler/icons-react";
 import { QueryClient, queryOptions, skipToken } from "@tanstack/react-query";
 import dayjs from "dayjs";
@@ -179,6 +180,7 @@ export const getLot = (lot: unknown) => {
 		.with("books", "book", () => MediaLot.Book)
 		.with("movies", "movie", () => MediaLot.Movie)
 		.with("tv", "show", "shows", () => MediaLot.Show)
+		.with("music", () => MediaLot.Music)
 		.with(
 			"visual_novel",
 			"visualnovel",
@@ -232,6 +234,10 @@ export const getLotGradient = (lot: MediaLot) =>
 			from: "green",
 			to: "yellow",
 		}))
+		.with(MediaLot.Music, () => ({
+			from: "orange",
+			to: "pink",
+		}))
 		.exhaustive();
 
 /**
@@ -269,6 +275,7 @@ export const getVerb = (verb: Verb, lot: MediaLot) =>
 				)
 				.with(
 					MediaLot.AudioBook,
+					MediaLot.Music,
 					MediaLot.VideoGame,
 					MediaLot.Podcast,
 					() => "play",
@@ -308,6 +315,7 @@ export const getMetadataIcon = (lot: MediaLot) =>
 	match(lot)
 		.with(MediaLot.Book, () => IconBook)
 		.with(MediaLot.Manga, () => IconBooks)
+		.with(MediaLot.Music, () => IconMusic)
 		.with(MediaLot.Movie, () => IconDeviceTv)
 		.with(MediaLot.Anime, () => IconDeviceTvOld)
 		.with(MediaLot.VisualNovel, () => IconBook2)
@@ -487,6 +495,7 @@ type EntityColor = Record<MediaLot | (string & {}), MantineColor>;
 
 export const MediaColors: EntityColor = {
 	ANIME: "blue",
+	MUSIC: "indigo.2",
 	AUDIO_BOOK: "orange",
 	BOOK: "lime",
 	MANGA: "purple",
diff --git a/apps/frontend/app/routes/_dashboard.media.groups.$action.tsx b/apps/frontend/app/routes/_dashboard.media.groups.$action.tsx
index d2555d11bc..87fd6e7583 100644
--- a/apps/frontend/app/routes/_dashboard.media.groups.$action.tsx
+++ b/apps/frontend/app/routes/_dashboard.media.groups.$action.tsx
@@ -75,6 +75,7 @@ enum Action {
 const SEARCH_SOURCES_ALLOWED: Partial<Record<MediaSource, MediaLot>> = {
 	[MediaSource.Tmdb]: MediaLot.Movie,
 	[MediaSource.Igdb]: MediaLot.VideoGame,
+	[MediaSource.YoutubeMusic]: MediaLot.Music,
 };
 
 export const loader = async ({ request, params }: LoaderFunctionArgs) => {
diff --git a/apps/frontend/app/routes/_dashboard.media.groups.item.$id._index.tsx b/apps/frontend/app/routes/_dashboard.media.groups.item.$id._index.tsx
index 72b5468c85..27c7c1d5c5 100644
--- a/apps/frontend/app/routes/_dashboard.media.groups.item.$id._index.tsx
+++ b/apps/frontend/app/routes/_dashboard.media.groups.item.$id._index.tsx
@@ -20,6 +20,7 @@ import {
 } from "@ryot/generated/graphql/backend/graphql";
 import {
 	IconDeviceTv,
+	IconInfoCircle,
 	IconMessageCircle2,
 	IconUser,
 } from "@tabler/icons-react";
@@ -86,9 +87,9 @@ export default function Page() {
 			<MediaDetailsLayout
 				images={loaderData.metadataGroupDetails.details.displayImages}
 				externalLink={{
-					source: loaderData.metadataGroupDetails.details.source,
 					lot: loaderData.metadataGroupDetails.details.lot,
-					href: loaderData.metadataGroupDetails.sourceUrl,
+					source: loaderData.metadataGroupDetails.details.source,
+					href: loaderData.metadataGroupDetails.details.sourceUrl,
 				}}
 			>
 				<Title id="group-title">
@@ -128,6 +129,12 @@ export default function Page() {
 								Reviews
 							</Tabs.Tab>
 						) : null}
+						<Tabs.Tab
+							value="overview"
+							leftSection={<IconInfoCircle size={16} />}
+						>
+							Overview
+						</Tabs.Tab>
 					</Tabs.List>
 					<Tabs.Panel value="media">
 						<MediaScrollArea>
@@ -208,6 +215,18 @@ export default function Page() {
 							</MediaScrollArea>
 						</Tabs.Panel>
 					) : null}
+					<Tabs.Panel value="overview">
+						{loaderData.metadataGroupDetails.details.description ? (
+							<div
+								// biome-ignore lint/security/noDangerouslySetInnerHtml: generated by the backend securely
+								dangerouslySetInnerHTML={{
+									__html: loaderData.metadataGroupDetails.details.description,
+								}}
+							/>
+						) : (
+							<Text>No description</Text>
+						)}
+					</Tabs.Panel>
 				</Tabs>
 			</MediaDetailsLayout>
 		</Container>
diff --git a/apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx b/apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx
index 71061f0612..290ebb358d 100644
--- a/apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx
+++ b/apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx
@@ -59,6 +59,7 @@ import {
 import {
 	changeCase,
 	formatDateToNaiveDate,
+	formatQuantityWithCompactNotation,
 	getActionIntent,
 	humanizeDuration,
 	isInteger,
@@ -404,6 +405,21 @@ export default function Page() {
 									)
 									.asMilliseconds(),
 							),
+						loaderData.metadataDetails.musicSpecifics?.duration &&
+							humanizeDuration(
+								dayjsLib
+									.duration(
+										loaderData.metadataDetails.musicSpecifics.duration,
+										"second",
+									)
+									.asMilliseconds(),
+							),
+						loaderData.metadataDetails.musicSpecifics?.viewCount &&
+							formatQuantityWithCompactNotation(
+								loaderData.metadataDetails.musicSpecifics.viewCount,
+							),
+						loaderData.metadataDetails.musicSpecifics?.byVariousArtists &&
+							"Various Artists",
 					]
 						.filter(Boolean)
 						.join(" • ")}
@@ -439,6 +455,7 @@ export default function Page() {
 										.with(MediaSource.Openlibrary, () => "openlibrary.svg")
 										.with(MediaSource.Tmdb, () => "tmdb.svg")
 										.with(MediaSource.Vndb, () => "vndb.ico")
+										.with(MediaSource.YoutubeMusic, () => "youtube-music.png")
 										.with(MediaSource.Custom, () => undefined)
 										.exhaustive()}`}
 								/>
@@ -467,6 +484,7 @@ export default function Page() {
 											MediaSource.Custom,
 											MediaSource.Itunes,
 											MediaSource.Openlibrary,
+											MediaSource.YoutubeMusic,
 											() => undefined,
 										)
 										.exhaustive()}
diff --git a/apps/frontend/app/routes/_dashboard.media.people.$action.tsx b/apps/frontend/app/routes/_dashboard.media.people.$action.tsx
index 46ae4c82fe..ce15266b90 100644
--- a/apps/frontend/app/routes/_dashboard.media.people.$action.tsx
+++ b/apps/frontend/app/routes/_dashboard.media.people.$action.tsx
@@ -80,6 +80,7 @@ const SEARCH_SOURCES_ALLOWED = [
 	MediaSource.Audible,
 	MediaSource.MangaUpdates,
 	MediaSource.Igdb,
+	MediaSource.YoutubeMusic,
 ] as const;
 
 export const loader = async ({ request, params }: LoaderFunctionArgs) => {
diff --git a/apps/frontend/app/routes/_dashboard.media.people.item.$id._index.tsx b/apps/frontend/app/routes/_dashboard.media.people.item.$id._index.tsx
index 89e01c5651..76e4df0f30 100644
--- a/apps/frontend/app/routes/_dashboard.media.people.item.$id._index.tsx
+++ b/apps/frontend/app/routes/_dashboard.media.people.item.$id._index.tsx
@@ -89,7 +89,7 @@ export default function Page() {
 				images={loaderData.personDetails.details.displayImages}
 				externalLink={{
 					source: loaderData.personDetails.details.source,
-					href: loaderData.personDetails.sourceUrl,
+					href: loaderData.personDetails.details.sourceUrl,
 				}}
 			>
 				<Title id="creator-title">
diff --git a/apps/frontend/public/provider-logos/youtube-music.png b/apps/frontend/public/provider-logos/youtube-music.png
new file mode 100644
index 0000000000..d4d4392573
Binary files /dev/null and b/apps/frontend/public/provider-logos/youtube-music.png differ
diff --git a/ci/cross-pre-build.sh b/ci/cross-pre-build.sh
index 3b15d0e343..b611d9aad2 100644
--- a/ci/cross-pre-build.sh
+++ b/ci/cross-pre-build.sh
@@ -1,6 +1,9 @@
 #!/usr/bin/env bash
 set -euxo pipefail
 
+apt-get update
+apt-get --assume-yes install patch
+
 if [[ "$CROSS_TARGET" == "aarch64-unknown-linux-gnu" ]]; then
   dpkg --add-architecture $CROSS_DEB_ARCH
   apt-get update && apt-get --assume-yes install libssl-dev:$CROSS_DEB_ARCH
diff --git a/crates/background/src/lib.rs b/crates/background/src/lib.rs
index 01fd0824e5..e50b86a756 100644
--- a/crates/background/src/lib.rs
+++ b/crates/background/src/lib.rs
@@ -31,7 +31,6 @@ pub enum ApplicationJob {
     PerformBackgroundTasks,
     RecalculateCalendarEvents,
     ReviseUserWorkouts(String),
-    PerformServerKeyValidation,
     UpdateMetadataGroup(String),
     UpdateMetadata(String, bool),
     HandleOnSeenComplete(String),
diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs
index 23a3651b07..e5ec0deebe 100644
--- a/crates/config/src/lib.rs
+++ b/crates/config/src/lib.rs
@@ -2,7 +2,7 @@ use std::path::PathBuf;
 
 use anyhow::Result;
 use async_graphql::SimpleObject;
-use common_utils::{IsFeatureEnabled, PROJECT_NAME};
+use common_utils::PROJECT_NAME;
 use env_utils::{DEFAULT_MAL_CLIENT_ID, DEFAULT_TMDB_ACCESS_TOKEN};
 use schematic::{derive_enum, validate::not_empty, Config, ConfigEnum, ConfigLoader, HandlerError};
 use serde::{Deserialize, Serialize};
@@ -61,7 +61,9 @@ pub struct AnimeAndMangaConfig {
     pub manga_updates: MangaUpdatesConfig,
 }
 
-impl IsFeatureEnabled for AnimeAndMangaConfig {}
+#[derive(Debug, Serialize, Deserialize, Clone, Config)]
+#[config(rename_all = "snake_case")]
+pub struct MusicConfig {}
 
 #[derive(Debug, Serialize, Deserialize, Clone, Config)]
 #[config(rename_all = "snake_case", env_prefix = "AUDIO_BOOKS_AUDIBLE_")]
@@ -79,8 +81,6 @@ pub struct AudioBookConfig {
     pub audible: AudibleConfig,
 }
 
-impl IsFeatureEnabled for AudioBookConfig {}
-
 derive_enum!(
     #[derive(ConfigEnum, Default)]
     pub enum OpenlibraryCoverImageSize {
@@ -121,8 +121,6 @@ pub struct BookConfig {
     pub google_books: GoogleBooksConfig,
 }
 
-impl IsFeatureEnabled for BookConfig {}
-
 #[derive(Debug, Serialize, Deserialize, Clone, Config, PartialEq, Eq)]
 #[config(rename_all = "snake_case", env_prefix = "DATABASE_")]
 pub struct DatabaseConfig {
@@ -162,8 +160,6 @@ pub struct MovieAndShowConfig {
     pub tmdb: TmdbConfig,
 }
 
-impl IsFeatureEnabled for MovieAndShowConfig {}
-
 #[derive(Debug, Serialize, Deserialize, Clone, Config)]
 #[config(rename_all = "snake_case", env_prefix = "PODCASTS_LISTENNOTES_")]
 pub struct ListenNotesConfig {
@@ -190,8 +186,6 @@ pub struct PodcastConfig {
     pub itunes: ITunesConfig,
 }
 
-impl IsFeatureEnabled for PodcastConfig {}
-
 #[derive(Debug, Serialize, Deserialize, Clone, Config)]
 #[config(rename_all = "snake_case", env_prefix = "VIDEO_GAMES_TWITCH_")]
 pub struct TwitchConfig {
@@ -230,8 +224,8 @@ pub struct VideoGameConfig {
     pub twitch: TwitchConfig,
 }
 
-impl IsFeatureEnabled for VideoGameConfig {
-    fn is_enabled(&self) -> bool {
+impl VideoGameConfig {
+    pub fn is_enabled(&self) -> bool {
         let mut enabled = false;
         if !self.twitch.client_id.is_empty() && !self.twitch.client_secret.is_empty() {
             enabled = true;
@@ -244,8 +238,6 @@ impl IsFeatureEnabled for VideoGameConfig {
 #[config(rename_all = "snake_case", env_prefix = "VISUAL_NOVEL_")]
 pub struct VisualNovelConfig {}
 
-impl IsFeatureEnabled for VisualNovelConfig {}
-
 #[derive(Debug, Serialize, Deserialize, Clone, Config)]
 #[config(rename_all = "snake_case", env_prefix = "FILE_STORAGE_")]
 pub struct FileStorageConfig {
@@ -264,8 +256,8 @@ pub struct FileStorageConfig {
     pub s3_url: String,
 }
 
-impl IsFeatureEnabled for FileStorageConfig {
-    fn is_enabled(&self) -> bool {
+impl FileStorageConfig {
+    pub fn is_enabled(&self) -> bool {
         let mut enabled = false;
         if !self.s3_access_key_id.is_empty()
             && !self.s3_bucket_name.is_empty()
@@ -331,8 +323,8 @@ pub struct SmtpConfig {
     pub mailbox: String,
 }
 
-impl IsFeatureEnabled for SmtpConfig {
-    fn is_enabled(&self) -> bool {
+impl SmtpConfig {
+    pub fn is_enabled(&self) -> bool {
         let mut enabled = false;
         if !self.server.is_empty() && !self.user.is_empty() && !self.password.is_empty() {
             enabled = true;
@@ -408,6 +400,9 @@ pub struct UsersConfig {
 #[derive(Debug, Serialize, Deserialize, Clone, Config)]
 #[config(rename_all = "snake_case")]
 pub struct AppConfig {
+    /// Settings related to music.
+    #[setting(nested)]
+    pub music: MusicConfig,
     /// Settings related to anime and manga.
     #[setting(nested)]
     pub anime_and_manga: AnimeAndMangaConfig,
diff --git a/crates/enums/src/lib.rs b/crates/enums/src/lib.rs
index c4ef6e6a31..cea1fff02b 100644
--- a/crates/enums/src/lib.rs
+++ b/crates/enums/src/lib.rs
@@ -28,14 +28,15 @@ use strum::Display;
 )]
 #[serde(rename_all = "snake_case")]
 pub enum MediaLot {
-    AudioBook,
-    Anime,
     #[default]
     Book,
-    Podcast,
-    Manga,
-    Movie,
     Show,
+    Movie,
+    Anime,
+    Manga,
+    Music,
+    Podcast,
+    AudioBook,
     VideoGame,
     VisualNovel,
 }
@@ -63,19 +64,20 @@ pub enum MediaLot {
 )]
 #[serde(rename_all = "snake_case")]
 pub enum MediaSource {
-    Anilist,
+    Mal,
+    Igdb,
+    Tmdb,
+    Vndb,
     #[default]
-    Audible,
     Custom,
-    GoogleBooks,
-    Igdb,
     Itunes,
+    Anilist,
+    Audible,
     Listennotes,
-    MangaUpdates,
-    Mal,
+    GoogleBooks,
     Openlibrary,
-    Tmdb,
-    Vndb,
+    MangaUpdates,
+    YoutubeMusic,
 }
 
 #[derive(
diff --git a/crates/migrations/src/lib.rs b/crates/migrations/src/lib.rs
index 764923cc0f..281693bb80 100644
--- a/crates/migrations/src/lib.rs
+++ b/crates/migrations/src/lib.rs
@@ -54,6 +54,7 @@ mod m20241126_changes_for_issue_1113;
 mod m20241129_changes_for_issue_1114;
 mod m20241214_create_user_notification;
 mod m20241215_changes_for_issue_1131;
+mod m20241220_changes_for_issue_49;
 
 pub use m20230410_create_metadata::Metadata as AliasedMetadata;
 pub use m20230413_create_person::Person as AliasedPerson;
@@ -129,6 +130,7 @@ impl MigratorTrait for Migrator {
             Box::new(m20241126_changes_for_issue_1113::Migration),
             Box::new(m20241214_create_user_notification::Migration),
             Box::new(m20241215_changes_for_issue_1131::Migration),
+            Box::new(m20241220_changes_for_issue_49::Migration),
         ]
     }
 }
diff --git a/crates/migrations/src/m20230410_create_metadata.rs b/crates/migrations/src/m20230410_create_metadata.rs
index 65f9d7d748..7a3346bd61 100644
--- a/crates/migrations/src/m20230410_create_metadata.rs
+++ b/crates/migrations/src/m20230410_create_metadata.rs
@@ -16,6 +16,7 @@ pub enum Metadata {
     // updated using jobs
     LastUpdatedOn,
     Title,
+    SourceUrl,
     Description,
     // the year this media item was released
     PublishYear,
@@ -53,8 +54,8 @@ pub enum Metadata {
     ShowSpecifics,
     VideoGameSpecifics,
     VisualNovelSpecifics,
+    MusicSpecifics,
     WatchProviders,
-    StateChanges,
     ExternalIdentifiers,
 }
 
@@ -83,6 +84,7 @@ impl MigrationTrait for Migration {
                             .default(Expr::current_timestamp()),
                     )
                     .col(ColumnDef::new(Metadata::Title).text().not_null())
+                    .col(ColumnDef::new(Metadata::SourceUrl).text())
                     .col(ColumnDef::new(Metadata::Description).text())
                     .col(ColumnDef::new(Metadata::PublishYear).integer())
                     .col(ColumnDef::new(Metadata::PublishDate).date())
@@ -103,9 +105,9 @@ impl MigrationTrait for Migration {
                     .col(ColumnDef::new(Metadata::VideoGameSpecifics).json_binary())
                     .col(ColumnDef::new(Metadata::VisualNovelSpecifics).json_binary())
                     .col(ColumnDef::new(Metadata::WatchProviders).json_binary())
-                    .col(ColumnDef::new(Metadata::StateChanges).json_binary())
                     .col(ColumnDef::new(Metadata::IsRecommendation).boolean())
                     .col(ColumnDef::new(Metadata::ExternalIdentifiers).json_binary())
+                    .col(ColumnDef::new(Metadata::MusicSpecifics).json_binary())
                     .to_owned(),
             )
             .await?;
diff --git a/crates/migrations/src/m20230413_create_person.rs b/crates/migrations/src/m20230413_create_person.rs
index 0c4aa9bcd4..f1d5adf3f2 100644
--- a/crates/migrations/src/m20230413_create_person.rs
+++ b/crates/migrations/src/m20230413_create_person.rs
@@ -28,6 +28,7 @@ pub enum Person {
     IsPartial,
     SourceSpecifics,
     StateChanges,
+    SourceUrl,
 }
 
 #[derive(Iden)]
@@ -74,6 +75,7 @@ impl MigrationTrait for Migration {
                     .col(ColumnDef::new(Person::IsPartial).boolean())
                     .col(ColumnDef::new(Person::SourceSpecifics).json_binary())
                     .col(ColumnDef::new(Person::StateChanges).json_binary())
+                    .col(ColumnDef::new(Person::SourceUrl).text())
                     .to_owned(),
             )
             .await?;
diff --git a/crates/migrations/src/m20230501_create_metadata_group.rs b/crates/migrations/src/m20230501_create_metadata_group.rs
index 8b9f44e845..c03fce9df9 100644
--- a/crates/migrations/src/m20230501_create_metadata_group.rs
+++ b/crates/migrations/src/m20230501_create_metadata_group.rs
@@ -15,6 +15,7 @@ pub enum MetadataGroup {
     Lot,
     Source,
     IsPartial,
+    SourceUrl,
 }
 
 #[async_trait::async_trait]
@@ -36,12 +37,9 @@ impl MigrationTrait for Migration {
                     .col(ColumnDef::new(MetadataGroup::Description).text())
                     .col(ColumnDef::new(MetadataGroup::Lot).text().not_null())
                     .col(ColumnDef::new(MetadataGroup::Source).text().not_null())
-                    .col(
-                        ColumnDef::new(MetadataGroup::Images)
-                            .json_binary()
-                            .not_null(),
-                    )
+                    .col(ColumnDef::new(MetadataGroup::Images).json_binary())
                     .col(ColumnDef::new(MetadataGroup::IsPartial).boolean())
+                    .col(ColumnDef::new(MetadataGroup::SourceUrl).text())
                     .to_owned(),
             )
             .await?;
diff --git a/crates/migrations/src/m20240827_create_daily_user_activity.rs b/crates/migrations/src/m20240827_create_daily_user_activity.rs
index 172d0f4876..5a511e4040 100644
--- a/crates/migrations/src/m20240827_create_daily_user_activity.rs
+++ b/crates/migrations/src/m20240827_create_daily_user_activity.rs
@@ -37,6 +37,8 @@ pub enum DailyUserActivity {
     MangaCount,
     MovieCount,
     MovieDuration,
+    MusicCount,
+    MusicDuration,
     ShowCount,
     ShowDuration,
     VideoGameCount,
@@ -99,6 +101,8 @@ pub async fn create_daily_user_activity_table(manager: &SchemaManager<'_>) -> Re
                 .col(integer_not_null(DailyUserActivity::MangaCount))
                 .col(integer_not_null(DailyUserActivity::MovieCount))
                 .col(integer_not_null(DailyUserActivity::MovieDuration))
+                .col(integer_not_null(DailyUserActivity::MusicCount))
+                .col(integer_not_null(DailyUserActivity::MusicDuration))
                 .col(integer_not_null(DailyUserActivity::ShowCount))
                 .col(integer_not_null(DailyUserActivity::ShowDuration))
                 .col(integer_not_null(DailyUserActivity::VideoGameCount))
diff --git a/crates/migrations/src/m20241220_changes_for_issue_49.rs b/crates/migrations/src/m20241220_changes_for_issue_49.rs
new file mode 100644
index 0000000000..cdc74bcd39
--- /dev/null
+++ b/crates/migrations/src/m20241220_changes_for_issue_49.rs
@@ -0,0 +1,49 @@
+use sea_orm_migration::prelude::*;
+
+use crate::m20240827_create_daily_user_activity::create_daily_user_activity_table;
+
+#[derive(DeriveMigrationName)]
+pub struct Migration;
+
+#[async_trait::async_trait]
+impl MigrationTrait for Migration {
+    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
+        let db = manager.get_connection();
+        if !manager.has_column("metadata", "music_specifics").await? {
+            db.execute_unprepared(r#"ALTER TABLE "metadata" ADD COLUMN "music_specifics" JSONB"#)
+                .await?;
+        }
+        if manager.has_column("metadata", "state_changes").await? {
+            db.execute_unprepared(r#"ALTER TABLE "metadata" DROP COLUMN "state_changes""#)
+                .await?;
+        }
+        let tables = vec!["metadata", "metadata_group", "person"];
+        for table in tables {
+            if !manager.has_column(table, "source_url").await? {
+                db.execute_unprepared(&format!(
+                    r#"ALTER TABLE "{}" ADD COLUMN "source_url" TEXT"#,
+                    table
+                ))
+                .await?;
+            }
+        }
+        if !manager
+            .has_column("daily_user_activity", "music_count")
+            .await?
+        {
+            db.execute_unprepared(r#"DROP TABLE "daily_user_activity""#)
+                .await?;
+            create_daily_user_activity_table(manager).await?;
+        }
+        db.execute_unprepared(r#"
+UPDATE "user" SET preferences = jsonb_set(preferences, '{features_enabled,media,music}', 'true', true);
+ALTER TABLE "metadata_group" ALTER COLUMN "images" DROP NOT NULL;
+"#)
+        .await?;
+        Ok(())
+    }
+
+    async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
+        Ok(())
+    }
+}
diff --git a/crates/models/common/src/lib.rs b/crates/models/common/src/lib.rs
index 83350a9260..e303a8e54d 100644
--- a/crates/models/common/src/lib.rs
+++ b/crates/models/common/src/lib.rs
@@ -323,7 +323,6 @@ pub enum ApplicationCacheKey {
     CoreDetails,
     IgdbSettings,
     TmdbSettings,
-    ServerKeyValidated,
     ListennotesSettings,
     UserAnalyticsParameters(UserLevelCacheKey<()>),
     PeopleSearch(UserLevelCacheKey<PeopleSearchInput>),
diff --git a/crates/models/database/src/daily_user_activity.rs b/crates/models/database/src/daily_user_activity.rs
index 513c21881a..6654fbd67f 100644
--- a/crates/models/database/src/daily_user_activity.rs
+++ b/crates/models/database/src/daily_user_activity.rs
@@ -31,6 +31,8 @@ pub struct Model {
     pub manga_count: i32,
     pub movie_count: i32,
     pub movie_duration: i32,
+    pub music_count: i32,
+    pub music_duration: i32,
     pub show_count: i32,
     pub show_duration: i32,
     pub video_game_count: i32,
diff --git a/crates/models/database/src/metadata.rs b/crates/models/database/src/metadata.rs
index 417fafd98a..6708bbcb52 100644
--- a/crates/models/database/src/metadata.rs
+++ b/crates/models/database/src/metadata.rs
@@ -4,8 +4,8 @@ use async_trait::async_trait;
 use chrono::NaiveDate;
 use enums::{MediaLot, MediaSource};
 use media_models::{
-    AnimeSpecifics, AudioBookSpecifics, BookSpecifics, ExternalIdentifiers, MangaSpecifics,
-    MetadataFreeCreator, MetadataImage, MetadataStateChanges, MetadataVideo, MovieSpecifics,
+    AnimeSpecifics, AudioBookSpecifics, BookSpecifics, MangaSpecifics, MetadataExternalIdentifiers,
+    MetadataFreeCreator, MetadataImage, MetadataVideo, MovieSpecifics, MusicSpecifics,
     PodcastSpecifics, ShowSpecifics, VideoGameSpecifics, VisualNovelSpecifics, WatchProvider,
 };
 use nanoid::nanoid;
@@ -26,6 +26,7 @@ pub struct Model {
     pub source: MediaSource,
     pub is_nsfw: Option<bool>,
     pub is_partial: Option<bool>,
+    pub source_url: Option<String>,
     pub is_recommendation: Option<bool>,
     pub description: Option<String>,
     pub original_language: Option<String>,
@@ -42,7 +43,7 @@ pub struct Model {
     #[sea_orm(column_type = "Json")]
     pub watch_providers: Option<Vec<WatchProvider>>,
     #[sea_orm(column_type = "Json")]
-    pub external_identifiers: Option<ExternalIdentifiers>,
+    pub external_identifiers: Option<MetadataExternalIdentifiers>,
     pub audio_book_specifics: Option<AudioBookSpecifics>,
     pub book_specifics: Option<BookSpecifics>,
     pub movie_specifics: Option<MovieSpecifics>,
@@ -52,7 +53,7 @@ pub struct Model {
     pub visual_novel_specifics: Option<VisualNovelSpecifics>,
     pub anime_specifics: Option<AnimeSpecifics>,
     pub manga_specifics: Option<MangaSpecifics>,
-    pub state_changes: Option<MetadataStateChanges>,
+    pub music_specifics: Option<MusicSpecifics>,
 }
 
 #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
diff --git a/crates/models/database/src/metadata_group.rs b/crates/models/database/src/metadata_group.rs
index aa27b3dbe5..dfbd9b8f06 100644
--- a/crates/models/database/src/metadata_group.rs
+++ b/crates/models/database/src/metadata_group.rs
@@ -13,6 +13,7 @@ use serde::{Deserialize, Serialize};
 #[sea_orm(table_name = "metadata_group")]
 #[graphql(name = "MetadataGroup")]
 #[boilermates("MetadataGroupWithoutId")]
+#[boilermates(attr_for("MetadataGroupWithoutId", "#[derive(Clone, Debug)]"))]
 pub struct Model {
     #[boilermates(not_in("MetadataGroupWithoutId"))]
     #[sea_orm(primary_key, auto_increment = false)]
@@ -24,12 +25,13 @@ pub struct Model {
     pub title: String,
     #[sea_orm(column_type = "Json")]
     #[graphql(skip)]
-    pub images: Vec<MetadataImage>,
+    pub images: Option<Vec<MetadataImage>>,
     #[sea_orm(ignore)]
     pub display_images: Vec<String>,
     #[boilermates(not_in("MetadataGroupWithoutId"))]
     pub is_partial: Option<bool>,
     pub description: Option<String>,
+    pub source_url: Option<String>,
 }
 
 #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
diff --git a/crates/models/database/src/person.rs b/crates/models/database/src/person.rs
index 4df7a9f7fa..268253ab8b 100644
--- a/crates/models/database/src/person.rs
+++ b/crates/models/database/src/person.rs
@@ -33,6 +33,7 @@ pub struct Model {
     pub death_date: Option<NaiveDate>,
     pub place: Option<String>,
     pub website: Option<String>,
+    pub source_url: Option<String>,
     #[graphql(skip)]
     pub source_specifics: Option<PersonSourceSpecifics>,
     #[graphql(skip)]
diff --git a/crates/models/dependent/src/lib.rs b/crates/models/dependent/src/lib.rs
index 8aa47ff775..d1a26a1d73 100644
--- a/crates/models/dependent/src/lib.rs
+++ b/crates/models/dependent/src/lib.rs
@@ -134,13 +134,11 @@ pub struct CollectionContents {
 pub struct PersonDetails {
     pub details: person::Model,
     pub contents: Vec<PersonDetailsGroupedByRole>,
-    pub source_url: Option<String>,
 }
 
 #[derive(Debug, Serialize, Deserialize, SimpleObject, Clone)]
 pub struct MetadataGroupDetails {
     pub details: metadata_group::Model,
-    pub source_url: Option<String>,
     pub contents: Vec<String>,
 }
 
@@ -198,6 +196,7 @@ pub struct MetadataLotSourceMappings {
     pub sources: Vec<MediaSource>,
 }
 
+#[skip_serializing_none]
 #[derive(PartialEq, Eq, Clone, Debug, SimpleObject, Serialize, Deserialize)]
 pub struct CoreDetails {
     pub page_size: i32,
@@ -290,7 +289,6 @@ pub struct UserWorkoutTemplateDetails {
     pub collections: Vec<collection::Model>,
 }
 
-#[skip_serializing_none]
 #[derive(
     Debug, Default, SimpleObject, Serialize, Deserialize, Clone, FromQueryResult, PartialEq, Eq,
 )]
@@ -330,7 +328,6 @@ pub struct DailyUserActivityItem {
     pub total_duration: i64,
 }
 
-#[skip_serializing_none]
 #[derive(Debug, Serialize, Deserialize, SimpleObject, Clone, PartialEq, Eq)]
 pub struct DailyUserActivitiesResponse {
     pub total_count: i64,
@@ -340,7 +337,6 @@ pub struct DailyUserActivitiesResponse {
     pub grouped_by: DailyUserActivitiesResponseGroupedBy,
 }
 
-#[skip_serializing_none]
 #[derive(
     Debug, SimpleObject, Serialize, Deserialize, FromJsonQueryResult, Clone, Eq, PartialEq,
 )]
@@ -349,7 +345,6 @@ pub struct FitnessAnalyticsExercise {
     pub exercise: String,
 }
 
-#[skip_serializing_none]
 #[derive(
     Debug, SimpleObject, Serialize, Deserialize, FromJsonQueryResult, Clone, Eq, PartialEq,
 )]
@@ -358,7 +353,6 @@ pub struct FitnessAnalyticsMuscle {
     pub muscle: ExerciseMuscle,
 }
 
-#[skip_serializing_none]
 #[derive(
     Debug, SimpleObject, Serialize, Deserialize, FromJsonQueryResult, Clone, Eq, PartialEq,
 )]
@@ -367,7 +361,6 @@ pub struct FitnessAnalyticsEquipment {
     pub equipment: ExerciseEquipment,
 }
 
-#[skip_serializing_none]
 #[derive(
     Debug, SimpleObject, Serialize, Deserialize, FromJsonQueryResult, Clone, Eq, PartialEq,
 )]
@@ -385,7 +378,6 @@ pub struct UserFitnessAnalytics {
     pub workout_equipments: Vec<FitnessAnalyticsEquipment>,
 }
 
-#[skip_serializing_none]
 #[derive(
     Debug, SimpleObject, Serialize, Deserialize, FromJsonQueryResult, Clone, Eq, PartialEq,
 )]
@@ -417,17 +409,18 @@ pub struct IgdbSettings {
     pub access_token: String,
 }
 
+#[skip_serializing_none]
 #[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize, Clone)]
 pub struct EmptyCacheValue {
     pub _empty: (),
 }
 
-pub type MetadataSearchResponse = SearchResults<MetadataSearchItemResponse>;
 pub type PeopleSearchResponse = SearchResults<PeopleSearchItem>;
+pub type MetadataSearchResponse = SearchResults<MetadataSearchItemResponse>;
 pub type MetadataGroupSearchResponse = SearchResults<MetadataGroupSearchItem>;
 
-#[skip_serializing_none]
 #[derive(Clone, Debug, PartialEq, FromJsonQueryResult, Serialize, Deserialize, Eq)]
+#[serde(untagged)]
 pub enum ApplicationCacheValue {
     Empty(EmptyCacheValue),
     CoreDetails(CoreDetails),
diff --git a/crates/models/media/src/lib.rs b/crates/models/media/src/lib.rs
index 6aa3b5c40f..e9f29efcf6 100644
--- a/crates/models/media/src/lib.rs
+++ b/crates/models/media/src/lib.rs
@@ -48,16 +48,17 @@ pub struct GenreListItem {
     pub num_items: Option<i64>,
 }
 
+#[skip_serializing_none]
 #[derive(
+    Eq,
+    Clone,
     Debug,
+    Default,
+    PartialEq,
     Serialize,
+    InputObject,
     Deserialize,
     SimpleObject,
-    Clone,
-    InputObject,
-    PartialEq,
-    Eq,
-    Default,
     FromJsonQueryResult,
 )]
 #[graphql(input_name = "AudioBookSpecificsInput")]
@@ -65,16 +66,17 @@ pub struct AudioBookSpecifics {
     pub runtime: Option<i32>,
 }
 
+#[skip_serializing_none]
 #[derive(
+    Eq,
     Debug,
+    Clone,
+    Default,
+    PartialEq,
     Serialize,
     Deserialize,
-    SimpleObject,
-    Clone,
     InputObject,
-    PartialEq,
-    Eq,
-    Default,
+    SimpleObject,
     FromJsonQueryResult,
 )]
 #[graphql(input_name = "BookSpecificsInput")]
@@ -82,16 +84,17 @@ pub struct BookSpecifics {
     pub pages: Option<i32>,
 }
 
+#[skip_serializing_none]
 #[derive(
+    Eq,
     Debug,
+    Clone,
+    Default,
+    PartialEq,
     Serialize,
     Deserialize,
-    SimpleObject,
-    Clone,
     InputObject,
-    Eq,
-    PartialEq,
-    Default,
+    SimpleObject,
     FromJsonQueryResult,
 )]
 #[graphql(input_name = "MovieSpecificsInput")]
@@ -99,17 +102,18 @@ pub struct MovieSpecifics {
     pub runtime: Option<i32>,
 }
 
+#[skip_serializing_none]
 #[derive(
+    Eq,
     Debug,
-    Serialize,
-    Deserialize,
-    SimpleObject,
     Clone,
     Default,
-    FromJsonQueryResult,
-    Eq,
     PartialEq,
+    Serialize,
+    Deserialize,
     InputObject,
+    SimpleObject,
+    FromJsonQueryResult,
 )]
 #[graphql(input_name = "PodcastSpecificsInput")]
 pub struct PodcastSpecifics {
@@ -117,17 +121,18 @@ pub struct PodcastSpecifics {
     pub total_episodes: usize,
 }
 
+#[skip_serializing_none]
 #[derive(
+    Eq,
     Debug,
+    Clone,
+    Default,
     PartialEq,
-    Eq,
     Serialize,
     Deserialize,
+    InputObject,
     SimpleObject,
-    Clone,
-    Default,
     FromJsonQueryResult,
-    InputObject,
 )]
 #[graphql(input_name = "PodcastEpisodeInput")]
 #[serde(default)]
@@ -144,17 +149,18 @@ pub struct PodcastEpisode {
     pub thumbnail: Option<String>,
 }
 
+#[skip_serializing_none]
 #[derive(
+    Eq,
     Debug,
+    Clone,
+    Default,
     PartialEq,
-    Eq,
     Serialize,
     Deserialize,
+    InputObject,
     SimpleObject,
-    Clone,
-    Default,
     FromJsonQueryResult,
-    InputObject,
 )]
 #[graphql(input_name = "ShowSpecificsInput")]
 pub struct ShowSpecifics {
@@ -164,18 +170,19 @@ pub struct ShowSpecifics {
     pub total_episodes: Option<usize>,
 }
 
+#[skip_serializing_none]
 #[derive(
+    Eq,
+    Hash,
     Debug,
+    Clone,
+    Default,
     PartialEq,
-    Eq,
     Serialize,
     Deserialize,
+    InputObject,
     SimpleObject,
-    Clone,
-    Default,
     FromJsonQueryResult,
-    Hash,
-    InputObject,
 )]
 #[graphql(input_name = "ShowSeasonSpecificsInput")]
 pub struct ShowSeason {
@@ -189,18 +196,19 @@ pub struct ShowSeason {
     pub backdrop_images: Vec<String>,
 }
 
+#[skip_serializing_none]
 #[derive(
+    Eq,
+    Hash,
     Debug,
+    Clone,
+    Default,
     PartialEq,
-    Eq,
     Serialize,
     Deserialize,
+    InputObject,
     SimpleObject,
-    Clone,
-    Default,
     FromJsonQueryResult,
-    Hash,
-    InputObject,
 )]
 #[graphql(input_name = "ShowEpisodeSpecificsInput")]
 pub struct ShowEpisode {
@@ -213,51 +221,54 @@ pub struct ShowEpisode {
     pub runtime: Option<i32>,
 }
 
+#[skip_serializing_none]
 #[derive(
+    Eq,
     Debug,
+    Clone,
+    Default,
     PartialEq,
-    Eq,
     Serialize,
     Deserialize,
+    InputObject,
     SimpleObject,
-    Clone,
-    Default,
     FromJsonQueryResult,
-    InputObject,
 )]
 #[graphql(input_name = "VideoGameSpecificsInput")]
 pub struct VideoGameSpecifics {
     pub platforms: Vec<String>,
 }
 
+#[skip_serializing_none]
 #[derive(
+    Eq,
     Debug,
+    Clone,
+    Default,
     PartialEq,
-    Eq,
     Serialize,
     Deserialize,
+    InputObject,
     SimpleObject,
-    Clone,
-    Default,
     FromJsonQueryResult,
-    InputObject,
 )]
 #[graphql(input_name = "VisualNovelSpecificsInput")]
 pub struct VisualNovelSpecifics {
     pub length: Option<i32>,
 }
 
+#[skip_serializing_none]
 #[derive(
+    Eq,
     Debug,
+    Clone,
+    Default,
     PartialEq,
-    Eq,
     Serialize,
     Deserialize,
+    InputObject,
     SimpleObject,
-    Clone,
-    Default,
     FromJsonQueryResult,
-    InputObject,
 )]
 #[graphql(input_name = "AnimeAiringScheduleSpecificsInput")]
 pub struct AnimeAiringScheduleSpecifics {
@@ -265,17 +276,18 @@ pub struct AnimeAiringScheduleSpecifics {
     pub airing_at: NaiveDateTime,
 }
 
+#[skip_serializing_none]
 #[derive(
+    Eq,
     Debug,
+    Clone,
+    Default,
     PartialEq,
-    Eq,
     Serialize,
     Deserialize,
+    InputObject,
     SimpleObject,
-    Clone,
-    Default,
     FromJsonQueryResult,
-    InputObject,
 )]
 #[graphql(input_name = "AnimeSpecificsInput")]
 pub struct AnimeSpecifics {
@@ -283,17 +295,38 @@ pub struct AnimeSpecifics {
     pub airing_schedule: Option<Vec<AnimeAiringScheduleSpecifics>>,
 }
 
+#[skip_serializing_none]
 #[derive(
-    Debug,
-    PartialEq,
     Eq,
+    Debug,
+    Clone,
+    Default,
     Serialize,
+    PartialEq,
+    InputObject,
     Deserialize,
     SimpleObject,
+    FromJsonQueryResult,
+)]
+#[graphql(input_name = "MusicSpecificsInput")]
+pub struct MusicSpecifics {
+    pub duration: Option<i32>,
+    pub view_count: Option<i32>,
+    pub by_various_artists: Option<bool>,
+}
+
+#[skip_serializing_none]
+#[derive(
+    Eq,
+    Debug,
     Clone,
     Default,
-    FromJsonQueryResult,
+    PartialEq,
+    Serialize,
+    Deserialize,
     InputObject,
+    SimpleObject,
+    FromJsonQueryResult,
 )]
 #[graphql(input_name = "MangaSpecificsInput")]
 pub struct MangaSpecifics {
@@ -398,6 +431,7 @@ pub struct MetadataPerson {
     pub gender: Option<String>,
     pub place: Option<String>,
     pub website: Option<String>,
+    pub source_url: Option<String>,
     pub description: Option<String>,
     pub images: Option<Vec<String>>,
     pub death_date: Option<NaiveDate>,
@@ -423,7 +457,7 @@ pub struct WatchProvider {
 #[derive(
     Clone, Debug, PartialEq, FromJsonQueryResult, Eq, Serialize, Deserialize, SimpleObject, Default,
 )]
-pub struct ExternalIdentifiers {
+pub struct MetadataExternalIdentifiers {
     pub tvdb_id: Option<i32>,
 }
 
@@ -433,6 +467,7 @@ pub struct MetadataDetails {
     pub is_nsfw: Option<bool>,
     pub title: String,
     pub source: MediaSource,
+    pub source_url: Option<String>,
     pub description: Option<String>,
     pub original_language: Option<String>,
     pub lot: MediaLot,
@@ -451,7 +486,7 @@ pub struct MetadataDetails {
     pub watch_providers: Vec<WatchProvider>,
     pub audio_book_specifics: Option<AudioBookSpecifics>,
     pub book_specifics: Option<BookSpecifics>,
-    pub external_identifiers: Option<ExternalIdentifiers>,
+    pub external_identifiers: Option<MetadataExternalIdentifiers>,
     pub movie_specifics: Option<MovieSpecifics>,
     pub podcast_specifics: Option<PodcastSpecifics>,
     pub show_specifics: Option<ShowSpecifics>,
@@ -459,6 +494,7 @@ pub struct MetadataDetails {
     pub visual_novel_specifics: Option<VisualNovelSpecifics>,
     pub anime_specifics: Option<AnimeSpecifics>,
     pub manga_specifics: Option<MangaSpecifics>,
+    pub music_specifics: Option<MusicSpecifics>,
 }
 
 /// A specific instance when an entity was seen.
@@ -792,14 +828,11 @@ pub struct MetadataGroupSearchItem {
 )]
 pub struct CommitMediaInput {
     pub lot: MediaLot,
-    pub source: MediaSource,
     pub identifier: String,
+    pub source: MediaSource,
     pub force_update: Option<bool>,
 }
 
-#[derive(Debug, Serialize, Deserialize, Clone, FromJsonQueryResult, Eq, PartialEq, Default)]
-pub struct MetadataStateChanges {}
-
 #[derive(
     Debug, Serialize, Deserialize, Clone, FromJsonQueryResult, Eq, PartialEq, Default, Hash,
 )]
@@ -959,21 +992,22 @@ pub struct DeployImportJobInput {
 pub struct CreateCustomMetadataInput {
     pub title: String,
     pub lot: MediaLot,
+    pub is_nsfw: Option<bool>,
+    pub publish_year: Option<i32>,
     pub description: Option<String>,
-    pub creators: Option<Vec<String>>,
     pub genres: Option<Vec<String>>,
     pub images: Option<Vec<String>>,
     pub videos: Option<Vec<String>>,
-    pub is_nsfw: Option<bool>,
-    pub publish_year: Option<i32>,
-    pub audio_book_specifics: Option<AudioBookSpecifics>,
+    pub creators: Option<Vec<String>>,
+    pub show_specifics: Option<ShowSpecifics>,
     pub book_specifics: Option<BookSpecifics>,
+    pub music_specifics: Option<MusicSpecifics>,
     pub movie_specifics: Option<MovieSpecifics>,
-    pub podcast_specifics: Option<PodcastSpecifics>,
-    pub show_specifics: Option<ShowSpecifics>,
-    pub video_game_specifics: Option<VideoGameSpecifics>,
     pub manga_specifics: Option<MangaSpecifics>,
     pub anime_specifics: Option<AnimeSpecifics>,
+    pub podcast_specifics: Option<PodcastSpecifics>,
+    pub audio_book_specifics: Option<AudioBookSpecifics>,
+    pub video_game_specifics: Option<VideoGameSpecifics>,
     pub visual_novel_specifics: Option<VisualNovelSpecifics>,
 }
 
@@ -1210,13 +1244,15 @@ pub struct GraphqlMetadataDetails {
     pub show_specifics: Option<ShowSpecifics>,
     pub book_specifics: Option<BookSpecifics>,
     pub movie_specifics: Option<MovieSpecifics>,
-    pub podcast_specifics: Option<PodcastSpecifics>,
+    pub music_specifics: Option<MusicSpecifics>,
     pub manga_specifics: Option<MangaSpecifics>,
     pub anime_specifics: Option<AnimeSpecifics>,
+    pub podcast_specifics: Option<PodcastSpecifics>,
     pub creators: Vec<MetadataCreatorGroupedByRole>,
     pub audio_book_specifics: Option<AudioBookSpecifics>,
     pub video_game_specifics: Option<VideoGameSpecifics>,
-    pub external_identifiers: Option<ExternalIdentifiers>,
+    #[graphql(skip)]
+    pub external_identifiers: Option<MetadataExternalIdentifiers>,
     pub visual_novel_specifics: Option<VisualNovelSpecifics>,
 }
 
diff --git a/crates/models/user/src/lib.rs b/crates/models/user/src/lib.rs
index 54415accba..673c1aa1cc 100644
--- a/crates/models/user/src/lib.rs
+++ b/crates/models/user/src/lib.rs
@@ -46,6 +46,8 @@ pub struct UserMediaFeaturesEnabledPreferences {
     #[educe(Default = true)]
     pub manga: bool,
     #[educe(Default = true)]
+    pub music: bool,
+    #[educe(Default = true)]
     pub movie: bool,
     #[educe(Default = true)]
     pub podcast: bool,
diff --git a/crates/providers/Cargo.toml b/crates/providers/Cargo.toml
index 8ce8c69875..131511b041 100644
--- a/crates/providers/Cargo.toml
+++ b/crates/providers/Cargo.toml
@@ -19,7 +19,6 @@ educe = { workspace = true }
 enums = { path = "../enums" }
 graphql_client = { workspace = true }
 hashbag = { workspace = true }
-isolang = { workspace = true }
 itertools = { workspace = true }
 media-models = { path = "../models/media" }
 paginate = { workspace = true }
@@ -28,6 +27,7 @@ reqwest = { workspace = true }
 rust_decimal = { workspace = true }
 rust_decimal_macros = { workspace = true }
 rust_iso3166 = { workspace = true }
+rustypipe = { workspace = true }
 scraper = { workspace = true }
 sea-orm = { workspace = true }
 serde = { workspace = true }
diff --git a/crates/providers/src/anilist/mod.rs b/crates/providers/src/anilist/mod.rs
index 298d920fdf..cb7eeb9dd5 100644
--- a/crates/providers/src/anilist/mod.rs
+++ b/crates/providers/src/anilist/mod.rs
@@ -5,7 +5,7 @@ use chrono::NaiveDate;
 use common_models::{PersonSourceSpecifics, SearchDetails, StoredUrl};
 use common_utils::PAGE_SIZE;
 use config::AnilistPreferredLanguage;
-use dependent_models::SearchResults;
+use dependent_models::{PeopleSearchResponse, SearchResults};
 use enums::{MediaLot, MediaSource};
 use graphql_client::{GraphQLQuery, Response};
 use itertools::Itertools;
@@ -18,7 +18,7 @@ use media_models::{
 use reqwest::Client;
 use rust_decimal::Decimal;
 use sea_orm::prelude::DateTimeUtc;
-use traits::{MediaProvider, MediaProviderLanguages};
+use traits::MediaProvider;
 
 static URL: &str = "https://graphql.anilist.co";
 static STUDIO_ROLE: &str = "Production Studio";
@@ -83,16 +83,6 @@ pub struct AnilistService {
     preferred_language: AnilistPreferredLanguage,
 }
 
-impl MediaProviderLanguages for AnilistService {
-    fn supported_languages() -> Vec<String> {
-        ["us"].into_iter().map(String::from).collect()
-    }
-
-    fn default_language() -> String {
-        "us".to_owned()
-    }
-}
-
 impl AnilistService {
     async fn new(config: &config::AnilistConfig) -> Self {
         let client = get_base_http_client(None);
@@ -137,7 +127,7 @@ impl MediaProvider for NonMediaAnilistService {
         page: Option<i32>,
         source_specifics: &Option<PersonSourceSpecifics>,
         _display_nsfw: bool,
-    ) -> Result<SearchResults<PeopleSearchItem>> {
+    ) -> Result<PeopleSearchResponse> {
         let is_studio = matches!(
             source_specifics,
             Some(PersonSourceSpecifics {
@@ -307,6 +297,7 @@ impl MediaProvider for NonMediaAnilistService {
                 website: details.site_url,
                 description: None,
                 gender: None,
+                source_url: None,
                 place: None,
                 images: None,
                 death_date: None,
@@ -442,6 +433,7 @@ impl MediaProvider for NonMediaAnilistService {
                 related,
                 source_specifics: source_specifics.to_owned(),
                 website: None,
+                source_url: None,
             }
         };
         Ok(data)
@@ -703,9 +695,10 @@ async fn media_details(
         title.romaji,
         preferred_language,
     );
+    let identifier = media.id.to_string();
     Ok(MetadataDetails {
-        title,
-        identifier: media.id.to_string(),
+        title: title.clone(),
+        identifier: identifier.clone(),
         is_nsfw: media.is_adult,
         source: MediaSource::Anilist,
         description: media.description,
@@ -713,6 +706,10 @@ async fn media_details(
         people,
         creators: vec![],
         url_images: images,
+        source_url: Some(format!(
+            "https://anilist.co/{}/{}/{}",
+            lot, identifier, title
+        )),
         videos,
         genres: genres.into_iter().unique().collect(),
         publish_year: year,
diff --git a/crates/providers/src/audible.rs b/crates/providers/src/audible.rs
index 660c0ceecd..3e4ebde88d 100644
--- a/crates/providers/src/audible.rs
+++ b/crates/providers/src/audible.rs
@@ -5,7 +5,7 @@ use common_models::{NamedObject, PersonSourceSpecifics, SearchDetails};
 use common_utils::{convert_date_to_year, convert_string_to_date, PAGE_SIZE};
 use convert_case::{Case, Casing};
 use database_models::metadata_group::MetadataGroupWithoutId;
-use dependent_models::SearchResults;
+use dependent_models::{PeopleSearchResponse, SearchResults};
 use educe::Educe;
 use enums::{MediaLot, MediaSource};
 use itertools::Itertools;
@@ -20,9 +20,8 @@ use rust_decimal::Decimal;
 use serde::{Deserialize, Serialize};
 use serde_json::json;
 use strum::{Display, EnumIter, IntoEnumIterator};
-use traits::{MediaProvider, MediaProviderLanguages};
+use traits::MediaProvider;
 
-static LOCALES: [&str; 10] = ["au", "ca", "de", "es", "fr", "in", "it", "jp", "gb", "us"];
 static AUDNEX_URL: &str = "https://api.audnex.us";
 
 #[derive(EnumIter, Display)]
@@ -134,16 +133,6 @@ pub struct AudibleService {
     locale: String,
 }
 
-impl MediaProviderLanguages for AudibleService {
-    fn supported_languages() -> Vec<String> {
-        LOCALES.into_iter().map(String::from).collect()
-    }
-
-    fn default_language() -> String {
-        "us".to_owned()
-    }
-}
-
 impl AudibleService {
     fn url_from_locale(locale: &str) -> String {
         let suffix = match locale {
@@ -181,7 +170,7 @@ impl MediaProvider for AudibleService {
         page: Option<i32>,
         _source_specifics: &Option<PersonSourceSpecifics>,
         _display_nsfw: bool,
-    ) -> Result<SearchResults<PeopleSearchItem>> {
+    ) -> Result<PeopleSearchResponse> {
         let internal_page: usize = page.unwrap_or(1).try_into().unwrap();
         let req_internal_page = internal_page - 1;
         let client = Client::new();
@@ -236,19 +225,24 @@ impl MediaProvider for AudibleService {
             .json()
             .await
             .map_err(|e| anyhow!(e))?;
+        let name = data.name;
         Ok(MetadataPerson {
             place: None,
             gender: None,
             website: None,
-            name: data.name,
             related: vec![],
             death_date: None,
             birth_date: None,
+            name: name.clone(),
             identifier: data.asin,
             source_specifics: None,
-            description: data.description,
             source: MediaSource::Audible,
+            description: data.description,
             images: Some(Vec::from_iter(data.image)),
+            source_url: Some(format!(
+                "https://www.audible.com/author/{}/{}",
+                name, identity
+            )),
         })
     }
 
@@ -293,16 +287,21 @@ impl MediaProvider for AudibleService {
                 is_recommendation: None,
             })
         }
+        let title = data.product.title;
         Ok((
             MetadataGroupWithoutId {
-                display_images: vec![],
-                parts: collection_contents.len().try_into().unwrap(),
-                identifier: identifier.to_owned(),
-                title: data.product.title,
+                images: None,
                 description: None,
-                images: vec![],
+                title: title.clone(),
+                display_images: vec![],
                 lot: MediaLot::AudioBook,
                 source: MediaSource::Audible,
+                identifier: identifier.to_owned(),
+                parts: collection_contents.len().try_into().unwrap(),
+                source_url: Some(format!(
+                    "https://www.audible.com/series/{}/{}",
+                    identifier, title
+                )),
             },
             collection_contents,
         ))
@@ -452,14 +451,20 @@ impl AudibleService {
             None
         };
         MetadataDetails {
-            identifier: item.asin,
+            people,
+            creators,
+            description,
+            url_images: images,
+            provider_rating: rating,
             lot: MediaLot::AudioBook,
+            title: item.title.clone(),
             source: MediaSource::Audible,
+            identifier: item.asin.clone(),
             is_nsfw: item.is_adult_product,
-            title: item.title,
-            description,
-            people,
-            creators,
+            source_url: Some(format!(
+                "https://www.audible.com/pd/{}/{}",
+                item.title, item.asin
+            )),
             genres: item
                 .category_ladders
                 .unwrap_or_default()
@@ -477,8 +482,6 @@ impl AudibleService {
             audio_book_specifics: Some(AudioBookSpecifics {
                 runtime: item.runtime_length_min,
             }),
-            url_images: images,
-            provider_rating: rating,
             ..Default::default()
         }
     }
diff --git a/crates/providers/src/google_books.rs b/crates/providers/src/google_books.rs
index 00846ae875..8d016ffdfb 100644
--- a/crates/providers/src/google_books.rs
+++ b/crates/providers/src/google_books.rs
@@ -17,7 +17,7 @@ use reqwest::{
 };
 use rust_decimal::Decimal;
 use serde::{Deserialize, Serialize};
-use traits::{MediaProvider, MediaProviderLanguages};
+use traits::{MediaProvider, };
 
 static URL: &str = "https://www.googleapis.com/books/v1/volumes";
 
@@ -27,16 +27,6 @@ pub struct GoogleBooksService {
     pass_raw_query: bool,
 }
 
-impl MediaProviderLanguages for GoogleBooksService {
-    fn supported_languages() -> Vec<String> {
-        vec!["us".to_owned()]
-    }
-
-    fn default_language() -> String {
-        "us".to_owned()
-    }
-}
-
 impl GoogleBooksService {
     pub async fn new(config: &config::GoogleBooksConfig) -> Self {
         let client = get_base_http_client(Some(vec![(
@@ -221,11 +211,11 @@ impl GoogleBooksService {
             genres.push(g);
         }
         MetadataDetails {
-            identifier: id,
             lot: MediaLot::Book,
-            source: MediaSource::GoogleBooks,
-            title: item.title,
+            identifier: id.clone(),
+            title: item.title.clone(),
             description: item.description,
+            source: MediaSource::GoogleBooks,
             creators: creators.into_iter().unique().collect(),
             genres: genres.into_iter().unique().collect(),
             publish_year: item.published_date.and_then(|d| convert_date_to_year(&d)),
@@ -235,6 +225,10 @@ impl GoogleBooksService {
             }),
             url_images: images.unique().collect(),
             provider_rating: item.average_rating,
+            source_url: Some(format!(
+                "https://www.google.co.in/books/edition/{}/{}",
+                item.title, id
+            )),
             ..Default::default()
         }
     }
diff --git a/crates/providers/src/igdb.rs b/crates/providers/src/igdb.rs
index 8f9a20400f..543313b4a5 100644
--- a/crates/providers/src/igdb.rs
+++ b/crates/providers/src/igdb.rs
@@ -9,7 +9,10 @@ use common_models::{
 };
 use common_utils::{ryot_log, PAGE_SIZE};
 use database_models::metadata_group::MetadataGroupWithoutId;
-use dependent_models::{ApplicationCacheValue, IgdbSettings, SearchResults};
+use dependent_models::{
+    ApplicationCacheValue, IgdbSettings, MetadataGroupSearchResponse, PeopleSearchResponse,
+    SearchResults,
+};
 use enums::{MediaLot, MediaSource};
 use itertools::Itertools;
 use media_models::{
@@ -28,7 +31,7 @@ use serde::{Deserialize, Serialize};
 use serde_json::{json, Value};
 use serde_with::{formats::Flexible, serde_as, TimestampSeconds};
 use supporting_service::SupportingService;
-use traits::{MediaProvider, MediaProviderLanguages};
+use traits::MediaProvider;
 
 static URL: &str = "https://api.igdb.com/v4";
 static IMAGE_URL: &str = "https://images.igdb.com/igdb/image/upload";
@@ -160,16 +163,6 @@ pub struct IgdbService {
     supporting_service: Arc<SupportingService>,
 }
 
-impl MediaProviderLanguages for IgdbService {
-    fn supported_languages() -> Vec<String> {
-        ["us"].into_iter().map(String::from).collect()
-    }
-
-    fn default_language() -> String {
-        "us".to_owned()
-    }
-}
-
 impl IgdbService {
     pub async fn new(ss: Arc<SupportingService>) -> Self {
         let config = ss.config.video_games.clone();
@@ -189,7 +182,7 @@ impl MediaProvider for IgdbService {
         query: &str,
         page: Option<i32>,
         _display_nsfw: bool,
-    ) -> Result<SearchResults<MetadataGroupSearchItem>> {
+    ) -> Result<MetadataGroupSearchResponse> {
         let client = self.get_client_config().await?;
         let req_body = format!(
             r#"
@@ -271,16 +264,18 @@ where id = {id};
                 }
             })
             .collect_vec();
+        let title = details.name.unwrap_or_default();
         Ok((
             MetadataGroupWithoutId {
-                display_images: vec![],
-                parts: items.len().try_into().unwrap(),
-                identifier: details.id.to_string(),
-                title: details.name.unwrap_or_default(),
+                images: None,
                 description: None,
-                images: vec![],
+                title: title.clone(),
+                display_images: vec![],
                 lot: MediaLot::VideoGame,
                 source: MediaSource::Igdb,
+                identifier: details.id.to_string(),
+                parts: items.len().try_into().unwrap(),
+                source_url: Some(format!("https://www.igdb.com/collection/{}", title)),
             },
             items,
         ))
@@ -292,7 +287,7 @@ where id = {id};
         page: Option<i32>,
         _source_specifics: &Option<PersonSourceSpecifics>,
         _display_nsfw: bool,
-    ) -> Result<SearchResults<PeopleSearchItem>> {
+    ) -> Result<PeopleSearchResponse> {
         let client = self.get_client_config().await?;
         let req_body = format!(
             r#"
@@ -394,14 +389,21 @@ where id = {id};
                 },
             }
         }));
+        let name = detail.name;
         Ok(MetadataPerson {
+            related,
+            gender: None,
+            birth_date: None,
+            death_date: None,
+            name: name.clone(),
+            source_specifics: None,
+            source: MediaSource::Igdb,
+            description: detail.description,
             identifier: detail.id.to_string(),
-            name: detail.name,
+            source_url: Some(format!("https://www.igdb.com/companies/{}", name)),
             images: Some(Vec::from_iter(
                 detail.logo.map(|l| self.get_cover_image_url(l.image_id)),
             )),
-            source: MediaSource::Igdb,
-            description: detail.description,
             place: detail
                 .country
                 .and_then(from_numeric)
@@ -411,11 +413,6 @@ where id = {id};
                 .unwrap_or_default()
                 .first()
                 .map(|i| i.url.clone()),
-            related,
-            birth_date: None,
-            death_date: None,
-            gender: None,
-            source_specifics: None,
         })
     }
 
@@ -539,9 +536,8 @@ impl IgdbService {
         let cc = &self.supporting_service.cache_service;
         let maybe_settings = cc
             .get_value::<IgdbSettings>(ApplicationCacheKey::IgdbSettings)
-            .await
-            .ok();
-        let access_token = if let Some(value) = maybe_settings.flatten() {
+            .await;
+        let access_token = if let Some(value) = maybe_settings {
             value.access_token
         } else {
             let access_token = self.get_access_token().await;
@@ -613,17 +609,19 @@ impl IgdbService {
                 source: MetadataVideoSource::Youtube,
             })
             .collect_vec();
+        let title = item.name.unwrap();
         MetadataDetails {
-            identifier: item.id.to_string(),
+            title: title.clone(),
             lot: MediaLot::VideoGame,
             source: MediaSource::Igdb,
-            title: item.name.unwrap(),
             description: item.summary,
+            identifier: item.id.to_string(),
             people,
             url_images: images,
             videos,
             publish_date: item.first_release_date.map(|d| d.date_naive()),
             publish_year: item.first_release_date.map(|d| d.year()),
+            source_url: Some(format!("https://www.igdb.com/games/{}", title)),
             genres: item
                 .genres
                 .unwrap_or_default()
diff --git a/crates/providers/src/itunes.rs b/crates/providers/src/itunes.rs
index bc186c16b7..2b9115dc42 100644
--- a/crates/providers/src/itunes.rs
+++ b/crates/providers/src/itunes.rs
@@ -14,7 +14,7 @@ use media_models::{
 use reqwest::Client;
 use sea_orm::prelude::ChronoDateTimeUtc;
 use serde::{Deserialize, Serialize};
-use traits::{MediaProvider, MediaProviderLanguages};
+use traits::{MediaProvider, };
 
 static URL: &str = "https://itunes.apple.com";
 
@@ -24,16 +24,6 @@ pub struct ITunesService {
     language: String,
 }
 
-impl MediaProviderLanguages for ITunesService {
-    fn supported_languages() -> Vec<String> {
-        ["en_us", "ja_jp"].into_iter().map(String::from).collect()
-    }
-
-    fn default_language() -> String {
-        "en_us".to_owned()
-    }
-}
-
 impl ITunesService {
     pub async fn new(config: &config::ITunesConfig) -> Self {
         let client = get_base_http_client(None);
@@ -154,16 +144,20 @@ impl MediaProvider for ITunesService {
             .collect_vec();
         episodes.reverse();
         Ok(MetadataDetails {
-            identifier: details.identifier,
-            title: details.title,
+            genres,
+            creators,
+            url_images,
+            description,
             publish_date,
-            publish_year: publish_date.map(|d| d.year()),
-            source: MediaSource::Itunes,
             lot: MediaLot::Podcast,
-            description,
-            url_images,
-            creators,
-            genres,
+            source: MediaSource::Itunes,
+            title: details.title.clone(),
+            identifier: details.identifier,
+            publish_year: publish_date.map(|d| d.year()),
+            source_url: Some(format!(
+                "https://podcasts.apple.com/us/podcast/{}/id{}",
+                details.title, identifier
+            )),
             podcast_specifics: Some(PodcastSpecifics {
                 total_episodes: episodes.len(),
                 episodes,
diff --git a/crates/providers/src/lib.rs b/crates/providers/src/lib.rs
index 113854ee55..a8f9282b89 100644
--- a/crates/providers/src/lib.rs
+++ b/crates/providers/src/lib.rs
@@ -9,3 +9,4 @@ pub mod manga_updates;
 pub mod openlibrary;
 pub mod tmdb;
 pub mod vndb;
+pub mod youtube_music;
diff --git a/crates/providers/src/listennotes.rs b/crates/providers/src/listennotes.rs
index 3ea99486fe..62a7e6cc5a 100644
--- a/crates/providers/src/listennotes.rs
+++ b/crates/providers/src/listennotes.rs
@@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize};
 use serde_json::json;
 use serde_with::{formats::Flexible, serde_as, TimestampMilliSeconds};
 use supporting_service::SupportingService;
-use traits::{MediaProvider, MediaProviderLanguages};
+use traits::MediaProvider;
 
 static URL: &str = "https://listen-api.listennotes.com/api/v2";
 
@@ -33,16 +33,6 @@ pub struct ListennotesService {
     supporting_service: Arc<SupportingService>,
 }
 
-impl MediaProviderLanguages for ListennotesService {
-    fn supported_languages() -> Vec<String> {
-        ["us"].into_iter().map(String::from).collect()
-    }
-
-    fn default_language() -> String {
-        "us".to_owned()
-    }
-}
-
 impl ListennotesService {
     pub async fn new(ss: Arc<SupportingService>) -> Self {
         let url = env::var("LISTENNOTES_API_URL")
@@ -188,9 +178,8 @@ impl ListennotesService {
         let cc = &self.supporting_service.cache_service;
         let maybe_settings = cc
             .get_value::<ListennotesSettings>(ApplicationCacheKey::ListennotesSettings)
-            .await
-            .ok();
-        let genres = if let Some(value) = maybe_settings.flatten() {
+            .await;
+        let genres = if let Some(value) = maybe_settings {
             value.genres
         } else {
             #[derive(Debug, Serialize, Deserialize, Default)]
@@ -266,12 +255,16 @@ impl ListennotesService {
         let podcast_data: Podcast = resp.json().await.map_err(|e| anyhow!(e))?;
         let genres = self.get_genres().await?;
         Ok(MetadataDetails {
-            identifier: podcast_data.id,
-            title: podcast_data.title,
-            is_nsfw: podcast_data.explicit_content,
-            description: podcast_data.description,
             lot: MediaLot::Podcast,
+            identifier: podcast_data.id,
             source: MediaSource::Listennotes,
+            title: podcast_data.title.clone(),
+            description: podcast_data.description,
+            is_nsfw: podcast_data.explicit_content,
+            source_url: Some(format!(
+                "https://www.listennotes.com/podcasts/{}-{}",
+                podcast_data.title, identifier
+            )),
             creators: Vec::from_iter(podcast_data.publisher.map(|p| MetadataFreeCreator {
                 name: p,
                 role: "Publishing".to_owned(),
diff --git a/crates/providers/src/mal.rs b/crates/providers/src/mal.rs
index 05cde95045..76e31523db 100644
--- a/crates/providers/src/mal.rs
+++ b/crates/providers/src/mal.rs
@@ -18,7 +18,7 @@ use reqwest::{
 use rust_decimal::Decimal;
 use serde::{Deserialize, Serialize};
 use serde_json::json;
-use traits::{MediaProvider, MediaProviderLanguages};
+use traits::{MediaProvider, };
 
 static URL: &str = "https://api.myanimelist.net/v2";
 
@@ -27,16 +27,6 @@ pub struct MalService {
     client: Client,
 }
 
-impl MediaProviderLanguages for MalService {
-    fn supported_languages() -> Vec<String> {
-        ["us"].into_iter().map(String::from).collect()
-    }
-
-    fn default_language() -> String {
-        "us".to_owned()
-    }
-}
-
 #[derive(Debug, Clone)]
 pub struct NonMediaMalService {}
 
@@ -256,13 +246,19 @@ async fn details(client: &Client, media_type: &str, id: &str) -> Result<Metadata
     }
     suggestions.shuffle(&mut rng());
     let is_nsfw = details.nsfw.map(|n| !matches!(n.as_str(), "white"));
+    let identifier = details.id.to_string();
+    let title = details.title;
     let data = MetadataDetails {
-        identifier: details.id.to_string(),
-        title: details.title,
+        identifier: identifier.clone(),
+        title: title.clone(),
         source: MediaSource::Mal,
         description: details.synopsis,
         lot,
         is_nsfw,
+        source_url: Some(format!(
+            "https://myanimelist.net/{}/{}/{}",
+            media_type, identifier, title
+        )),
         production_status: details.status.map(|s| s.to_case(Case::Title)),
         genres: details
             .genres
diff --git a/crates/providers/src/manga_updates.rs b/crates/providers/src/manga_updates.rs
index 7b037a8e44..11ef20c704 100644
--- a/crates/providers/src/manga_updates.rs
+++ b/crates/providers/src/manga_updates.rs
@@ -4,7 +4,7 @@ use async_trait::async_trait;
 use chrono::NaiveDate;
 use common_models::{PersonSourceSpecifics, SearchDetails};
 use common_utils::PAGE_SIZE;
-use dependent_models::SearchResults;
+use dependent_models::{PeopleSearchResponse, SearchResults};
 use enums::{MediaLot, MediaSource};
 use itertools::Itertools;
 use media_models::{
@@ -15,7 +15,7 @@ use media_models::{
 use reqwest::Client;
 use rust_decimal::Decimal;
 use serde::{Deserialize, Serialize};
-use traits::{MediaProvider, MediaProviderLanguages};
+use traits::{MediaProvider, };
 
 static URL: &str = "https://api.mangaupdates.com/v1";
 
@@ -24,16 +24,6 @@ pub struct MangaUpdatesService {
     client: Client,
 }
 
-impl MediaProviderLanguages for MangaUpdatesService {
-    fn supported_languages() -> Vec<String> {
-        vec!["us".to_owned()]
-    }
-
-    fn default_language() -> String {
-        "us".to_owned()
-    }
-}
-
 impl MangaUpdatesService {
     pub async fn new(_config: &config::MangaUpdatesConfig) -> Self {
         let client = get_base_http_client(None);
@@ -172,7 +162,7 @@ impl MediaProvider for MangaUpdatesService {
         page: Option<i32>,
         _source_specifics: &Option<PersonSourceSpecifics>,
         _display_nsfw: bool,
-    ) -> Result<SearchResults<PeopleSearchItem>> {
+    ) -> Result<PeopleSearchResponse> {
         let data: MetadataSearchResponse<PersonItemResponse> = self
             .client
             .post(format!("{}/authors/search", URL))
@@ -251,11 +241,17 @@ impl MediaProvider for MangaUpdatesService {
             })
             .collect_vec();
         let resp = MetadataPerson {
-            identifier: identity.to_owned(),
-            source: MediaSource::MangaUpdates,
-            name: data.name.unwrap(),
+            related,
+            website: None,
+            death_date: None,
+            source_url: None,
+            description: None,
             gender: data.gender,
             place: data.birthplace,
+            source_specifics: None,
+            name: data.name.unwrap(),
+            identifier: identity.to_owned(),
+            source: MediaSource::MangaUpdates,
             images: Some(Vec::from_iter(data.image.and_then(|i| i.url.original))),
             birth_date: data.birthday.and_then(|b| {
                 if let (Some(y), Some(m), Some(d)) = (b.year, b.month, b.day) {
@@ -264,11 +260,6 @@ impl MediaProvider for MangaUpdatesService {
                     None
                 }
             }),
-            related,
-            death_date: None,
-            description: None,
-            website: None,
-            source_specifics: None,
         };
         Ok(resp)
     }
diff --git a/crates/providers/src/openlibrary.rs b/crates/providers/src/openlibrary.rs
index 51de37df31..231b1e4c7c 100644
--- a/crates/providers/src/openlibrary.rs
+++ b/crates/providers/src/openlibrary.rs
@@ -5,7 +5,7 @@ use chrono::{Datelike, NaiveDate};
 use common_models::{PersonSourceSpecifics, SearchDetails};
 use common_utils::{ryot_log, PAGE_SIZE};
 use convert_case::{Case, Casing};
-use dependent_models::SearchResults;
+use dependent_models::{PeopleSearchResponse, SearchResults};
 use enums::{MediaLot, MediaSource};
 use itertools::Itertools;
 use media_models::{
@@ -17,7 +17,7 @@ use reqwest::Client;
 use scraper::{Html, Selector};
 use serde::{Deserialize, Serialize};
 use serde_json::json;
-use traits::{MediaProvider, MediaProviderLanguages};
+use traits::{MediaProvider, };
 
 static URL: &str = "https://openlibrary.org";
 static IMAGE_BASE_URL: &str = "https://covers.openlibrary.org";
@@ -91,16 +91,6 @@ struct OpenAuthorLibrarySearchResponse {
     docs: Vec<PeopleSearchOpenlibraryAuthor>,
 }
 
-impl MediaProviderLanguages for OpenlibraryService {
-    fn supported_languages() -> Vec<String> {
-        ["us"].into_iter().map(String::from).collect()
-    }
-
-    fn default_language() -> String {
-        "us".to_owned()
-    }
-}
-
 impl OpenlibraryService {
     pub async fn new(config: &config::OpenlibraryConfig) -> Self {
         let client = get_base_http_client(None);
@@ -175,7 +165,7 @@ impl MediaProvider for OpenlibraryService {
         page: Option<i32>,
         _source_specifics: &Option<PersonSourceSpecifics>,
         _display_nsfw: bool,
-    ) -> Result<SearchResults<PeopleSearchItem>> {
+    ) -> Result<PeopleSearchResponse> {
         let page = page.unwrap_or(1);
         let rsp = self
             .client
@@ -215,12 +205,12 @@ impl MediaProvider for OpenlibraryService {
 
     async fn person_details(
         &self,
-        identity: &str,
+        identifier: &str,
         _source_specifics: &Option<PersonSourceSpecifics>,
     ) -> Result<MetadataPerson> {
         let rsp = self
             .client
-            .get(format!("{}/authors/{}.json", URL, identity))
+            .get(format!("{}/authors/{}.json", URL, identifier))
             .send()
             .await
             .map_err(|e| anyhow!(e))?;
@@ -240,7 +230,7 @@ impl MediaProvider for OpenlibraryService {
             .collect();
         let author_works: OpenlibraryEditionsResponse = self
             .client
-            .get(format!("{}/authors/{}/works.json", URL, identity))
+            .get(format!("{}/authors/{}/works.json", URL, identifier))
             .query(&serde_json::json!({ "limit": 600 }))
             .send()
             .await
@@ -275,21 +265,26 @@ impl MediaProvider for OpenlibraryService {
             }
         }
         ryot_log!(debug, "Found {} related works.", related.len());
+        let name = data.name;
         Ok(MetadataPerson {
-            identifier,
-            source: MediaSource::Openlibrary,
-            name: data.name,
+            related,
+            place: None,
             description,
+            gender: None,
+            name: name.clone(),
             images: Some(images),
+            source_specifics: None,
+            identifier: identifier.clone(),
+            source: MediaSource::Openlibrary,
+            birth_date: data.birth_date.and_then(|b| parse_date(&b)),
+            death_date: data.death_date.and_then(|b| parse_date(&b)),
+            source_url: Some(format!(
+                "https://openlibrary.org/authors/{}/{}",
+                identifier, name
+            )),
             website: data
                 .links
                 .and_then(|l| l.first().and_then(|a| a.url.clone())),
-            birth_date: data.birth_date.and_then(|b| parse_date(&b)),
-            death_date: data.death_date.and_then(|b| parse_date(&b)),
-            related,
-            gender: None,
-            place: None,
-            source_specifics: None,
         })
     }
 
@@ -447,21 +442,25 @@ impl MediaProvider for OpenlibraryService {
                 });
             }
         }
-
+        let identifier = get_key(&data.key);
         Ok(MetadataDetails {
-            identifier: get_key(&data.key),
-            title: data.title,
-            description,
-            lot: MediaLot::Book,
-            source: MediaSource::Openlibrary,
             people,
             genres,
+            description,
+            suggestions,
             url_images: images,
+            lot: MediaLot::Book,
+            title: data.title.clone(),
+            identifier: identifier.clone(),
+            source: MediaSource::Openlibrary,
             publish_year: first_release_date.map(|d| d.year()),
+            source_url: Some(format!(
+                "https://openlibrary.org/works/{}/{}",
+                identifier, data.title
+            )),
             book_specifics: Some(BookSpecifics {
                 pages: Some(num_pages),
             }),
-            suggestions,
             ..Default::default()
         })
     }
diff --git a/crates/providers/src/tmdb.rs b/crates/providers/src/tmdb.rs
index 20d7d42ffb..f15f5b6a9c 100644
--- a/crates/providers/src/tmdb.rs
+++ b/crates/providers/src/tmdb.rs
@@ -12,12 +12,15 @@ use common_models::{
 };
 use common_utils::{convert_date_to_year, convert_string_to_date, SHOW_SPECIAL_SEASON_NAMES};
 use database_models::metadata_group::MetadataGroupWithoutId;
-use dependent_models::{ApplicationCacheValue, SearchResults, TmdbLanguage, TmdbSettings};
+use dependent_models::{
+    ApplicationCacheValue, MetadataGroupSearchResponse, PeopleSearchResponse, SearchResults,
+    TmdbLanguage, TmdbSettings,
+};
 use enums::{MediaLot, MediaSource};
 use hashbag::HashBag;
 use itertools::Itertools;
 use media_models::{
-    ExternalIdentifiers, MetadataDetails, MetadataGroupSearchItem, MetadataImage,
+    MetadataDetails, MetadataExternalIdentifiers, MetadataGroupSearchItem, MetadataImage,
     MetadataImageForMediaDetails, MetadataPerson, MetadataPersonRelated, MetadataSearchItem,
     MetadataVideo, MetadataVideoSource, MovieSpecifics, PartialMetadataPerson,
     PartialMetadataWithoutId, PeopleSearchItem, ShowEpisode, ShowSeason, ShowSpecifics,
@@ -33,7 +36,7 @@ use sea_orm::prelude::DateTimeUtc;
 use serde::{Deserialize, Serialize};
 use serde_json::json;
 use supporting_service::SupportingService;
-use traits::{MediaProvider, MediaProviderLanguages};
+use traits::MediaProvider;
 
 static URL: &str = "https://api.themoviedb.org/3";
 
@@ -375,7 +378,7 @@ impl TmdbService {
         &self,
         type_: &str,
         identifier: &str,
-    ) -> Result<ExternalIdentifiers> {
+    ) -> Result<MetadataExternalIdentifiers> {
         let rsp = self
             .client
             .get(format!("{}/{}/{}/external_ids", URL, type_, identifier))
@@ -386,18 +389,6 @@ impl TmdbService {
     }
 }
 
-impl MediaProviderLanguages for TmdbService {
-    fn supported_languages() -> Vec<String> {
-        isolang::languages()
-            .filter_map(|l| l.to_639_1().map(String::from))
-            .collect()
-    }
-
-    fn default_language() -> String {
-        "en".to_owned()
-    }
-}
-
 pub struct NonMediaTmdbService {
     base: TmdbService,
 }
@@ -418,7 +409,7 @@ impl MediaProvider for NonMediaTmdbService {
         page: Option<i32>,
         source_specifics: &Option<PersonSourceSpecifics>,
         display_nsfw: bool,
-    ) -> Result<SearchResults<PeopleSearchItem>> {
+    ) -> Result<PeopleSearchResponse> {
         let language = &self
             .base
             .supporting_service
@@ -474,7 +465,7 @@ impl MediaProvider for NonMediaTmdbService {
 
     async fn person_details(
         &self,
-        identity: &str,
+        identifier: &str,
         source_specifics: &Option<PersonSourceSpecifics>,
     ) -> Result<MetadataPerson> {
         let type_ = match source_specifics {
@@ -487,7 +478,7 @@ impl MediaProvider for NonMediaTmdbService {
         let details: TmdbNonMediaEntity = self
             .base
             .client
-            .get(format!("{}/{}/{}", URL, type_, identity))
+            .get(format!("{}/{}/{}", URL, type_, identifier))
             .query(&json!({ "language": self.base.language }))
             .send()
             .await
@@ -497,7 +488,7 @@ impl MediaProvider for NonMediaTmdbService {
             .map_err(|e| anyhow!(e))?;
         let mut images = vec![];
         self.base
-            .save_all_images(type_, identity, &mut images)
+            .save_all_images(type_, identifier, &mut images)
             .await?;
         let images = images
             .into_iter()
@@ -510,7 +501,7 @@ impl MediaProvider for NonMediaTmdbService {
             let cred_det: TmdbCreditsResponse = self
                 .base
                 .client
-                .get(format!("{}/{}/{}/combined_credits", URL, type_, identity))
+                .get(format!("{}/{}/{}/combined_credits", URL, type_, identifier))
                 .query(&json!({ "language": self.base.language }))
                 .send()
                 .await
@@ -545,7 +536,7 @@ impl MediaProvider for NonMediaTmdbService {
                         .client
                         .get(format!("{}/discover/{}", URL, m_typ))
                         .query(
-                            &json!({ "with_companies": identity, "page": i, "language": self.base.language }),
+                            &json!({ "with_companies": identifier, "page": i, "language": self.base.language }),
                         )
                         .send()
                         .await
@@ -575,24 +566,29 @@ impl MediaProvider for NonMediaTmdbService {
                 }
             }
         }
+        let name = details.name;
         let resp = MetadataPerson {
-            name: details.name,
+            related,
+            name: name.clone(),
             images: Some(images),
-            identifier: details.id.to_string(),
-            description: description.and_then(|s| if s.as_str() == "" { None } else { Some(s) }),
             source: MediaSource::Tmdb,
-            place: details.origin_country.or(details.place_of_birth),
             website: details.homepage,
             birth_date: details.birthday,
             death_date: details.deathday,
+            identifier: details.id.to_string(),
+            source_specifics: source_specifics.to_owned(),
+            place: details.origin_country.or(details.place_of_birth),
+            source_url: Some(format!(
+                "https://www.themoviedb.org/person/{}-{}",
+                identifier, name
+            )),
+            description: description.and_then(|s| if s.as_str() == "" { None } else { Some(s) }),
             gender: details.gender.and_then(|g| match g {
                 1 => Some("Female".to_owned()),
                 2 => Some("Male".to_owned()),
                 3 => Some("Non-Binary".to_owned()),
                 _ => None,
             }),
-            source_specifics: source_specifics.to_owned(),
-            related,
         };
         Ok(resp)
     }
@@ -790,21 +786,22 @@ impl MediaProvider for TmdbMovieService {
             .base
             .get_external_identifiers("movie", identifier)
             .await?;
+        let title = data.title.unwrap();
         Ok(MetadataDetails {
+            people,
+            title: title.clone(),
             identifier: data.id.to_string(),
             is_nsfw: data.adult,
-            original_language: self.base.get_language_name(data.original_language),
             lot: MediaLot::Movie,
             source: MediaSource::Tmdb,
             production_status: data.status,
-            title: data.title.unwrap(),
             genres: data
                 .genres
                 .unwrap_or_default()
                 .into_iter()
                 .map(|g| g.name)
                 .collect(),
-            people,
+            original_language: self.base.get_language_name(data.original_language),
             url_images: image_ids
                 .into_iter()
                 .unique()
@@ -823,6 +820,10 @@ impl MediaProvider for TmdbMovieService {
                 runtime: data.runtime,
             }),
             suggestions,
+            source_url: Some(format!(
+                "https://www.themoviedb.org/movie/{}-{}",
+                data.id, title
+            )),
             provider_rating: if let Some(av) = data.vote_average {
                 if av != dec!(0) {
                     Some(av * dec!(10))
@@ -853,7 +854,7 @@ impl MediaProvider for TmdbMovieService {
         query: &str,
         page: Option<i32>,
         display_nsfw: bool,
-    ) -> Result<SearchResults<MetadataGroupSearchItem>> {
+    ) -> Result<MetadataGroupSearchResponse> {
         let page = page.unwrap_or(1);
         let rsp = self
             .base
@@ -939,22 +940,29 @@ impl MediaProvider for TmdbMovieService {
                 is_recommendation: None,
             })
             .collect_vec();
+        let title = replace_from_end(data.name, " Collection", "");
         Ok((
             MetadataGroupWithoutId {
-                display_images: vec![],
-                parts: parts.len().try_into().unwrap(),
-                identifier: identifier.to_owned(),
-                title: replace_from_end(data.name, " Collection", ""),
-                description: data.overview,
-                images: images
-                    .into_iter()
-                    .unique()
-                    .map(|p| MetadataImage {
-                        url: StoredUrl::Url(self.base.get_image_url(p)),
-                    })
-                    .collect(),
                 lot: MediaLot::Movie,
+                title: title.clone(),
+                display_images: vec![],
                 source: MediaSource::Tmdb,
+                description: data.overview,
+                identifier: identifier.to_owned(),
+                parts: parts.len().try_into().unwrap(),
+                source_url: Some(format!(
+                    "https://www.themoviedb.org/collections/{}-{}",
+                    identifier, title
+                )),
+                images: Some(
+                    images
+                        .into_iter()
+                        .unique()
+                        .map(|p| MetadataImage {
+                            url: StoredUrl::Url(self.base.get_image_url(p)),
+                        })
+                        .collect(),
+                ),
             },
             parts,
         ))
@@ -1144,16 +1152,17 @@ impl MediaProvider for TmdbShowService {
             .count();
         let watch_providers = self.base.get_all_watch_providers("tv", identifier).await?;
         let external_identifiers = self.base.get_external_identifiers("tv", identifier).await?;
+        let title = show_data.name.unwrap();
         Ok(MetadataDetails {
-            identifier: show_data.id.to_string(),
-            title: show_data.name.unwrap(),
-            is_nsfw: show_data.adult,
-            original_language: self.base.get_language_name(show_data.original_language),
+            people,
             lot: MediaLot::Show,
-            production_status: show_data.status,
+            title: title.clone(),
+            is_nsfw: show_data.adult,
             source: MediaSource::Tmdb,
             description: show_data.overview,
-            people,
+            production_status: show_data.status,
+            identifier: show_data.id.to_string(),
+            original_language: self.base.get_language_name(show_data.original_language),
             genres: show_data
                 .genres
                 .unwrap_or_default()
@@ -1164,6 +1173,10 @@ impl MediaProvider for TmdbShowService {
             publish_date: convert_string_to_date(
                 &show_data.first_air_date.clone().unwrap_or_default(),
             ),
+            source_url: Some(format!(
+                "https://www.themoviedb.org/tv/{}-{}",
+                show_data.id, title
+            )),
             url_images: image_ids
                 .into_iter()
                 .unique()
@@ -1320,9 +1333,8 @@ async fn get_settings(
     let cc = &supporting_service.cache_service;
     let maybe_settings = cc
         .get_value::<TmdbSettings>(ApplicationCacheKey::TmdbSettings)
-        .await
-        .ok();
-    let tmdb_settings = if let Some(setting) = maybe_settings.flatten() {
+        .await;
+    let tmdb_settings = if let Some(setting) = maybe_settings {
         setting
     } else {
         #[derive(Debug, Serialize, Deserialize, Clone)]
diff --git a/crates/providers/src/vndb.rs b/crates/providers/src/vndb.rs
index 42dce07244..23cb9ba537 100644
--- a/crates/providers/src/vndb.rs
+++ b/crates/providers/src/vndb.rs
@@ -3,7 +3,7 @@ use application_utils::get_base_http_client;
 use async_trait::async_trait;
 use common_models::{NamedObject, PersonSourceSpecifics, SearchDetails};
 use common_utils::{convert_date_to_year, convert_string_to_date, PAGE_SIZE};
-use dependent_models::SearchResults;
+use dependent_models::{PeopleSearchResponse, SearchResults};
 use enums::{MediaLot, MediaSource};
 use itertools::Itertools;
 use media_models::{
@@ -13,7 +13,7 @@ use media_models::{
 use reqwest::Client;
 use rust_decimal::Decimal;
 use serde::{Deserialize, Serialize};
-use traits::{MediaProvider, MediaProviderLanguages};
+use traits::MediaProvider;
 
 static URL: &str = "https://api.vndb.org/kana";
 const METADATA_FIELDS_SMALL: &str = "title,image.url,released,screenshots.url,developers.name";
@@ -28,16 +28,6 @@ pub struct VndbService {
     client: Client,
 }
 
-impl MediaProviderLanguages for VndbService {
-    fn supported_languages() -> Vec<String> {
-        vec!["us".to_owned()]
-    }
-
-    fn default_language() -> String {
-        "us".to_owned()
-    }
-}
-
 impl VndbService {
     pub async fn new(_config: &config::VisualNovelConfig) -> Self {
         let client = get_base_http_client(None);
@@ -87,7 +77,7 @@ impl MediaProvider for VndbService {
         page: Option<i32>,
         _source_specifics: &Option<PersonSourceSpecifics>,
         _display_nsfw: bool,
-    ) -> Result<SearchResults<PeopleSearchItem>> {
+    ) -> Result<PeopleSearchResponse> {
         let data = self
             .client
             .post(format!("{}/producer", URL))
@@ -148,18 +138,19 @@ impl MediaProvider for VndbService {
         let data: SearchResponse = rsp.json().await.map_err(|e| anyhow!(e))?;
         let item = data.results.unwrap_or_default().pop().unwrap();
         Ok(MetadataPerson {
-            identifier: item.id,
-            source: MediaSource::Vndb,
-            name: item.title.unwrap(),
-            description: item.description,
-            related: vec![],
+            place: None,
             gender: None,
             images: None,
+            website: None,
+            related: vec![],
             death_date: None,
             birth_date: None,
-            place: None,
-            website: None,
+            source_url: None,
+            identifier: item.id,
             source_specifics: None,
+            name: item.title.unwrap(),
+            source: MediaSource::Vndb,
+            description: item.description,
         })
     }
 
@@ -265,16 +256,18 @@ impl VndbService {
             .into_iter()
             .map(|t| t.name)
             .collect_vec();
+        let identifier = item.id;
         MetadataDetails {
-            identifier: item.id,
-            lot: MediaLot::VisualNovel,
             source: MediaSource::Vndb,
+            lot: MediaLot::VisualNovel,
+            identifier: identifier.clone(),
             production_status: item.devstatus.map(|s| match s {
                 0 => "Finished".to_owned(),
                 1 => "In development".to_owned(),
                 2 => "Cancelled".to_owned(),
                 _ => unreachable!(),
             }),
+            source_url: Some(format!("https://vndb.org/{}", identifier)),
             title: item.title.unwrap(),
             description: item.description,
             people: people.into_iter().unique().collect(),
diff --git a/crates/providers/src/youtube_music.rs b/crates/providers/src/youtube_music.rs
new file mode 100644
index 0000000000..48a003a9c3
--- /dev/null
+++ b/crates/providers/src/youtube_music.rs
@@ -0,0 +1,279 @@
+use anyhow::Result;
+use async_trait::async_trait;
+use common_models::{PersonSourceSpecifics, SearchDetails, StoredUrl};
+use common_utils::TEMP_DIR;
+use database_models::metadata_group::MetadataGroupWithoutId;
+use dependent_models::{MetadataGroupSearchResponse, PeopleSearchResponse, SearchResults};
+use enums::{MediaLot, MediaSource};
+use itertools::Itertools;
+use media_models::{
+    MetadataDetails, MetadataGroupSearchItem, MetadataImage, MetadataImageForMediaDetails,
+    MetadataPerson, MetadataPersonRelated, MetadataSearchItem, MusicSpecifics,
+    PartialMetadataPerson, PartialMetadataWithoutId, PeopleSearchItem,
+};
+use rustypipe::{
+    client::{RustyPipe, RustyPipeQuery},
+    model::{richtext::ToHtml, Thumbnail},
+};
+use traits::MediaProvider;
+
+pub struct YoutubeMusicService {
+    client: RustyPipeQuery,
+}
+
+impl YoutubeMusicService {
+    pub async fn new() -> Self {
+        let client = RustyPipe::builder().storage_dir(TEMP_DIR).build().unwrap();
+        Self {
+            client: client.query(),
+        }
+    }
+}
+
+impl YoutubeMusicService {
+    fn order_images_by_size(&self, images: &[Thumbnail]) -> Vec<Thumbnail> {
+        images
+            .iter()
+            .cloned()
+            .sorted_by_key(|i| i.width * i.height)
+            .rev()
+            .collect()
+    }
+
+    fn largest_image(&self, images: &[Thumbnail]) -> Option<Thumbnail> {
+        self.order_images_by_size(images).first().cloned()
+    }
+}
+
+#[async_trait]
+impl MediaProvider for YoutubeMusicService {
+    async fn metadata_details(&self, identifier: &str) -> Result<MetadataDetails> {
+        let details = self.client.music_details(identifier).await?;
+        let suggestions = if let Some(related_id) = details.related_id {
+            let related = self.client.music_related(related_id).await?;
+            related
+                .tracks
+                .into_iter()
+                .map(|t| PartialMetadataWithoutId {
+                    title: t.name,
+                    identifier: t.id,
+                    lot: MediaLot::Music,
+                    is_recommendation: None,
+                    source: MediaSource::YoutubeMusic,
+                    image: self.largest_image(&t.cover).map(|c| c.url.to_owned()),
+                })
+                .collect()
+        } else {
+            vec![]
+        };
+        let identifier = details.track.id;
+        Ok(MetadataDetails {
+            suggestions,
+            lot: MediaLot::Music,
+            title: details.track.name,
+            identifier: identifier.clone(),
+            source: MediaSource::YoutubeMusic,
+            group_identifiers: details.track.album.into_iter().map(|a| a.id).collect(),
+            source_url: Some(format!("https://music.youtube.com/watch?v={}", identifier)),
+            music_specifics: Some(MusicSpecifics {
+                by_various_artists: Some(details.track.by_va),
+                duration: details.track.duration.map(|d| d.try_into().unwrap()),
+                view_count: details.track.view_count.map(|v| v.try_into().unwrap()),
+            }),
+            url_images: self
+                .order_images_by_size(&details.track.cover)
+                .into_iter()
+                .map(|t| MetadataImageForMediaDetails { image: t.url })
+                .collect(),
+            people: details
+                .track
+                .artists
+                .into_iter()
+                .filter_map(|a| {
+                    a.id.map(|id| PartialMetadataPerson {
+                        name: a.name,
+                        identifier: id,
+                        character: None,
+                        source_specifics: None,
+                        role: "Artist".to_string(),
+                        source: MediaSource::YoutubeMusic,
+                    })
+                })
+                .collect(),
+            ..Default::default()
+        })
+    }
+
+    async fn metadata_search(
+        &self,
+        query: &str,
+        _page: Option<i32>,
+        _display_nsfw: bool,
+    ) -> Result<SearchResults<MetadataSearchItem>> {
+        let results = self.client.music_search_tracks(query).await?;
+        let data = SearchResults {
+            details: SearchDetails {
+                total: 1,
+                next_page: None,
+            },
+            items: results
+                .items
+                .items
+                .into_iter()
+                .map(|i| MetadataSearchItem {
+                    title: i.name,
+                    identifier: i.id,
+                    publish_year: None,
+                    image: self.largest_image(&i.cover).map(|t| t.url.to_owned()),
+                })
+                .collect(),
+        };
+        Ok(data)
+    }
+
+    async fn metadata_group_details(
+        &self,
+        identifier: &str,
+    ) -> Result<(MetadataGroupWithoutId, Vec<PartialMetadataWithoutId>)> {
+        let album = self.client.music_album(identifier).await?;
+        let title = album.name;
+        Ok((
+            MetadataGroupWithoutId {
+                title: title.clone(),
+                lot: MediaLot::Music,
+                identifier: album.id,
+                display_images: vec![],
+                source: MediaSource::YoutubeMusic,
+                parts: album.tracks.len().try_into().unwrap(),
+                description: album.description.map(|d| d.to_html()),
+                source_url: album
+                    .playlist_id
+                    .map(|id| format!("https://music.youtube.com/playlist?list={}", id)),
+                images: Some(
+                    self.largest_image(&album.cover)
+                        .into_iter()
+                        .map(|c| MetadataImage {
+                            url: StoredUrl::Url(c.url),
+                        })
+                        .collect(),
+                ),
+            },
+            album
+                .tracks
+                .into_iter()
+                .map(|t| PartialMetadataWithoutId {
+                    title: t.name,
+                    identifier: t.id,
+                    lot: MediaLot::Music,
+                    is_recommendation: None,
+                    source: MediaSource::YoutubeMusic,
+                    image: self.largest_image(&t.cover).map(|t| t.url.to_owned()),
+                })
+                .collect(),
+        ))
+    }
+
+    async fn metadata_group_search(
+        &self,
+        query: &str,
+        _page: Option<i32>,
+        _display_nsfw: bool,
+    ) -> Result<MetadataGroupSearchResponse> {
+        let data = self.client.music_search_albums(query).await?;
+        Ok(SearchResults {
+            details: SearchDetails {
+                total: 1,
+                next_page: None,
+            },
+            items: data
+                .items
+                .items
+                .into_iter()
+                .map(|t| MetadataGroupSearchItem {
+                    parts: None,
+                    name: t.name,
+                    identifier: t.id,
+                    image: self.largest_image(&t.cover).map(|t| t.url.to_owned()),
+                })
+                .collect(),
+        })
+    }
+
+    async fn person_details(
+        &self,
+        identifier: &str,
+        _source_specifics: &Option<PersonSourceSpecifics>,
+    ) -> Result<MetadataPerson> {
+        let data = self.client.music_artist(identifier, false).await?;
+        let related = if let Some(playlist_id) = data.tracks_playlist_id {
+            let items = self.client.music_playlist(playlist_id).await?;
+            items
+                .tracks
+                .items
+                .into_iter()
+                .map(|t| MetadataPersonRelated {
+                    character: None,
+                    role: "Artist".to_string(),
+                    metadata: PartialMetadataWithoutId {
+                        title: t.name,
+                        identifier: t.id,
+                        lot: MediaLot::Music,
+                        is_recommendation: None,
+                        source: MediaSource::YoutubeMusic,
+                        image: self.largest_image(&t.cover).map(|t| t.url.to_owned()),
+                    },
+                })
+                .collect()
+        } else {
+            vec![]
+        };
+        let identifier = data.id;
+        Ok(MetadataPerson {
+            related,
+            place: None,
+            gender: None,
+            website: None,
+            name: data.name,
+            birth_date: None,
+            death_date: None,
+            source_specifics: None,
+            description: data.description,
+            identifier: identifier.clone(),
+            source: MediaSource::YoutubeMusic,
+            source_url: Some(format!("https://music.youtube.com/channel/{}", identifier)),
+            images: Some(
+                self.largest_image(&data.header_image)
+                    .into_iter()
+                    .map(|t| t.url.to_owned())
+                    .collect(),
+            ),
+        })
+    }
+
+    async fn people_search(
+        &self,
+        query: &str,
+        _page: Option<i32>,
+        _source_specifics: &Option<PersonSourceSpecifics>,
+        _display_nsfw: bool,
+    ) -> Result<PeopleSearchResponse> {
+        let data = self.client.music_search_artists(query).await?;
+        Ok(SearchResults {
+            details: SearchDetails {
+                total: 1,
+                next_page: None,
+            },
+            items: data
+                .items
+                .items
+                .into_iter()
+                .map(|t| PeopleSearchItem {
+                    name: t.name,
+                    identifier: t.id,
+                    birth_year: None,
+                    image: self.largest_image(&t.avatar).map(|t| t.url.to_owned()),
+                })
+                .collect(),
+        })
+    }
+}
diff --git a/crates/services/cache/src/lib.rs b/crates/services/cache/src/lib.rs
index 18ea61ea69..78be3e8ab1 100644
--- a/crates/services/cache/src/lib.rs
+++ b/crates/services/cache/src/lib.rs
@@ -30,7 +30,6 @@ impl CacheService {
         match key {
             ApplicationCacheKey::IgdbSettings
             | ApplicationCacheKey::ListennotesSettings
-            | ApplicationCacheKey::ServerKeyValidated
             | ApplicationCacheKey::TmdbSettings => None,
 
             ApplicationCacheKey::CoreDetails
@@ -80,14 +79,12 @@ impl CacheService {
         Ok(insert_id)
     }
 
-    pub async fn get_value<T: DeserializeOwned>(
-        &self,
-        key: ApplicationCacheKey,
-    ) -> Result<Option<T>> {
+    pub async fn get_value<T: DeserializeOwned>(&self, key: ApplicationCacheKey) -> Option<T> {
         let cache = ApplicationCache::find()
             .filter(application_cache::Column::Key.eq(key.clone()))
             .one(&self.db)
-            .await?;
+            .await
+            .ok()?;
         let Some(db_value) = cache
             .filter(|cache| {
                 cache
@@ -96,10 +93,9 @@ impl CacheService {
             })
             .map(|m| m.value)
         else {
-            return Ok(None);
+            return None;
         };
-        let parsed = serde_json::from_value::<T>(db_value);
-        Ok(parsed.ok())
+        serde_json::from_value::<T>(db_value).ok()
     }
 
     pub async fn expire_key(&self, key: ApplicationCacheKey) -> Result<bool> {
diff --git a/crates/services/exporter/src/lib.rs b/crates/services/exporter/src/lib.rs
index 142c9ad834..eeeed08dd1 100644
--- a/crates/services/exporter/src/lib.rs
+++ b/crates/services/exporter/src/lib.rs
@@ -4,7 +4,7 @@ use async_graphql::{Error, Result};
 use background::ApplicationJob;
 use chrono::{DateTime, Utc};
 use common_models::ExportJob;
-use common_utils::{IsFeatureEnabled, TEMP_DIR};
+use common_utils::TEMP_DIR;
 use database_models::{
     exercise,
     prelude::{
diff --git a/crates/services/importer/src/hevy.rs b/crates/services/importer/src/hevy.rs
index 91a2a7ac54..706274f9f7 100644
--- a/crates/services/importer/src/hevy.rs
+++ b/crates/services/importer/src/hevy.rs
@@ -174,5 +174,5 @@ pub async fn import(
 }
 
 fn parse_date_string(input: &str) -> NaiveDateTime {
-    NaiveDateTime::parse_from_str(&input, "%d %b %Y, %H:%M").unwrap()
+    NaiveDateTime::parse_from_str(input, "%d %b %Y, %H:%M").unwrap()
 }
diff --git a/crates/services/importer/src/lib.rs b/crates/services/importer/src/lib.rs
index 87972744a9..b47c3e8e72 100644
--- a/crates/services/importer/src/lib.rs
+++ b/crates/services/importer/src/lib.rs
@@ -217,7 +217,7 @@ pub mod utils {
             .filter(exercise::Column::Name.eq(exercise_name))
             .one(&ss.db)
             .await?;
-        let generated_id = generate_exercise_id(&exercise_name, exercise_lot, user_id);
+        let generated_id = generate_exercise_id(exercise_name, exercise_lot, user_id);
         let exercise_id = match existing_exercise {
             Some(db_ex) if db_ex.source == ExerciseSource::Github || db_ex.id == generated_id => {
                 db_ex.id
diff --git a/crates/services/integration/src/yank/audiobookshelf.rs b/crates/services/integration/src/yank/audiobookshelf.rs
index 0fa7531bd3..8d6979d838 100644
--- a/crates/services/integration/src/yank/audiobookshelf.rs
+++ b/crates/services/integration/src/yank/audiobookshelf.rs
@@ -222,5 +222,5 @@ pub async fn sync_to_owned_collection(
                 }));
         }
     }
-    todo!()
+    Ok(result)
 }
diff --git a/crates/services/miscellaneous/Cargo.toml b/crates/services/miscellaneous/Cargo.toml
index e7671178ed..c6b39224cd 100644
--- a/crates/services/miscellaneous/Cargo.toml
+++ b/crates/services/miscellaneous/Cargo.toml
@@ -15,7 +15,6 @@ database-utils = { path = "../../utils/database" }
 dependent-models = { path = "../../models/dependent" }
 dependent-utils = { path = "../../utils/dependent" }
 enums = { path = "../../enums" }
-env-utils = { path = "../../utils/env" }
 futures = { workspace = true }
 itertools = { workspace = true }
 markdown = { workspace = true }
@@ -30,13 +29,10 @@ sea-orm = { workspace = true }
 sea-query = { workspace = true }
 serde = { workspace = true }
 serde_json = { workspace = true }
-serde_with = { workspace = true }
-slug = { workspace = true }
 supporting-service = { path = "../supporting" }
 tokio = { workspace = true }
 tracing = { workspace = true }
 traits = { path = "../../traits" }
-unkey = { workspace = true }
 user-models = { path = "../../models/user" }
 uuid = { workspace = true }
 
diff --git a/crates/services/miscellaneous/src/lib.rs b/crates/services/miscellaneous/src/lib.rs
index 56da9c7a5e..e9a0f5011a 100644
--- a/crates/services/miscellaneous/src/lib.rs
+++ b/crates/services/miscellaneous/src/lib.rs
@@ -10,17 +10,15 @@ use application_utils::{
 };
 use async_graphql::{Error, Result};
 use background::{ApplicationJob, CoreApplicationJob};
-use chrono::{Days, Duration, NaiveDate, TimeZone, Utc};
+use chrono::{Days, Duration, NaiveDate, Utc};
 use common_models::{
-    ApplicationCacheKey, BackendError, BackgroundJob, ChangeCollectionToEntityInput,
-    DefaultCollection, IdAndNamedObject, MediaStateChanged, MetadataGroupSearchInput,
-    MetadataSearchInput, PeopleSearchInput, ProgressUpdateCacheInput, SearchDetails, SearchInput,
-    StoredUrl, StringIdObject, UserLevelCacheKey,
+    ApplicationCacheKey, BackgroundJob, ChangeCollectionToEntityInput, DefaultCollection,
+    IdAndNamedObject, MediaStateChanged, MetadataGroupSearchInput, MetadataSearchInput,
+    PeopleSearchInput, ProgressUpdateCacheInput, SearchDetails, SearchInput, StoredUrl,
+    StringIdObject, UserLevelCacheKey,
 };
 use common_utils::{
-    convert_naive_to_utc, get_first_and_last_day_of_month, ryot_log, IsFeatureEnabled,
-    COMPILATION_TIMESTAMP, EXERCISE_LOT_MAPPINGS, MEDIA_LOT_MAPPINGS, PAGE_SIZE,
-    SHOW_SPECIAL_SEASON_NAMES,
+    get_first_and_last_day_of_month, ryot_log, PAGE_SIZE, SHOW_SPECIAL_SEASON_NAMES,
 };
 use database_models::{
     access_link, application_cache, calendar_event, collection, collection_to_entity,
@@ -29,8 +27,8 @@ use database_models::{
     metadata_to_metadata_group, metadata_to_person, monitored_entity, notification_platform,
     person,
     prelude::{
-        AccessLink, ApplicationCache, CalendarEvent, Collection, CollectionToEntity, Exercise,
-        Genre, ImportReport, Metadata, MetadataGroup, MetadataToGenre, MetadataToMetadata,
+        AccessLink, ApplicationCache, CalendarEvent, Collection, CollectionToEntity, Genre,
+        ImportReport, Metadata, MetadataGroup, MetadataToGenre, MetadataToMetadata,
         MetadataToMetadataGroup, MetadataToPerson, MonitoredEntity, NotificationPlatform, Person,
         Review, Seen, User, UserNotification, UserToEntity,
     },
@@ -42,11 +40,9 @@ use database_utils::{
     remove_entity_from_collection, revoke_access_link, user_by_id,
 };
 use dependent_models::{
-    ApplicationCacheValue, CoreDetails, EmptyCacheValue, ExerciseFilters, ExerciseParameters,
-    ExerciseParametersLotMapping, GenreDetails, MetadataBaseData, MetadataGroupDetails,
-    MetadataGroupSearchResponse, MetadataLotSourceMappings, MetadataSearchResponse,
-    PeopleSearchResponse, PersonDetails, ProviderLanguageInformation, SearchResults,
-    UserMetadataDetails, UserMetadataGroupDetails, UserPersonDetails,
+    ApplicationCacheValue, CoreDetails, GenreDetails, MetadataBaseData, MetadataGroupDetails,
+    MetadataGroupSearchResponse, MetadataSearchResponse, PeopleSearchResponse, PersonDetails,
+    SearchResults, UserMetadataDetails, UserMetadataGroupDetails, UserPersonDetails,
 };
 use dependent_utils::{
     add_entity_to_collection, commit_metadata, commit_metadata_group_internal,
@@ -60,11 +56,9 @@ use dependent_utils::{
     update_metadata_and_notify_users,
 };
 use enums::{
-    EntityLot, ExerciseEquipment, ExerciseForce, ExerciseLevel, ExerciseLot, ExerciseMechanic,
-    ExerciseMuscle, MediaLot, MediaSource, MetadataToMetadataRelation, SeenState,
-    UserNotificationLot, UserToMediaReason,
+    EntityLot, MediaLot, MediaSource, MetadataToMetadataRelation, SeenState, UserNotificationLot,
+    UserToMediaReason,
 };
-use env_utils::{APP_VERSION, UNKEY_API_ID};
 use futures::TryStreamExt;
 use itertools::Itertools;
 use markdown::{to_html_with_options as markdown_to_html_opts, CompileOptions, Options};
@@ -91,54 +85,32 @@ use migrations::{
 use nanoid::nanoid;
 use notification_service::send_notification;
 use providers::{
-    anilist::{AnilistService, NonMediaAnilistService},
-    audible::AudibleService,
-    google_books::GoogleBooksService,
-    igdb::IgdbService,
-    itunes::ITunesService,
-    listennotes::ListennotesService,
-    mal::{MalService, NonMediaMalService},
-    manga_updates::MangaUpdatesService,
-    openlibrary::OpenlibraryService,
-    tmdb::TmdbService,
-    vndb::VndbService,
+    anilist::NonMediaAnilistService, audible::AudibleService, google_books::GoogleBooksService,
+    igdb::IgdbService, itunes::ITunesService, listennotes::ListennotesService,
+    mal::NonMediaMalService, manga_updates::MangaUpdatesService, vndb::VndbService,
+    youtube_music::YoutubeMusicService,
 };
 use rust_decimal::Decimal;
 use rust_decimal_macros::dec;
 use sea_orm::{
     prelude::DateTimeUtc, query::UpdateMany, sea_query::NullOrdering, ActiveModelTrait,
     ActiveValue, ColumnTrait, ConnectionTrait, DatabaseBackend, DatabaseConnection, EntityTrait,
-    FromQueryResult, ItemsAndPagesNumber, Iterable, JoinType, ModelTrait, Order, PaginatorTrait,
-    QueryFilter, QueryOrder, QuerySelect, QueryTrait, RelationTrait, Statement, TransactionTrait,
+    FromQueryResult, ItemsAndPagesNumber, JoinType, ModelTrait, Order, PaginatorTrait, QueryFilter,
+    QueryOrder, QuerySelect, QueryTrait, RelationTrait, Statement, TransactionTrait,
 };
 use sea_query::{
     extension::postgres::PgExpr, Alias, Asterisk, Cond, Condition, Expr, Func, PgFunc,
     PostgresQueryBuilder, Query, SelectStatement,
 };
 use serde::{Deserialize, Serialize};
-use serde_with::skip_serializing_none;
 use supporting_service::SupportingService;
 use tokio::time::{sleep, Duration as TokioDuration};
-use traits::{MediaProvider, MediaProviderLanguages, TraceOk};
-use unkey::{models::VerifyKeyRequest, Client};
+use traits::{MediaProvider, TraceOk};
 use user_models::{DashboardElementLot, UserReviewScale};
 use uuid::Uuid;
 
 type Provider = Box<(dyn MediaProvider + Send + Sync)>;
 
-#[derive(Debug, Clone)]
-struct CustomService {}
-
-impl MediaProviderLanguages for CustomService {
-    fn supported_languages() -> Vec<String> {
-        ["us"].into_iter().map(String::from).collect()
-    }
-
-    fn default_language() -> String {
-        "us".to_owned()
-    }
-}
-
 pub struct MiscellaneousService(pub Arc<SupportingService>);
 
 impl MiscellaneousService {
@@ -252,128 +224,7 @@ ORDER BY RANDOM() LIMIT 10;
     }
 
     pub async fn core_details(&self) -> Result<CoreDetails> {
-        if let Some(cached) = self
-            .0
-            .cache_service
-            .get_value(ApplicationCacheKey::CoreDetails)
-            .await?
-        {
-            return Ok(cached);
-        }
-        let mut files_enabled = self.0.config.file_storage.is_enabled();
-        if files_enabled && !self.0.file_storage_service.is_enabled().await {
-            files_enabled = false;
-        }
-        let download_required = Exercise::find().count(&self.0.db).await? == 0;
-        let core_details = CoreDetails {
-            page_size: PAGE_SIZE,
-            version: APP_VERSION.to_owned(),
-            file_storage_enabled: files_enabled,
-            frontend: self.0.config.frontend.clone(),
-            website_url: "https://ryot.io".to_owned(),
-            oidc_enabled: self.0.oidc_client.is_some(),
-            docs_link: "https://docs.ryot.io".to_owned(),
-            backend_errors: BackendError::iter().collect(),
-            disable_telemetry: self.0.config.disable_telemetry,
-            smtp_enabled: self.0.config.server.smtp.is_enabled(),
-            signup_allowed: self.0.config.users.allow_registration,
-            local_auth_disabled: self.0.config.users.disable_local_auth,
-            repository_link: "https://github.com/ignisda/ryot".to_owned(),
-            token_valid_for_days: self.0.config.users.token_valid_for_days,
-            is_server_key_validated: self.0.is_server_key_validated().await?,
-            metadata_lot_source_mappings: MEDIA_LOT_MAPPINGS
-                .iter()
-                .map(|(lot, sources)| MetadataLotSourceMappings {
-                    lot: *lot,
-                    sources: sources.to_vec(),
-                })
-                .collect(),
-            exercise_parameters: ExerciseParameters {
-                filters: ExerciseFilters {
-                    lot: ExerciseLot::iter().collect_vec(),
-                    level: ExerciseLevel::iter().collect_vec(),
-                    force: ExerciseForce::iter().collect_vec(),
-                    mechanic: ExerciseMechanic::iter().collect_vec(),
-                    equipment: ExerciseEquipment::iter().collect_vec(),
-                    muscle: ExerciseMuscle::iter().collect_vec(),
-                },
-                download_required,
-                lot_mapping: EXERCISE_LOT_MAPPINGS
-                    .iter()
-                    .map(|(lot, pbs)| ExerciseParametersLotMapping {
-                        lot: *lot,
-                        bests: pbs.to_vec(),
-                    })
-                    .collect(),
-            },
-            metadata_provider_languages: MediaSource::iter()
-                .map(|source| {
-                    let (supported, default) = match source {
-                        MediaSource::Itunes => (
-                            ITunesService::supported_languages(),
-                            ITunesService::default_language(),
-                        ),
-                        MediaSource::Audible => (
-                            AudibleService::supported_languages(),
-                            AudibleService::default_language(),
-                        ),
-                        MediaSource::Openlibrary => (
-                            OpenlibraryService::supported_languages(),
-                            OpenlibraryService::default_language(),
-                        ),
-                        MediaSource::Tmdb => (
-                            TmdbService::supported_languages(),
-                            TmdbService::default_language(),
-                        ),
-                        MediaSource::Listennotes => (
-                            ListennotesService::supported_languages(),
-                            ListennotesService::default_language(),
-                        ),
-                        MediaSource::GoogleBooks => (
-                            GoogleBooksService::supported_languages(),
-                            GoogleBooksService::default_language(),
-                        ),
-                        MediaSource::Igdb => (
-                            IgdbService::supported_languages(),
-                            IgdbService::default_language(),
-                        ),
-                        MediaSource::MangaUpdates => (
-                            MangaUpdatesService::supported_languages(),
-                            MangaUpdatesService::default_language(),
-                        ),
-                        MediaSource::Anilist => (
-                            AnilistService::supported_languages(),
-                            AnilistService::default_language(),
-                        ),
-                        MediaSource::Mal => (
-                            MalService::supported_languages(),
-                            MalService::default_language(),
-                        ),
-                        MediaSource::Custom => (
-                            CustomService::supported_languages(),
-                            CustomService::default_language(),
-                        ),
-                        MediaSource::Vndb => (
-                            VndbService::supported_languages(),
-                            VndbService::default_language(),
-                        ),
-                    };
-                    ProviderLanguageInformation {
-                        source,
-                        default,
-                        supported,
-                    }
-                })
-                .collect(),
-        };
-        self.0
-            .cache_service
-            .set_key(
-                ApplicationCacheKey::CoreDetails,
-                ApplicationCacheValue::CoreDetails(core_details.clone()),
-            )
-            .await?;
-        Ok(core_details)
+        self.0.core_details().await
     }
 
     async fn metadata_assets(&self, meta: &metadata::Model) -> Result<GraphqlMediaAssets> {
@@ -546,55 +397,6 @@ ORDER BY RANDOM() LIMIT 10;
             genres,
             suggestions,
         } = self.generic_metadata(metadata_id).await?;
-        let slug = slug::slugify(&model.title);
-        let identifier = &model.identifier;
-        let source_url = match model.source {
-            MediaSource::Custom => None,
-            // DEV: This is updated by the specifics
-            MediaSource::MangaUpdates => None,
-            MediaSource::Itunes => Some(format!(
-                "https://podcasts.apple.com/us/podcast/{slug}/id{identifier}"
-            )),
-            MediaSource::GoogleBooks => Some(format!(
-                "https://www.google.co.in/books/edition/{slug}/{identifier}"
-            )),
-            MediaSource::Audible => Some(format!("https://www.audible.com/pd/{slug}/{identifier}")),
-            MediaSource::Openlibrary => {
-                Some(format!("https://openlibrary.org/works/{identifier}/{slug}"))
-            }
-            MediaSource::Tmdb => {
-                let bw = match model.lot {
-                    MediaLot::Movie => "movie",
-                    MediaLot::Show => "tv",
-                    _ => unreachable!(),
-                };
-                Some(format!(
-                    "https://www.themoviedb.org/{bw}/{identifier}-{slug}"
-                ))
-            }
-            MediaSource::Listennotes => Some(format!(
-                "https://www.listennotes.com/podcasts/{slug}-{identifier}"
-            )),
-            MediaSource::Igdb => Some(format!("https://www.igdb.com/games/{slug}")),
-            MediaSource::Anilist => {
-                let bw = match model.lot {
-                    MediaLot::Anime => "anime",
-                    MediaLot::Manga => "manga",
-                    _ => unreachable!(),
-                };
-                Some(format!("https://anilist.co/{bw}/{identifier}/{slug}"))
-            }
-            MediaSource::Mal => {
-                let bw = match model.lot {
-                    MediaLot::Anime => "anime",
-                    MediaLot::Manga => "manga",
-                    _ => unreachable!(),
-                };
-                Some(format!("https://myanimelist.net/{bw}/{identifier}/{slug}"))
-            }
-            MediaSource::Vndb => Some(format!("https://vndb.org/{identifier}")),
-        };
-
         let group = {
             let association = MetadataToMetadataGroup::find()
                 .filter(metadata_to_metadata_group::Column::MetadataId.eq(metadata_id))
@@ -623,7 +425,6 @@ ORDER BY RANDOM() LIMIT 10;
             assets,
             genres,
             creators,
-            source_url,
             suggestions,
             id: model.id,
             lot: model.lot,
@@ -631,6 +432,7 @@ ORDER BY RANDOM() LIMIT 10;
             title: model.title,
             source: model.source,
             is_nsfw: model.is_nsfw,
+            source_url: model.source_url,
             is_partial: model.is_partial,
             identifier: model.identifier,
             description: model.description,
@@ -639,6 +441,7 @@ ORDER BY RANDOM() LIMIT 10;
             book_specifics: model.book_specifics,
             show_specifics: model.show_specifics,
             movie_specifics: model.movie_specifics,
+            music_specifics: model.music_specifics,
             manga_specifics: model.manga_specifics,
             anime_specifics: model.anime_specifics,
             provider_rating: model.provider_rating,
@@ -1677,11 +1480,12 @@ ORDER BY RANDOM() LIMIT 10;
         user_id: &String,
         input: MetadataSearchInput,
     ) -> Result<MetadataSearchResponse> {
+        let cc = &self.0.cache_service;
         let cache_key = ApplicationCacheKey::MetadataSearch(UserLevelCacheKey {
             input: input.clone(),
             user_id: user_id.to_owned(),
         });
-        if let Some(cached) = self.0.cache_service.get_value(cache_key.clone()).await? {
+        if let Some(cached) = cc.get_value(cache_key.clone()).await {
             return Ok(cached);
         }
         let query = input.search.query.unwrap_or_default();
@@ -1752,13 +1556,11 @@ ORDER BY RANDOM() LIMIT 10;
             details: results.details,
             items: data,
         };
-        self.0
-            .cache_service
-            .set_key(
-                cache_key,
-                ApplicationCacheValue::MetadataSearch(results.clone()),
-            )
-            .await?;
+        cc.set_key(
+            cache_key,
+            ApplicationCacheValue::MetadataSearch(results.clone()),
+        )
+        .await?;
         Ok(results)
     }
 
@@ -1767,11 +1569,12 @@ ORDER BY RANDOM() LIMIT 10;
         user_id: &String,
         input: PeopleSearchInput,
     ) -> Result<PeopleSearchResponse> {
+        let cc = &self.0.cache_service;
         let cache_key = ApplicationCacheKey::PeopleSearch(UserLevelCacheKey {
             input: input.clone(),
             user_id: user_id.clone(),
         });
-        if let Some(results) = self.0.cache_service.get_value(cache_key.clone()).await? {
+        if let Some(results) = cc.get_value(cache_key.clone()).await {
             return Ok(results);
         }
         let query = input.search.query.unwrap_or_default();
@@ -1794,13 +1597,11 @@ ORDER BY RANDOM() LIMIT 10;
                 preferences.general.display_nsfw,
             )
             .await?;
-        self.0
-            .cache_service
-            .set_key(
-                cache_key,
-                ApplicationCacheValue::PeopleSearch(results.clone()),
-            )
-            .await?;
+        cc.set_key(
+            cache_key,
+            ApplicationCacheValue::PeopleSearch(results.clone()),
+        )
+        .await?;
         Ok(results)
     }
 
@@ -1809,11 +1610,12 @@ ORDER BY RANDOM() LIMIT 10;
         user_id: &String,
         input: MetadataGroupSearchInput,
     ) -> Result<MetadataGroupSearchResponse> {
+        let cc = &self.0.cache_service;
         let cache_key = ApplicationCacheKey::MetadataGroupSearch(UserLevelCacheKey {
             input: input.clone(),
             user_id: user_id.clone(),
         });
-        if let Some(results) = self.0.cache_service.get_value(cache_key.clone()).await? {
+        if let Some(results) = cc.get_value(cache_key.clone()).await {
             return Ok(results);
         }
         let query = input.search.query.unwrap_or_default();
@@ -1831,19 +1633,18 @@ ORDER BY RANDOM() LIMIT 10;
         let results = provider
             .metadata_group_search(&query, input.search.page, preferences.general.display_nsfw)
             .await?;
-        self.0
-            .cache_service
-            .set_key(
-                cache_key,
-                ApplicationCacheValue::MetadataGroupSearch(results.clone()),
-            )
-            .await?;
+        cc.set_key(
+            cache_key,
+            ApplicationCacheValue::MetadataGroupSearch(results.clone()),
+        )
+        .await?;
         Ok(results)
     }
 
     async fn get_non_metadata_provider(&self, source: MediaSource) -> Result<Provider> {
         let err = || Err(Error::new("This source is not supported".to_owned()));
         let service: Provider = match source {
+            MediaSource::YoutubeMusic => Box::new(YoutubeMusicService::new().await),
             MediaSource::Vndb => Box::new(VndbService::new(&self.0.config.visual_novels).await),
             MediaSource::Openlibrary => Box::new(get_openlibrary_service(&self.0.config).await?),
             MediaSource::Itunes => {
@@ -2015,34 +1816,36 @@ ORDER BY RANDOM() LIMIT 10;
             })
             .collect();
         let is_partial = match input.lot {
-            MediaLot::Anime => input.anime_specifics.is_none(),
-            MediaLot::AudioBook => input.audio_book_specifics.is_none(),
+            MediaLot::Show => input.show_specifics.is_none(),
             MediaLot::Book => input.book_specifics.is_none(),
+            MediaLot::Music => input.music_specifics.is_none(),
+            MediaLot::Anime => input.anime_specifics.is_none(),
             MediaLot::Manga => input.manga_specifics.is_none(),
             MediaLot::Movie => input.movie_specifics.is_none(),
             MediaLot::Podcast => input.podcast_specifics.is_none(),
-            MediaLot::Show => input.show_specifics.is_none(),
+            MediaLot::AudioBook => input.audio_book_specifics.is_none(),
             MediaLot::VideoGame => input.video_game_specifics.is_none(),
             MediaLot::VisualNovel => input.visual_novel_specifics.is_none(),
         };
         let details = MetadataDetails {
+            videos,
+            creators,
             identifier,
-            title: input.title,
-            description: input.description,
             lot: input.lot,
-            source: MediaSource::Custom,
-            creators,
-            genres: input.genres.unwrap_or_default(),
+            title: input.title,
             s3_images: images,
-            videos,
+            source: MediaSource::Custom,
+            description: input.description,
             publish_year: input.publish_year,
-            anime_specifics: input.anime_specifics,
-            audio_book_specifics: input.audio_book_specifics,
+            show_specifics: input.show_specifics,
             book_specifics: input.book_specifics,
             manga_specifics: input.manga_specifics,
+            anime_specifics: input.anime_specifics,
             movie_specifics: input.movie_specifics,
+            music_specifics: input.music_specifics,
+            genres: input.genres.unwrap_or_default(),
             podcast_specifics: input.podcast_specifics,
-            show_specifics: input.show_specifics,
+            audio_book_specifics: input.audio_book_specifics,
             video_game_specifics: input.video_game_specifics,
             visual_novel_specifics: input.visual_novel_specifics,
             ..Default::default()
@@ -2350,33 +2153,7 @@ ORDER BY RANDOM() LIMIT 10;
             })
             .sorted_by_key(|f| Reverse(f.count))
             .collect_vec();
-        let slug = slug::slugify(&details.name);
-        let identifier = &details.identifier;
-        let source_url = match details.source {
-            MediaSource::Custom
-            | MediaSource::Anilist
-            | MediaSource::Listennotes
-            | MediaSource::Itunes
-            | MediaSource::MangaUpdates
-            | MediaSource::Mal
-            | MediaSource::Vndb
-            | MediaSource::GoogleBooks => None,
-            MediaSource::Audible => Some(format!(
-                "https://www.audible.com/author/{slug}/{identifier}"
-            )),
-            MediaSource::Openlibrary => Some(format!(
-                "https://openlibrary.org/authors/{identifier}/{slug}"
-            )),
-            MediaSource::Tmdb => Some(format!(
-                "https://www.themoviedb.org/person/{identifier}-{slug}"
-            )),
-            MediaSource::Igdb => Some(format!("https://www.igdb.com/companies/{slug}")),
-        };
-        Ok(PersonDetails {
-            details,
-            contents,
-            source_url,
-        })
+        Ok(PersonDetails { details, contents })
     }
 
     pub async fn genre_details(&self, input: GenreDetailsInput) -> Result<GenreDetails> {
@@ -2425,7 +2202,7 @@ ORDER BY RANDOM() LIMIT 10;
             .await?
             .unwrap();
         let mut images = vec![];
-        for image in group.images.iter() {
+        for image in group.images.clone().unwrap_or_default().iter() {
             images.push(
                 self.0
                     .file_storage_service
@@ -2434,28 +2211,6 @@ ORDER BY RANDOM() LIMIT 10;
             );
         }
         group.display_images = images;
-        let slug = slug::slugify(&group.title);
-        let identifier = &group.identifier;
-
-        let source_url = match group.source {
-            MediaSource::Custom
-            | MediaSource::Anilist
-            | MediaSource::Listennotes
-            | MediaSource::Itunes
-            | MediaSource::MangaUpdates
-            | MediaSource::Mal
-            | MediaSource::Openlibrary
-            | MediaSource::Vndb
-            | MediaSource::GoogleBooks => None,
-            MediaSource::Audible => Some(format!(
-                "https://www.audible.com/series/{slug}/{identifier}"
-            )),
-            MediaSource::Tmdb => Some(format!(
-                "https://www.themoviedb.org/collections/{identifier}-{slug}"
-            )),
-            MediaSource::Igdb => Some(format!("https://www.igdb.com/collection/{slug}")),
-        };
-
         let contents = MetadataToMetadataGroup::find()
             .select_only()
             .column(metadata_to_metadata_group::Column::MetadataId)
@@ -2466,7 +2221,6 @@ ORDER BY RANDOM() LIMIT 10;
             .await?;
         Ok(MetadataGroupDetails {
             details: group,
-            source_url,
             contents,
         })
     }
@@ -2766,14 +2520,17 @@ ORDER BY RANDOM() LIMIT 10;
             .person_details(&person.identifier, &person.source_specifics)
             .await?;
         ryot_log!(debug, "Updating person for {:?}", person_id);
-        let images = provider_person.images.map(|images| {
-            images
-                .into_iter()
-                .map(|i| MetadataImage {
-                    url: StoredUrl::Url(i),
-                })
-                .collect()
-        });
+        let images = provider_person
+            .images
+            .map(|images| {
+                images
+                    .into_iter()
+                    .map(|i| MetadataImage {
+                        url: StoredUrl::Url(i),
+                    })
+                    .collect()
+            })
+            .filter(|i: &Vec<MetadataImage>| !i.is_empty());
         let mut default_state_changes = person.clone().state_changes.unwrap_or_default();
         let mut to_update_person: person::ActiveModel = person.clone().into();
         to_update_person.last_updated_on = ActiveValue::Set(Utc::now());
@@ -2783,6 +2540,7 @@ ORDER BY RANDOM() LIMIT 10;
         to_update_person.death_date = ActiveValue::Set(provider_person.death_date);
         to_update_person.place = ActiveValue::Set(provider_person.place);
         to_update_person.website = ActiveValue::Set(provider_person.website);
+        to_update_person.source_url = ActiveValue::Set(provider_person.source_url);
         to_update_person.images = ActiveValue::Set(images);
         to_update_person.is_partial = ActiveValue::Set(Some(false));
         to_update_person.name = ActiveValue::Set(provider_person.name);
@@ -3158,65 +2916,6 @@ ORDER BY RANDOM() LIMIT 10;
         Ok(())
     }
 
-    async fn get_is_server_key_validated(&self) -> bool {
-        let pro_key = &self.0.config.server.pro_key;
-        if pro_key.is_empty() {
-            return false;
-        }
-        ryot_log!(debug, "Verifying pro key for API ID: {:#?}", UNKEY_API_ID);
-        let compile_timestamp = Utc.timestamp_opt(COMPILATION_TIMESTAMP, 0).unwrap();
-        #[skip_serializing_none]
-        #[derive(Debug, Serialize, Clone, Deserialize)]
-        struct Meta {
-            expiry: Option<NaiveDate>,
-        }
-        let unkey_client = Client::new("public");
-        let verify_request = VerifyKeyRequest::new(pro_key, &UNKEY_API_ID.to_string());
-        let validated_key = match unkey_client.verify_key(verify_request).await {
-            Ok(verify_response) => {
-                if !verify_response.valid {
-                    ryot_log!(debug, "Pro key is no longer valid.");
-                    return false;
-                }
-                verify_response
-            }
-            Err(verify_error) => {
-                ryot_log!(debug, "Pro key verification error: {:?}", verify_error);
-                return false;
-            }
-        };
-        let key_meta = validated_key
-            .meta
-            .map(|meta| serde_json::from_value::<Meta>(meta).unwrap());
-        ryot_log!(debug, "Expiry: {:?}", key_meta.clone().map(|m| m.expiry));
-        if let Some(meta) = key_meta {
-            if let Some(expiry) = meta.expiry {
-                if compile_timestamp > convert_naive_to_utc(expiry) {
-                    ryot_log!(warn, "Pro key has expired. Please renew your subscription.");
-                    return false;
-                }
-            }
-        }
-        ryot_log!(debug, "Pro key verified successfully");
-        true
-    }
-
-    pub async fn perform_server_key_validation(&self) -> Result<()> {
-        let is_server_key_validated = self.get_is_server_key_validated().await;
-        let cs = &self.0.cache_service;
-        if is_server_key_validated {
-            cs.set_key(
-                ApplicationCacheKey::ServerKeyValidated,
-                ApplicationCacheValue::Empty(EmptyCacheValue::default()),
-            )
-            .await?;
-        } else {
-            cs.expire_key(ApplicationCacheKey::ServerKeyValidated)
-                .await?;
-        }
-        Ok(())
-    }
-
     pub async fn sync_integrations_data_to_owned_collection(&self) -> Result<()> {
         self.0
             .perform_application_job(ApplicationJob::SyncIntegrationsData)
diff --git a/crates/services/statistics/src/lib.rs b/crates/services/statistics/src/lib.rs
index 007588debf..d1a941ad66 100644
--- a/crates/services/statistics/src/lib.rs
+++ b/crates/services/statistics/src/lib.rs
@@ -38,11 +38,12 @@ impl StatisticsService {
         &self,
         user_id: &String,
     ) -> Result<ApplicationDateRange> {
+        let cc = &self.0.cache_service;
         let cache_key = ApplicationCacheKey::UserAnalyticsParameters(UserLevelCacheKey {
             input: (),
             user_id: user_id.to_owned(),
         });
-        if let Some(cached) = self.0.cache_service.get_value(cache_key.clone()).await? {
+        if let Some(cached) = cc.get_value(cache_key.clone()).await {
             return Ok(cached);
         }
         let get_date = |ordering: Order| {
@@ -64,13 +65,11 @@ impl StatisticsService {
             end_date,
             start_date,
         };
-        self.0
-            .cache_service
-            .set_key(
-                cache_key,
-                ApplicationCacheValue::UserAnalyticsParameters(response.clone()),
-            )
-            .await?;
+        cc.set_key(
+            cache_key,
+            ApplicationCacheValue::UserAnalyticsParameters(response.clone()),
+        )
+        .await?;
         Ok(response)
     }
 
@@ -274,12 +273,8 @@ impl StatisticsService {
             input: input.clone(),
             user_id: user_id.to_owned(),
         });
-        if let Some(cached) = self
-            .0
-            .cache_service
-            .get_value::<UserAnalytics>(cache_key.clone())
-            .await?
-        {
+        let cc = &self.0.cache_service;
+        if let Some(cached) = cc.get_value::<UserAnalytics>(cache_key.clone()).await {
             return Ok(cached);
         }
         #[derive(Debug, DerivePartialModel, FromQueryResult)]
@@ -379,13 +374,11 @@ impl StatisticsService {
                 workout_personal_bests,
             },
         };
-        self.0
-            .cache_service
-            .set_key(
-                cache_key,
-                ApplicationCacheValue::UserAnalytics(response.clone()),
-            )
-            .await?;
+        cc.set_key(
+            cache_key,
+            ApplicationCacheValue::UserAnalytics(response.clone()),
+        )
+        .await?;
         Ok(response)
     }
 }
diff --git a/crates/services/supporting/Cargo.toml b/crates/services/supporting/Cargo.toml
index b6b6fa185c..84a4e09e6f 100644
--- a/crates/services/supporting/Cargo.toml
+++ b/crates/services/supporting/Cargo.toml
@@ -8,10 +8,26 @@ async-graphql = { workspace = true }
 apalis = { workspace = true }
 background = { path = "../../background" }
 cache-service = { path = "../../services/cache" }
-common-models = { path = "../../models/common" }
+chrono = { workspace = true }
 chrono-tz = { workspace = true }
+common-models = { path = "../../models/common" }
+common-utils = { path = "../../utils/common" }
 config = { path = "../../config" }
+database-models = { path = "../../models/database" }
 dependent-models = { path = "../../models/dependent" }
+enums = { path = "../../enums" }
+env-utils = { path = "../../utils/env" }
 file-storage-service = { path = "../../services/file-storage" }
+isolang = { workspace = true }
+itertools = { workspace = true }
 openidconnect = { workspace = true }
+rustypipe = { workspace = true }
 sea-orm = { workspace = true }
+serde = { workspace = true }
+serde_json = { workspace = true }
+serde_with = { workspace = true }
+tracing = { workspace = true }
+unkey = { workspace = true }
+
+[package.metadata.cargo-machete]
+ignored = ["tracing"]
diff --git a/crates/services/supporting/src/lib.rs b/crates/services/supporting/src/lib.rs
index 5d176e5038..8b9d01be85 100644
--- a/crates/services/supporting/src/lib.rs
+++ b/crates/services/supporting/src/lib.rs
@@ -4,11 +4,30 @@ use apalis::prelude::{MemoryStorage, MessageQueue};
 use async_graphql::Result;
 use background::{ApplicationJob, CoreApplicationJob};
 use cache_service::CacheService;
-use common_models::ApplicationCacheKey;
-use dependent_models::EmptyCacheValue;
+use chrono::{NaiveDate, TimeZone, Utc};
+use common_models::{ApplicationCacheKey, BackendError};
+use common_utils::{
+    convert_naive_to_utc, ryot_log, COMPILATION_TIMESTAMP, EXERCISE_LOT_MAPPINGS,
+    MEDIA_LOT_MAPPINGS, PAGE_SIZE,
+};
+use database_models::prelude::Exercise;
+use dependent_models::{
+    ApplicationCacheValue, CoreDetails, ExerciseFilters, ExerciseParameters,
+    ExerciseParametersLotMapping, MetadataLotSourceMappings, ProviderLanguageInformation,
+};
+use enums::{
+    ExerciseEquipment, ExerciseForce, ExerciseLevel, ExerciseLot, ExerciseMechanic, ExerciseMuscle,
+    MediaSource,
+};
+use env_utils::{APP_VERSION, UNKEY_API_ID};
 use file_storage_service::FileStorageService;
+use itertools::Itertools;
 use openidconnect::core::CoreClient;
-use sea_orm::DatabaseConnection;
+use rustypipe::param::{Language, LANGUAGES};
+use sea_orm::{DatabaseConnection, EntityTrait, Iterable, PaginatorTrait};
+use serde::{Deserialize, Serialize};
+use serde_with::skip_serializing_none;
+use unkey::{models::VerifyKeyRequest, Client};
 
 pub struct SupportingService {
     pub db: DatabaseConnection,
@@ -64,10 +83,175 @@ impl SupportingService {
         Ok(())
     }
 
+    async fn get_is_server_key_validated(&self) -> bool {
+        let pro_key = &self.config.server.pro_key;
+        if pro_key.is_empty() {
+            return false;
+        }
+        ryot_log!(debug, "Verifying pro key for API ID: {:#?}", UNKEY_API_ID);
+        let compile_timestamp = Utc.timestamp_opt(COMPILATION_TIMESTAMP, 0).unwrap();
+        #[skip_serializing_none]
+        #[derive(Debug, Serialize, Clone, Deserialize)]
+        struct Meta {
+            expiry: Option<NaiveDate>,
+        }
+        let unkey_client = Client::new("public");
+        let verify_request = VerifyKeyRequest::new(pro_key, &UNKEY_API_ID.to_string());
+        let validated_key = match unkey_client.verify_key(verify_request).await {
+            Ok(verify_response) => {
+                if !verify_response.valid {
+                    ryot_log!(debug, "Pro key is no longer valid.");
+                    return false;
+                }
+                verify_response
+            }
+            Err(verify_error) => {
+                ryot_log!(debug, "Pro key verification error: {:?}", verify_error);
+                return false;
+            }
+        };
+        let key_meta = validated_key
+            .meta
+            .map(|meta| serde_json::from_value::<Meta>(meta).unwrap());
+        ryot_log!(debug, "Expiry: {:?}", key_meta.clone().map(|m| m.expiry));
+        if let Some(meta) = key_meta {
+            if let Some(expiry) = meta.expiry {
+                if compile_timestamp > convert_naive_to_utc(expiry) {
+                    ryot_log!(warn, "Pro key has expired. Please renew your subscription.");
+                    return false;
+                }
+            }
+        }
+        ryot_log!(debug, "Pro key verified successfully");
+        true
+    }
+
+    pub async fn core_details(&self) -> Result<CoreDetails> {
+        let cc = &self.cache_service;
+        if let Some(cached) = cc.get_value(ApplicationCacheKey::CoreDetails).await {
+            return Ok(cached);
+        }
+        let mut files_enabled = self.config.file_storage.is_enabled();
+        if files_enabled && !self.file_storage_service.is_enabled().await {
+            files_enabled = false;
+        }
+        let download_required = Exercise::find().count(&self.db).await? == 0;
+        let core_details = CoreDetails {
+            page_size: PAGE_SIZE,
+            version: APP_VERSION.to_owned(),
+            file_storage_enabled: files_enabled,
+            frontend: self.config.frontend.clone(),
+            website_url: "https://ryot.io".to_owned(),
+            oidc_enabled: self.oidc_client.is_some(),
+            docs_link: "https://docs.ryot.io".to_owned(),
+            backend_errors: BackendError::iter().collect(),
+            disable_telemetry: self.config.disable_telemetry,
+            smtp_enabled: self.config.server.smtp.is_enabled(),
+            signup_allowed: self.config.users.allow_registration,
+            local_auth_disabled: self.config.users.disable_local_auth,
+            token_valid_for_days: self.config.users.token_valid_for_days,
+            repository_link: "https://github.com/ignisda/ryot".to_owned(),
+            is_server_key_validated: self.get_is_server_key_validated().await,
+            metadata_lot_source_mappings: MEDIA_LOT_MAPPINGS
+                .iter()
+                .map(|(lot, sources)| MetadataLotSourceMappings {
+                    lot: *lot,
+                    sources: sources.to_vec(),
+                })
+                .collect(),
+            exercise_parameters: ExerciseParameters {
+                download_required,
+                filters: ExerciseFilters {
+                    lot: ExerciseLot::iter().collect_vec(),
+                    level: ExerciseLevel::iter().collect_vec(),
+                    force: ExerciseForce::iter().collect_vec(),
+                    mechanic: ExerciseMechanic::iter().collect_vec(),
+                    equipment: ExerciseEquipment::iter().collect_vec(),
+                    muscle: ExerciseMuscle::iter().collect_vec(),
+                },
+                lot_mapping: EXERCISE_LOT_MAPPINGS
+                    .iter()
+                    .map(|(lot, pbs)| ExerciseParametersLotMapping {
+                        lot: *lot,
+                        bests: pbs.to_vec(),
+                    })
+                    .collect(),
+            },
+            metadata_provider_languages: MediaSource::iter()
+                .map(|source| {
+                    let (supported, default) = match source {
+                        MediaSource::YoutubeMusic => (
+                            LANGUAGES.iter().map(|l| l.name().to_owned()).collect(),
+                            Language::En.name().to_owned(),
+                        ),
+                        MediaSource::Itunes => (
+                            ["en_us", "ja_jp"].into_iter().map(String::from).collect(),
+                            "en_us".to_owned(),
+                        ),
+                        MediaSource::Audible => (
+                            ["au", "ca", "de", "es", "fr", "in", "it", "jp", "gb", "us"]
+                                .into_iter()
+                                .map(String::from)
+                                .collect(),
+                            "us".to_owned(),
+                        ),
+                        MediaSource::Openlibrary => (
+                            ["us"].into_iter().map(String::from).collect(),
+                            "us".to_owned(),
+                        ),
+                        MediaSource::Tmdb => (
+                            isolang::languages()
+                                .filter_map(|l| l.to_639_1().map(String::from))
+                                .collect(),
+                            "en".to_owned(),
+                        ),
+                        MediaSource::Listennotes => (
+                            ["us"].into_iter().map(String::from).collect(),
+                            "us".to_owned(),
+                        ),
+                        MediaSource::GoogleBooks => (vec!["us".to_owned()], "us".to_owned()),
+                        MediaSource::Igdb => (
+                            ["us"].into_iter().map(String::from).collect(),
+                            "us".to_owned(),
+                        ),
+                        MediaSource::MangaUpdates => (
+                            ["us"].into_iter().map(String::from).collect(),
+                            "us".to_owned(),
+                        ),
+                        MediaSource::Anilist => (
+                            ["us"].into_iter().map(String::from).collect(),
+                            "us".to_owned(),
+                        ),
+                        MediaSource::Mal => (
+                            ["us"].into_iter().map(String::from).collect(),
+                            "us".to_owned(),
+                        ),
+                        MediaSource::Custom => (
+                            ["us"].into_iter().map(String::from).collect(),
+                            "us".to_owned(),
+                        ),
+                        MediaSource::Vndb => (
+                            ["us"].into_iter().map(String::from).collect(),
+                            "us".to_owned(),
+                        ),
+                    };
+                    ProviderLanguageInformation {
+                        source,
+                        default,
+                        supported,
+                    }
+                })
+                .collect(),
+        };
+        cc.set_key(
+            ApplicationCacheKey::CoreDetails,
+            ApplicationCacheValue::CoreDetails(core_details.clone()),
+        )
+        .await?;
+        Ok(core_details)
+    }
+
     pub async fn is_server_key_validated(&self) -> Result<bool> {
-        self.cache_service
-            .get_value::<EmptyCacheValue>(ApplicationCacheKey::ServerKeyValidated)
-            .await
-            .map(|v| v.is_some())
+        Ok(self.core_details().await?.is_server_key_validated)
     }
 }
diff --git a/crates/services/user/src/lib.rs b/crates/services/user/src/lib.rs
index 6ce81ff644..47e15e6cec 100644
--- a/crates/services/user/src/lib.rs
+++ b/crates/services/user/src/lib.rs
@@ -660,6 +660,10 @@ impl UserService {
                                         preferences.features_enabled.media.movie =
                                             value_bool.unwrap()
                                     }
+                                    "music" => {
+                                        preferences.features_enabled.media.music =
+                                            value_bool.unwrap()
+                                    }
                                     "podcast" => {
                                         preferences.features_enabled.media.podcast =
                                             value_bool.unwrap()
diff --git a/crates/traits/src/lib.rs b/crates/traits/src/lib.rs
index 7862e1dd15..2c3b984e51 100644
--- a/crates/traits/src/lib.rs
+++ b/crates/traits/src/lib.rs
@@ -8,11 +8,8 @@ use common_models::{BackendError, PersonSourceSpecifics};
 use common_utils::ryot_log;
 use database_models::metadata_group::MetadataGroupWithoutId;
 use database_utils::check_token;
-use dependent_models::SearchResults;
-use media_models::{
-    MetadataDetails, MetadataGroupSearchItem, MetadataPerson, MetadataSearchItem,
-    PartialMetadataWithoutId, PeopleSearchItem,
-};
+use dependent_models::{MetadataGroupSearchResponse, PeopleSearchResponse, SearchResults};
+use media_models::{MetadataDetails, MetadataPerson, MetadataSearchItem, PartialMetadataWithoutId};
 use sea_orm::{prelude::DateTimeUtc, DatabaseConnection};
 
 #[async_trait]
@@ -48,7 +45,7 @@ pub trait MediaProvider {
         page: Option<i32>,
         source_specifics: &Option<PersonSourceSpecifics>,
         display_nsfw: bool,
-    ) -> Result<SearchResults<PeopleSearchItem>> {
+    ) -> Result<PeopleSearchResponse> {
         bail!("This provider does not support searching people")
     }
 
@@ -56,7 +53,7 @@ pub trait MediaProvider {
     #[allow(unused_variables)]
     async fn person_details(
         &self,
-        identity: &str,
+        identifier: &str,
         source_specifics: &Option<PersonSourceSpecifics>,
     ) -> Result<MetadataPerson> {
         bail!("This provider does not support getting person details")
@@ -69,7 +66,7 @@ pub trait MediaProvider {
         query: &str,
         page: Option<i32>,
         display_nsfw: bool,
-    ) -> Result<SearchResults<MetadataGroupSearchItem>> {
+    ) -> Result<MetadataGroupSearchResponse> {
         bail!("This provider does not support searching metadata groups")
     }
 
@@ -92,14 +89,6 @@ pub trait MediaProvider {
     }
 }
 
-pub trait MediaProviderLanguages {
-    /// Get all the languages that a provider supports.
-    fn supported_languages() -> Vec<String>;
-
-    /// The default language to be used for this provider.
-    fn default_language() -> String;
-}
-
 #[async_trait]
 pub trait AuthProvider {
     #[allow(dead_code)]
diff --git a/crates/utils/common/src/lib.rs b/crates/utils/common/src/lib.rs
index 95f7d0dd52..17ac5c46c3 100644
--- a/crates/utils/common/src/lib.rs
+++ b/crates/utils/common/src/lib.rs
@@ -65,6 +65,7 @@ pub const MEDIA_LOT_MAPPINGS: &[(MediaLot, &[MediaSource])] = &[
         ],
     ),
     (MediaLot::Movie, &[MediaSource::Tmdb]),
+    (MediaLot::Music, &[MediaSource::YoutubeMusic]),
     (MediaLot::Show, &[MediaSource::Tmdb]),
     (MediaLot::VisualNovel, &[MediaSource::Vndb]),
 ];
@@ -88,13 +89,6 @@ macro_rules! ryot_log {
     };
 }
 
-/// Determine whether a feature is enabled
-pub trait IsFeatureEnabled {
-    fn is_enabled(&self) -> bool {
-        true
-    }
-}
-
 pub fn get_first_and_last_day_of_month(year: i32, month: u32) -> (NaiveDate, NaiveDate) {
     let first_day = NaiveDate::from_ymd_opt(year, month, 1).unwrap();
     let last_day = NaiveDate::from_ymd_opt(year, month + 1, 1)
diff --git a/crates/utils/database/src/lib.rs b/crates/utils/database/src/lib.rs
index 34be1e3f4e..fea2ed60b1 100644
--- a/crates/utils/database/src/lib.rs
+++ b/crates/utils/database/src/lib.rs
@@ -10,7 +10,7 @@ use common_models::{
     BackendError, ChangeCollectionToEntityInput, DailyUserActivityHourRecord,
     DailyUserActivityHourRecordEntity, DefaultCollection, IdAndNamedObject, StringIdObject,
 };
-use common_utils::{ryot_log, IsFeatureEnabled};
+use common_utils::ryot_log;
 use database_models::{
     access_link, collection, collection_to_entity, daily_user_activity,
     functions::associate_user_with_entity,
@@ -30,9 +30,9 @@ use jwt_service::{verify, Claims};
 use markdown::to_html as markdown_to_html;
 use media_models::{
     AnimeSpecifics, AudioBookSpecifics, BookSpecifics, CreateOrUpdateCollectionInput,
-    MangaSpecifics, MovieSpecifics, PodcastSpecifics, ReviewItem, SeenAnimeExtraInformation,
-    SeenMangaExtraInformation, SeenPodcastExtraInformation, SeenShowExtraInformation,
-    ShowSpecifics, VideoGameSpecifics, VisualNovelSpecifics,
+    MangaSpecifics, MovieSpecifics, MusicSpecifics, PodcastSpecifics, ReviewItem,
+    SeenAnimeExtraInformation, SeenMangaExtraInformation, SeenPodcastExtraInformation,
+    SeenShowExtraInformation, ShowSpecifics, VideoGameSpecifics, VisualNovelSpecifics,
 };
 use migrations::AliasedCollectionToEntity;
 use rust_decimal::{prelude::ToPrimitive, Decimal};
@@ -65,26 +65,10 @@ pub fn ilike_sql(value: &str) -> String {
 }
 
 pub async fn user_by_id(user_id: &String, ss: &Arc<SupportingService>) -> Result<user::Model> {
-    let mut user = User::find_by_id(user_id)
+    let user = User::find_by_id(user_id)
         .one(&ss.db)
         .await?
         .ok_or_else(|| Error::new("No user found"))?;
-    let config = &ss.config;
-    let features_enabled = &mut user.preferences.features_enabled;
-    features_enabled.media.anime =
-        config.anime_and_manga.is_enabled() && features_enabled.media.anime;
-    features_enabled.media.audio_book =
-        config.audio_books.is_enabled() && features_enabled.media.audio_book;
-    features_enabled.media.book = config.books.is_enabled() && features_enabled.media.book;
-    features_enabled.media.show =
-        config.movies_and_shows.is_enabled() && features_enabled.media.show;
-    features_enabled.media.manga =
-        config.anime_and_manga.is_enabled() && features_enabled.media.manga;
-    features_enabled.media.movie =
-        config.movies_and_shows.is_enabled() && features_enabled.media.movie;
-    features_enabled.media.podcast = config.podcasts.is_enabled() && features_enabled.media.podcast;
-    features_enabled.media.video_game =
-        config.video_games.is_enabled() && features_enabled.media.video_game;
     Ok(user)
 }
 
@@ -507,6 +491,7 @@ pub async fn calculate_user_activities_and_summary(
         audio_book_specifics: Option<AudioBookSpecifics>,
         book_specifics: Option<BookSpecifics>,
         movie_specifics: Option<MovieSpecifics>,
+        music_specifics: Option<MusicSpecifics>,
         podcast_specifics: Option<PodcastSpecifics>,
         show_specifics: Option<ShowSpecifics>,
         video_game_specifics: Option<VideoGameSpecifics>,
@@ -601,6 +586,7 @@ pub async fn calculate_user_activities_and_summary(
             metadata::Column::AudioBookSpecifics,
             metadata::Column::BookSpecifics,
             metadata::Column::MovieSpecifics,
+            metadata::Column::MusicSpecifics,
             metadata::Column::PodcastSpecifics,
             metadata::Column::ShowSpecifics,
             metadata::Column::VideoGameSpecifics,
@@ -648,6 +634,10 @@ pub async fn calculate_user_activities_and_summary(
             if let Some(runtime) = movie_extra.runtime {
                 activity.movie_duration += runtime;
             }
+        } else if let Some(music_extra) = seen.music_specifics {
+            if let Some(runtime) = music_extra.duration {
+                activity.music_duration += runtime;
+            }
         } else if let Some(book_extra) = seen.book_specifics {
             if let Some(pages) = book_extra.pages {
                 activity.book_pages += pages;
@@ -665,6 +655,7 @@ pub async fn calculate_user_activities_and_summary(
         match seen.metadata_lot {
             MediaLot::Book => activity.book_count += 1,
             MediaLot::Show => activity.show_count += 1,
+            MediaLot::Music => activity.music_count += 1,
             MediaLot::Anime => activity.anime_count += 1,
             MediaLot::Movie => activity.movie_count += 1,
             MediaLot::Manga => activity.manga_count += 1,
diff --git a/crates/utils/dependent/src/lib.rs b/crates/utils/dependent/src/lib.rs
index 02f89f8676..6dcc0dbc26 100644
--- a/crates/utils/dependent/src/lib.rs
+++ b/crates/utils/dependent/src/lib.rs
@@ -64,6 +64,7 @@ use providers::{
     openlibrary::OpenlibraryService,
     tmdb::{NonMediaTmdbService, TmdbMovieService, TmdbShowService},
     vndb::VndbService,
+    youtube_music::YoutubeMusicService,
 };
 use rust_decimal::{
     prelude::{FromPrimitive, One, ToPrimitive},
@@ -132,6 +133,7 @@ pub async fn get_metadata_provider(
 ) -> Result<Provider> {
     let err = || Err(Error::new("This source is not supported".to_owned()));
     let service: Provider = match source {
+        MediaSource::YoutubeMusic => Box::new(YoutubeMusicService::new().await),
         MediaSource::Vndb => Box::new(VndbService::new(&ss.config.visual_novels).await),
         MediaSource::Openlibrary => Box::new(get_openlibrary_service(&ss.config).await?),
         MediaSource::Itunes => Box::new(ITunesService::new(&ss.config.podcasts.itunes).await),
@@ -415,7 +417,6 @@ pub async fn update_metadata(
     let notifications = match maybe_details {
         Ok(details) => {
             let mut notifications = vec![];
-
             let meta = Metadata::find_by_id(metadata_id)
                 .one(&ss.db)
                 .await
@@ -594,9 +595,13 @@ pub async fn update_metadata(
             meta.title = ActiveValue::Set(details.title);
             meta.is_nsfw = ActiveValue::Set(details.is_nsfw);
             meta.is_partial = ActiveValue::Set(Some(false));
+            meta.source_url = ActiveValue::Set(details.source_url);
             meta.provider_rating = ActiveValue::Set(details.provider_rating);
             meta.description = ActiveValue::Set(details.description);
-            meta.images = ActiveValue::Set(Some(images));
+            meta.images = ActiveValue::Set(match images.is_empty() {
+                true => None,
+                false => Some(images),
+            });
             meta.videos = ActiveValue::Set(Some(details.videos));
             meta.production_status = ActiveValue::Set(details.production_status);
             meta.original_language = ActiveValue::Set(details.original_language);
@@ -613,6 +618,7 @@ pub async fn update_metadata(
             meta.book_specifics = ActiveValue::Set(details.book_specifics);
             meta.video_game_specifics = ActiveValue::Set(details.video_game_specifics);
             meta.visual_novel_specifics = ActiveValue::Set(details.visual_novel_specifics);
+            meta.music_specifics = ActiveValue::Set(details.music_specifics);
             meta.external_identifiers = ActiveValue::Set(details.external_identifiers);
             let metadata = meta.update(&ss.db).await.unwrap();
 
@@ -771,11 +777,18 @@ pub async fn commit_metadata_internal(
         lot: ActiveValue::Set(details.lot),
         source: ActiveValue::Set(details.source),
         title: ActiveValue::Set(details.title),
+        source_url: ActiveValue::Set(details.source_url),
         description: ActiveValue::Set(details.description),
         publish_year: ActiveValue::Set(details.publish_year),
         publish_date: ActiveValue::Set(details.publish_date),
-        images: ActiveValue::Set(Some(images)),
-        videos: ActiveValue::Set(Some(details.videos)),
+        images: ActiveValue::Set(match images.is_empty() {
+            true => None,
+            false => Some(images),
+        }),
+        videos: ActiveValue::Set(match details.videos.is_empty() {
+            true => None,
+            false => Some(details.videos),
+        }),
         identifier: ActiveValue::Set(details.identifier),
         audio_book_specifics: ActiveValue::Set(details.audio_book_specifics),
         anime_specifics: ActiveValue::Set(details.anime_specifics),
@@ -788,6 +801,7 @@ pub async fn commit_metadata_internal(
         visual_novel_specifics: ActiveValue::Set(details.visual_novel_specifics),
         provider_rating: ActiveValue::Set(details.provider_rating),
         production_status: ActiveValue::Set(details.production_status),
+        music_specifics: ActiveValue::Set(details.music_specifics),
         original_language: ActiveValue::Set(details.original_language),
         external_identifiers: ActiveValue::Set(details.external_identifiers),
         is_nsfw: ActiveValue::Set(details.is_nsfw),
@@ -1055,8 +1069,17 @@ pub async fn commit_metadata_group_internal(
     let provider = get_metadata_provider(lot, source, ss).await?;
     let (group_details, associated_items) = provider.metadata_group_details(identifier).await?;
     let group_id = match existing_group {
-        Some(eg) => eg.id,
+        Some(eg) => {
+            let mut eg: metadata_group::ActiveModel = eg.into();
+            eg.parts = ActiveValue::Set(group_details.parts);
+            eg.source_url = ActiveValue::Set(group_details.source_url);
+            eg.images = ActiveValue::Set(group_details.images.filter(|i| !i.is_empty()));
+            let eg = eg.update(&ss.db).await?;
+            eg.id
+        }
         None => {
+            let mut group_details = group_details.clone();
+            group_details.images = group_details.images.filter(|i| !i.is_empty());
             let mut db_group: metadata_group::ActiveModel =
                 group_details.into_model("".to_string(), None).into();
             db_group.id = ActiveValue::NotSet;
@@ -1292,7 +1315,7 @@ pub async fn get_entity_recently_consumed(
                 },
             },
         ))
-        .await?
+        .await
         .is_some())
 }
 
@@ -1319,7 +1342,7 @@ pub async fn progress_update(
         let in_cache = ss
             .cache_service
             .get_value::<EmptyCacheValue>(cache.clone())
-            .await?;
+            .await;
         if in_cache.is_some() {
             ryot_log!(debug, "Seen is already in cache");
             return Ok(ProgressUpdateResultUnion::Error(ProgressUpdateError {
diff --git a/docs/includes/backend-config-schema.yaml b/docs/includes/backend-config-schema.yaml
index e8b53224e2..5df653dcd7 100644
--- a/docs/includes/backend-config-schema.yaml
+++ b/docs/includes/backend-config-schema.yaml
@@ -129,6 +129,9 @@ movies_and_shows:
     # @envvar MOVIES_AND_SHOWS_TMDB_LOCALE
     locale: "en"
 
+# Settings related to music.
+music: {}
+
 # Settings related to podcasts.
 podcasts:
   # Settings related to iTunes.
diff --git a/docs/includes/export-schema.ts b/docs/includes/export-schema.ts
index 7377e3f1f7..856a04df66 100644
--- a/docs/includes/export-schema.ts
+++ b/docs/includes/export-schema.ts
@@ -108,7 +108,7 @@ export interface UserMeasurement {
 }
 
 /** The different types of media that can be stored. */
-export type MediaLot = 'audio_book' | 'anime' | 'book' | 'podcast' | 'manga' | 'movie' | 'show' | 'video_game' | 'visual_novel';
+export type MediaLot = 'book' | 'show' | 'movie' | 'anime' | 'manga' | 'music' | 'podcast' | 'audio_book' | 'video_game' | 'visual_novel';
 
 /** A specific instance when an entity was seen. */
 export interface ImportOrExportMetadataItemSeen {
@@ -135,7 +135,7 @@ export interface ImportOrExportMetadataItemSeen {
 }
 
 /** The different sources (or providers) from which data can be obtained from. */
-export type MediaSource = 'anilist' | 'audible' | 'custom' | 'google_books' | 'igdb' | 'itunes' | 'listennotes' | 'manga_updates' | 'mal' | 'openlibrary' | 'tmdb' | 'vndb';
+export type MediaSource = 'mal' | 'igdb' | 'tmdb' | 'vndb' | 'custom' | 'itunes' | 'anilist' | 'audible' | 'listennotes' | 'google_books' | 'openlibrary' | 'manga_updates' | 'youtube_music';
 
 /** Details about a specific media item that needs to be imported or exported. */
 export interface ImportOrExportMetadataItem {
@@ -147,7 +147,7 @@ export interface ImportOrExportMetadataItem {
 	 * The type of media.
 	 *
 	 * @default 'book'
-	 * @type {'audio_book' | 'anime' | 'book' | 'podcast' | 'manga' | 'movie' | 'show' | 'video_game' | 'visual_novel'}
+	 * @type {'book' | 'show' | 'movie' | 'anime' | 'manga' | 'music' | 'podcast' | 'audio_book' | 'video_game' | 'visual_novel'}
 	 */
 	lot: MediaLot;
 	/** The review history for the user. */
@@ -157,8 +157,8 @@ export interface ImportOrExportMetadataItem {
 	/**
 	 * The source of media.
 	 *
-	 * @default 'audible'
-	 * @type {'anilist' | 'audible' | 'custom' | 'google_books' | 'igdb' | 'itunes' | 'listennotes' | 'manga_updates' | 'mal' | 'openlibrary' | 'tmdb' | 'vndb'}
+	 * @default 'custom'
+	 * @type {'mal' | 'igdb' | 'tmdb' | 'vndb' | 'custom' | 'itunes' | 'anilist' | 'audible' | 'listennotes' | 'google_books' | 'openlibrary' | 'manga_updates' | 'youtube_music'}
 	 */
 	source: MediaSource;
 	/** An string to help identify it in the original source. */
@@ -175,7 +175,7 @@ export interface ImportOrExportMetadataGroupItem {
 	 * The type of media.
 	 *
 	 * @default 'book'
-	 * @type {'audio_book' | 'anime' | 'book' | 'podcast' | 'manga' | 'movie' | 'show' | 'video_game' | 'visual_novel'}
+	 * @type {'book' | 'show' | 'movie' | 'anime' | 'manga' | 'music' | 'podcast' | 'audio_book' | 'video_game' | 'visual_novel'}
 	 */
 	lot: MediaLot;
 	/** The review history for the user. */
@@ -183,8 +183,8 @@ export interface ImportOrExportMetadataGroupItem {
 	/**
 	 * The source of media.
 	 *
-	 * @default 'audible'
-	 * @type {'anilist' | 'audible' | 'custom' | 'google_books' | 'igdb' | 'itunes' | 'listennotes' | 'manga_updates' | 'mal' | 'openlibrary' | 'tmdb' | 'vndb'}
+	 * @default 'custom'
+	 * @type {'mal' | 'igdb' | 'tmdb' | 'vndb' | 'custom' | 'itunes' | 'anilist' | 'audible' | 'listennotes' | 'google_books' | 'openlibrary' | 'manga_updates' | 'youtube_music'}
 	 */
 	source: MediaSource;
 	/** Name of the group. */
@@ -209,8 +209,8 @@ export interface ImportOrExportPersonItem {
 	/**
 	 * The source of data.
 	 *
-	 * @default 'audible'
-	 * @type {'anilist' | 'audible' | 'custom' | 'google_books' | 'igdb' | 'itunes' | 'listennotes' | 'manga_updates' | 'mal' | 'openlibrary' | 'tmdb' | 'vndb'}
+	 * @default 'custom'
+	 * @type {'mal' | 'igdb' | 'tmdb' | 'vndb' | 'custom' | 'itunes' | 'anilist' | 'audible' | 'listennotes' | 'google_books' | 'openlibrary' | 'manga_updates' | 'youtube_music'}
 	 */
 	source: MediaSource;
 	/** The source specific data. */
diff --git a/libs/generated/src/graphql/backend/gql.ts b/libs/generated/src/graphql/backend/gql.ts
index 8e21cb579b..4381a12bdf 100644
--- a/libs/generated/src/graphql/backend/gql.ts
+++ b/libs/generated/src/graphql/backend/gql.ts
@@ -15,14 +15,14 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/
  */
 const documents = {
     "mutation RegisterUser($input: RegisterUserInput!) {\n  registerUser(input: $input) {\n    __typename\n    ... on RegisterError {\n      error\n    }\n    ... on StringIdObject {\n      id\n    }\n  }\n}\n\nmutation LoginUser($input: AuthUserInput!) {\n  loginUser(input: $input) {\n    __typename\n    ... on LoginError {\n      error\n    }\n    ... on LoginResponse {\n      apiKey\n    }\n  }\n}\n\nmutation AddEntityToCollection($input: ChangeCollectionToEntityInput!) {\n  addEntityToCollection(input: $input)\n}\n\nmutation CommitMetadata($input: CommitMediaInput!) {\n  commitMetadata(input: $input) {\n    id\n  }\n}\n\nmutation CommitMetadataGroup($input: CommitMediaInput!) {\n  commitMetadataGroup(input: $input) {\n    id\n  }\n}\n\nmutation CommitPerson($input: CommitPersonInput!) {\n  commitPerson(input: $input) {\n    id\n  }\n}\n\nmutation CreateCustomExercise($input: ExerciseInput!) {\n  createCustomExercise(input: $input)\n}\n\nmutation UpdateCustomExercise($input: UpdateCustomExerciseInput!) {\n  updateCustomExercise(input: $input)\n}\n\nmutation UpdateUserIntegration($input: UpdateUserIntegrationInput!) {\n  updateUserIntegration(input: $input)\n}\n\nmutation CreateCustomMetadata($input: CreateCustomMetadataInput!) {\n  createCustomMetadata(input: $input) {\n    id\n  }\n}\n\nmutation CreateOrUpdateCollection($input: CreateOrUpdateCollectionInput!) {\n  createOrUpdateCollection(input: $input) {\n    id\n  }\n}\n\nmutation CreateReviewComment($input: CreateReviewCommentInput!) {\n  createReviewComment(input: $input)\n}\n\nmutation CreateUserMeasurement($input: UserMeasurementInput!) {\n  createUserMeasurement(input: $input)\n}\n\nmutation CreateUserNotificationPlatform($input: CreateUserNotificationPlatformInput!) {\n  createUserNotificationPlatform(input: $input)\n}\n\nmutation CreateUserIntegration($input: CreateUserIntegrationInput!) {\n  createUserIntegration(input: $input) {\n    id\n  }\n}\n\nmutation CreateOrUpdateUserWorkout($input: UserWorkoutInput!) {\n  createOrUpdateUserWorkout(input: $input)\n}\n\nmutation CreateOrUpdateUserWorkoutTemplate($input: UserWorkoutInput!) {\n  createOrUpdateUserWorkoutTemplate(input: $input)\n}\n\nmutation DeleteCollection($collectionName: String!) {\n  deleteCollection(collectionName: $collectionName)\n}\n\nmutation DeleteReview($reviewId: String!) {\n  deleteReview(reviewId: $reviewId)\n}\n\nmutation DeleteS3Object($key: String!) {\n  deleteS3Object(key: $key)\n}\n\nmutation DeleteSeenItem($seenId: String!) {\n  deleteSeenItem(seenId: $seenId) {\n    id\n  }\n}\n\nmutation DeleteUser($toDeleteUserId: String!) {\n  deleteUser(toDeleteUserId: $toDeleteUserId)\n}\n\nmutation DeleteUserIntegration($integrationId: String!) {\n  deleteUserIntegration(integrationId: $integrationId)\n}\n\nmutation DeleteUserMeasurement($timestamp: DateTime!) {\n  deleteUserMeasurement(timestamp: $timestamp)\n}\n\nmutation DeleteUserNotificationPlatform($notificationId: String!) {\n  deleteUserNotificationPlatform(notificationId: $notificationId)\n}\n\nmutation DeleteUserWorkout($workoutId: String!) {\n  deleteUserWorkout(workoutId: $workoutId)\n}\n\nmutation DeleteUserWorkoutTemplate($workoutTemplateId: String!) {\n  deleteUserWorkoutTemplate(workoutTemplateId: $workoutTemplateId)\n}\n\nmutation DeployBackgroundJob($jobName: BackgroundJob!) {\n  deployBackgroundJob(jobName: $jobName)\n}\n\nmutation DeployBulkProgressUpdate($input: [ProgressUpdateInput!]!) {\n  deployBulkProgressUpdate(input: $input)\n}\n\nmutation DeployExportJob {\n  deployExportJob\n}\n\nmutation DeployImportJob($input: DeployImportJobInput!) {\n  deployImportJob(input: $input)\n}\n\nmutation DeployUpdateMetadataJob($metadataId: String!) {\n  deployUpdateMetadataJob(metadataId: $metadataId)\n}\n\nmutation DeployUpdatePersonJob($personId: String!) {\n  deployUpdatePersonJob(personId: $personId)\n}\n\nmutation DeployUpdateMetadataGroupJob($metadataGroupId: String!) {\n  deployUpdateMetadataGroupJob(metadataGroupId: $metadataGroupId)\n}\n\nmutation UpdateSeenItem($input: UpdateSeenItemInput!) {\n  updateSeenItem(input: $input)\n}\n\nmutation UpdateUserNotificationPlatform($input: UpdateUserNotificationPlatformInput!) {\n  updateUserNotificationPlatform(input: $input)\n}\n\nmutation UpdateUserWorkoutAttributes($input: UpdateUserWorkoutAttributesInput!) {\n  updateUserWorkoutAttributes(input: $input)\n}\n\nmutation GenerateAuthToken {\n  generateAuthToken\n}\n\nmutation MergeMetadata($mergeFrom: String!, $mergeInto: String!) {\n  mergeMetadata(mergeFrom: $mergeFrom, mergeInto: $mergeInto)\n}\n\nmutation DisassociateMetadata($metadataId: String!) {\n  disassociateMetadata(metadataId: $metadataId)\n}\n\nmutation CreateOrUpdateReview($input: CreateOrUpdateReviewInput!) {\n  createOrUpdateReview(input: $input) {\n    id\n  }\n}\n\nmutation PresignedPutS3Url($input: PresignedPutUrlInput!) {\n  presignedPutS3Url(input: $input) {\n    key\n    uploadUrl\n  }\n}\n\nmutation RemoveEntityFromCollection($input: ChangeCollectionToEntityInput!) {\n  removeEntityFromCollection(input: $input) {\n    id\n  }\n}\n\nmutation TestUserNotificationPlatforms {\n  testUserNotificationPlatforms\n}\n\nmutation UpdateUser($input: UpdateUserInput!) {\n  updateUser(input: $input) {\n    id\n  }\n}\n\nmutation UpdateUserPreference($input: UpdateComplexJsonInput!) {\n  updateUserPreference(input: $input)\n}\n\nmutation CreateAccessLink($input: CreateAccessLinkInput!) {\n  createAccessLink(input: $input) {\n    id\n  }\n}\n\nmutation ProcessAccessLink($input: ProcessAccessLinkInput!) {\n  processAccessLink(input: $input) {\n    __typename\n    ... on ProcessAccessLinkError {\n      error\n    }\n    ... on ProcessAccessLinkResponse {\n      apiKey\n      redirectTo\n      tokenValidForDays\n    }\n  }\n}\n\nmutation RevokeAccessLink($accessLinkId: String!) {\n  revokeAccessLink(accessLinkId: $accessLinkId)\n}\n\nmutation UpdateUserExerciseSettings($input: UpdateUserExerciseSettings!) {\n  updateUserExerciseSettings(input: $input)\n}\n\nmutation MergeExercise($mergeFrom: String!, $mergeInto: String!) {\n  mergeExercise(mergeFrom: $mergeFrom, mergeInto: $mergeInto)\n}": types.RegisterUserDocument,
-    "query MetadataDetails($metadataId: String!) {\n  metadataDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    source\n    isNsfw\n    isPartial\n    sourceUrl\n    identifier\n    description\n    suggestions\n    publishYear\n    publishDate\n    providerRating\n    productionStatus\n    originalLanguage\n    genres {\n      id\n      name\n    }\n    group {\n      id\n      name\n      part\n    }\n    assets {\n      images\n      videos {\n        videoId\n        source\n      }\n    }\n    creators {\n      name\n      items {\n        id\n        name\n        image\n        character\n      }\n    }\n    watchProviders {\n      name\n      image\n      languages\n    }\n    animeSpecifics {\n      episodes\n    }\n    audioBookSpecifics {\n      runtime\n    }\n    bookSpecifics {\n      pages\n    }\n    movieSpecifics {\n      runtime\n    }\n    mangaSpecifics {\n      volumes\n      chapters\n    }\n    podcastSpecifics {\n      episodes {\n        id\n        title\n        overview\n        thumbnail\n        number\n        runtime\n        publishDate\n      }\n      totalEpisodes\n    }\n    showSpecifics {\n      totalSeasons\n      totalEpisodes\n      runtime\n      seasons {\n        id\n        seasonNumber\n        name\n        overview\n        backdropImages\n        posterImages\n        episodes {\n          id\n          name\n          posterImages\n          episodeNumber\n          publishDate\n          name\n          overview\n          runtime\n        }\n      }\n    }\n    visualNovelSpecifics {\n      length\n    }\n    videoGameSpecifics {\n      platforms\n    }\n  }\n}": types.MetadataDetailsDocument,
-    "query PersonDetails($personId: String!) {\n  personDetails(personId: $personId) {\n    sourceUrl\n    details {\n      id\n      name\n      source\n      identifier\n      isPartial\n      description\n      birthDate\n      deathDate\n      place\n      website\n      gender\n      displayImages\n    }\n    contents {\n      name\n      count\n      items {\n        character\n        metadataId\n      }\n    }\n  }\n}": types.PersonDetailsDocument,
+    "query MetadataDetails($metadataId: String!) {\n  metadataDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    source\n    isNsfw\n    isPartial\n    sourceUrl\n    identifier\n    description\n    suggestions\n    publishYear\n    publishDate\n    providerRating\n    productionStatus\n    originalLanguage\n    genres {\n      id\n      name\n    }\n    group {\n      id\n      name\n      part\n    }\n    assets {\n      images\n      videos {\n        videoId\n        source\n      }\n    }\n    creators {\n      name\n      items {\n        id\n        name\n        image\n        character\n      }\n    }\n    watchProviders {\n      name\n      image\n      languages\n    }\n    animeSpecifics {\n      episodes\n    }\n    audioBookSpecifics {\n      runtime\n    }\n    bookSpecifics {\n      pages\n    }\n    movieSpecifics {\n      runtime\n    }\n    mangaSpecifics {\n      volumes\n      chapters\n    }\n    podcastSpecifics {\n      episodes {\n        id\n        title\n        overview\n        thumbnail\n        number\n        runtime\n        publishDate\n      }\n      totalEpisodes\n    }\n    showSpecifics {\n      totalSeasons\n      totalEpisodes\n      runtime\n      seasons {\n        id\n        seasonNumber\n        name\n        overview\n        backdropImages\n        posterImages\n        episodes {\n          id\n          name\n          posterImages\n          episodeNumber\n          publishDate\n          name\n          overview\n          runtime\n        }\n      }\n    }\n    visualNovelSpecifics {\n      length\n    }\n    videoGameSpecifics {\n      platforms\n    }\n    musicSpecifics {\n      duration\n      viewCount\n      byVariousArtists\n    }\n  }\n}": types.MetadataDetailsDocument,
+    "query PersonDetails($personId: String!) {\n  personDetails(personId: $personId) {\n    details {\n      id\n      name\n      source\n      identifier\n      isPartial\n      description\n      birthDate\n      deathDate\n      place\n      website\n      gender\n      displayImages\n      sourceUrl\n    }\n    contents {\n      name\n      count\n      items {\n        character\n        metadataId\n      }\n    }\n  }\n}": types.PersonDetailsDocument,
     "query UserAnalytics($input: UserAnalyticsInput!) {\n  userAnalytics(input: $input) {\n    hours {\n      hour\n      entities {\n        entityId\n        entityLot\n        metadataLot\n      }\n    }\n    activities {\n      groupedBy\n      totalCount\n      totalDuration\n      items {\n        day\n        bookCount\n        showCount\n        totalCount\n        movieCount\n        animeCount\n        mangaCount\n        workoutCount\n        podcastCount\n        totalDuration\n        totalBookPages\n        audioBookCount\n        videoGameCount\n        totalReviewCount\n        visualNovelCount\n        totalWorkoutReps\n        totalShowDuration\n        totalMovieDuration\n        totalMetadataCount\n        totalWorkoutWeight\n        totalPodcastDuration\n        totalWorkoutDistance\n        totalWorkoutRestTime\n        totalWorkoutDuration\n        userMeasurementCount\n        totalVideoGameDuration\n        totalAudioBookDuration\n        totalPersonReviewCount\n        totalVisualNovelDuration\n        totalMetadataReviewCount\n        totalWorkoutPersonalBests\n        totalCollectionReviewCount\n        totalMetadataGroupReviewCount\n      }\n    }\n    fitness {\n      workoutReps\n      workoutCount\n      workoutWeight\n      workoutDistance\n      workoutDuration\n      workoutRestTime\n      measurementCount\n      workoutPersonalBests\n      workoutExercises {\n        count\n        exercise\n      }\n      workoutMuscles {\n        count\n        muscle\n      }\n      workoutEquipments {\n        count\n        equipment\n      }\n    }\n  }\n}": types.UserAnalyticsDocument,
-    "query UserDetails {\n  userDetails {\n    __typename\n    ... on User {\n      id\n      lot\n      name\n      isDisabled\n      oidcIssuerId\n      preferences {\n        general {\n          reviewScale\n          gridPacking\n          displayNsfw\n          disableVideos\n          persistQueries\n          disableReviews\n          disableIntegrations\n          disableWatchProviders\n          disableNavigationAnimation\n          dashboard {\n            hidden\n            section\n            numElements\n            deduplicateMedia\n          }\n          watchProviders {\n            lot\n            values\n          }\n        }\n        fitness {\n          logging {\n            muteSounds\n            showDetailsWhileEditing\n          }\n          exercises {\n            unitSystem\n            setRestTimers {\n              ...SetRestTimersPart\n            }\n          }\n          measurements {\n            custom {\n              name\n              dataType\n            }\n            inbuilt {\n              weight\n              bodyMassIndex\n              totalBodyWater\n              muscle\n              leanBodyMass\n              bodyFat\n              boneMass\n              visceralFat\n              waistCircumference\n              waistToHeightRatio\n              hipCircumference\n              waistToHipRatio\n              chestCircumference\n              thighCircumference\n              bicepsCircumference\n              neckCircumference\n              bodyFatCaliper\n              chestSkinfold\n              abdominalSkinfold\n              thighSkinfold\n              basalMetabolicRate\n              totalDailyEnergyExpenditure\n              calories\n            }\n          }\n        }\n        notifications {\n          toSend\n          enabled\n        }\n        featuresEnabled {\n          analytics {\n            enabled\n          }\n          others {\n            calendar\n            collections\n          }\n          fitness {\n            enabled\n            workouts\n            templates\n            measurements\n          }\n          media {\n            enabled\n            anime\n            audioBook\n            book\n            manga\n            movie\n            podcast\n            show\n            videoGame\n            visualNovel\n            people\n            groups\n            genres\n          }\n        }\n      }\n    }\n  }\n}": types.UserDetailsDocument,
+    "query UserDetails {\n  userDetails {\n    __typename\n    ... on User {\n      id\n      lot\n      name\n      isDisabled\n      oidcIssuerId\n      preferences {\n        general {\n          reviewScale\n          gridPacking\n          displayNsfw\n          disableVideos\n          persistQueries\n          disableReviews\n          disableIntegrations\n          disableWatchProviders\n          disableNavigationAnimation\n          dashboard {\n            hidden\n            section\n            numElements\n            deduplicateMedia\n          }\n          watchProviders {\n            lot\n            values\n          }\n        }\n        fitness {\n          logging {\n            muteSounds\n            showDetailsWhileEditing\n          }\n          exercises {\n            unitSystem\n            setRestTimers {\n              ...SetRestTimersPart\n            }\n          }\n          measurements {\n            custom {\n              name\n              dataType\n            }\n            inbuilt {\n              weight\n              bodyMassIndex\n              totalBodyWater\n              muscle\n              leanBodyMass\n              bodyFat\n              boneMass\n              visceralFat\n              waistCircumference\n              waistToHeightRatio\n              hipCircumference\n              waistToHipRatio\n              chestCircumference\n              thighCircumference\n              bicepsCircumference\n              neckCircumference\n              bodyFatCaliper\n              chestSkinfold\n              abdominalSkinfold\n              thighSkinfold\n              basalMetabolicRate\n              totalDailyEnergyExpenditure\n              calories\n            }\n          }\n        }\n        notifications {\n          toSend\n          enabled\n        }\n        featuresEnabled {\n          analytics {\n            enabled\n          }\n          others {\n            calendar\n            collections\n          }\n          fitness {\n            enabled\n            workouts\n            templates\n            measurements\n          }\n          media {\n            enabled\n            show\n            book\n            anime\n            manga\n            music\n            movie\n            groups\n            people\n            genres\n            podcast\n            audioBook\n            videoGame\n            visualNovel\n          }\n        }\n      }\n    }\n  }\n}": types.UserDetailsDocument,
     "query UserExerciseDetails($exerciseId: String!) {\n  userExerciseDetails(exerciseId: $exerciseId) {\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n    history {\n      idx\n      workoutId\n      workoutEndOn\n      bestSet {\n        ...WorkoutSetRecordPart\n      }\n    }\n    details {\n      exerciseId\n      createdOn\n      lastUpdatedOn\n      exerciseNumTimesInteracted\n      exerciseExtraInformation {\n        settings {\n          excludeFromAnalytics\n          setRestTimers {\n            ...SetRestTimersPart\n          }\n        }\n        lifetimeStats {\n          weight\n          reps\n          distance\n          duration\n          personalBestsAchieved\n        }\n        personalBests {\n          lot\n          sets {\n            setIdx\n            workoutId\n            exerciseIdx\n          }\n        }\n      }\n    }\n  }\n}": types.UserExerciseDetailsDocument,
     "query UserMeasurementsList($input: UserMeasurementsListInput!) {\n  userMeasurementsList(input: $input) {\n    timestamp\n    name\n    comment\n    stats {\n      weight\n      bodyMassIndex\n      totalBodyWater\n      muscle\n      leanBodyMass\n      bodyFat\n      boneMass\n      visceralFat\n      waistCircumference\n      waistToHeightRatio\n      hipCircumference\n      waistToHipRatio\n      chestCircumference\n      thighCircumference\n      bicepsCircumference\n      neckCircumference\n      bodyFatCaliper\n      chestSkinfold\n      abdominalSkinfold\n      thighSkinfold\n      basalMetabolicRate\n      totalDailyEnergyExpenditure\n      calories\n      custom\n    }\n  }\n}": types.UserMeasurementsListDocument,
     "query UserMetadataDetails($metadataId: String!) {\n  userMetadataDetails(metadataId: $metadataId) {\n    mediaReason\n    hasInteracted\n    averageRating\n    seenByAllCount\n    seenByUserCount\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    history {\n      ...SeenPart\n    }\n    nextEntry {\n      season\n      volume\n      episode\n      chapter\n    }\n    inProgress {\n      ...SeenPart\n    }\n    collections {\n      ...CollectionPart\n    }\n    showProgress {\n      timesSeen\n      seasonNumber\n      episodes {\n        episodeNumber\n        timesSeen\n      }\n    }\n    podcastProgress {\n      episodeNumber\n      timesSeen\n    }\n  }\n}": types.UserMetadataDetailsDocument,
-    "query GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n  userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery UserExports {\n  userExports {\n    url\n    size\n    endedAt\n    startedAt\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    provider\n    createdOn\n    isDisabled\n    maximumProgress\n    minimumProgress\n    lastTriggeredOn\n    syncToOwnedCollection\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    isDisabled\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    lot\n    name\n    isDisabled\n  }\n}\n\nquery UserRecommendations {\n  userRecommendations\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: MetadataGroupsListInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAccessLinks {\n  userAccessLinks {\n    id\n    name\n    isDemo\n    createdOn\n    expiresOn\n    timesUsed\n    isRevoked\n    maximumUses\n    isAccountDefault\n    isMutationAllowed\n  }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n  exerciseDetails(exerciseId: $exerciseId) {\n    id\n    lot\n    name\n    level\n    force\n    source\n    muscles\n    mechanic\n    equipment\n    createdByUserId\n    attributes {\n      instructions\n      images\n    }\n  }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n  exercisesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      lot\n      name\n      image\n      muscle\n      lastUpdatedOn\n      numTimesInteracted\n    }\n  }\n}\n\nquery ImportReports {\n  importReports {\n    id\n    source\n    progress\n    startedOn\n    finishedOn\n    wasSuccess\n    details {\n      import {\n        total\n      }\n      failedItems {\n        lot\n        step\n        error\n        identifier\n      }\n    }\n  }\n}\n\nquery GenresList($input: SearchInput!) {\n  genresList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      numItems\n    }\n  }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n  genreDetails(input: $input) {\n    details {\n      id\n      name\n      numItems\n    }\n    contents {\n      details {\n        total\n        nextPage\n      }\n      items\n    }\n  }\n}\n\nquery CollectionContents($input: CollectionContentsInput!) {\n  collectionContents(input: $input) {\n    user {\n      id\n      name\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n    results {\n      details {\n        total\n        nextPage\n      }\n      items {\n        entityId\n        entityLot\n      }\n    }\n    details {\n      name\n      description\n      createdOn\n    }\n  }\n}\n\nquery CoreDetails {\n  coreDetails {\n    version\n    docsLink\n    pageSize\n    websiteUrl\n    smtpEnabled\n    oidcEnabled\n    signupAllowed\n    repositoryLink\n    disableTelemetry\n    tokenValidForDays\n    localAuthDisabled\n    fileStorageEnabled\n    isServerKeyValidated\n    metadataLotSourceMappings {\n      lot\n      sources\n    }\n    metadataProviderLanguages {\n      source\n      default\n      supported\n    }\n    frontend {\n      url\n      oidcButtonLabel\n      dashboardMessage\n      umami {\n        domains\n        scriptUrl\n        websiteId\n      }\n    }\n    exerciseParameters {\n      downloadRequired\n      filters {\n        type\n        level\n        force\n        mechanic\n        equipment\n        muscle\n      }\n      lotMapping {\n        lot\n        bests\n      }\n    }\n  }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n  metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    contents\n    sourceUrl\n    details {\n      id\n      title\n      lot\n      source\n      displayImages\n      identifier\n      parts\n      isPartial\n    }\n  }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n  metadataGroupSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      parts\n    }\n  }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n  metadataList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n  metadataSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      databaseId\n      hasInteracted\n      item {\n        identifier\n        title\n        image\n        publishYear\n      }\n    }\n  }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n  peopleSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      birthYear\n    }\n  }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}\n\nquery UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    recentlyConsumed\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n  userWorkoutDetails(workoutId: $workoutId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      endTime\n      duration\n      startTime\n      templateId\n      repeatedFrom\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n  userWorkoutsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n  userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      createdOn\n      visibility\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n  userWorkoutTemplatesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAnalyticsParameters {\n  userAnalyticsParameters {\n    endDate\n    startDate\n  }\n}": types.GetOidcRedirectUrlDocument,
+    "query GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n  userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery UserExports {\n  userExports {\n    url\n    size\n    endedAt\n    startedAt\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    provider\n    createdOn\n    isDisabled\n    maximumProgress\n    minimumProgress\n    lastTriggeredOn\n    syncToOwnedCollection\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    isDisabled\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    lot\n    name\n    isDisabled\n  }\n}\n\nquery UserRecommendations {\n  userRecommendations\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: MetadataGroupsListInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAccessLinks {\n  userAccessLinks {\n    id\n    name\n    isDemo\n    createdOn\n    expiresOn\n    timesUsed\n    isRevoked\n    maximumUses\n    isAccountDefault\n    isMutationAllowed\n  }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n  exerciseDetails(exerciseId: $exerciseId) {\n    id\n    lot\n    name\n    level\n    force\n    source\n    muscles\n    mechanic\n    equipment\n    createdByUserId\n    attributes {\n      instructions\n      images\n    }\n  }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n  exercisesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      lot\n      name\n      image\n      muscle\n      lastUpdatedOn\n      numTimesInteracted\n    }\n  }\n}\n\nquery ImportReports {\n  importReports {\n    id\n    source\n    progress\n    startedOn\n    finishedOn\n    wasSuccess\n    details {\n      import {\n        total\n      }\n      failedItems {\n        lot\n        step\n        error\n        identifier\n      }\n    }\n  }\n}\n\nquery GenresList($input: SearchInput!) {\n  genresList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      numItems\n    }\n  }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n  genreDetails(input: $input) {\n    details {\n      id\n      name\n      numItems\n    }\n    contents {\n      details {\n        total\n        nextPage\n      }\n      items\n    }\n  }\n}\n\nquery CollectionContents($input: CollectionContentsInput!) {\n  collectionContents(input: $input) {\n    user {\n      id\n      name\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n    results {\n      details {\n        total\n        nextPage\n      }\n      items {\n        entityId\n        entityLot\n      }\n    }\n    details {\n      name\n      description\n      createdOn\n    }\n  }\n}\n\nquery CoreDetails {\n  coreDetails {\n    version\n    docsLink\n    pageSize\n    websiteUrl\n    smtpEnabled\n    oidcEnabled\n    signupAllowed\n    repositoryLink\n    disableTelemetry\n    tokenValidForDays\n    localAuthDisabled\n    fileStorageEnabled\n    isServerKeyValidated\n    metadataLotSourceMappings {\n      lot\n      sources\n    }\n    metadataProviderLanguages {\n      source\n      default\n      supported\n    }\n    frontend {\n      url\n      oidcButtonLabel\n      dashboardMessage\n      umami {\n        domains\n        scriptUrl\n        websiteId\n      }\n    }\n    exerciseParameters {\n      downloadRequired\n      filters {\n        type\n        level\n        force\n        mechanic\n        equipment\n        muscle\n      }\n      lotMapping {\n        lot\n        bests\n      }\n    }\n  }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n  metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    contents\n    details {\n      id\n      lot\n      title\n      parts\n      source\n      isPartial\n      sourceUrl\n      identifier\n      description\n      displayImages\n    }\n  }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n  metadataGroupSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      parts\n    }\n  }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n  metadataList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n  metadataSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      databaseId\n      hasInteracted\n      item {\n        identifier\n        title\n        image\n        publishYear\n      }\n    }\n  }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n  peopleSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      birthYear\n    }\n  }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}\n\nquery UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    recentlyConsumed\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n  userWorkoutDetails(workoutId: $workoutId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      endTime\n      duration\n      startTime\n      templateId\n      repeatedFrom\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n  userWorkoutsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n  userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      createdOn\n      visibility\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n  userWorkoutTemplatesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAnalyticsParameters {\n  userAnalyticsParameters {\n    endDate\n    startDate\n  }\n}": types.GetOidcRedirectUrlDocument,
     "fragment SeenPodcastExtraInformationPart on SeenPodcastExtraInformation {\n  episode\n}\n\nfragment SeenShowExtraInformationPart on SeenShowExtraInformation {\n  episode\n  season\n}\n\nfragment SeenAnimeExtraInformationPart on SeenAnimeExtraInformation {\n  episode\n}\n\nfragment SeenMangaExtraInformationPart on SeenMangaExtraInformation {\n  volume\n  chapter\n}\n\nfragment CalendarEventPart on GraphqlCalendarEvent {\n  date\n  metadataId\n  metadataLot\n  episodeName\n  metadataTitle\n  metadataImage\n  calendarEventId\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n}\n\nfragment SeenPart on Seen {\n  id\n  state\n  progress\n  reviewId\n  startedOn\n  finishedOn\n  lastUpdatedOn\n  manualTimeSpent\n  numTimesUpdated\n  providerWatchedOn\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n  mangaExtraInformation {\n    ...SeenMangaExtraInformationPart\n  }\n}\n\nfragment MetadataSearchItemPart on MetadataSearchItem {\n  title\n  image\n  identifier\n  publishYear\n}\n\nfragment WorkoutOrExerciseTotalsPart on WorkoutOrExerciseTotals {\n  reps\n  weight\n  distance\n  duration\n  restTime\n  personalBestsAchieved\n}\n\nfragment EntityAssetsPart on EntityAssets {\n  images\n  videos\n}\n\nfragment WorkoutSetStatisticPart on WorkoutSetStatistic {\n  reps\n  pace\n  oneRm\n  weight\n  volume\n  duration\n  distance\n}\n\nfragment WorkoutSetRecordPart on WorkoutSetRecord {\n  lot\n  personalBests\n  statistic {\n    ...WorkoutSetStatisticPart\n  }\n}\n\nfragment WorkoutSummaryPart on WorkoutSummary {\n  total {\n    ...WorkoutOrExerciseTotalsPart\n  }\n  exercises {\n    id\n    lot\n    numSets\n    bestSet {\n      ...WorkoutSetRecordPart\n    }\n  }\n  focused {\n    lots {\n      lot\n      exercises\n    }\n    levels {\n      level\n      exercises\n    }\n    forces {\n      force\n      exercises\n    }\n    muscles {\n      muscle\n      exercises\n    }\n    equipments {\n      equipment\n      exercises\n    }\n  }\n}\n\nfragment CollectionPart on Collection {\n  id\n  name\n  userId\n}\n\nfragment ReviewItemPart on ReviewItem {\n  id\n  rating\n  postedOn\n  isSpoiler\n  visibility\n  textOriginal\n  textRendered\n  seenItemsAssociatedWith\n  postedBy {\n    id\n    name\n  }\n  comments {\n    id\n    text\n    likedBy\n    createdOn\n    user {\n      id\n      name\n    }\n  }\n  showExtraInformation {\n    season\n    episode\n  }\n  podcastExtraInformation {\n    episode\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n  mangaExtraInformation {\n    ...SeenMangaExtraInformationPart\n  }\n}\n\nfragment WorkoutInformationPart on WorkoutInformation {\n  comment\n  assets {\n    ...EntityAssetsPart\n  }\n  supersets {\n    color\n    exercises\n  }\n  exercises {\n    id\n    lot\n    notes\n    total {\n      ...WorkoutOrExerciseTotalsPart\n    }\n    assets {\n      ...EntityAssetsPart\n    }\n    sets {\n      lot\n      rpe\n      note\n      restTime\n      confirmedAt\n      personalBests\n      statistic {\n        ...WorkoutSetStatisticPart\n      }\n    }\n  }\n}\n\nfragment SetRestTimersPart on SetRestTimersSettings {\n  drop\n  warmup\n  normal\n  failure\n}": types.SeenPodcastExtraInformationPartFragmentDoc,
 };
 
@@ -47,11 +47,11 @@ export function graphql(source: "mutation RegisterUser($input: RegisterUserInput
 /**
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
  */
-export function graphql(source: "query MetadataDetails($metadataId: String!) {\n  metadataDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    source\n    isNsfw\n    isPartial\n    sourceUrl\n    identifier\n    description\n    suggestions\n    publishYear\n    publishDate\n    providerRating\n    productionStatus\n    originalLanguage\n    genres {\n      id\n      name\n    }\n    group {\n      id\n      name\n      part\n    }\n    assets {\n      images\n      videos {\n        videoId\n        source\n      }\n    }\n    creators {\n      name\n      items {\n        id\n        name\n        image\n        character\n      }\n    }\n    watchProviders {\n      name\n      image\n      languages\n    }\n    animeSpecifics {\n      episodes\n    }\n    audioBookSpecifics {\n      runtime\n    }\n    bookSpecifics {\n      pages\n    }\n    movieSpecifics {\n      runtime\n    }\n    mangaSpecifics {\n      volumes\n      chapters\n    }\n    podcastSpecifics {\n      episodes {\n        id\n        title\n        overview\n        thumbnail\n        number\n        runtime\n        publishDate\n      }\n      totalEpisodes\n    }\n    showSpecifics {\n      totalSeasons\n      totalEpisodes\n      runtime\n      seasons {\n        id\n        seasonNumber\n        name\n        overview\n        backdropImages\n        posterImages\n        episodes {\n          id\n          name\n          posterImages\n          episodeNumber\n          publishDate\n          name\n          overview\n          runtime\n        }\n      }\n    }\n    visualNovelSpecifics {\n      length\n    }\n    videoGameSpecifics {\n      platforms\n    }\n  }\n}"): (typeof documents)["query MetadataDetails($metadataId: String!) {\n  metadataDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    source\n    isNsfw\n    isPartial\n    sourceUrl\n    identifier\n    description\n    suggestions\n    publishYear\n    publishDate\n    providerRating\n    productionStatus\n    originalLanguage\n    genres {\n      id\n      name\n    }\n    group {\n      id\n      name\n      part\n    }\n    assets {\n      images\n      videos {\n        videoId\n        source\n      }\n    }\n    creators {\n      name\n      items {\n        id\n        name\n        image\n        character\n      }\n    }\n    watchProviders {\n      name\n      image\n      languages\n    }\n    animeSpecifics {\n      episodes\n    }\n    audioBookSpecifics {\n      runtime\n    }\n    bookSpecifics {\n      pages\n    }\n    movieSpecifics {\n      runtime\n    }\n    mangaSpecifics {\n      volumes\n      chapters\n    }\n    podcastSpecifics {\n      episodes {\n        id\n        title\n        overview\n        thumbnail\n        number\n        runtime\n        publishDate\n      }\n      totalEpisodes\n    }\n    showSpecifics {\n      totalSeasons\n      totalEpisodes\n      runtime\n      seasons {\n        id\n        seasonNumber\n        name\n        overview\n        backdropImages\n        posterImages\n        episodes {\n          id\n          name\n          posterImages\n          episodeNumber\n          publishDate\n          name\n          overview\n          runtime\n        }\n      }\n    }\n    visualNovelSpecifics {\n      length\n    }\n    videoGameSpecifics {\n      platforms\n    }\n  }\n}"];
+export function graphql(source: "query MetadataDetails($metadataId: String!) {\n  metadataDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    source\n    isNsfw\n    isPartial\n    sourceUrl\n    identifier\n    description\n    suggestions\n    publishYear\n    publishDate\n    providerRating\n    productionStatus\n    originalLanguage\n    genres {\n      id\n      name\n    }\n    group {\n      id\n      name\n      part\n    }\n    assets {\n      images\n      videos {\n        videoId\n        source\n      }\n    }\n    creators {\n      name\n      items {\n        id\n        name\n        image\n        character\n      }\n    }\n    watchProviders {\n      name\n      image\n      languages\n    }\n    animeSpecifics {\n      episodes\n    }\n    audioBookSpecifics {\n      runtime\n    }\n    bookSpecifics {\n      pages\n    }\n    movieSpecifics {\n      runtime\n    }\n    mangaSpecifics {\n      volumes\n      chapters\n    }\n    podcastSpecifics {\n      episodes {\n        id\n        title\n        overview\n        thumbnail\n        number\n        runtime\n        publishDate\n      }\n      totalEpisodes\n    }\n    showSpecifics {\n      totalSeasons\n      totalEpisodes\n      runtime\n      seasons {\n        id\n        seasonNumber\n        name\n        overview\n        backdropImages\n        posterImages\n        episodes {\n          id\n          name\n          posterImages\n          episodeNumber\n          publishDate\n          name\n          overview\n          runtime\n        }\n      }\n    }\n    visualNovelSpecifics {\n      length\n    }\n    videoGameSpecifics {\n      platforms\n    }\n    musicSpecifics {\n      duration\n      viewCount\n      byVariousArtists\n    }\n  }\n}"): (typeof documents)["query MetadataDetails($metadataId: String!) {\n  metadataDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    source\n    isNsfw\n    isPartial\n    sourceUrl\n    identifier\n    description\n    suggestions\n    publishYear\n    publishDate\n    providerRating\n    productionStatus\n    originalLanguage\n    genres {\n      id\n      name\n    }\n    group {\n      id\n      name\n      part\n    }\n    assets {\n      images\n      videos {\n        videoId\n        source\n      }\n    }\n    creators {\n      name\n      items {\n        id\n        name\n        image\n        character\n      }\n    }\n    watchProviders {\n      name\n      image\n      languages\n    }\n    animeSpecifics {\n      episodes\n    }\n    audioBookSpecifics {\n      runtime\n    }\n    bookSpecifics {\n      pages\n    }\n    movieSpecifics {\n      runtime\n    }\n    mangaSpecifics {\n      volumes\n      chapters\n    }\n    podcastSpecifics {\n      episodes {\n        id\n        title\n        overview\n        thumbnail\n        number\n        runtime\n        publishDate\n      }\n      totalEpisodes\n    }\n    showSpecifics {\n      totalSeasons\n      totalEpisodes\n      runtime\n      seasons {\n        id\n        seasonNumber\n        name\n        overview\n        backdropImages\n        posterImages\n        episodes {\n          id\n          name\n          posterImages\n          episodeNumber\n          publishDate\n          name\n          overview\n          runtime\n        }\n      }\n    }\n    visualNovelSpecifics {\n      length\n    }\n    videoGameSpecifics {\n      platforms\n    }\n    musicSpecifics {\n      duration\n      viewCount\n      byVariousArtists\n    }\n  }\n}"];
 /**
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
  */
-export function graphql(source: "query PersonDetails($personId: String!) {\n  personDetails(personId: $personId) {\n    sourceUrl\n    details {\n      id\n      name\n      source\n      identifier\n      isPartial\n      description\n      birthDate\n      deathDate\n      place\n      website\n      gender\n      displayImages\n    }\n    contents {\n      name\n      count\n      items {\n        character\n        metadataId\n      }\n    }\n  }\n}"): (typeof documents)["query PersonDetails($personId: String!) {\n  personDetails(personId: $personId) {\n    sourceUrl\n    details {\n      id\n      name\n      source\n      identifier\n      isPartial\n      description\n      birthDate\n      deathDate\n      place\n      website\n      gender\n      displayImages\n    }\n    contents {\n      name\n      count\n      items {\n        character\n        metadataId\n      }\n    }\n  }\n}"];
+export function graphql(source: "query PersonDetails($personId: String!) {\n  personDetails(personId: $personId) {\n    details {\n      id\n      name\n      source\n      identifier\n      isPartial\n      description\n      birthDate\n      deathDate\n      place\n      website\n      gender\n      displayImages\n      sourceUrl\n    }\n    contents {\n      name\n      count\n      items {\n        character\n        metadataId\n      }\n    }\n  }\n}"): (typeof documents)["query PersonDetails($personId: String!) {\n  personDetails(personId: $personId) {\n    details {\n      id\n      name\n      source\n      identifier\n      isPartial\n      description\n      birthDate\n      deathDate\n      place\n      website\n      gender\n      displayImages\n      sourceUrl\n    }\n    contents {\n      name\n      count\n      items {\n        character\n        metadataId\n      }\n    }\n  }\n}"];
 /**
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
  */
@@ -59,7 +59,7 @@ export function graphql(source: "query UserAnalytics($input: UserAnalyticsInput!
 /**
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
  */
-export function graphql(source: "query UserDetails {\n  userDetails {\n    __typename\n    ... on User {\n      id\n      lot\n      name\n      isDisabled\n      oidcIssuerId\n      preferences {\n        general {\n          reviewScale\n          gridPacking\n          displayNsfw\n          disableVideos\n          persistQueries\n          disableReviews\n          disableIntegrations\n          disableWatchProviders\n          disableNavigationAnimation\n          dashboard {\n            hidden\n            section\n            numElements\n            deduplicateMedia\n          }\n          watchProviders {\n            lot\n            values\n          }\n        }\n        fitness {\n          logging {\n            muteSounds\n            showDetailsWhileEditing\n          }\n          exercises {\n            unitSystem\n            setRestTimers {\n              ...SetRestTimersPart\n            }\n          }\n          measurements {\n            custom {\n              name\n              dataType\n            }\n            inbuilt {\n              weight\n              bodyMassIndex\n              totalBodyWater\n              muscle\n              leanBodyMass\n              bodyFat\n              boneMass\n              visceralFat\n              waistCircumference\n              waistToHeightRatio\n              hipCircumference\n              waistToHipRatio\n              chestCircumference\n              thighCircumference\n              bicepsCircumference\n              neckCircumference\n              bodyFatCaliper\n              chestSkinfold\n              abdominalSkinfold\n              thighSkinfold\n              basalMetabolicRate\n              totalDailyEnergyExpenditure\n              calories\n            }\n          }\n        }\n        notifications {\n          toSend\n          enabled\n        }\n        featuresEnabled {\n          analytics {\n            enabled\n          }\n          others {\n            calendar\n            collections\n          }\n          fitness {\n            enabled\n            workouts\n            templates\n            measurements\n          }\n          media {\n            enabled\n            anime\n            audioBook\n            book\n            manga\n            movie\n            podcast\n            show\n            videoGame\n            visualNovel\n            people\n            groups\n            genres\n          }\n        }\n      }\n    }\n  }\n}"): (typeof documents)["query UserDetails {\n  userDetails {\n    __typename\n    ... on User {\n      id\n      lot\n      name\n      isDisabled\n      oidcIssuerId\n      preferences {\n        general {\n          reviewScale\n          gridPacking\n          displayNsfw\n          disableVideos\n          persistQueries\n          disableReviews\n          disableIntegrations\n          disableWatchProviders\n          disableNavigationAnimation\n          dashboard {\n            hidden\n            section\n            numElements\n            deduplicateMedia\n          }\n          watchProviders {\n            lot\n            values\n          }\n        }\n        fitness {\n          logging {\n            muteSounds\n            showDetailsWhileEditing\n          }\n          exercises {\n            unitSystem\n            setRestTimers {\n              ...SetRestTimersPart\n            }\n          }\n          measurements {\n            custom {\n              name\n              dataType\n            }\n            inbuilt {\n              weight\n              bodyMassIndex\n              totalBodyWater\n              muscle\n              leanBodyMass\n              bodyFat\n              boneMass\n              visceralFat\n              waistCircumference\n              waistToHeightRatio\n              hipCircumference\n              waistToHipRatio\n              chestCircumference\n              thighCircumference\n              bicepsCircumference\n              neckCircumference\n              bodyFatCaliper\n              chestSkinfold\n              abdominalSkinfold\n              thighSkinfold\n              basalMetabolicRate\n              totalDailyEnergyExpenditure\n              calories\n            }\n          }\n        }\n        notifications {\n          toSend\n          enabled\n        }\n        featuresEnabled {\n          analytics {\n            enabled\n          }\n          others {\n            calendar\n            collections\n          }\n          fitness {\n            enabled\n            workouts\n            templates\n            measurements\n          }\n          media {\n            enabled\n            anime\n            audioBook\n            book\n            manga\n            movie\n            podcast\n            show\n            videoGame\n            visualNovel\n            people\n            groups\n            genres\n          }\n        }\n      }\n    }\n  }\n}"];
+export function graphql(source: "query UserDetails {\n  userDetails {\n    __typename\n    ... on User {\n      id\n      lot\n      name\n      isDisabled\n      oidcIssuerId\n      preferences {\n        general {\n          reviewScale\n          gridPacking\n          displayNsfw\n          disableVideos\n          persistQueries\n          disableReviews\n          disableIntegrations\n          disableWatchProviders\n          disableNavigationAnimation\n          dashboard {\n            hidden\n            section\n            numElements\n            deduplicateMedia\n          }\n          watchProviders {\n            lot\n            values\n          }\n        }\n        fitness {\n          logging {\n            muteSounds\n            showDetailsWhileEditing\n          }\n          exercises {\n            unitSystem\n            setRestTimers {\n              ...SetRestTimersPart\n            }\n          }\n          measurements {\n            custom {\n              name\n              dataType\n            }\n            inbuilt {\n              weight\n              bodyMassIndex\n              totalBodyWater\n              muscle\n              leanBodyMass\n              bodyFat\n              boneMass\n              visceralFat\n              waistCircumference\n              waistToHeightRatio\n              hipCircumference\n              waistToHipRatio\n              chestCircumference\n              thighCircumference\n              bicepsCircumference\n              neckCircumference\n              bodyFatCaliper\n              chestSkinfold\n              abdominalSkinfold\n              thighSkinfold\n              basalMetabolicRate\n              totalDailyEnergyExpenditure\n              calories\n            }\n          }\n        }\n        notifications {\n          toSend\n          enabled\n        }\n        featuresEnabled {\n          analytics {\n            enabled\n          }\n          others {\n            calendar\n            collections\n          }\n          fitness {\n            enabled\n            workouts\n            templates\n            measurements\n          }\n          media {\n            enabled\n            show\n            book\n            anime\n            manga\n            music\n            movie\n            groups\n            people\n            genres\n            podcast\n            audioBook\n            videoGame\n            visualNovel\n          }\n        }\n      }\n    }\n  }\n}"): (typeof documents)["query UserDetails {\n  userDetails {\n    __typename\n    ... on User {\n      id\n      lot\n      name\n      isDisabled\n      oidcIssuerId\n      preferences {\n        general {\n          reviewScale\n          gridPacking\n          displayNsfw\n          disableVideos\n          persistQueries\n          disableReviews\n          disableIntegrations\n          disableWatchProviders\n          disableNavigationAnimation\n          dashboard {\n            hidden\n            section\n            numElements\n            deduplicateMedia\n          }\n          watchProviders {\n            lot\n            values\n          }\n        }\n        fitness {\n          logging {\n            muteSounds\n            showDetailsWhileEditing\n          }\n          exercises {\n            unitSystem\n            setRestTimers {\n              ...SetRestTimersPart\n            }\n          }\n          measurements {\n            custom {\n              name\n              dataType\n            }\n            inbuilt {\n              weight\n              bodyMassIndex\n              totalBodyWater\n              muscle\n              leanBodyMass\n              bodyFat\n              boneMass\n              visceralFat\n              waistCircumference\n              waistToHeightRatio\n              hipCircumference\n              waistToHipRatio\n              chestCircumference\n              thighCircumference\n              bicepsCircumference\n              neckCircumference\n              bodyFatCaliper\n              chestSkinfold\n              abdominalSkinfold\n              thighSkinfold\n              basalMetabolicRate\n              totalDailyEnergyExpenditure\n              calories\n            }\n          }\n        }\n        notifications {\n          toSend\n          enabled\n        }\n        featuresEnabled {\n          analytics {\n            enabled\n          }\n          others {\n            calendar\n            collections\n          }\n          fitness {\n            enabled\n            workouts\n            templates\n            measurements\n          }\n          media {\n            enabled\n            show\n            book\n            anime\n            manga\n            music\n            movie\n            groups\n            people\n            genres\n            podcast\n            audioBook\n            videoGame\n            visualNovel\n          }\n        }\n      }\n    }\n  }\n}"];
 /**
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
  */
@@ -75,7 +75,7 @@ export function graphql(source: "query UserMetadataDetails($metadataId: String!)
 /**
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
  */
-export function graphql(source: "query GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n  userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery UserExports {\n  userExports {\n    url\n    size\n    endedAt\n    startedAt\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    provider\n    createdOn\n    isDisabled\n    maximumProgress\n    minimumProgress\n    lastTriggeredOn\n    syncToOwnedCollection\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    isDisabled\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    lot\n    name\n    isDisabled\n  }\n}\n\nquery UserRecommendations {\n  userRecommendations\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: MetadataGroupsListInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAccessLinks {\n  userAccessLinks {\n    id\n    name\n    isDemo\n    createdOn\n    expiresOn\n    timesUsed\n    isRevoked\n    maximumUses\n    isAccountDefault\n    isMutationAllowed\n  }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n  exerciseDetails(exerciseId: $exerciseId) {\n    id\n    lot\n    name\n    level\n    force\n    source\n    muscles\n    mechanic\n    equipment\n    createdByUserId\n    attributes {\n      instructions\n      images\n    }\n  }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n  exercisesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      lot\n      name\n      image\n      muscle\n      lastUpdatedOn\n      numTimesInteracted\n    }\n  }\n}\n\nquery ImportReports {\n  importReports {\n    id\n    source\n    progress\n    startedOn\n    finishedOn\n    wasSuccess\n    details {\n      import {\n        total\n      }\n      failedItems {\n        lot\n        step\n        error\n        identifier\n      }\n    }\n  }\n}\n\nquery GenresList($input: SearchInput!) {\n  genresList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      numItems\n    }\n  }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n  genreDetails(input: $input) {\n    details {\n      id\n      name\n      numItems\n    }\n    contents {\n      details {\n        total\n        nextPage\n      }\n      items\n    }\n  }\n}\n\nquery CollectionContents($input: CollectionContentsInput!) {\n  collectionContents(input: $input) {\n    user {\n      id\n      name\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n    results {\n      details {\n        total\n        nextPage\n      }\n      items {\n        entityId\n        entityLot\n      }\n    }\n    details {\n      name\n      description\n      createdOn\n    }\n  }\n}\n\nquery CoreDetails {\n  coreDetails {\n    version\n    docsLink\n    pageSize\n    websiteUrl\n    smtpEnabled\n    oidcEnabled\n    signupAllowed\n    repositoryLink\n    disableTelemetry\n    tokenValidForDays\n    localAuthDisabled\n    fileStorageEnabled\n    isServerKeyValidated\n    metadataLotSourceMappings {\n      lot\n      sources\n    }\n    metadataProviderLanguages {\n      source\n      default\n      supported\n    }\n    frontend {\n      url\n      oidcButtonLabel\n      dashboardMessage\n      umami {\n        domains\n        scriptUrl\n        websiteId\n      }\n    }\n    exerciseParameters {\n      downloadRequired\n      filters {\n        type\n        level\n        force\n        mechanic\n        equipment\n        muscle\n      }\n      lotMapping {\n        lot\n        bests\n      }\n    }\n  }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n  metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    contents\n    sourceUrl\n    details {\n      id\n      title\n      lot\n      source\n      displayImages\n      identifier\n      parts\n      isPartial\n    }\n  }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n  metadataGroupSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      parts\n    }\n  }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n  metadataList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n  metadataSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      databaseId\n      hasInteracted\n      item {\n        identifier\n        title\n        image\n        publishYear\n      }\n    }\n  }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n  peopleSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      birthYear\n    }\n  }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}\n\nquery UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    recentlyConsumed\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n  userWorkoutDetails(workoutId: $workoutId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      endTime\n      duration\n      startTime\n      templateId\n      repeatedFrom\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n  userWorkoutsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n  userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      createdOn\n      visibility\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n  userWorkoutTemplatesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAnalyticsParameters {\n  userAnalyticsParameters {\n    endDate\n    startDate\n  }\n}"): (typeof documents)["query GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n  userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery UserExports {\n  userExports {\n    url\n    size\n    endedAt\n    startedAt\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    provider\n    createdOn\n    isDisabled\n    maximumProgress\n    minimumProgress\n    lastTriggeredOn\n    syncToOwnedCollection\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    isDisabled\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    lot\n    name\n    isDisabled\n  }\n}\n\nquery UserRecommendations {\n  userRecommendations\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: MetadataGroupsListInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAccessLinks {\n  userAccessLinks {\n    id\n    name\n    isDemo\n    createdOn\n    expiresOn\n    timesUsed\n    isRevoked\n    maximumUses\n    isAccountDefault\n    isMutationAllowed\n  }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n  exerciseDetails(exerciseId: $exerciseId) {\n    id\n    lot\n    name\n    level\n    force\n    source\n    muscles\n    mechanic\n    equipment\n    createdByUserId\n    attributes {\n      instructions\n      images\n    }\n  }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n  exercisesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      lot\n      name\n      image\n      muscle\n      lastUpdatedOn\n      numTimesInteracted\n    }\n  }\n}\n\nquery ImportReports {\n  importReports {\n    id\n    source\n    progress\n    startedOn\n    finishedOn\n    wasSuccess\n    details {\n      import {\n        total\n      }\n      failedItems {\n        lot\n        step\n        error\n        identifier\n      }\n    }\n  }\n}\n\nquery GenresList($input: SearchInput!) {\n  genresList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      numItems\n    }\n  }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n  genreDetails(input: $input) {\n    details {\n      id\n      name\n      numItems\n    }\n    contents {\n      details {\n        total\n        nextPage\n      }\n      items\n    }\n  }\n}\n\nquery CollectionContents($input: CollectionContentsInput!) {\n  collectionContents(input: $input) {\n    user {\n      id\n      name\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n    results {\n      details {\n        total\n        nextPage\n      }\n      items {\n        entityId\n        entityLot\n      }\n    }\n    details {\n      name\n      description\n      createdOn\n    }\n  }\n}\n\nquery CoreDetails {\n  coreDetails {\n    version\n    docsLink\n    pageSize\n    websiteUrl\n    smtpEnabled\n    oidcEnabled\n    signupAllowed\n    repositoryLink\n    disableTelemetry\n    tokenValidForDays\n    localAuthDisabled\n    fileStorageEnabled\n    isServerKeyValidated\n    metadataLotSourceMappings {\n      lot\n      sources\n    }\n    metadataProviderLanguages {\n      source\n      default\n      supported\n    }\n    frontend {\n      url\n      oidcButtonLabel\n      dashboardMessage\n      umami {\n        domains\n        scriptUrl\n        websiteId\n      }\n    }\n    exerciseParameters {\n      downloadRequired\n      filters {\n        type\n        level\n        force\n        mechanic\n        equipment\n        muscle\n      }\n      lotMapping {\n        lot\n        bests\n      }\n    }\n  }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n  metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    contents\n    sourceUrl\n    details {\n      id\n      title\n      lot\n      source\n      displayImages\n      identifier\n      parts\n      isPartial\n    }\n  }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n  metadataGroupSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      parts\n    }\n  }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n  metadataList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n  metadataSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      databaseId\n      hasInteracted\n      item {\n        identifier\n        title\n        image\n        publishYear\n      }\n    }\n  }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n  peopleSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      birthYear\n    }\n  }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}\n\nquery UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    recentlyConsumed\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n  userWorkoutDetails(workoutId: $workoutId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      endTime\n      duration\n      startTime\n      templateId\n      repeatedFrom\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n  userWorkoutsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n  userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      createdOn\n      visibility\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n  userWorkoutTemplatesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAnalyticsParameters {\n  userAnalyticsParameters {\n    endDate\n    startDate\n  }\n}"];
+export function graphql(source: "query GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n  userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery UserExports {\n  userExports {\n    url\n    size\n    endedAt\n    startedAt\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    provider\n    createdOn\n    isDisabled\n    maximumProgress\n    minimumProgress\n    lastTriggeredOn\n    syncToOwnedCollection\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    isDisabled\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    lot\n    name\n    isDisabled\n  }\n}\n\nquery UserRecommendations {\n  userRecommendations\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: MetadataGroupsListInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAccessLinks {\n  userAccessLinks {\n    id\n    name\n    isDemo\n    createdOn\n    expiresOn\n    timesUsed\n    isRevoked\n    maximumUses\n    isAccountDefault\n    isMutationAllowed\n  }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n  exerciseDetails(exerciseId: $exerciseId) {\n    id\n    lot\n    name\n    level\n    force\n    source\n    muscles\n    mechanic\n    equipment\n    createdByUserId\n    attributes {\n      instructions\n      images\n    }\n  }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n  exercisesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      lot\n      name\n      image\n      muscle\n      lastUpdatedOn\n      numTimesInteracted\n    }\n  }\n}\n\nquery ImportReports {\n  importReports {\n    id\n    source\n    progress\n    startedOn\n    finishedOn\n    wasSuccess\n    details {\n      import {\n        total\n      }\n      failedItems {\n        lot\n        step\n        error\n        identifier\n      }\n    }\n  }\n}\n\nquery GenresList($input: SearchInput!) {\n  genresList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      numItems\n    }\n  }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n  genreDetails(input: $input) {\n    details {\n      id\n      name\n      numItems\n    }\n    contents {\n      details {\n        total\n        nextPage\n      }\n      items\n    }\n  }\n}\n\nquery CollectionContents($input: CollectionContentsInput!) {\n  collectionContents(input: $input) {\n    user {\n      id\n      name\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n    results {\n      details {\n        total\n        nextPage\n      }\n      items {\n        entityId\n        entityLot\n      }\n    }\n    details {\n      name\n      description\n      createdOn\n    }\n  }\n}\n\nquery CoreDetails {\n  coreDetails {\n    version\n    docsLink\n    pageSize\n    websiteUrl\n    smtpEnabled\n    oidcEnabled\n    signupAllowed\n    repositoryLink\n    disableTelemetry\n    tokenValidForDays\n    localAuthDisabled\n    fileStorageEnabled\n    isServerKeyValidated\n    metadataLotSourceMappings {\n      lot\n      sources\n    }\n    metadataProviderLanguages {\n      source\n      default\n      supported\n    }\n    frontend {\n      url\n      oidcButtonLabel\n      dashboardMessage\n      umami {\n        domains\n        scriptUrl\n        websiteId\n      }\n    }\n    exerciseParameters {\n      downloadRequired\n      filters {\n        type\n        level\n        force\n        mechanic\n        equipment\n        muscle\n      }\n      lotMapping {\n        lot\n        bests\n      }\n    }\n  }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n  metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    contents\n    details {\n      id\n      lot\n      title\n      parts\n      source\n      isPartial\n      sourceUrl\n      identifier\n      description\n      displayImages\n    }\n  }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n  metadataGroupSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      parts\n    }\n  }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n  metadataList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n  metadataSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      databaseId\n      hasInteracted\n      item {\n        identifier\n        title\n        image\n        publishYear\n      }\n    }\n  }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n  peopleSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      birthYear\n    }\n  }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}\n\nquery UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    recentlyConsumed\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n  userWorkoutDetails(workoutId: $workoutId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      endTime\n      duration\n      startTime\n      templateId\n      repeatedFrom\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n  userWorkoutsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n  userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      createdOn\n      visibility\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n  userWorkoutTemplatesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAnalyticsParameters {\n  userAnalyticsParameters {\n    endDate\n    startDate\n  }\n}"): (typeof documents)["query GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n  userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery UserExports {\n  userExports {\n    url\n    size\n    endedAt\n    startedAt\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    provider\n    createdOn\n    isDisabled\n    maximumProgress\n    minimumProgress\n    lastTriggeredOn\n    syncToOwnedCollection\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    isDisabled\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    lot\n    name\n    isDisabled\n  }\n}\n\nquery UserRecommendations {\n  userRecommendations\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: MetadataGroupsListInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAccessLinks {\n  userAccessLinks {\n    id\n    name\n    isDemo\n    createdOn\n    expiresOn\n    timesUsed\n    isRevoked\n    maximumUses\n    isAccountDefault\n    isMutationAllowed\n  }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n  exerciseDetails(exerciseId: $exerciseId) {\n    id\n    lot\n    name\n    level\n    force\n    source\n    muscles\n    mechanic\n    equipment\n    createdByUserId\n    attributes {\n      instructions\n      images\n    }\n  }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n  exercisesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      lot\n      name\n      image\n      muscle\n      lastUpdatedOn\n      numTimesInteracted\n    }\n  }\n}\n\nquery ImportReports {\n  importReports {\n    id\n    source\n    progress\n    startedOn\n    finishedOn\n    wasSuccess\n    details {\n      import {\n        total\n      }\n      failedItems {\n        lot\n        step\n        error\n        identifier\n      }\n    }\n  }\n}\n\nquery GenresList($input: SearchInput!) {\n  genresList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      numItems\n    }\n  }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n  genreDetails(input: $input) {\n    details {\n      id\n      name\n      numItems\n    }\n    contents {\n      details {\n        total\n        nextPage\n      }\n      items\n    }\n  }\n}\n\nquery CollectionContents($input: CollectionContentsInput!) {\n  collectionContents(input: $input) {\n    user {\n      id\n      name\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n    results {\n      details {\n        total\n        nextPage\n      }\n      items {\n        entityId\n        entityLot\n      }\n    }\n    details {\n      name\n      description\n      createdOn\n    }\n  }\n}\n\nquery CoreDetails {\n  coreDetails {\n    version\n    docsLink\n    pageSize\n    websiteUrl\n    smtpEnabled\n    oidcEnabled\n    signupAllowed\n    repositoryLink\n    disableTelemetry\n    tokenValidForDays\n    localAuthDisabled\n    fileStorageEnabled\n    isServerKeyValidated\n    metadataLotSourceMappings {\n      lot\n      sources\n    }\n    metadataProviderLanguages {\n      source\n      default\n      supported\n    }\n    frontend {\n      url\n      oidcButtonLabel\n      dashboardMessage\n      umami {\n        domains\n        scriptUrl\n        websiteId\n      }\n    }\n    exerciseParameters {\n      downloadRequired\n      filters {\n        type\n        level\n        force\n        mechanic\n        equipment\n        muscle\n      }\n      lotMapping {\n        lot\n        bests\n      }\n    }\n  }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n  metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    contents\n    details {\n      id\n      lot\n      title\n      parts\n      source\n      isPartial\n      sourceUrl\n      identifier\n      description\n      displayImages\n    }\n  }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n  metadataGroupSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      parts\n    }\n  }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n  metadataList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n  metadataSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      databaseId\n      hasInteracted\n      item {\n        identifier\n        title\n        image\n        publishYear\n      }\n    }\n  }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n  peopleSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      birthYear\n    }\n  }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}\n\nquery UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    recentlyConsumed\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n  userWorkoutDetails(workoutId: $workoutId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      endTime\n      duration\n      startTime\n      templateId\n      repeatedFrom\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n  userWorkoutsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n  userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      createdOn\n      visibility\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n  userWorkoutTemplatesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAnalyticsParameters {\n  userAnalyticsParameters {\n    endDate\n    startDate\n  }\n}"];
 /**
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
  */
diff --git a/libs/generated/src/graphql/backend/graphql.ts b/libs/generated/src/graphql/backend/graphql.ts
index 5a72a4b41c..17410ceb09 100644
--- a/libs/generated/src/graphql/backend/graphql.ts
+++ b/libs/generated/src/graphql/backend/graphql.ts
@@ -270,6 +270,7 @@ export type CreateCustomMetadataInput = {
   lot: MediaLot;
   mangaSpecifics?: InputMaybe<MangaSpecificsInput>;
   movieSpecifics?: InputMaybe<MovieSpecificsInput>;
+  musicSpecifics?: InputMaybe<MusicSpecificsInput>;
   podcastSpecifics?: InputMaybe<PodcastSpecificsInput>;
   publishYear?: InputMaybe<Scalars['Int']['input']>;
   showSpecifics?: InputMaybe<ShowSpecificsInput>;
@@ -665,10 +666,6 @@ export type ExportJob = {
   url: Scalars['String']['output'];
 };
 
-export type ExternalIdentifiers = {
-  tvdbId?: Maybe<Scalars['Int']['output']>;
-};
-
 export type FitnessAnalyticsEquipment = {
   count: Scalars['Int']['output'];
   equipment: ExerciseEquipment;
@@ -752,7 +749,6 @@ export type GraphqlMetadataDetails = {
   bookSpecifics?: Maybe<BookSpecifics>;
   creators: Array<MetadataCreatorGroupedByRole>;
   description?: Maybe<Scalars['String']['output']>;
-  externalIdentifiers?: Maybe<ExternalIdentifiers>;
   genres: Array<GenreListItem>;
   group?: Maybe<GraphqlMetadataGroup>;
   id: Scalars['String']['output'];
@@ -762,6 +758,7 @@ export type GraphqlMetadataDetails = {
   lot: MediaLot;
   mangaSpecifics?: Maybe<MangaSpecifics>;
   movieSpecifics?: Maybe<MovieSpecifics>;
+  musicSpecifics?: Maybe<MusicSpecifics>;
   originalLanguage?: Maybe<Scalars['String']['output']>;
   podcastSpecifics?: Maybe<PodcastSpecifics>;
   productionStatus?: Maybe<Scalars['String']['output']>;
@@ -995,6 +992,7 @@ export enum MediaLot {
   Book = 'BOOK',
   Manga = 'MANGA',
   Movie = 'MOVIE',
+  Music = 'MUSIC',
   Podcast = 'PODCAST',
   Show = 'SHOW',
   VideoGame = 'VIDEO_GAME',
@@ -1028,7 +1026,8 @@ export enum MediaSource {
   MangaUpdates = 'MANGA_UPDATES',
   Openlibrary = 'OPENLIBRARY',
   Tmdb = 'TMDB',
-  Vndb = 'VNDB'
+  Vndb = 'VNDB',
+  YoutubeMusic = 'YOUTUBE_MUSIC'
 }
 
 export enum MediaStateChanged {
@@ -1065,13 +1064,13 @@ export type MetadataGroup = {
   lot: MediaLot;
   parts: Scalars['Int']['output'];
   source: MediaSource;
+  sourceUrl?: Maybe<Scalars['String']['output']>;
   title: Scalars['String']['output'];
 };
 
 export type MetadataGroupDetails = {
   contents: Array<Scalars['String']['output']>;
   details: MetadataGroup;
-  sourceUrl?: Maybe<Scalars['String']['output']>;
 };
 
 export type MetadataGroupSearchInput = {
@@ -1161,6 +1160,18 @@ export type MovieSpecificsInput = {
   runtime?: InputMaybe<Scalars['Int']['input']>;
 };
 
+export type MusicSpecifics = {
+  byVariousArtists?: Maybe<Scalars['Boolean']['output']>;
+  duration?: Maybe<Scalars['Int']['output']>;
+  viewCount?: Maybe<Scalars['Int']['output']>;
+};
+
+export type MusicSpecificsInput = {
+  byVariousArtists?: InputMaybe<Scalars['Boolean']['input']>;
+  duration?: InputMaybe<Scalars['Int']['input']>;
+  viewCount?: InputMaybe<Scalars['Int']['input']>;
+};
+
 export type MutationRoot = {
   /** Add a entity to a collection if it is not there, otherwise do nothing. */
   addEntityToCollection: Scalars['Boolean']['output'];
@@ -1600,6 +1611,7 @@ export type Person = {
   name: Scalars['String']['output'];
   place?: Maybe<Scalars['String']['output']>;
   source: MediaSource;
+  sourceUrl?: Maybe<Scalars['String']['output']>;
   website?: Maybe<Scalars['String']['output']>;
 };
 
@@ -1611,7 +1623,6 @@ export enum PersonAndMetadataGroupsSortBy {
 export type PersonDetails = {
   contents: Array<PersonDetailsGroupedByRole>;
   details: Person;
-  sourceUrl?: Maybe<Scalars['String']['output']>;
 };
 
 export type PersonDetailsGroupedByRole = {
@@ -2486,6 +2497,7 @@ export type UserMediaFeaturesEnabledPreferences = {
   groups: Scalars['Boolean']['output'];
   manga: Scalars['Boolean']['output'];
   movie: Scalars['Boolean']['output'];
+  music: Scalars['Boolean']['output'];
   people: Scalars['Boolean']['output'];
   podcast: Scalars['Boolean']['output'];
   show: Scalars['Boolean']['output'];
@@ -3194,14 +3206,14 @@ export type MetadataDetailsQueryVariables = Exact<{
 }>;
 
 
-export type MetadataDetailsQuery = { metadataDetails: { id: string, lot: MediaLot, title: string, source: MediaSource, isNsfw?: boolean | null, isPartial?: boolean | null, sourceUrl?: string | null, identifier: string, description?: string | null, suggestions: Array<string>, publishYear?: number | null, publishDate?: string | null, providerRating?: string | null, productionStatus?: string | null, originalLanguage?: string | null, genres: Array<{ id: string, name: string }>, group?: { id: string, name: string, part: number } | null, assets: { images: Array<string>, videos: Array<{ videoId: string, source: MetadataVideoSource }> }, creators: Array<{ name: string, items: Array<{ id?: string | null, name: string, image?: string | null, character?: string | null }> }>, watchProviders: Array<{ name: string, image?: string | null, languages: Array<string> }>, animeSpecifics?: { episodes?: number | null } | null, audioBookSpecifics?: { runtime?: number | null } | null, bookSpecifics?: { pages?: number | null } | null, movieSpecifics?: { runtime?: number | null } | null, mangaSpecifics?: { volumes?: number | null, chapters?: string | null } | null, podcastSpecifics?: { totalEpisodes: number, episodes: Array<{ id: string, title: string, overview?: string | null, thumbnail?: string | null, number: number, runtime?: number | null, publishDate: string }> } | null, showSpecifics?: { totalSeasons?: number | null, totalEpisodes?: number | null, runtime?: number | null, seasons: Array<{ id: number, seasonNumber: number, name: string, overview?: string | null, backdropImages: Array<string>, posterImages: Array<string>, episodes: Array<{ id: number, name: string, posterImages: Array<string>, episodeNumber: number, publishDate?: string | null, overview?: string | null, runtime?: number | null }> }> } | null, visualNovelSpecifics?: { length?: number | null } | null, videoGameSpecifics?: { platforms: Array<string> } | null } };
+export type MetadataDetailsQuery = { metadataDetails: { id: string, lot: MediaLot, title: string, source: MediaSource, isNsfw?: boolean | null, isPartial?: boolean | null, sourceUrl?: string | null, identifier: string, description?: string | null, suggestions: Array<string>, publishYear?: number | null, publishDate?: string | null, providerRating?: string | null, productionStatus?: string | null, originalLanguage?: string | null, genres: Array<{ id: string, name: string }>, group?: { id: string, name: string, part: number } | null, assets: { images: Array<string>, videos: Array<{ videoId: string, source: MetadataVideoSource }> }, creators: Array<{ name: string, items: Array<{ id?: string | null, name: string, image?: string | null, character?: string | null }> }>, watchProviders: Array<{ name: string, image?: string | null, languages: Array<string> }>, animeSpecifics?: { episodes?: number | null } | null, audioBookSpecifics?: { runtime?: number | null } | null, bookSpecifics?: { pages?: number | null } | null, movieSpecifics?: { runtime?: number | null } | null, mangaSpecifics?: { volumes?: number | null, chapters?: string | null } | null, podcastSpecifics?: { totalEpisodes: number, episodes: Array<{ id: string, title: string, overview?: string | null, thumbnail?: string | null, number: number, runtime?: number | null, publishDate: string }> } | null, showSpecifics?: { totalSeasons?: number | null, totalEpisodes?: number | null, runtime?: number | null, seasons: Array<{ id: number, seasonNumber: number, name: string, overview?: string | null, backdropImages: Array<string>, posterImages: Array<string>, episodes: Array<{ id: number, name: string, posterImages: Array<string>, episodeNumber: number, publishDate?: string | null, overview?: string | null, runtime?: number | null }> }> } | null, visualNovelSpecifics?: { length?: number | null } | null, videoGameSpecifics?: { platforms: Array<string> } | null, musicSpecifics?: { duration?: number | null, viewCount?: number | null, byVariousArtists?: boolean | null } | null } };
 
 export type PersonDetailsQueryVariables = Exact<{
   personId: Scalars['String']['input'];
 }>;
 
 
-export type PersonDetailsQuery = { personDetails: { sourceUrl?: string | null, details: { id: string, name: string, source: MediaSource, identifier: string, isPartial?: boolean | null, description?: string | null, birthDate?: string | null, deathDate?: string | null, place?: string | null, website?: string | null, gender?: string | null, displayImages: Array<string> }, contents: Array<{ name: string, count: number, items: Array<{ character?: string | null, metadataId: string }> }> } };
+export type PersonDetailsQuery = { personDetails: { details: { id: string, name: string, source: MediaSource, identifier: string, isPartial?: boolean | null, description?: string | null, birthDate?: string | null, deathDate?: string | null, place?: string | null, website?: string | null, gender?: string | null, displayImages: Array<string>, sourceUrl?: string | null }, contents: Array<{ name: string, count: number, items: Array<{ character?: string | null, metadataId: string }> }> } };
 
 export type UserAnalyticsQueryVariables = Exact<{
   input: UserAnalyticsInput;
@@ -3213,7 +3225,7 @@ export type UserAnalyticsQuery = { userAnalytics: { hours: Array<{ hour: number,
 export type UserDetailsQueryVariables = Exact<{ [key: string]: never; }>;
 
 
-export type UserDetailsQuery = { userDetails: { __typename: 'User', id: string, lot: UserLot, name: string, isDisabled?: boolean | null, oidcIssuerId?: string | null, preferences: { general: { reviewScale: UserReviewScale, gridPacking: GridPacking, displayNsfw: boolean, disableVideos: boolean, persistQueries: boolean, disableReviews: boolean, disableIntegrations: boolean, disableWatchProviders: boolean, disableNavigationAnimation: boolean, dashboard: Array<{ hidden: boolean, section: DashboardElementLot, numElements?: number | null, deduplicateMedia?: boolean | null }>, watchProviders: Array<{ lot: MediaLot, values: Array<string> }> }, fitness: { logging: { muteSounds: boolean, showDetailsWhileEditing: boolean }, exercises: { unitSystem: UserUnitSystem, setRestTimers: { drop?: number | null, warmup?: number | null, normal?: number | null, failure?: number | null } }, measurements: { custom: Array<{ name: string, dataType: UserCustomMeasurementDataType }>, inbuilt: { weight: boolean, bodyMassIndex: boolean, totalBodyWater: boolean, muscle: boolean, leanBodyMass: boolean, bodyFat: boolean, boneMass: boolean, visceralFat: boolean, waistCircumference: boolean, waistToHeightRatio: boolean, hipCircumference: boolean, waistToHipRatio: boolean, chestCircumference: boolean, thighCircumference: boolean, bicepsCircumference: boolean, neckCircumference: boolean, bodyFatCaliper: boolean, chestSkinfold: boolean, abdominalSkinfold: boolean, thighSkinfold: boolean, basalMetabolicRate: boolean, totalDailyEnergyExpenditure: boolean, calories: boolean } } }, notifications: { toSend: Array<MediaStateChanged>, enabled: boolean }, featuresEnabled: { analytics: { enabled: boolean }, others: { calendar: boolean, collections: boolean }, fitness: { enabled: boolean, workouts: boolean, templates: boolean, measurements: boolean }, media: { enabled: boolean, anime: boolean, audioBook: boolean, book: boolean, manga: boolean, movie: boolean, podcast: boolean, show: boolean, videoGame: boolean, visualNovel: boolean, people: boolean, groups: boolean, genres: boolean } } } } | { __typename: 'UserDetailsError' } };
+export type UserDetailsQuery = { userDetails: { __typename: 'User', id: string, lot: UserLot, name: string, isDisabled?: boolean | null, oidcIssuerId?: string | null, preferences: { general: { reviewScale: UserReviewScale, gridPacking: GridPacking, displayNsfw: boolean, disableVideos: boolean, persistQueries: boolean, disableReviews: boolean, disableIntegrations: boolean, disableWatchProviders: boolean, disableNavigationAnimation: boolean, dashboard: Array<{ hidden: boolean, section: DashboardElementLot, numElements?: number | null, deduplicateMedia?: boolean | null }>, watchProviders: Array<{ lot: MediaLot, values: Array<string> }> }, fitness: { logging: { muteSounds: boolean, showDetailsWhileEditing: boolean }, exercises: { unitSystem: UserUnitSystem, setRestTimers: { drop?: number | null, warmup?: number | null, normal?: number | null, failure?: number | null } }, measurements: { custom: Array<{ name: string, dataType: UserCustomMeasurementDataType }>, inbuilt: { weight: boolean, bodyMassIndex: boolean, totalBodyWater: boolean, muscle: boolean, leanBodyMass: boolean, bodyFat: boolean, boneMass: boolean, visceralFat: boolean, waistCircumference: boolean, waistToHeightRatio: boolean, hipCircumference: boolean, waistToHipRatio: boolean, chestCircumference: boolean, thighCircumference: boolean, bicepsCircumference: boolean, neckCircumference: boolean, bodyFatCaliper: boolean, chestSkinfold: boolean, abdominalSkinfold: boolean, thighSkinfold: boolean, basalMetabolicRate: boolean, totalDailyEnergyExpenditure: boolean, calories: boolean } } }, notifications: { toSend: Array<MediaStateChanged>, enabled: boolean }, featuresEnabled: { analytics: { enabled: boolean }, others: { calendar: boolean, collections: boolean }, fitness: { enabled: boolean, workouts: boolean, templates: boolean, measurements: boolean }, media: { enabled: boolean, show: boolean, book: boolean, anime: boolean, manga: boolean, music: boolean, movie: boolean, groups: boolean, people: boolean, genres: boolean, podcast: boolean, audioBook: boolean, videoGame: boolean, visualNovel: boolean } } } } | { __typename: 'UserDetailsError' } };
 
 export type UserExerciseDetailsQueryVariables = Exact<{
   exerciseId: Scalars['String']['input'];
@@ -3386,7 +3398,7 @@ export type MetadataGroupDetailsQueryVariables = Exact<{
 }>;
 
 
-export type MetadataGroupDetailsQuery = { metadataGroupDetails: { contents: Array<string>, sourceUrl?: string | null, details: { id: string, title: string, lot: MediaLot, source: MediaSource, displayImages: Array<string>, identifier: string, parts: number, isPartial?: boolean | null } } };
+export type MetadataGroupDetailsQuery = { metadataGroupDetails: { contents: Array<string>, details: { id: string, lot: MediaLot, title: string, parts: number, source: MediaSource, isPartial?: boolean | null, sourceUrl?: string | null, identifier: string, description?: string | null, displayImages: Array<string> } } };
 
 export type MetadataGroupSearchQueryVariables = Exact<{
   input: MetadataGroupSearchInput;
@@ -3562,10 +3574,10 @@ export const ProcessAccessLinkDocument = {"kind":"Document","definitions":[{"kin
 export const RevokeAccessLinkDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RevokeAccessLink"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"accessLinkId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"revokeAccessLink"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"accessLinkId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"accessLinkId"}}}]}]}}]} as unknown as DocumentNode<RevokeAccessLinkMutation, RevokeAccessLinkMutationVariables>;
 export const UpdateUserExerciseSettingsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateUserExerciseSettings"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateUserExerciseSettings"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateUserExerciseSettings"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode<UpdateUserExerciseSettingsMutation, UpdateUserExerciseSettingsMutationVariables>;
 export const MergeExerciseDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"MergeExercise"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"mergeFrom"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"mergeInto"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"mergeExercise"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"mergeFrom"},"value":{"kind":"Variable","name":{"kind":"Name","value":"mergeFrom"}}},{"kind":"Argument","name":{"kind":"Name","value":"mergeInto"},"value":{"kind":"Variable","name":{"kind":"Name","value":"mergeInto"}}}]}]}}]} as unknown as DocumentNode<MergeExerciseMutation, MergeExerciseMutationVariables>;
-export const MetadataDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"metadataId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"metadataDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"metadataId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"metadataId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"isNsfw"}},{"kind":"Field","name":{"kind":"Name","value":"isPartial"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"suggestions"}},{"kind":"Field","name":{"kind":"Name","value":"publishYear"}},{"kind":"Field","name":{"kind":"Name","value":"publishDate"}},{"kind":"Field","name":{"kind":"Name","value":"providerRating"}},{"kind":"Field","name":{"kind":"Name","value":"productionStatus"}},{"kind":"Field","name":{"kind":"Name","value":"originalLanguage"}},{"kind":"Field","name":{"kind":"Name","value":"genres"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"part"}}]}},{"kind":"Field","name":{"kind":"Name","value":"assets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"images"}},{"kind":"Field","name":{"kind":"Name","value":"videos"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"videoId"}},{"kind":"Field","name":{"kind":"Name","value":"source"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"creators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"character"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"watchProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"languages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodes"}}]}},{"kind":"Field","name":{"kind":"Name","value":"audioBookSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"runtime"}}]}},{"kind":"Field","name":{"kind":"Name","value":"bookSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"movieSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"runtime"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"volumes"}},{"kind":"Field","name":{"kind":"Name","value":"chapters"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"overview"}},{"kind":"Field","name":{"kind":"Name","value":"thumbnail"}},{"kind":"Field","name":{"kind":"Name","value":"number"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"publishDate"}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalEpisodes"}}]}},{"kind":"Field","name":{"kind":"Name","value":"showSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalSeasons"}},{"kind":"Field","name":{"kind":"Name","value":"totalEpisodes"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"seasons"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"seasonNumber"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"overview"}},{"kind":"Field","name":{"kind":"Name","value":"backdropImages"}},{"kind":"Field","name":{"kind":"Name","value":"posterImages"}},{"kind":"Field","name":{"kind":"Name","value":"episodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"posterImages"}},{"kind":"Field","name":{"kind":"Name","value":"episodeNumber"}},{"kind":"Field","name":{"kind":"Name","value":"publishDate"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"overview"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"visualNovelSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"length"}}]}},{"kind":"Field","name":{"kind":"Name","value":"videoGameSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"platforms"}}]}}]}}]}}]} as unknown as DocumentNode<MetadataDetailsQuery, MetadataDetailsQueryVariables>;
-export const PersonDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PersonDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"personId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"personId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"personId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}},{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"isPartial"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"birthDate"}},{"kind":"Field","name":{"kind":"Name","value":"deathDate"}},{"kind":"Field","name":{"kind":"Name","value":"place"}},{"kind":"Field","name":{"kind":"Name","value":"website"}},{"kind":"Field","name":{"kind":"Name","value":"gender"}},{"kind":"Field","name":{"kind":"Name","value":"displayImages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"contents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"character"}},{"kind":"Field","name":{"kind":"Name","value":"metadataId"}}]}}]}}]}}]}}]} as unknown as DocumentNode<PersonDetailsQuery, PersonDetailsQueryVariables>;
+export const MetadataDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"metadataId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"metadataDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"metadataId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"metadataId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"isNsfw"}},{"kind":"Field","name":{"kind":"Name","value":"isPartial"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"suggestions"}},{"kind":"Field","name":{"kind":"Name","value":"publishYear"}},{"kind":"Field","name":{"kind":"Name","value":"publishDate"}},{"kind":"Field","name":{"kind":"Name","value":"providerRating"}},{"kind":"Field","name":{"kind":"Name","value":"productionStatus"}},{"kind":"Field","name":{"kind":"Name","value":"originalLanguage"}},{"kind":"Field","name":{"kind":"Name","value":"genres"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"part"}}]}},{"kind":"Field","name":{"kind":"Name","value":"assets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"images"}},{"kind":"Field","name":{"kind":"Name","value":"videos"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"videoId"}},{"kind":"Field","name":{"kind":"Name","value":"source"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"creators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"character"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"watchProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"languages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodes"}}]}},{"kind":"Field","name":{"kind":"Name","value":"audioBookSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"runtime"}}]}},{"kind":"Field","name":{"kind":"Name","value":"bookSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"movieSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"runtime"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"volumes"}},{"kind":"Field","name":{"kind":"Name","value":"chapters"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"overview"}},{"kind":"Field","name":{"kind":"Name","value":"thumbnail"}},{"kind":"Field","name":{"kind":"Name","value":"number"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"publishDate"}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalEpisodes"}}]}},{"kind":"Field","name":{"kind":"Name","value":"showSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalSeasons"}},{"kind":"Field","name":{"kind":"Name","value":"totalEpisodes"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"seasons"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"seasonNumber"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"overview"}},{"kind":"Field","name":{"kind":"Name","value":"backdropImages"}},{"kind":"Field","name":{"kind":"Name","value":"posterImages"}},{"kind":"Field","name":{"kind":"Name","value":"episodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"posterImages"}},{"kind":"Field","name":{"kind":"Name","value":"episodeNumber"}},{"kind":"Field","name":{"kind":"Name","value":"publishDate"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"overview"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"visualNovelSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"length"}}]}},{"kind":"Field","name":{"kind":"Name","value":"videoGameSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"platforms"}}]}},{"kind":"Field","name":{"kind":"Name","value":"musicSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"viewCount"}},{"kind":"Field","name":{"kind":"Name","value":"byVariousArtists"}}]}}]}}]}}]} as unknown as DocumentNode<MetadataDetailsQuery, MetadataDetailsQueryVariables>;
+export const PersonDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PersonDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"personId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"personId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"personId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"isPartial"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"birthDate"}},{"kind":"Field","name":{"kind":"Name","value":"deathDate"}},{"kind":"Field","name":{"kind":"Name","value":"place"}},{"kind":"Field","name":{"kind":"Name","value":"website"}},{"kind":"Field","name":{"kind":"Name","value":"gender"}},{"kind":"Field","name":{"kind":"Name","value":"displayImages"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"contents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"character"}},{"kind":"Field","name":{"kind":"Name","value":"metadataId"}}]}}]}}]}}]}}]} as unknown as DocumentNode<PersonDetailsQuery, PersonDetailsQueryVariables>;
 export const UserAnalyticsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserAnalytics"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UserAnalyticsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userAnalytics"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hours"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hour"}},{"kind":"Field","name":{"kind":"Name","value":"entities"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entityId"}},{"kind":"Field","name":{"kind":"Name","value":"entityLot"}},{"kind":"Field","name":{"kind":"Name","value":"metadataLot"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"activities"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"groupedBy"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalDuration"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"day"}},{"kind":"Field","name":{"kind":"Name","value":"bookCount"}},{"kind":"Field","name":{"kind":"Name","value":"showCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"movieCount"}},{"kind":"Field","name":{"kind":"Name","value":"animeCount"}},{"kind":"Field","name":{"kind":"Name","value":"mangaCount"}},{"kind":"Field","name":{"kind":"Name","value":"workoutCount"}},{"kind":"Field","name":{"kind":"Name","value":"podcastCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalBookPages"}},{"kind":"Field","name":{"kind":"Name","value":"audioBookCount"}},{"kind":"Field","name":{"kind":"Name","value":"videoGameCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalReviewCount"}},{"kind":"Field","name":{"kind":"Name","value":"visualNovelCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutReps"}},{"kind":"Field","name":{"kind":"Name","value":"totalShowDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalMovieDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalMetadataCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutWeight"}},{"kind":"Field","name":{"kind":"Name","value":"totalPodcastDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutDistance"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutRestTime"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutDuration"}},{"kind":"Field","name":{"kind":"Name","value":"userMeasurementCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalVideoGameDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalAudioBookDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalPersonReviewCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalVisualNovelDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalMetadataReviewCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutPersonalBests"}},{"kind":"Field","name":{"kind":"Name","value":"totalCollectionReviewCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalMetadataGroupReviewCount"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workoutReps"}},{"kind":"Field","name":{"kind":"Name","value":"workoutCount"}},{"kind":"Field","name":{"kind":"Name","value":"workoutWeight"}},{"kind":"Field","name":{"kind":"Name","value":"workoutDistance"}},{"kind":"Field","name":{"kind":"Name","value":"workoutDuration"}},{"kind":"Field","name":{"kind":"Name","value":"workoutRestTime"}},{"kind":"Field","name":{"kind":"Name","value":"measurementCount"}},{"kind":"Field","name":{"kind":"Name","value":"workoutPersonalBests"}},{"kind":"Field","name":{"kind":"Name","value":"workoutExercises"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"exercise"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workoutMuscles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workoutEquipments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"equipment"}}]}}]}}]}}]}}]} as unknown as DocumentNode<UserAnalyticsQuery, UserAnalyticsQueryVariables>;
-export const UserDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isDisabled"}},{"kind":"Field","name":{"kind":"Name","value":"oidcIssuerId"}},{"kind":"Field","name":{"kind":"Name","value":"preferences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"reviewScale"}},{"kind":"Field","name":{"kind":"Name","value":"gridPacking"}},{"kind":"Field","name":{"kind":"Name","value":"displayNsfw"}},{"kind":"Field","name":{"kind":"Name","value":"disableVideos"}},{"kind":"Field","name":{"kind":"Name","value":"persistQueries"}},{"kind":"Field","name":{"kind":"Name","value":"disableReviews"}},{"kind":"Field","name":{"kind":"Name","value":"disableIntegrations"}},{"kind":"Field","name":{"kind":"Name","value":"disableWatchProviders"}},{"kind":"Field","name":{"kind":"Name","value":"disableNavigationAnimation"}},{"kind":"Field","name":{"kind":"Name","value":"dashboard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hidden"}},{"kind":"Field","name":{"kind":"Name","value":"section"}},{"kind":"Field","name":{"kind":"Name","value":"numElements"}},{"kind":"Field","name":{"kind":"Name","value":"deduplicateMedia"}}]}},{"kind":"Field","name":{"kind":"Name","value":"watchProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"values"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"logging"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"muteSounds"}},{"kind":"Field","name":{"kind":"Name","value":"showDetailsWhileEditing"}}]}},{"kind":"Field","name":{"kind":"Name","value":"exercises"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unitSystem"}},{"kind":"Field","name":{"kind":"Name","value":"setRestTimers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SetRestTimersPart"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"measurements"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"custom"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"dataType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"inbuilt"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"bodyMassIndex"}},{"kind":"Field","name":{"kind":"Name","value":"totalBodyWater"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}},{"kind":"Field","name":{"kind":"Name","value":"leanBodyMass"}},{"kind":"Field","name":{"kind":"Name","value":"bodyFat"}},{"kind":"Field","name":{"kind":"Name","value":"boneMass"}},{"kind":"Field","name":{"kind":"Name","value":"visceralFat"}},{"kind":"Field","name":{"kind":"Name","value":"waistCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"waistToHeightRatio"}},{"kind":"Field","name":{"kind":"Name","value":"hipCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"waistToHipRatio"}},{"kind":"Field","name":{"kind":"Name","value":"chestCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"thighCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"bicepsCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"neckCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"bodyFatCaliper"}},{"kind":"Field","name":{"kind":"Name","value":"chestSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"abdominalSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"thighSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"basalMetabolicRate"}},{"kind":"Field","name":{"kind":"Name","value":"totalDailyEnergyExpenditure"}},{"kind":"Field","name":{"kind":"Name","value":"calories"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"notifications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"toSend"}},{"kind":"Field","name":{"kind":"Name","value":"enabled"}}]}},{"kind":"Field","name":{"kind":"Name","value":"featuresEnabled"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"analytics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}}]}},{"kind":"Field","name":{"kind":"Name","value":"others"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"calendar"}},{"kind":"Field","name":{"kind":"Name","value":"collections"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"workouts"}},{"kind":"Field","name":{"kind":"Name","value":"templates"}},{"kind":"Field","name":{"kind":"Name","value":"measurements"}}]}},{"kind":"Field","name":{"kind":"Name","value":"media"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"anime"}},{"kind":"Field","name":{"kind":"Name","value":"audioBook"}},{"kind":"Field","name":{"kind":"Name","value":"book"}},{"kind":"Field","name":{"kind":"Name","value":"manga"}},{"kind":"Field","name":{"kind":"Name","value":"movie"}},{"kind":"Field","name":{"kind":"Name","value":"podcast"}},{"kind":"Field","name":{"kind":"Name","value":"show"}},{"kind":"Field","name":{"kind":"Name","value":"videoGame"}},{"kind":"Field","name":{"kind":"Name","value":"visualNovel"}},{"kind":"Field","name":{"kind":"Name","value":"people"}},{"kind":"Field","name":{"kind":"Name","value":"groups"}},{"kind":"Field","name":{"kind":"Name","value":"genres"}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SetRestTimersPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SetRestTimersSettings"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"drop"}},{"kind":"Field","name":{"kind":"Name","value":"warmup"}},{"kind":"Field","name":{"kind":"Name","value":"normal"}},{"kind":"Field","name":{"kind":"Name","value":"failure"}}]}}]} as unknown as DocumentNode<UserDetailsQuery, UserDetailsQueryVariables>;
+export const UserDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isDisabled"}},{"kind":"Field","name":{"kind":"Name","value":"oidcIssuerId"}},{"kind":"Field","name":{"kind":"Name","value":"preferences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"reviewScale"}},{"kind":"Field","name":{"kind":"Name","value":"gridPacking"}},{"kind":"Field","name":{"kind":"Name","value":"displayNsfw"}},{"kind":"Field","name":{"kind":"Name","value":"disableVideos"}},{"kind":"Field","name":{"kind":"Name","value":"persistQueries"}},{"kind":"Field","name":{"kind":"Name","value":"disableReviews"}},{"kind":"Field","name":{"kind":"Name","value":"disableIntegrations"}},{"kind":"Field","name":{"kind":"Name","value":"disableWatchProviders"}},{"kind":"Field","name":{"kind":"Name","value":"disableNavigationAnimation"}},{"kind":"Field","name":{"kind":"Name","value":"dashboard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hidden"}},{"kind":"Field","name":{"kind":"Name","value":"section"}},{"kind":"Field","name":{"kind":"Name","value":"numElements"}},{"kind":"Field","name":{"kind":"Name","value":"deduplicateMedia"}}]}},{"kind":"Field","name":{"kind":"Name","value":"watchProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"values"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"logging"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"muteSounds"}},{"kind":"Field","name":{"kind":"Name","value":"showDetailsWhileEditing"}}]}},{"kind":"Field","name":{"kind":"Name","value":"exercises"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unitSystem"}},{"kind":"Field","name":{"kind":"Name","value":"setRestTimers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SetRestTimersPart"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"measurements"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"custom"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"dataType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"inbuilt"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"bodyMassIndex"}},{"kind":"Field","name":{"kind":"Name","value":"totalBodyWater"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}},{"kind":"Field","name":{"kind":"Name","value":"leanBodyMass"}},{"kind":"Field","name":{"kind":"Name","value":"bodyFat"}},{"kind":"Field","name":{"kind":"Name","value":"boneMass"}},{"kind":"Field","name":{"kind":"Name","value":"visceralFat"}},{"kind":"Field","name":{"kind":"Name","value":"waistCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"waistToHeightRatio"}},{"kind":"Field","name":{"kind":"Name","value":"hipCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"waistToHipRatio"}},{"kind":"Field","name":{"kind":"Name","value":"chestCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"thighCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"bicepsCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"neckCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"bodyFatCaliper"}},{"kind":"Field","name":{"kind":"Name","value":"chestSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"abdominalSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"thighSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"basalMetabolicRate"}},{"kind":"Field","name":{"kind":"Name","value":"totalDailyEnergyExpenditure"}},{"kind":"Field","name":{"kind":"Name","value":"calories"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"notifications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"toSend"}},{"kind":"Field","name":{"kind":"Name","value":"enabled"}}]}},{"kind":"Field","name":{"kind":"Name","value":"featuresEnabled"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"analytics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}}]}},{"kind":"Field","name":{"kind":"Name","value":"others"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"calendar"}},{"kind":"Field","name":{"kind":"Name","value":"collections"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"workouts"}},{"kind":"Field","name":{"kind":"Name","value":"templates"}},{"kind":"Field","name":{"kind":"Name","value":"measurements"}}]}},{"kind":"Field","name":{"kind":"Name","value":"media"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"show"}},{"kind":"Field","name":{"kind":"Name","value":"book"}},{"kind":"Field","name":{"kind":"Name","value":"anime"}},{"kind":"Field","name":{"kind":"Name","value":"manga"}},{"kind":"Field","name":{"kind":"Name","value":"music"}},{"kind":"Field","name":{"kind":"Name","value":"movie"}},{"kind":"Field","name":{"kind":"Name","value":"groups"}},{"kind":"Field","name":{"kind":"Name","value":"people"}},{"kind":"Field","name":{"kind":"Name","value":"genres"}},{"kind":"Field","name":{"kind":"Name","value":"podcast"}},{"kind":"Field","name":{"kind":"Name","value":"audioBook"}},{"kind":"Field","name":{"kind":"Name","value":"videoGame"}},{"kind":"Field","name":{"kind":"Name","value":"visualNovel"}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SetRestTimersPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SetRestTimersSettings"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"drop"}},{"kind":"Field","name":{"kind":"Name","value":"warmup"}},{"kind":"Field","name":{"kind":"Name","value":"normal"}},{"kind":"Field","name":{"kind":"Name","value":"failure"}}]}}]} as unknown as DocumentNode<UserDetailsQuery, UserDetailsQueryVariables>;
 export const UserExerciseDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserExerciseDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"exerciseId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userExerciseDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"exerciseId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"exerciseId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"reviews"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ReviewItemPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"history"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"idx"}},{"kind":"Field","name":{"kind":"Name","value":"workoutId"}},{"kind":"Field","name":{"kind":"Name","value":"workoutEndOn"}},{"kind":"Field","name":{"kind":"Name","value":"bestSet"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutSetRecordPart"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"exerciseId"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedOn"}},{"kind":"Field","name":{"kind":"Name","value":"exerciseNumTimesInteracted"}},{"kind":"Field","name":{"kind":"Name","value":"exerciseExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"settings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"excludeFromAnalytics"}},{"kind":"Field","name":{"kind":"Name","value":"setRestTimers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SetRestTimersPart"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"lifetimeStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"reps"}},{"kind":"Field","name":{"kind":"Name","value":"distance"}},{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"personalBestsAchieved"}}]}},{"kind":"Field","name":{"kind":"Name","value":"personalBests"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"sets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setIdx"}},{"kind":"Field","name":{"kind":"Name","value":"workoutId"}},{"kind":"Field","name":{"kind":"Name","value":"exerciseIdx"}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenAnimeExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenMangaExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"chapter"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkoutSetStatisticPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkoutSetStatistic"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"reps"}},{"kind":"Field","name":{"kind":"Name","value":"pace"}},{"kind":"Field","name":{"kind":"Name","value":"oneRm"}},{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"distance"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Collection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"userId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ReviewItemPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ReviewItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"rating"}},{"kind":"Field","name":{"kind":"Name","value":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"seenItemsAssociatedWith"}},{"kind":"Field","name":{"kind":"Name","value":"postedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"comments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"text"}},{"kind":"Field","name":{"kind":"Name","value":"likedBy"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkoutSetRecordPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkoutSetRecord"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"personalBests"}},{"kind":"Field","name":{"kind":"Name","value":"statistic"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutSetStatisticPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SetRestTimersPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SetRestTimersSettings"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"drop"}},{"kind":"Field","name":{"kind":"Name","value":"warmup"}},{"kind":"Field","name":{"kind":"Name","value":"normal"}},{"kind":"Field","name":{"kind":"Name","value":"failure"}}]}}]} as unknown as DocumentNode<UserExerciseDetailsQuery, UserExerciseDetailsQueryVariables>;
 export const UserMeasurementsListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserMeasurementsList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UserMeasurementsListInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userMeasurementsList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"timestamp"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"comment"}},{"kind":"Field","name":{"kind":"Name","value":"stats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"bodyMassIndex"}},{"kind":"Field","name":{"kind":"Name","value":"totalBodyWater"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}},{"kind":"Field","name":{"kind":"Name","value":"leanBodyMass"}},{"kind":"Field","name":{"kind":"Name","value":"bodyFat"}},{"kind":"Field","name":{"kind":"Name","value":"boneMass"}},{"kind":"Field","name":{"kind":"Name","value":"visceralFat"}},{"kind":"Field","name":{"kind":"Name","value":"waistCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"waistToHeightRatio"}},{"kind":"Field","name":{"kind":"Name","value":"hipCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"waistToHipRatio"}},{"kind":"Field","name":{"kind":"Name","value":"chestCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"thighCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"bicepsCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"neckCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"bodyFatCaliper"}},{"kind":"Field","name":{"kind":"Name","value":"chestSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"abdominalSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"thighSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"basalMetabolicRate"}},{"kind":"Field","name":{"kind":"Name","value":"totalDailyEnergyExpenditure"}},{"kind":"Field","name":{"kind":"Name","value":"calories"}},{"kind":"Field","name":{"kind":"Name","value":"custom"}}]}}]}}]}}]} as unknown as DocumentNode<UserMeasurementsListQuery, UserMeasurementsListQueryVariables>;
 export const UserMetadataDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserMetadataDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"metadataId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userMetadataDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"metadataId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"metadataId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"mediaReason"}},{"kind":"Field","name":{"kind":"Name","value":"hasInteracted"}},{"kind":"Field","name":{"kind":"Name","value":"averageRating"}},{"kind":"Field","name":{"kind":"Name","value":"seenByAllCount"}},{"kind":"Field","name":{"kind":"Name","value":"seenByUserCount"}},{"kind":"Field","name":{"kind":"Name","value":"recentlyConsumed"}},{"kind":"Field","name":{"kind":"Name","value":"reviews"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ReviewItemPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"history"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"nextEntry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"chapter"}}]}},{"kind":"Field","name":{"kind":"Name","value":"inProgress"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"showProgress"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"timesSeen"}},{"kind":"Field","name":{"kind":"Name","value":"seasonNumber"}},{"kind":"Field","name":{"kind":"Name","value":"episodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodeNumber"}},{"kind":"Field","name":{"kind":"Name","value":"timesSeen"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastProgress"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodeNumber"}},{"kind":"Field","name":{"kind":"Name","value":"timesSeen"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenAnimeExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenMangaExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"chapter"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ReviewItemPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ReviewItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"rating"}},{"kind":"Field","name":{"kind":"Name","value":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"seenItemsAssociatedWith"}},{"kind":"Field","name":{"kind":"Name","value":"postedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"comments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"text"}},{"kind":"Field","name":{"kind":"Name","value":"likedBy"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Seen"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"progress"}},{"kind":"Field","name":{"kind":"Name","value":"reviewId"}},{"kind":"Field","name":{"kind":"Name","value":"startedOn"}},{"kind":"Field","name":{"kind":"Name","value":"finishedOn"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedOn"}},{"kind":"Field","name":{"kind":"Name","value":"manualTimeSpent"}},{"kind":"Field","name":{"kind":"Name","value":"numTimesUpdated"}},{"kind":"Field","name":{"kind":"Name","value":"providerWatchedOn"}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Collection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"userId"}}]}}]} as unknown as DocumentNode<UserMetadataDetailsQuery, UserMetadataDetailsQueryVariables>;
@@ -3592,7 +3604,7 @@ export const GenresListDocument = {"kind":"Document","definitions":[{"kind":"Ope
 export const GenreDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GenreDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GenreDetailsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"genreDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"numItems"}}]}},{"kind":"Field","name":{"kind":"Name","value":"contents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"nextPage"}}]}},{"kind":"Field","name":{"kind":"Name","value":"items"}}]}}]}}]}}]} as unknown as DocumentNode<GenreDetailsQuery, GenreDetailsQueryVariables>;
 export const CollectionContentsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CollectionContents"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CollectionContentsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"collectionContents"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"reviews"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ReviewItemPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"results"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"nextPage"}}]}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entityId"}},{"kind":"Field","name":{"kind":"Name","value":"entityLot"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenAnimeExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenMangaExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"chapter"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ReviewItemPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ReviewItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"rating"}},{"kind":"Field","name":{"kind":"Name","value":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"seenItemsAssociatedWith"}},{"kind":"Field","name":{"kind":"Name","value":"postedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"comments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"text"}},{"kind":"Field","name":{"kind":"Name","value":"likedBy"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"}}]}}]}}]} as unknown as DocumentNode<CollectionContentsQuery, CollectionContentsQueryVariables>;
 export const CoreDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CoreDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"coreDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"docsLink"}},{"kind":"Field","name":{"kind":"Name","value":"pageSize"}},{"kind":"Field","name":{"kind":"Name","value":"websiteUrl"}},{"kind":"Field","name":{"kind":"Name","value":"smtpEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"oidcEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"signupAllowed"}},{"kind":"Field","name":{"kind":"Name","value":"repositoryLink"}},{"kind":"Field","name":{"kind":"Name","value":"disableTelemetry"}},{"kind":"Field","name":{"kind":"Name","value":"tokenValidForDays"}},{"kind":"Field","name":{"kind":"Name","value":"localAuthDisabled"}},{"kind":"Field","name":{"kind":"Name","value":"fileStorageEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"isServerKeyValidated"}},{"kind":"Field","name":{"kind":"Name","value":"metadataLotSourceMappings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"sources"}}]}},{"kind":"Field","name":{"kind":"Name","value":"metadataProviderLanguages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"default"}},{"kind":"Field","name":{"kind":"Name","value":"supported"}}]}},{"kind":"Field","name":{"kind":"Name","value":"frontend"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"oidcButtonLabel"}},{"kind":"Field","name":{"kind":"Name","value":"dashboardMessage"}},{"kind":"Field","name":{"kind":"Name","value":"umami"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"domains"}},{"kind":"Field","name":{"kind":"Name","value":"scriptUrl"}},{"kind":"Field","name":{"kind":"Name","value":"websiteId"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"exerciseParameters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"downloadRequired"}},{"kind":"Field","name":{"kind":"Name","value":"filters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"level"}},{"kind":"Field","name":{"kind":"Name","value":"force"}},{"kind":"Field","name":{"kind":"Name","value":"mechanic"}},{"kind":"Field","name":{"kind":"Name","value":"equipment"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lotMapping"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"bests"}}]}}]}}]}}]}}]} as unknown as DocumentNode<CoreDetailsQuery, CoreDetailsQueryVariables>;
-export const MetadataGroupDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataGroupDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"metadataGroupId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"metadataGroupDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"metadataGroupId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"metadataGroupId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"contents"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}},{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"displayImages"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"parts"}},{"kind":"Field","name":{"kind":"Name","value":"isPartial"}}]}}]}}]}}]} as unknown as DocumentNode<MetadataGroupDetailsQuery, MetadataGroupDetailsQueryVariables>;
+export const MetadataGroupDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataGroupDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"metadataGroupId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"metadataGroupDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"metadataGroupId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"metadataGroupId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"contents"}},{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"parts"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"isPartial"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"displayImages"}}]}}]}}]}}]} as unknown as DocumentNode<MetadataGroupDetailsQuery, MetadataGroupDetailsQueryVariables>;
 export const MetadataGroupSearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataGroupSearch"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MetadataGroupSearchInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"metadataGroupSearch"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"nextPage"}}]}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"parts"}}]}}]}}]}}]} as unknown as DocumentNode<MetadataGroupSearchQuery, MetadataGroupSearchQueryVariables>;
 export const MetadataListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MetadataListInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"metadataList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"nextPage"}}]}},{"kind":"Field","name":{"kind":"Name","value":"items"}}]}}]}}]} as unknown as DocumentNode<MetadataListQuery, MetadataListQueryVariables>;
 export const MetadataSearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataSearch"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MetadataSearchInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"metadataSearch"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"nextPage"}}]}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"databaseId"}},{"kind":"Field","name":{"kind":"Name","value":"hasInteracted"}},{"kind":"Field","name":{"kind":"Name","value":"item"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"publishYear"}}]}}]}}]}}]}}]} as unknown as DocumentNode<MetadataSearchQuery, MetadataSearchQueryVariables>;
diff --git a/libs/generated/src/graphql/backend/types.generated.ts b/libs/generated/src/graphql/backend/types.generated.ts
index 3efe9d0b3f..31a3c4235f 100644
--- a/libs/generated/src/graphql/backend/types.generated.ts
+++ b/libs/generated/src/graphql/backend/types.generated.ts
@@ -256,6 +256,7 @@ export type CreateCustomMetadataInput = {
   lot: MediaLot;
   mangaSpecifics?: InputMaybe<MangaSpecificsInput>;
   movieSpecifics?: InputMaybe<MovieSpecificsInput>;
+  musicSpecifics?: InputMaybe<MusicSpecificsInput>;
   podcastSpecifics?: InputMaybe<PodcastSpecificsInput>;
   publishYear?: InputMaybe<Scalars['Int']['input']>;
   showSpecifics?: InputMaybe<ShowSpecificsInput>;
@@ -666,11 +667,6 @@ export type ExportJob = {
   url: Scalars['String']['output'];
 };
 
-export type ExternalIdentifiers = {
-  __typename?: 'ExternalIdentifiers';
-  tvdbId?: Maybe<Scalars['Int']['output']>;
-};
-
 export type FitnessAnalyticsEquipment = {
   __typename?: 'FitnessAnalyticsEquipment';
   count: Scalars['Int']['output'];
@@ -765,7 +761,6 @@ export type GraphqlMetadataDetails = {
   bookSpecifics?: Maybe<BookSpecifics>;
   creators: Array<MetadataCreatorGroupedByRole>;
   description?: Maybe<Scalars['String']['output']>;
-  externalIdentifiers?: Maybe<ExternalIdentifiers>;
   genres: Array<GenreListItem>;
   group?: Maybe<GraphqlMetadataGroup>;
   id: Scalars['String']['output'];
@@ -775,6 +770,7 @@ export type GraphqlMetadataDetails = {
   lot: MediaLot;
   mangaSpecifics?: Maybe<MangaSpecifics>;
   movieSpecifics?: Maybe<MovieSpecifics>;
+  musicSpecifics?: Maybe<MusicSpecifics>;
   originalLanguage?: Maybe<Scalars['String']['output']>;
   podcastSpecifics?: Maybe<PodcastSpecifics>;
   productionStatus?: Maybe<Scalars['String']['output']>;
@@ -1023,6 +1019,7 @@ export enum MediaLot {
   Book = 'BOOK',
   Manga = 'MANGA',
   Movie = 'MOVIE',
+  Music = 'MUSIC',
   Podcast = 'PODCAST',
   Show = 'SHOW',
   VideoGame = 'VIDEO_GAME',
@@ -1056,7 +1053,8 @@ export enum MediaSource {
   MangaUpdates = 'MANGA_UPDATES',
   Openlibrary = 'OPENLIBRARY',
   Tmdb = 'TMDB',
-  Vndb = 'VNDB'
+  Vndb = 'VNDB',
+  YoutubeMusic = 'YOUTUBE_MUSIC'
 }
 
 export enum MediaStateChanged {
@@ -1096,6 +1094,7 @@ export type MetadataGroup = {
   lot: MediaLot;
   parts: Scalars['Int']['output'];
   source: MediaSource;
+  sourceUrl?: Maybe<Scalars['String']['output']>;
   title: Scalars['String']['output'];
 };
 
@@ -1103,7 +1102,6 @@ export type MetadataGroupDetails = {
   __typename?: 'MetadataGroupDetails';
   contents: Array<Scalars['String']['output']>;
   details: MetadataGroup;
-  sourceUrl?: Maybe<Scalars['String']['output']>;
 };
 
 export type MetadataGroupSearchInput = {
@@ -1201,6 +1199,19 @@ export type MovieSpecificsInput = {
   runtime?: InputMaybe<Scalars['Int']['input']>;
 };
 
+export type MusicSpecifics = {
+  __typename?: 'MusicSpecifics';
+  byVariousArtists?: Maybe<Scalars['Boolean']['output']>;
+  duration?: Maybe<Scalars['Int']['output']>;
+  viewCount?: Maybe<Scalars['Int']['output']>;
+};
+
+export type MusicSpecificsInput = {
+  byVariousArtists?: InputMaybe<Scalars['Boolean']['input']>;
+  duration?: InputMaybe<Scalars['Int']['input']>;
+  viewCount?: InputMaybe<Scalars['Int']['input']>;
+};
+
 export type MutationRoot = {
   __typename?: 'MutationRoot';
   /** Add a entity to a collection if it is not there, otherwise do nothing. */
@@ -1646,6 +1657,7 @@ export type Person = {
   name: Scalars['String']['output'];
   place?: Maybe<Scalars['String']['output']>;
   source: MediaSource;
+  sourceUrl?: Maybe<Scalars['String']['output']>;
   website?: Maybe<Scalars['String']['output']>;
 };
 
@@ -1658,7 +1670,6 @@ export type PersonDetails = {
   __typename?: 'PersonDetails';
   contents: Array<PersonDetailsGroupedByRole>;
   details: Person;
-  sourceUrl?: Maybe<Scalars['String']['output']>;
 };
 
 export type PersonDetailsGroupedByRole = {
@@ -2578,6 +2589,7 @@ export type UserMediaFeaturesEnabledPreferences = {
   groups: Scalars['Boolean']['output'];
   manga: Scalars['Boolean']['output'];
   movie: Scalars['Boolean']['output'];
+  music: Scalars['Boolean']['output'];
   people: Scalars['Boolean']['output'];
   podcast: Scalars['Boolean']['output'];
   show: Scalars['Boolean']['output'];
diff --git a/libs/graphql/src/backend/queries/MetadataDetails.gql b/libs/graphql/src/backend/queries/MetadataDetails.gql
index caa2c9aea4..f9a366fc39 100644
--- a/libs/graphql/src/backend/queries/MetadataDetails.gql
+++ b/libs/graphql/src/backend/queries/MetadataDetails.gql
@@ -102,5 +102,10 @@ query MetadataDetails($metadataId: String!) {
 		videoGameSpecifics {
 			platforms
 		}
+		musicSpecifics {
+			duration
+			viewCount
+			byVariousArtists
+		}
 	}
 }
diff --git a/libs/graphql/src/backend/queries/PersonDetails.gql b/libs/graphql/src/backend/queries/PersonDetails.gql
index d8d31b67c3..16a397aaeb 100644
--- a/libs/graphql/src/backend/queries/PersonDetails.gql
+++ b/libs/graphql/src/backend/queries/PersonDetails.gql
@@ -1,6 +1,5 @@
 query PersonDetails($personId: String!) {
 	personDetails(personId: $personId) {
-		sourceUrl
 		details {
 			id
 			name
@@ -14,6 +13,7 @@ query PersonDetails($personId: String!) {
 			website
 			gender
 			displayImages
+			sourceUrl
 		}
 		contents {
 			name
diff --git a/libs/graphql/src/backend/queries/UserDetails.gql b/libs/graphql/src/backend/queries/UserDetails.gql
index b879d2f02d..dcc283e628 100644
--- a/libs/graphql/src/backend/queries/UserDetails.gql
+++ b/libs/graphql/src/backend/queries/UserDetails.gql
@@ -92,18 +92,19 @@ query UserDetails {
 					}
 					media {
 						enabled
-						anime
-						audioBook
+						show
 						book
+						anime
 						manga
+						music
 						movie
+						groups
+						people
+						genres
 						podcast
-						show
+						audioBook
 						videoGame
 						visualNovel
-						people
-						groups
-						genres
 					}
 				}
 			}
diff --git a/libs/graphql/src/backend/queries/combined.gql b/libs/graphql/src/backend/queries/combined.gql
index 66b6f85754..083eb64f55 100644
--- a/libs/graphql/src/backend/queries/combined.gql
+++ b/libs/graphql/src/backend/queries/combined.gql
@@ -320,16 +320,17 @@ query CoreDetails {
 query MetadataGroupDetails($metadataGroupId: String!) {
 	metadataGroupDetails(metadataGroupId: $metadataGroupId) {
 		contents
-		sourceUrl
 		details {
 			id
-			title
 			lot
-			source
-			displayImages
-			identifier
+			title
 			parts
+			source
 			isPartial
+			sourceUrl
+			identifier
+			description
+			displayImages
 		}
 	}
 }


Test patch: