feat(cache): TMDB image proxy, posters, cache admin API and settings cards
GET /img/tmdb/{size}/{file} ported from MP ImageHelper / fetch_image
(sanitized cache path under DATA_DIR/cache/images, GLOBAL_IMAGE_CACHE
read switch, md5 ETag / 304, day-based cleanup job). Cache module adds
cache.sweep / cache.images_clean jobs and /cache/tmdb, /cache/images
list and clear endpoints. Library list/detail and the search palette
show posters with the neutral placeholder as fallback; metadata settings
get TMDB cache and image cache cards. Runbook docs/runbook/cache.md and
porting notes docs/porting/moviepilot-tmdb-cache.md.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
946fca5e55
commit
7862573944
16 files changed
+1028
-9
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import { tmdbImageUrl } from "@umt/contracts";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { Clapperboard, Film, Layers, Tv } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
import { type ReactNode, useState } from "react";
|
||||
import { StatusBadge, type StatusTone } from "~/components/console/status-badge.tsx";
|
||||
import { cn } from "~/lib/utils.ts";
|
||||
|
||||
@@ -117,19 +118,43 @@ export function ChipToggleGroup<T extends string>({
|
||||
|
||||
const KIND_ICON: Record<string, LucideIcon> = { movie: Film, series: Tv, variety: Clapperboard, collection: Layers };
|
||||
|
||||
/** 封面占位:无彩材质 + 类型图标 + 首字(不引入海报色彩) */
|
||||
/** media_entity.metadata 里的最终海报路径(metadata.posterPath.value) */
|
||||
export function posterPathOf(metadata: unknown): string | null {
|
||||
const v = (metadata as { posterPath?: { value?: unknown } } | null)?.posterPath?.value;
|
||||
return typeof v === "string" && v ? v : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 封面:有 TMDB 海报路径时经图片代理显示海报,加载失败或没有海报时回退无彩占位(类型图标 + 首字)。
|
||||
*/
|
||||
export function CoverPlaceholder({
|
||||
title,
|
||||
kind,
|
||||
small = false,
|
||||
className,
|
||||
posterPath,
|
||||
}: {
|
||||
title: string;
|
||||
kind: string;
|
||||
/** 列表缩略:只显示首字,不叠类型图标 */
|
||||
small?: boolean;
|
||||
className?: string;
|
||||
/** TMDB poster_path */
|
||||
posterPath?: string | null | undefined;
|
||||
}) {
|
||||
const [failed, setFailed] = useState(false);
|
||||
const src = failed ? null : tmdbImageUrl(posterPath, small ? "w154" : "w342");
|
||||
if (src) {
|
||||
return (
|
||||
<img
|
||||
src={src}
|
||||
alt=""
|
||||
loading="lazy"
|
||||
onError={() => setFailed(true)}
|
||||
className={cn("shrink-0 rounded-lg border border-border bg-foreground/[0.05] object-cover", className)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const Icon = KIND_ICON[kind] ?? Film;
|
||||
const glyph = title.replace(/^w32-/, "").trim().charAt(0) || "?";
|
||||
return (
|
||||
|
||||
Reference in new issue
Block a user