finish simplification of ids into single value

This commit is contained in:
Chris Kruining 2025-06-05 10:24:37 +02:00
parent 61795fdc5e
commit 7b363964f7
No known key found for this signature in database
GPG key ID: EB894A3560CCCAD2
10 changed files with 86 additions and 132 deletions

View file

@ -84,12 +84,12 @@ export const listItemIds = query(
return Object.fromEntries(
data.Items?.map((item) => {
const type = {
Movie: 'movie',
Series: 'tv',
}[item.Type as string] ?? 'unknown';
Movie: 'm',
Series: 's',
}[item.Type as string] ?? '';
return [
`${type}-${item.ProviderIds!["Tmdb"]!}`,
`${type}${item.ProviderIds!["Tmdb"]!}`,
{ jellyfin: item.Id! },
];
}) ?? []
@ -125,7 +125,7 @@ export const listItems = query(
}
return (
data.Items?.map((item) => mapToEntry(item)) ?? []
data.Items?.map((item) => toEntry(item)) ?? []
);
},
"jellyfin.listItems",
@ -166,7 +166,7 @@ export const getRandomItems = query(
});
return (
data?.Items?.map((item) => mapToEntry(item)) ?? []
data?.Items?.map((item) => toEntry(item)) ?? []
);
},
"jellyfin.listRandomItems",
@ -201,7 +201,7 @@ export const getItem = query(
return undefined;
}
return mapToEntry(data);
return toEntry(data);
},
"jellyfin.getItem",
);
@ -357,21 +357,20 @@ function assertNoErrors<T>(
}
}
const mapToEntry = (item: components['schemas']['BaseItemDto']): Entry => {
const toEntry = (item: components['schemas']['BaseItemDto']): Entry => {
const type = {
Movie: 'movie',
Series: 'tv',
Movie: 'm',
Series: 's',
}[item.Type as string] as any;
return {
type,
id: item.ProviderIds!["Tmdb"]!,
title: item.Name!,
overview: item.Overview!,
thumbnail: new URL(`/Items/${item.Id}/Images/Primary`, getBaseUrl()), //await getItemImage(data.Id!, 'Primary'),
image: new URL(`/Items/${item.Id}/Images/Backdrop`, getBaseUrl()),
providers: {
jellyfin: item.Id
}
};
id: `${type}${item.ProviderIds!["Tmdb"]!}`,
title: item.Name!,
overview: item.Overview!,
thumbnail: new URL(`/Items/${item.Id}/Images/Primary`, getBaseUrl()), //await getItemImage(data.Id!, 'Primary'),
image: new URL(`/Items/${item.Id}/Images/Backdrop`, getBaseUrl()),
providers: {
jellyfin: item.Id
}
};
};

View file

@ -1,9 +1,19 @@
import createClient from "openapi-fetch";
import { query } from "@solidjs/router";
import { Entry, SearchResult } from "../types";
import { paths as pathsV3 } from "./tmdb.generated";
import { paths as pathsV3, operations } from "./tmdb.generated";
import { paths as pathsV4 } from "./tmdb.not.generated";
interface TMDBItem {
id: number;
media_type: string;
name?: string;
title?: string;
overview?: string;
backdrop_path?: string;
poster_path?: string;
}
const getClients = () => {
"use server";
@ -28,20 +38,27 @@ const getClients = () => {
};
export const getEntry = query(
async (type: Entry['type'], id: string): Promise<Entry | undefined> => {
async (id: string): Promise<Entry | undefined> => {
"use server";
const [ clientV3 ] = getClients();
const mediaType = ({
m: 'movie',
s: 'tv',
} as const)[id[0]]!;
const endpoint = ({
movie: "/movie/{movie_id}",
tv: '/tv/{series_id}',
} as const)[type];
} as const)[mediaType];
const params = ({
movie: { movie_id: Number.parseInt(id) },
tv: { series_id: Number.parseInt(id) },
} as const)[type];
movie: { movie_id: Number.parseInt(id.slice(1)) },
tv: { series_id: Number.parseInt(id.slice(1)) },
} as const)[mediaType];
console.log(`going to fetch from '${endpoint}' with id '${id}'`)
const { data } = await clientV3.GET(endpoint, {
params: {
@ -53,14 +70,7 @@ export const getEntry = query(
return undefined;
}
return {
type,
id: String(data.id ?? -1),
title: data.title!,
overview: data.overview,
thumbnail: `http://image.tmdb.org/t/p/w342${data.poster_path}`,
image: `http://image.tmdb.org/t/p/original${data.backdrop_path}`,
};
return toEntry(data as any, mediaType);
},
"tmdb.getEntry",
);
@ -85,22 +95,13 @@ export const getRecommendations = query(async (): Promise<Entry[]> => {
return [];
}
return data?.results.map(
({ id, title, overview, poster_path, backdrop_path }) => ({
type: 'movie',
id: String(id ?? -1),
title: title!,
overview,
thumbnail: `http://image.tmdb.org/t/p/w342${poster_path}`,
image: `http://image.tmdb.org/t/p/original${backdrop_path}`,
}),
);
return data?.results.map((item) => toEntry(item));
}, "tmdb.getRecommendations");
export const getDiscovery = query(async (): Promise<Entry[]> => {
"use server";
const [ clientV3 ] = getClients();
const [ clientV3 ] = getClients();
const [{ data: movies }, { data: series }] = await Promise.all([
clientV3.GET("/discover/movie"),
@ -111,25 +112,8 @@ export const getDiscovery = query(async (): Promise<Entry[]> => {
return [];
}
const movieEntries = movies?.results?.slice(0, 10)
.map(({ id, title, overview, poster_path, backdrop_path }): Entry => ({
type: 'movie',
id: String(id ?? -1),
title: title!,
overview,
thumbnail: `http://image.tmdb.org/t/p/w342${poster_path}`,
image: `http://image.tmdb.org/t/p/original${backdrop_path}`,
})) ?? []
const seriesEntries = series?.results?.slice(0, 10)
.map(({ id, name, overview, poster_path, backdrop_path }): Entry => ({
type: 'tv',
id: String(id ?? -1),
title: name!,
overview,
thumbnail: `http://image.tmdb.org/t/p/w342${poster_path}`,
image: `http://image.tmdb.org/t/p/original${backdrop_path}`,
})) ?? []
const movieEntries = movies?.results?.slice(0, 10).map((item): Entry => toEntry(item)) ?? []
const seriesEntries = series?.results?.slice(0, 10).map((item): Entry => toEntry(item)) ?? []
return movieEntries.concat(seriesEntries);
}, "tmdb.getDiscovery");
@ -158,18 +142,23 @@ export const searchMulti = query(async (query: string, page: number = 1): Promis
return { count: 0, pages: 0, results: [] };
}
console.log(`loaded page ${page}, found ${data.results?.length} results`);
console.log(data.results[0]);
return { count: data.total_results!, pages: data.total_pages!, results: data.results?.filter(({ media_type }) => media_type === 'movie' || media_type === 'tv').map((item): Entry => toEntry(item)) ?? [] };
}, "tmdb.search.multi");
return { count: data.total_results!, pages: data.total_pages!, results: data.results?.filter(({ media_type }) => media_type === 'movie' || media_type === 'tv').map(({ id, name, title, media_type, overview, backdrop_path, poster_path }): Entry => ({
type: ({
movie: 'movie',
tv: 'tv',
}[media_type ?? '']) as any,
id: String(id),
title: `${name ?? title ?? ''} (${media_type})`,
function toEntry(item: TMDBItem): Entry;
function toEntry(item: Omit<TMDBItem, 'media_type'>, mediaType: string): Entry;
function toEntry({ id, name, title, overview, media_type = '', backdrop_path, poster_path }: TMDBItem | Omit<TMDBItem, 'media_type'>, mediaType?: string): Entry {
const type = ({
movie: 'm',
tv: 's',
}[(mediaType ?? media_type) as string]);
return ({
id: `${type}${String(id ?? -1)}`,
title: `${name ?? title ?? ''}`,
overview,
thumbnail: `http://image.tmdb.org/t/p/w342${poster_path}`,
image: `http://image.tmdb.org/t/p/original${backdrop_path}`,
})) ?? [] };
}, "tmdb.search.multi");
});
};

View file

@ -1,6 +1,6 @@
import { toSlug } from "~/utilities";
import type { Entry } from "./types";
export const emptyEntry = Object.freeze<Entry>({ type: 'movie', id: '0', title: '' });
export const emptyEntry = Object.freeze<Entry>({ id: '0', title: '' });
export const createSlug = (entry: Entry) => toSlug(`${entry.title}-${entry.type}-${entry.id}`);
export const createSlug = (entry: Entry) => toSlug(`${entry.title}-${entry.id}`);

View file

@ -15,10 +15,10 @@ const jellyfinUserId = "a9c51af84bf54578a99ab4dd0ebf0763";
const lookupTable = query(async () => listItemIds(), 'content.lookupTable');
export const getHighlights = () => getContinueWatching(jellyfinUserId);
export const getStream = query(async (type: Entry['type'], id: string, range: string) => {
export const getStream = query(async (id: string, range: string) => {
const table = await lookupTable();
return getItemStream(table[`${type}-${id}`].jellyfin, range);
return getItemStream(table[id].jellyfin, range);
}, 'content.stream');
export const listCategories = query(async (): Promise<Category[]> => {
@ -35,16 +35,16 @@ export const listCategories = query(async (): Promise<Category[]> => {
export const getEntryFromSlug = query(
async (slug: string): Promise<Entry | undefined> => {
const { type, id } = slug.match(/^.+-(?<type>\w+)-(?<id>\w+)$/)?.groups ?? {};
const { id } = slug.match(/^.+-(?<id>\w+)$/)?.groups ?? {};
return getTmdbEntry(type as any, id);
return getTmdbEntry(id);
},
"content.getFromSlug",
);
export const getEntry = query(
async (type: Entry['type'], id: Entry["id"]): Promise<Entry | undefined> => {
return getTmdbEntry(type, id);
async (id: Entry["id"]): Promise<Entry | undefined> => {
return getTmdbEntry(id);
},
"content.get",
);

View file

@ -4,7 +4,6 @@ export interface Category {
}
export interface Entry {
type: 'movie'|'tv';
id: string;
title: string;
overview?: string;

View file

@ -90,8 +90,7 @@ export const Player: Component<PlayerProps> = (props) => {
<video
ref={setVideo}
src={`/api/content/${props.entry.type}/${props.entry.id}/stream`}
// src="https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4"
src={`/api/content/${props.entry.id}/stream`}
poster={props.entry.image}
lang="en"
>

View file

@ -0,0 +1,14 @@
import { APIEvent } from "@solidjs/start/server";
import { getStream } from "~/features/content";
export const GET = async ({ request, params: { id } }: APIEvent) => {
"use server";
const range = request.headers.get("range");
if (range === null) {
return new Response("Requires Range header", { status: 400 });
}
return getStream(id, range);
};

View file

@ -1,46 +0,0 @@
import { APIEvent } from "@solidjs/start/server";
import { getStream } from "~/features/content";
// const CHUNK_SIZE = 1 * 1e6; // 1MB
export const GET = async ({ request, params: { type, id } }: APIEvent) => {
"use server";
const range = request.headers.get("range");
if (range === null) {
return new Response("Requires Range header", { status: 400 });
}
return getStream(type, id, range);
// try {
// const file = Bun.file(
// import.meta.dirname + "/SampleVideo_1280x720_10mb.mp4",
// );
// if ((await file.exists()) !== true) {
// return new Response("File not found", { status: 404 });
// }
// const videoSize = file.size;
// const start = Number.parseInt(range.replace(/\D/g, ""));
// const end = Math.min(start + CHUNK_SIZE - 1, videoSize - 1);
// console.log(`streaming slice(${start}, ${end})`);
// return new Response(file.slice(start, end), {
// status: 206,
// headers: {
// 'Accept-Ranges': 'bytes',
// 'Content-Range': `bytes ${start}-${end}/${videoSize}`,
// 'Content-Length': `${end - start + 1}`,
// 'Content-Type': 'video/mp4',
// },
// });
// } catch (e) {
// console.error(e);
// throw e;
// }
};