feat(scrape): TMDB 标志写 clearlogo.png

- 富详情请求加 images(include_image_language=zh,en,null),作品原语言不是中英文时另取 /images;
  选一张 PNG 标志(原语言 → 中文 → 英文 → 无文字,同语言按评分)存作品字段 logoPath,变化时触发重刮
- 刮削布局新角色 clearlogo:电影写影片目录 clearlogo.png、剧集写剧集根目录,库根 / 平铺电影 <视频名>-clearlogo.png;
  覆盖方式与 hash 规则同其它图片
- 布局金样差分登记偏离 tmdb_clearlogo;mock TMDB 支持 logos 与 /images

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-06 19:37:34 +08:00
1 parent 1dca95f883
commit 680419bb64
16 files changed
+379 -12

No files matched your search

+14 -1
View File
@@ -7,6 +7,8 @@
// - 剧集:作品目录 tvshow.nfo、poster.jpg、fanart.jpg;季目录 season.nfo;季海报同时写作品目录
// `season01-poster.jpg`(特别篇 `season-specials-poster.jpg`)与季目录 `poster.jpg`(MP 同);
// - 每集:`<视频名>.nfo`;剧照 `<视频名>-thumb.jpg`(Kodi / Jellyfin / Emby 都认;MP 写 `<视频名>.jpg`)。
// - 标志(TMDB logos,MP 只在接 fanart.tv 时写):电影写在影片目录、剧集写在剧集根目录,文件名 `clearlogo.png`
// (Kodi / Emby / Jellyfin 通用);平铺 / 库根的电影用 `<视频名>-clearlogo.png`。
// 视频直接在库根(没有作品目录)或电影目录里还有别的作品时,电影图片改用 `<视频名>-poster.jpg` / `<视频名>-fanart.jpg`,
// 剧集不写 tvshow / 季文件(不能往库根里放 tvshow.nfo)。
@@ -42,6 +44,7 @@ export type ScrapeTargetRole =
| "episode_nfo"
| "poster"
| "fanart"
| "clearlogo"
| "season_poster"
| "episode_thumb";
@@ -61,6 +64,8 @@ export interface ScrapeVideo {
export interface ScrapeImages {
posterPath?: string | null;
backdropPath?: string | null;
/** TMDB 标志(PNG) */
logoPath?: string | null;
/** 季号 → 季海报路径 */
seasonPosters?: ReadonlyMap<number, string | null>;
/** "季:集" → 剧照路径 */
@@ -165,7 +170,7 @@ export function planScrapeTargets(input: ScrapeLayoutInput): ScrapeTarget[] {
}
if (!input.options.images || input.parts?.work === false) return out;
const inWork = workDir && !input.sharedDir && videos.some((v) => isUnder(v.path, workDir));
const art = (role: "poster" | "fanart", p: string | null | undefined) => {
const art = (role: "poster" | "fanart" | "clearlogo", p: string | null | undefined) => {
if (!p) return;
if (inWork) add({ path: join(workDir, `${role}${extOf(p)}`), kind: "image", role, imagePath: p });
else
@@ -182,6 +187,7 @@ export function planScrapeTargets(input: ScrapeLayoutInput): ScrapeTarget[] {
};
art("poster", img.posterPath);
art("fanart", img.backdropPath);
art("clearlogo", img.logoPath);
return out;
}
@@ -206,6 +212,13 @@ export function planScrapeTargets(input: ScrapeLayoutInput): ScrapeTarget[] {
role: "fanart",
imagePath: img.backdropPath,
});
if (img.logoPath)
add({
path: join(workDir, `clearlogo${extOf(img.logoPath)}`),
kind: "image",
role: "clearlogo",
imagePath: img.logoPath,
});
}
}
// 季:按视频所在目录(不等于作品目录的才算季目录);没有作品目录时只写季目录里的 season.nfo / poster