mirror of
https://github.com/sussy-code/smov.git
synced 2025-01-17 01:51:24 +01:00
Metadata fetching
This commit is contained in:
parent
e48af381c5
commit
2bf0b5b03c
5 changed files with 89 additions and 41 deletions
|
@ -1,4 +1,22 @@
|
||||||
import { ScrapeMedia } from "@movie-web/providers";
|
import { MetaOutput, ScrapeMedia } from "@movie-web/providers";
|
||||||
|
|
||||||
|
import { mwFetch } from "@/backend/helpers/fetch";
|
||||||
|
|
||||||
|
let metaDataCache: MetaOutput[] | null = null;
|
||||||
|
|
||||||
|
export function setCachedMetadata(data: MetaOutput[]) {
|
||||||
|
metaDataCache = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCachedMetadata(): MetaOutput[] {
|
||||||
|
return metaDataCache ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchMetadata(base: string) {
|
||||||
|
if (metaDataCache) return;
|
||||||
|
const data = await mwFetch<MetaOutput[][]>(`${base}/metadata`);
|
||||||
|
metaDataCache = data.flat();
|
||||||
|
}
|
||||||
|
|
||||||
function scrapeMediaToQueryMedia(media: ScrapeMedia) {
|
function scrapeMediaToQueryMedia(media: ScrapeMedia) {
|
||||||
let extra: Record<string, string> = {};
|
let extra: Record<string, string> = {};
|
||||||
|
@ -15,6 +33,7 @@ function scrapeMediaToQueryMedia(media: ScrapeMedia) {
|
||||||
type: media.type,
|
type: media.type,
|
||||||
releaseYear: media.releaseYear.toString(),
|
releaseYear: media.releaseYear.toString(),
|
||||||
imdbId: media.imdbId,
|
imdbId: media.imdbId,
|
||||||
|
tmdbId: media.tmdbId,
|
||||||
title: media.title,
|
title: media.title,
|
||||||
...extra,
|
...extra,
|
||||||
};
|
};
|
||||||
|
@ -48,8 +67,31 @@ export function makeProviderUrl(base: string) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function connectServerSideEvents(url: string, endEvents: string[]) {
|
export function connectServerSideEvents<T>(url: string, endEvents: string[]) {
|
||||||
const;
|
const eventSource = new EventSource(url);
|
||||||
|
let promReject: (reason?: any) => void;
|
||||||
|
let promResolve: (value: T) => void;
|
||||||
|
const promise = new Promise<T>((resolve, reject) => {
|
||||||
|
promResolve = resolve;
|
||||||
|
promReject = reject;
|
||||||
|
});
|
||||||
|
|
||||||
return {};
|
endEvents.forEach((evt) => {
|
||||||
|
eventSource.addEventListener(evt, (e) => {
|
||||||
|
eventSource.close();
|
||||||
|
promResolve(JSON.parse(e.data));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
eventSource.addEventListener("error", (err) => {
|
||||||
|
console.error("Failed to connect to SSE", err);
|
||||||
|
promReject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
promise: () => promise,
|
||||||
|
on<Data>(event: string, cb: (data: Data) => void) {
|
||||||
|
eventSource.addEventListener(event, (e) => cb(JSON.parse(e.data)));
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { getCachedMetadata } from "@/backend/helpers/providerApi";
|
||||||
import { Toggle } from "@/components/buttons/Toggle";
|
import { Toggle } from "@/components/buttons/Toggle";
|
||||||
import { Icon, Icons } from "@/components/Icon";
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
import { useCaptions } from "@/components/player/hooks/useCaptions";
|
import { useCaptions } from "@/components/player/hooks/useCaptions";
|
||||||
|
@ -10,7 +11,6 @@ import { useOverlayRouter } from "@/hooks/useOverlayRouter";
|
||||||
import { usePlayerStore } from "@/stores/player/store";
|
import { usePlayerStore } from "@/stores/player/store";
|
||||||
import { qualityToString } from "@/stores/player/utils/qualities";
|
import { qualityToString } from "@/stores/player/utils/qualities";
|
||||||
import { useSubtitleStore } from "@/stores/subtitles";
|
import { useSubtitleStore } from "@/stores/subtitles";
|
||||||
import { providers } from "@/utils/providers";
|
|
||||||
|
|
||||||
export function SettingsMenu({ id }: { id: string }) {
|
export function SettingsMenu({ id }: { id: string }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -23,7 +23,10 @@ export function SettingsMenu({ id }: { id: string }) {
|
||||||
const currentSourceId = usePlayerStore((s) => s.sourceId);
|
const currentSourceId = usePlayerStore((s) => s.sourceId);
|
||||||
const sourceName = useMemo(() => {
|
const sourceName = useMemo(() => {
|
||||||
if (!currentSourceId) return "...";
|
if (!currentSourceId) return "...";
|
||||||
return providers.getMetadata(currentSourceId)?.name ?? "...";
|
const source = getCachedMetadata().find(
|
||||||
|
(src) => src.id === currentSourceId
|
||||||
|
);
|
||||||
|
return source?.name ?? "...";
|
||||||
}, [currentSourceId]);
|
}, [currentSourceId]);
|
||||||
const { toggleLastUsed } = useCaptions();
|
const { toggleLastUsed } = useCaptions();
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { ReactNode, useEffect, useMemo, useRef } from "react";
|
import { ReactNode, useEffect, useMemo, useRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { getCachedMetadata } from "@/backend/helpers/providerApi";
|
||||||
import { Loading } from "@/components/layout/Loading";
|
import { Loading } from "@/components/layout/Loading";
|
||||||
import {
|
import {
|
||||||
useEmbedScraping,
|
useEmbedScraping,
|
||||||
|
@ -33,7 +34,7 @@ export function EmbedOption(props: {
|
||||||
|
|
||||||
const embedName = useMemo(() => {
|
const embedName = useMemo(() => {
|
||||||
if (!props.embedId) return unknownEmbedName;
|
if (!props.embedId) return unknownEmbedName;
|
||||||
const sourceMeta = providers.getMetadata(props.embedId);
|
const sourceMeta = getCachedMetadata().find((s) => s.id === props.embedId);
|
||||||
return sourceMeta?.name ?? unknownEmbedName;
|
return sourceMeta?.name ?? unknownEmbedName;
|
||||||
}, [props.embedId, unknownEmbedName]);
|
}, [props.embedId, unknownEmbedName]);
|
||||||
|
|
||||||
|
@ -61,7 +62,7 @@ export function EmbedSelectionView({ sourceId, id }: EmbedSelectionViewProps) {
|
||||||
|
|
||||||
const sourceName = useMemo(() => {
|
const sourceName = useMemo(() => {
|
||||||
if (!sourceId) return "...";
|
if (!sourceId) return "...";
|
||||||
const sourceMeta = providers.getMetadata(sourceId);
|
const sourceMeta = getCachedMetadata().find((s) => s.id === sourceId);
|
||||||
return sourceMeta?.name ?? "...";
|
return sourceMeta?.name ?? "...";
|
||||||
}, [sourceId]);
|
}, [sourceId]);
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,11 @@ import {
|
||||||
} from "@movie-web/providers";
|
} from "@movie-web/providers";
|
||||||
import { RefObject, useCallback, useEffect, useRef, useState } from "react";
|
import { RefObject, useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
import { makeProviderUrl } from "@/backend/helpers/providerApi";
|
import {
|
||||||
|
connectServerSideEvents,
|
||||||
|
getCachedMetadata,
|
||||||
|
makeProviderUrl,
|
||||||
|
} from "@/backend/helpers/providerApi";
|
||||||
import { getLoadbalancedProviderApiUrl, providers } from "@/utils/providers";
|
import { getLoadbalancedProviderApiUrl, providers } from "@/utils/providers";
|
||||||
|
|
||||||
export interface ScrapingItems {
|
export interface ScrapingItems {
|
||||||
|
@ -37,7 +41,7 @@ function useBaseScrape() {
|
||||||
setSources(
|
setSources(
|
||||||
evt.sourceIds
|
evt.sourceIds
|
||||||
.map((v) => {
|
.map((v) => {
|
||||||
const source = providers.getMetadata(v);
|
const source = getCachedMetadata().find((s) => s.id === v);
|
||||||
if (!source) throw new Error("invalid source id");
|
if (!source) throw new Error("invalid source id");
|
||||||
const out: ScrapingSegment = {
|
const out: ScrapingSegment = {
|
||||||
name: source.name,
|
name: source.name,
|
||||||
|
@ -80,7 +84,9 @@ function useBaseScrape() {
|
||||||
(evt: ScraperEvent<"discoverEmbeds">) => {
|
(evt: ScraperEvent<"discoverEmbeds">) => {
|
||||||
setSources((s) => {
|
setSources((s) => {
|
||||||
evt.embeds.forEach((v) => {
|
evt.embeds.forEach((v) => {
|
||||||
const source = providers.getMetadata(v.embedScraperId);
|
const source = getCachedMetadata().find(
|
||||||
|
(src) => src.id === v.embedScraperId
|
||||||
|
);
|
||||||
if (!source) throw new Error("invalid source id");
|
if (!source) throw new Error("invalid source id");
|
||||||
const out: ScrapingSegment = {
|
const out: ScrapingSegment = {
|
||||||
embedId: v.embedScraperId,
|
embedId: v.embedScraperId,
|
||||||
|
@ -149,37 +155,18 @@ export function useScrape() {
|
||||||
const providerApiUrl = getLoadbalancedProviderApiUrl();
|
const providerApiUrl = getLoadbalancedProviderApiUrl();
|
||||||
if (providerApiUrl) {
|
if (providerApiUrl) {
|
||||||
startScrape();
|
startScrape();
|
||||||
const sseOutput = await new Promise<RunOutput | null>(
|
|
||||||
(resolve, reject) => {
|
|
||||||
const baseUrlMaker = makeProviderUrl(providerApiUrl);
|
const baseUrlMaker = makeProviderUrl(providerApiUrl);
|
||||||
const scrapeEvents = new EventSource(baseUrlMaker.scrapeAll(media));
|
const conn = connectServerSideEvents<RunOutput | "">(
|
||||||
scrapeEvents.addEventListener("init", (e) => {
|
baseUrlMaker.scrapeAll(media),
|
||||||
initEvent(JSON.parse(e.data));
|
["completed", "noOutput"]
|
||||||
});
|
|
||||||
scrapeEvents.addEventListener("error", (err) => {
|
|
||||||
console.error("failed to use provider api", err);
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
scrapeEvents.addEventListener("start", (e) =>
|
|
||||||
startEvent(JSON.parse(e.data))
|
|
||||||
);
|
);
|
||||||
scrapeEvents.addEventListener("update", (e) =>
|
conn.on("init", initEvent);
|
||||||
updateEvent(JSON.parse(e.data))
|
conn.on("start", startEvent);
|
||||||
);
|
conn.on("update", updateEvent);
|
||||||
scrapeEvents.addEventListener("discoverEmbeds", (e) =>
|
conn.on("discoverEmbeds", discoverEmbedsEvent);
|
||||||
discoverEmbedsEvent(JSON.parse(e.data))
|
const sseOutput = await conn.promise();
|
||||||
);
|
|
||||||
scrapeEvents.addEventListener("completed", (e) => {
|
return getResult(sseOutput === "" ? null : sseOutput);
|
||||||
scrapeEvents.close();
|
|
||||||
resolve(JSON.parse(e.data));
|
|
||||||
});
|
|
||||||
scrapeEvents.addEventListener("noOutput", () => {
|
|
||||||
scrapeEvents.close();
|
|
||||||
resolve(null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return getResult(sseOutput);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!providers) return null;
|
if (!providers) return null;
|
||||||
|
|
|
@ -3,6 +3,10 @@ import { useHistory, useParams } from "react-router-dom";
|
||||||
import { useAsync } from "react-use";
|
import { useAsync } from "react-use";
|
||||||
import type { AsyncReturnType } from "type-fest";
|
import type { AsyncReturnType } from "type-fest";
|
||||||
|
|
||||||
|
import {
|
||||||
|
fetchMetadata,
|
||||||
|
setCachedMetadata,
|
||||||
|
} from "@/backend/helpers/providerApi";
|
||||||
import { DetailedMeta, getMetaFromId } from "@/backend/metadata/getmeta";
|
import { DetailedMeta, getMetaFromId } from "@/backend/metadata/getmeta";
|
||||||
import { decodeTMDBId } from "@/backend/metadata/tmdb";
|
import { decodeTMDBId } from "@/backend/metadata/tmdb";
|
||||||
import { MWMediaType } from "@/backend/metadata/types/mw";
|
import { MWMediaType } from "@/backend/metadata/types/mw";
|
||||||
|
@ -14,6 +18,7 @@ import { Paragraph } from "@/components/text/Paragraph";
|
||||||
import { Title } from "@/components/text/Title";
|
import { Title } from "@/components/text/Title";
|
||||||
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
|
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
|
||||||
import { conf } from "@/setup/config";
|
import { conf } from "@/setup/config";
|
||||||
|
import { getLoadbalancedProviderApiUrl, providers } from "@/utils/providers";
|
||||||
|
|
||||||
export interface MetaPartProps {
|
export interface MetaPartProps {
|
||||||
onGetMeta?: (meta: DetailedMeta, episodeId?: string) => void;
|
onGetMeta?: (meta: DetailedMeta, episodeId?: string) => void;
|
||||||
|
@ -36,6 +41,16 @@ export function MetaPart(props: MetaPartProps) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const { error, value, loading } = useAsync(async () => {
|
const { error, value, loading } = useAsync(async () => {
|
||||||
|
const providerApiUrl = getLoadbalancedProviderApiUrl();
|
||||||
|
if (providerApiUrl) {
|
||||||
|
await fetchMetadata(providerApiUrl);
|
||||||
|
} else {
|
||||||
|
setCachedMetadata([
|
||||||
|
...providers.listSources(),
|
||||||
|
...providers.listEmbeds(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
let data: ReturnType<typeof decodeTMDBId> = null;
|
let data: ReturnType<typeof decodeTMDBId> = null;
|
||||||
try {
|
try {
|
||||||
data = decodeTMDBId(params.media);
|
data = decodeTMDBId(params.media);
|
||||||
|
|
Loading…
Reference in a new issue