diff --git a/src/app.html b/src/app.html index b88fb0d..b50429d 100644 --- a/src/app.html +++ b/src/app.html @@ -3,6 +3,10 @@ Reiverr + + + + diff --git a/src/lib/jellyfin/jellyfin.ts b/src/lib/jellyfin/jellyfin.ts index a4a7850..dab0787 100644 --- a/src/lib/jellyfin/jellyfin.ts +++ b/src/lib/jellyfin/jellyfin.ts @@ -26,354 +26,350 @@ export const getJellyfinContinueWatching = () => fields: ['ProviderIds'] } } - }).then((r) => { - console.log(r.data); - return r.data?.Items; - }); + }).then((r) => r.data?.Items); -export const requestJellyfinContinueWatching = () => request(getJellyfinContinueWatching); - -export const getJellyfinItemByTmdbId = () => - request((tmdbId: string) => - JellyfinApi.get('/Users/{userId}/Items', { - params: { - path: { - userId: JELLYFIN_USER_ID - }, - query: { - hasTmdbId: true, - recursive: true, - isMovie: true, - fields: ['ProviderIds'] - } - } - }).then((r) => r.data?.Items?.find((i) => i.ProviderIds?.Tmdb == tmdbId)) - ); - -export const getJellyfinPlaybackInfo = () => - request((id: string) => - JellyfinApi.post('/Items/{itemId}/PlaybackInfo', { - params: { - path: { - itemId: id - }, - query: { - userId: JELLYFIN_USER_ID, - startTimeTicks: 0, - autoOpenLiveStream: true, - maxStreamingBitrate: 140000000 - } +export const getJellyfinItemByTmdbId = (tmdbId: string) => + JellyfinApi.get('/Users/{userId}/Items', { + params: { + path: { + userId: JELLYFIN_USER_ID }, - body: { - DeviceProfile: { - CodecProfiles: [ - { - Codec: 'aac', - Conditions: [ - { - Condition: 'Equals', - IsRequired: false, - Property: 'IsSecondaryAudio', - Value: 'false' - } - ], - Type: 'VideoAudio' - }, - { - Conditions: [ - { - Condition: 'Equals', - IsRequired: false, - Property: 'IsSecondaryAudio', - Value: 'false' - } - ], - Type: 'VideoAudio' - }, - { - Codec: 'h264', - Conditions: [ - { - Condition: 'NotEquals', - IsRequired: false, - Property: 'IsAnamorphic', - Value: 'true' - }, - { - Condition: 'EqualsAny', - IsRequired: false, - Property: 'VideoProfile', - Value: 'high|main|baseline|constrained baseline' - }, - { - Condition: 'EqualsAny', - IsRequired: false, - Property: 'VideoRangeType', - Value: 'SDR' - }, - { - Condition: 'LessThanEqual', - IsRequired: false, - Property: 'VideoLevel', - Value: '52' - }, - { - Condition: 'NotEquals', - IsRequired: false, - Property: 'IsInterlaced', - Value: 'true' - } - ], - Type: 'Video' - }, - { - Codec: 'hevc', - Conditions: [ - { - Condition: 'NotEquals', - IsRequired: false, - Property: 'IsAnamorphic', - Value: 'true' - }, - { - Condition: 'EqualsAny', - IsRequired: false, - Property: 'VideoProfile', - Value: 'main' - }, - { - Condition: 'EqualsAny', - IsRequired: false, - Property: 'VideoRangeType', - Value: 'SDR' - }, - { - Condition: 'LessThanEqual', - IsRequired: false, - Property: 'VideoLevel', - Value: '120' - }, - { - Condition: 'NotEquals', - IsRequired: false, - Property: 'IsInterlaced', - Value: 'true' - } - ], - Type: 'Video' - }, - { - Codec: 'vp9', - Conditions: [ - { - Condition: 'EqualsAny', - IsRequired: false, - Property: 'VideoRangeType', - Value: 'SDR|HDR10|HLG' - } - ], - Type: 'Video' - }, - { - Codec: 'av1', - Conditions: [ - { - Condition: 'EqualsAny', - IsRequired: false, - Property: 'VideoRangeType', - Value: 'SDR|HDR10|HLG' - } - ], - Type: 'Video' - } - ], - ContainerProfiles: [], - DirectPlayProfiles: [ - { - AudioCodec: 'vorbis,opus', - Container: 'webm', - Type: 'Video', - VideoCodec: 'vp8,vp9,av1' - }, - { - AudioCodec: 'aac,mp3,opus,flac,alac,vorbis', - Container: 'mp4,m4v', - Type: 'Video', - VideoCodec: 'h264,vp9,av1' - }, - { - Container: 'opus', - Type: 'Audio' - }, - { - AudioCodec: 'opus', - Container: 'webm', - Type: 'Audio' - }, - { - Container: 'mp3', - Type: 'Audio' - }, - { - Container: 'aac', - Type: 'Audio' - }, - { - AudioCodec: 'aac', - Container: 'm4a', - Type: 'Audio' - }, - { - AudioCodec: 'aac', - Container: 'm4b', - Type: 'Audio' - }, - { - Container: 'flac', - Type: 'Audio' - }, - { - Container: 'alac', - Type: 'Audio' - }, - { - AudioCodec: 'alac', - Container: 'm4a', - Type: 'Audio' - }, - { - AudioCodec: 'alac', - Container: 'm4b', - Type: 'Audio' - }, - { - Container: 'webma', - Type: 'Audio' - }, - { - AudioCodec: 'webma', - Container: 'webm', - Type: 'Audio' - }, - { - Container: 'wav', - Type: 'Audio' - }, - { - Container: 'ogg', - Type: 'Audio' - } - ], - MaxStaticBitrate: 100000000, - MaxStreamingBitrate: 120000000, - MusicStreamingTranscodingBitrate: 384000, - ResponseProfiles: [ - { - Container: 'm4v', - MimeType: 'video/mp4', - Type: 'Video' - } - ], - SubtitleProfiles: [ - { - Format: 'vtt', - Method: 'External' - }, - { - Format: 'ass', - Method: 'External' - }, - { - Format: 'ssa', - Method: 'External' - } - ], - TranscodingProfiles: [ - { - AudioCodec: 'aac', - BreakOnNonKeyFrames: true, - Container: 'ts', - Context: 'Streaming', - MaxAudioChannels: '2', - Protocol: 'hls', - Type: 'Audio' - }, - { - AudioCodec: 'aac', - Container: 'aac', - Context: 'Streaming', - MaxAudioChannels: '2', - Protocol: 'http', - Type: 'Audio' - }, - { - AudioCodec: 'mp3', - Container: 'mp3', - Context: 'Streaming', - MaxAudioChannels: '2', - Protocol: 'http', - Type: 'Audio' - }, - { - AudioCodec: 'opus', - Container: 'opus', - Context: 'Streaming', - MaxAudioChannels: '2', - Protocol: 'http', - Type: 'Audio' - }, - { - AudioCodec: 'wav', - Container: 'wav', - Context: 'Streaming', - MaxAudioChannels: '2', - Protocol: 'http', - Type: 'Audio' - }, - { - AudioCodec: 'opus', - Container: 'opus', - Context: 'Static', - MaxAudioChannels: '2', - Protocol: 'http', - Type: 'Audio' - }, - { - AudioCodec: 'mp3', - Container: 'mp3', - Context: 'Static', - MaxAudioChannels: '2', - Protocol: 'http', - Type: 'Audio' - }, - { - AudioCodec: 'aac', - Container: 'aac', - Context: 'Static', - MaxAudioChannels: '2', - Protocol: 'http', - Type: 'Audio' - }, - { - AudioCodec: 'wav', - Container: 'wav', - Context: 'Static', - MaxAudioChannels: '2', - Protocol: 'http', - Type: 'Audio' - }, - { - AudioCodec: 'aac,mp3', - BreakOnNonKeyFrames: true, - Container: 'ts', - Context: 'Streaming', - MaxAudioChannels: '2', - Protocol: 'hls', - Type: 'Video', - VideoCodec: 'h264' - } - ] - } + query: { + hasTmdbId: true, + recursive: true, + isMovie: true, + fields: ['ProviderIds'] } - }).then((r) => r.data?.MediaSources?.[0]?.TranscodingUrl) - ); + } + }).then((r) => r.data?.Items?.find((i) => i.ProviderIds?.Tmdb == tmdbId)); + +export const requestJellyfinItemByTmdbId = () => + request((tmdbId: string) => getJellyfinItemByTmdbId(tmdbId)); + +export const getJellyfinPlaybackInfo = () => request(fetchJellyfinPlaybackUrl); + +export const fetchJellyfinPlaybackUrl = (id: string) => + JellyfinApi.post('/Items/{itemId}/PlaybackInfo', { + params: { + path: { + itemId: id + }, + query: { + userId: JELLYFIN_USER_ID, + startTimeTicks: 0, + autoOpenLiveStream: true, + maxStreamingBitrate: 140000000 + } + }, + body: { + DeviceProfile: { + CodecProfiles: [ + { + Codec: 'aac', + Conditions: [ + { + Condition: 'Equals', + IsRequired: false, + Property: 'IsSecondaryAudio', + Value: 'false' + } + ], + Type: 'VideoAudio' + }, + { + Conditions: [ + { + Condition: 'Equals', + IsRequired: false, + Property: 'IsSecondaryAudio', + Value: 'false' + } + ], + Type: 'VideoAudio' + }, + { + Codec: 'h264', + Conditions: [ + { + Condition: 'NotEquals', + IsRequired: false, + Property: 'IsAnamorphic', + Value: 'true' + }, + { + Condition: 'EqualsAny', + IsRequired: false, + Property: 'VideoProfile', + Value: 'high|main|baseline|constrained baseline' + }, + { + Condition: 'EqualsAny', + IsRequired: false, + Property: 'VideoRangeType', + Value: 'SDR' + }, + { + Condition: 'LessThanEqual', + IsRequired: false, + Property: 'VideoLevel', + Value: '52' + }, + { + Condition: 'NotEquals', + IsRequired: false, + Property: 'IsInterlaced', + Value: 'true' + } + ], + Type: 'Video' + }, + { + Codec: 'hevc', + Conditions: [ + { + Condition: 'NotEquals', + IsRequired: false, + Property: 'IsAnamorphic', + Value: 'true' + }, + { + Condition: 'EqualsAny', + IsRequired: false, + Property: 'VideoProfile', + Value: 'main' + }, + { + Condition: 'EqualsAny', + IsRequired: false, + Property: 'VideoRangeType', + Value: 'SDR' + }, + { + Condition: 'LessThanEqual', + IsRequired: false, + Property: 'VideoLevel', + Value: '120' + }, + { + Condition: 'NotEquals', + IsRequired: false, + Property: 'IsInterlaced', + Value: 'true' + } + ], + Type: 'Video' + }, + { + Codec: 'vp9', + Conditions: [ + { + Condition: 'EqualsAny', + IsRequired: false, + Property: 'VideoRangeType', + Value: 'SDR|HDR10|HLG' + } + ], + Type: 'Video' + }, + { + Codec: 'av1', + Conditions: [ + { + Condition: 'EqualsAny', + IsRequired: false, + Property: 'VideoRangeType', + Value: 'SDR|HDR10|HLG' + } + ], + Type: 'Video' + } + ], + ContainerProfiles: [], + DirectPlayProfiles: [ + { + AudioCodec: 'vorbis,opus', + Container: 'webm', + Type: 'Video', + VideoCodec: 'vp8,vp9,av1' + }, + { + AudioCodec: 'aac,mp3,opus,flac,alac,vorbis', + Container: 'mp4,m4v', + Type: 'Video', + VideoCodec: 'h264,vp9,av1' + }, + { + Container: 'opus', + Type: 'Audio' + }, + { + AudioCodec: 'opus', + Container: 'webm', + Type: 'Audio' + }, + { + Container: 'mp3', + Type: 'Audio' + }, + { + Container: 'aac', + Type: 'Audio' + }, + { + AudioCodec: 'aac', + Container: 'm4a', + Type: 'Audio' + }, + { + AudioCodec: 'aac', + Container: 'm4b', + Type: 'Audio' + }, + { + Container: 'flac', + Type: 'Audio' + }, + { + Container: 'alac', + Type: 'Audio' + }, + { + AudioCodec: 'alac', + Container: 'm4a', + Type: 'Audio' + }, + { + AudioCodec: 'alac', + Container: 'm4b', + Type: 'Audio' + }, + { + Container: 'webma', + Type: 'Audio' + }, + { + AudioCodec: 'webma', + Container: 'webm', + Type: 'Audio' + }, + { + Container: 'wav', + Type: 'Audio' + }, + { + Container: 'ogg', + Type: 'Audio' + } + ], + MaxStaticBitrate: 100000000, + MaxStreamingBitrate: 120000000, + MusicStreamingTranscodingBitrate: 384000, + ResponseProfiles: [ + { + Container: 'm4v', + MimeType: 'video/mp4', + Type: 'Video' + } + ], + SubtitleProfiles: [ + { + Format: 'vtt', + Method: 'External' + }, + { + Format: 'ass', + Method: 'External' + }, + { + Format: 'ssa', + Method: 'External' + } + ], + TranscodingProfiles: [ + { + AudioCodec: 'aac', + BreakOnNonKeyFrames: true, + Container: 'ts', + Context: 'Streaming', + MaxAudioChannels: '2', + Protocol: 'hls', + Type: 'Audio' + }, + { + AudioCodec: 'aac', + Container: 'aac', + Context: 'Streaming', + MaxAudioChannels: '2', + Protocol: 'http', + Type: 'Audio' + }, + { + AudioCodec: 'mp3', + Container: 'mp3', + Context: 'Streaming', + MaxAudioChannels: '2', + Protocol: 'http', + Type: 'Audio' + }, + { + AudioCodec: 'opus', + Container: 'opus', + Context: 'Streaming', + MaxAudioChannels: '2', + Protocol: 'http', + Type: 'Audio' + }, + { + AudioCodec: 'wav', + Container: 'wav', + Context: 'Streaming', + MaxAudioChannels: '2', + Protocol: 'http', + Type: 'Audio' + }, + { + AudioCodec: 'opus', + Container: 'opus', + Context: 'Static', + MaxAudioChannels: '2', + Protocol: 'http', + Type: 'Audio' + }, + { + AudioCodec: 'mp3', + Container: 'mp3', + Context: 'Static', + MaxAudioChannels: '2', + Protocol: 'http', + Type: 'Audio' + }, + { + AudioCodec: 'aac', + Container: 'aac', + Context: 'Static', + MaxAudioChannels: '2', + Protocol: 'http', + Type: 'Audio' + }, + { + AudioCodec: 'wav', + Container: 'wav', + Context: 'Static', + MaxAudioChannels: '2', + Protocol: 'http', + Type: 'Audio' + }, + { + AudioCodec: 'aac,mp3', + BreakOnNonKeyFrames: true, + Container: 'ts', + Context: 'Streaming', + MaxAudioChannels: '2', + Protocol: 'hls', + Type: 'Video', + VideoCodec: 'h264' + } + ] + } + } + }).then((r) => r.data?.MediaSources?.[0]?.TranscodingUrl); diff --git a/src/lib/radarr/radarr.ts b/src/lib/radarr/radarr.ts index b89781e..c7c67b1 100644 --- a/src/lib/radarr/radarr.ts +++ b/src/lib/radarr/radarr.ts @@ -30,55 +30,56 @@ export const RadarrApi = createClient({ } }); -export const getRadarrMovie = () => - request((tmdbId: string) => - RadarrApi.get('/api/v3/movie', { - params: { - query: { - tmdbId: Number(tmdbId) - } +export const requestRadarrMovie = () => request(getRadarrMovie); + +export const getRadarrMovie = (tmdbId: string) => + RadarrApi.get('/api/v3/movie', { + params: { + query: { + tmdbId: Number(tmdbId) } - }).then((r) => r.data?.find((m) => (m.tmdbId as any) == tmdbId)) - ); + } + }).then((r) => r.data?.find((m) => (m.tmdbId as any) == tmdbId)); -export const addRadarrMovie = () => - request(async (tmdbId: string) => { - const tmdbMovie = await fetchTmdbMovie(tmdbId); - const radarrMovie = await getMovieByTmdbIdByTmdbId(tmdbId); - console.log('fetched movies', tmdbMovie, radarrMovie); +export const requestAddRadarrMovie = () => request(addRadarrMovie); - if (radarrMovie?.id) throw new Error('Movie already exists'); +export const addRadarrMovie = async (tmdbId: string) => { + const tmdbMovie = await fetchTmdbMovie(tmdbId); + const radarrMovie = await getMovieByTmdbIdByTmdbId(tmdbId); + console.log('fetched movies', tmdbMovie, radarrMovie); - if (!tmdbMovie) throw new Error('Movie not found'); + if (radarrMovie?.id) throw new Error('Movie already exists'); - const qualityProfile = 4; - const options: RadarrMovieOptions = { - qualityProfileId: qualityProfile, - profileId: qualityProfile, - rootFolderPath: '/movies', - minimumAvailability: 'announced', - title: tmdbMovie.title, - tmdbId: tmdbMovie.id, - year: Number((await tmdbMovie).release_date.slice(0, 4)), - monitored: false, - tags: [], - searchNow: false - }; + if (!tmdbMovie) throw new Error('Movie not found'); - return RadarrApi.post('/api/v3/movie', { - params: {}, - body: options - }).then((r) => r.data); - }); + const qualityProfile = 4; + const options: RadarrMovieOptions = { + qualityProfileId: qualityProfile, + profileId: qualityProfile, + rootFolderPath: '/movies', + minimumAvailability: 'announced', + title: tmdbMovie.title, + tmdbId: tmdbMovie.id, + year: Number((await tmdbMovie).release_date.slice(0, 4)), + monitored: false, + tags: [], + searchNow: false + }; -export const getReleases = () => + return RadarrApi.post('/api/v3/movie', { + params: {}, + body: options + }).then((r) => r.data); +}; + +export const requestRadarrReleases = () => request((movieId: string) => RadarrApi.get('/api/v3/release', { params: { query: { movieId: Number(movieId) } } }).then( (r) => r.data ) ); -export const queueRelease = () => +export const requestQueueRadarrRelease = () => request((guid: string) => RadarrApi.post('/api/v3/release', { params: {}, @@ -89,19 +90,18 @@ export const queueRelease = () => }) ); -export const getQueuedById = () => - request((id: string) => - getQueue().then((queue) => queue?.records?.filter((r) => (r?.movie?.id as any) == id)) - ); +export const requestRadarrQueuedById = () => request(getRadarrQueuedById); -const getQueue = () => +export const getRadarrQueuedById = (id: string) => RadarrApi.get('/api/v3/queue', { params: { query: { includeMovie: true } } - }).then((r) => r.data); + }) + .then((r) => r.data) + .then((queue) => queue?.records?.find((r) => (r?.movie?.id as any) == id)); const getMovieByTmdbIdByTmdbId = (tmdbId: string) => RadarrApi.get('/api/v3/movie/lookup/tmdb', { diff --git a/src/lib/utils.ts b/src/lib/utils.ts index c7c7e20..b1e1422 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -57,3 +57,17 @@ export function request(fetcher: (arg: A) => Promise, args: A | undefin load }; } + +export const getFadeIndex = () => { + const obj: any = { + index: -1 + }; + + function getNext() { + return ++obj.index; + } + + obj.getNextFadeIndex = getNext; + + return obj; +}; diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts index bda6b41..5372646 100644 --- a/src/routes/+page.server.ts +++ b/src/routes/+page.server.ts @@ -12,8 +12,6 @@ export const load = (async () => { const itemsFiltered = items?.filter((i) => i.ProviderIds?.Tmdb); if (!itemsFiltered?.length) return; - console.log(itemsFiltered.map((i) => i.RunTimeTicks)); - const firstMovie = await fetchTmdbMovie(String(itemsFiltered[0].ProviderIds?.Tmdb)); return { diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index d2bf6db..25ff321 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -27,21 +27,21 @@ }); -
- {#if movies[index]} - {#await Promise.all(movies) then awaitedMovies} - - - - {/await} - {/if} -
+{#if movies[index]} + {#await Promise.all(movies) then awaitedMovies} + + + + {:catch err} + Error occurred {JSON.stringify(err)} + {/await} +{/if} {#await data.streamed.continueWatching then continueWatching} {#if continueWatching?.items?.length} diff --git a/src/routes/components/HeightHider.svelte b/src/routes/components/HeightHider.svelte new file mode 100644 index 0000000..48b2a53 --- /dev/null +++ b/src/routes/components/HeightHider.svelte @@ -0,0 +1,14 @@ + + +
+ +
diff --git a/src/routes/components/Navbar/Navbar.svelte b/src/routes/components/Navbar/Navbar.svelte index 689e8a1..e6060de 100644 --- a/src/routes/components/Navbar/Navbar.svelte +++ b/src/routes/components/Navbar/Navbar.svelte @@ -31,10 +31,10 @@
-
+

Reiverr

-
+
diff --git a/src/routes/components/RequestModal/RequestModal.svelte b/src/routes/components/RequestModal/RequestModal.svelte index d2820a7..284e404 100644 --- a/src/routes/components/RequestModal/RequestModal.svelte +++ b/src/routes/components/RequestModal/RequestModal.svelte @@ -1,7 +1,7 @@ + +{#await response then data} + {#if data} +
+ {#if !data.canStream && !data.isDownloading} +
+

No sources found

+

+ No local or remote sources found for this title. You can configure your sources on the sources page. +

+
+ {/if} + + {#if data.isAdded && data.radarrMovie} +
+
+
Local Library
+ + + +
+ {#each data.radarrDownload || [] as downloadingFile} +
+
+ {downloadingFile.quality.quality.resolution}p + +

{formatSize(downloadingFile.size)} on disk

+ +

+ {downloadingFile.quality.quality.source} +

+ +

+ Completed in {downloadingFile.timeleft} +

+
+ +
+ {/each} + {#each data?.radarrMovie?.movieFile ? [data.radarrMovie.movieFile] : [] as movieFile (movieFile.id)} +
+
+ {movieFile.quality.quality.resolution}p + +

{formatSize(movieFile.size)} on disk

+ +

+ {movieFile.quality.quality.source} +

+ +

+ {movieFile.mediaInfo.videoCodec} +

+
+ +
+ {/each} + {#if !data?.radarrMovie?.movieFile && !data.radarrDownload} +
Click + to add files
+ {/if} +
+ {/if} +
+ +
+ {#if !data.isAdded || data.hasLocalFiles} + {#if !data.isAdded} + + {/if} + {#if data.hasLocalFiles} + + {/if} + {/if} +
+ + {#if data.isAdded && data.radarrMovie} + refetch()} + /> + {/if} + {:else} + no data + {/if} +{:catch err} + Could not load local movie data. + {JSON.stringify(err)} +{/await} diff --git a/src/routes/components/ResourceDetails/ResourceDetails.svelte b/src/routes/components/ResourceDetails/ResourceDetails.svelte index f81d0c0..576bacd 100644 --- a/src/routes/components/ResourceDetails/ResourceDetails.svelte +++ b/src/routes/components/ResourceDetails/ResourceDetails.svelte @@ -1,21 +1,29 @@ -
- {#key video?.key + movie.id} -
-