mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 05:05:38 -04:00
feat: generate and display thumbhash blur placeholders for TMDB images
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import { db } from "@sofa/db/client";
|
||||
import { and, inArray } from "@sofa/db/helpers";
|
||||
import { titles } from "@sofa/db/schema";
|
||||
|
||||
type BrowseLookup = {
|
||||
tmdbId: number;
|
||||
type: "movie" | "tv";
|
||||
};
|
||||
|
||||
export function browseLookupKey({ tmdbId, type }: BrowseLookup): string {
|
||||
return `${tmdbId}-${type}`;
|
||||
}
|
||||
|
||||
export function getBrowsePosterThumbHashes(lookups: BrowseLookup[]) {
|
||||
if (lookups.length === 0) {
|
||||
return new Map<string, string | null>();
|
||||
}
|
||||
|
||||
const tmdbIds = [...new Set(lookups.map((lookup) => lookup.tmdbId))];
|
||||
const mediaTypes = [...new Set(lookups.map((lookup) => lookup.type))];
|
||||
|
||||
const rows = db
|
||||
.select({
|
||||
tmdbId: titles.tmdbId,
|
||||
type: titles.type,
|
||||
posterThumbHash: titles.posterThumbHash,
|
||||
})
|
||||
.from(titles)
|
||||
.where(
|
||||
and(inArray(titles.tmdbId, tmdbIds), inArray(titles.type, mediaTypes)),
|
||||
)
|
||||
.all();
|
||||
|
||||
return new Map(
|
||||
rows.map((row) => [
|
||||
browseLookupKey({
|
||||
tmdbId: row.tmdbId,
|
||||
type: row.type as "movie" | "tv",
|
||||
}),
|
||||
row.posterThumbHash,
|
||||
]),
|
||||
);
|
||||
}
|
||||
@@ -25,6 +25,7 @@ export const continueWatching = os.dashboard.continueWatching
|
||||
id: item.title.id,
|
||||
title: item.title.title,
|
||||
backdropPath: tmdbImageUrl(item.title.backdropPath, "backdrops"),
|
||||
backdropThumbHash: item.title.backdropThumbHash,
|
||||
},
|
||||
nextEpisode: item.nextEpisode
|
||||
? {
|
||||
@@ -32,6 +33,7 @@ export const continueWatching = os.dashboard.continueWatching
|
||||
episodeNumber: item.nextEpisode.episodeNumber,
|
||||
name: item.nextEpisode.name,
|
||||
stillPath: tmdbImageUrl(item.nextEpisode.stillPath, "stills"),
|
||||
stillThumbHash: item.nextEpisode.stillThumbHash,
|
||||
}
|
||||
: null,
|
||||
totalEpisodes: item.totalEpisodes,
|
||||
@@ -50,6 +52,7 @@ export const library = os.dashboard.library
|
||||
type: t.type,
|
||||
title: t.title,
|
||||
posterPath: tmdbImageUrl(t.posterPath, "posters"),
|
||||
posterThumbHash: t.posterThumbHash ?? null,
|
||||
releaseDate: t.releaseDate ?? null,
|
||||
firstAirDate: t.firstAirDate ?? null,
|
||||
voteAverage: t.voteAverage,
|
||||
@@ -71,6 +74,7 @@ export const recommendations = os.dashboard.recommendations
|
||||
type: t.type,
|
||||
title: t.title,
|
||||
posterPath: tmdbImageUrl(t.posterPath, "posters"),
|
||||
posterThumbHash: t.posterThumbHash ?? null,
|
||||
releaseDate: t.releaseDate ?? null,
|
||||
firstAirDate: t.firstAirDate ?? null,
|
||||
voteAverage: t.voteAverage,
|
||||
|
||||
@@ -8,6 +8,10 @@ import { isTmdbConfigured } from "@sofa/tmdb/config";
|
||||
import { tmdbImageUrl } from "@sofa/tmdb/image";
|
||||
import { os } from "../context";
|
||||
import { authed } from "../middleware";
|
||||
import {
|
||||
browseLookupKey,
|
||||
getBrowsePosterThumbHashes,
|
||||
} from "./browse-thumbhashes";
|
||||
|
||||
export const discover = os.discover
|
||||
.use(authed)
|
||||
@@ -31,7 +35,7 @@ export const discover = os.discover
|
||||
first_air_date?: string;
|
||||
};
|
||||
|
||||
const items = ((results.results ?? []) as DiscoverResult[])
|
||||
const baseItems = ((results.results ?? []) as DiscoverResult[])
|
||||
.filter((r) => r.poster_path)
|
||||
.map((r) => ({
|
||||
tmdbId: r.id,
|
||||
@@ -42,6 +46,11 @@ export const discover = os.discover
|
||||
firstAirDate: (r.first_air_date as string | undefined) ?? null,
|
||||
voteAverage: r.vote_average ?? null,
|
||||
}));
|
||||
const posterThumbHashes = getBrowsePosterThumbHashes(baseItems);
|
||||
const items = baseItems.map((item) => ({
|
||||
...item,
|
||||
posterThumbHash: posterThumbHashes.get(browseLookupKey(item)) ?? null,
|
||||
}));
|
||||
|
||||
const lookups = items.map((r) => ({ tmdbId: r.tmdbId, type: r.type }));
|
||||
const [userStatuses, episodeProgress] =
|
||||
|
||||
@@ -8,6 +8,10 @@ import { isTmdbConfigured } from "@sofa/tmdb/config";
|
||||
import { tmdbImageUrl } from "@sofa/tmdb/image";
|
||||
import { os } from "../context";
|
||||
import { authed } from "../middleware";
|
||||
import {
|
||||
browseLookupKey,
|
||||
getBrowsePosterThumbHashes,
|
||||
} from "./browse-thumbhashes";
|
||||
|
||||
function requireTmdb() {
|
||||
if (!isTmdbConfigured()) {
|
||||
@@ -25,7 +29,7 @@ export const trending = os.explore.trending
|
||||
const data = await getTrending(input.type, "day");
|
||||
const results = (data.results ?? []) as Record<string, unknown>[];
|
||||
|
||||
const items = results
|
||||
const baseItems = results
|
||||
.filter((r) => r.poster_path)
|
||||
.map((r) => {
|
||||
const mediaType =
|
||||
@@ -45,6 +49,11 @@ export const trending = os.explore.trending
|
||||
voteAverage: (r.vote_average as number | undefined) ?? null,
|
||||
};
|
||||
});
|
||||
const posterThumbHashes = getBrowsePosterThumbHashes(baseItems);
|
||||
const items = baseItems.map((item) => ({
|
||||
...item,
|
||||
posterThumbHash: posterThumbHashes.get(browseLookupKey(item)) ?? null,
|
||||
}));
|
||||
|
||||
const heroResult = results.find(
|
||||
(r) =>
|
||||
@@ -83,7 +92,7 @@ export const popular = os.explore.popular
|
||||
requireTmdb();
|
||||
|
||||
const data = await getPopular(input.type);
|
||||
const items = ((data.results ?? []) as Record<string, unknown>[])
|
||||
const baseItems = ((data.results ?? []) as Record<string, unknown>[])
|
||||
.filter((r) => r.poster_path)
|
||||
.map((r) => ({
|
||||
tmdbId: r.id as number,
|
||||
@@ -94,6 +103,11 @@ export const popular = os.explore.popular
|
||||
firstAirDate: (r.first_air_date as string | undefined) ?? null,
|
||||
voteAverage: (r.vote_average as number | undefined) ?? null,
|
||||
}));
|
||||
const posterThumbHashes = getBrowsePosterThumbHashes(baseItems);
|
||||
const items = baseItems.map((item) => ({
|
||||
...item,
|
||||
posterThumbHash: posterThumbHashes.get(browseLookupKey(item)) ?? null,
|
||||
}));
|
||||
|
||||
const lookups = items.map((r) => ({ tmdbId: r.tmdbId, type: r.type }));
|
||||
const [userStatuses, episodeProgress] =
|
||||
|
||||
Reference in New Issue
Block a user