fix(scrape): 平铺目录只刮本作品视频、图片按视频名命名;图片扩展名按实际类型;取消时记录不停在进行中
- 作品重刮对电影传入该作品已入库的视频,不给同目录别的电影写本作品 NFO; - 电影目录里有别的作品的视频时海报 / 背景图写 <视频名>-poster / -fanart,不写目录共用的 poster.jpg; - 图片扩展名按下载到的 MIME,URL 认不出或与内容不符时以内容为准; - 任务被取消时记录收尾为失败 scrape_canceled,保留已写文件结果。 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
cdf62dbf02
commit
89aa70bb9c
4 files changed
+220
-18
No files matched your search
@@ -50,7 +50,7 @@ describe.skipIf(!hasPg)("刮削:多集文件、综艺日期期数、平铺目录
|
||||
let lib: TempTree;
|
||||
let rootId = "";
|
||||
let seasonDetail: (n: number) => Promise<TmdbRichSeasonDetail | null> = async () => null;
|
||||
const imageMime = "image/jpeg";
|
||||
let imageMime = "image/jpeg";
|
||||
|
||||
beforeAll(async () => {
|
||||
lib = await createTempTree(
|
||||
@@ -280,4 +280,98 @@ describe.skipIf(!hasPg)("刮削:多集文件、综艺日期期数、平铺目录
|
||||
expect(nfo).toContain("<episode>5</episode>");
|
||||
expect(r.status).toBe("succeeded");
|
||||
});
|
||||
|
||||
async function registerVideo(entityId: string, path: string) {
|
||||
const a = await h.db
|
||||
.insertInto("media_asset")
|
||||
.values({ media_entity_id: entityId, kind: "video", status: "active" })
|
||||
.returning("id")
|
||||
.executeTakeFirstOrThrow();
|
||||
await h.db
|
||||
.insertInto("asset_location")
|
||||
.values({
|
||||
media_asset_id: a.id,
|
||||
location_kind: "library",
|
||||
library_root_id: rootId,
|
||||
relative_path: path,
|
||||
status: "available",
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
it("平铺模板:作品重刮只刮本作品的视频,图片按 <视频名>-poster 命名,不写目录共用的 poster", async () => {
|
||||
const meta = (t: string) => ({ title: f(t), posterPath: f(`/${t}.jpg`), backdropPath: f(`/${t}-bg.jpg`) });
|
||||
const inception = await entity("movie", "盗梦空间", meta("盗梦空间"), { id: "27205", type: "movie" });
|
||||
const interstellar = await entity("movie", "星际穿越", meta("星际穿越"), { id: "157336", type: "movie" });
|
||||
await registerVideo(inception, "Movies/盗梦空间 (2010).mkv");
|
||||
await registerVideo(interstellar, "Movies/星际穿越 (2014).mkv");
|
||||
const q = await scrapeEntity(h.d, inception, { trigger: "manual", mode: "overwrite" });
|
||||
expect(q).toHaveLength(1);
|
||||
const r = await runScrapeRecord(h.d, q[0]!.recordId);
|
||||
expect(r.status).toBe("succeeded");
|
||||
expect(r.videos).toEqual(["Movies/盗梦空间 (2010).mkv"]);
|
||||
expect(r.files.map((x) => x.path).sort()).toEqual([
|
||||
"Movies/盗梦空间 (2010)-fanart.jpg",
|
||||
"Movies/盗梦空间 (2010)-poster.jpg",
|
||||
"Movies/盗梦空间 (2010).nfo",
|
||||
]);
|
||||
await expect(stat(lib.path("Movies/星际穿越 (2014).nfo"))).rejects.toThrow();
|
||||
await expect(stat(lib.path("Movies/poster.jpg"))).rejects.toThrow();
|
||||
expect(await readFile(lib.path("Movies/盗梦空间 (2010).nfo"), "utf8")).toContain("<title>盗梦空间</title>");
|
||||
});
|
||||
|
||||
it("单部电影的目录照旧写 poster / fanart;图片扩展名按实际类型", async () => {
|
||||
const avatar = await entity(
|
||||
"movie",
|
||||
"阿凡达",
|
||||
{ title: f("阿凡达"), posterPath: f("https://img.example.test/avatar.webp") },
|
||||
{ id: "19995", type: "movie" },
|
||||
);
|
||||
await registerVideo(avatar, "Single/阿凡达 (2009)/阿凡达 (2009).mkv");
|
||||
imageMime = "image/png";
|
||||
try {
|
||||
const q = await scrapeEntity(h.d, avatar, { trigger: "manual", mode: "overwrite" });
|
||||
const r = await runScrapeRecord(h.d, q[0]!.recordId);
|
||||
expect(r.status).toBe("succeeded");
|
||||
expect(r.files.find((x) => x.role === "poster")).toMatchObject({
|
||||
path: "Single/阿凡达 (2009)/poster.png",
|
||||
action: "written",
|
||||
});
|
||||
await expect(stat(lib.path("Single/阿凡达 (2009)/poster.webp"))).rejects.toThrow();
|
||||
expect(new Uint8Array(await readFile(lib.path("Single/阿凡达 (2009)/poster.png")))).toEqual(PNG);
|
||||
} finally {
|
||||
imageMime = "image/jpeg";
|
||||
}
|
||||
});
|
||||
|
||||
it("任务被取消:记录收尾为失败(scrape_canceled),不停在进行中", async () => {
|
||||
const id = await entity("movie", "取消", { title: f("取消") }, { id: "1", type: "movie" });
|
||||
const match: ScrapeMatch = {
|
||||
mediaEntityId: id,
|
||||
provider: "tmdb",
|
||||
externalId: "1",
|
||||
kind: "movie",
|
||||
title: "取消",
|
||||
year: null,
|
||||
source: "library",
|
||||
};
|
||||
const q = await queueScrape(h.d, {
|
||||
rootId: `lib:${rootId}`,
|
||||
rootPath: lib.root,
|
||||
libraryRootId: rootId,
|
||||
path: "Single/阿凡达 (2009)",
|
||||
match,
|
||||
trigger: "manual",
|
||||
});
|
||||
const ac = new AbortController();
|
||||
ac.abort();
|
||||
await expect(runScrapeRecord(h.d, q.recordId, ac.signal)).rejects.toThrow();
|
||||
const row = await h.db
|
||||
.selectFrom("scrape_record")
|
||||
.select(["status", "reason", "finished_at"])
|
||||
.where("id", "=", q.recordId)
|
||||
.executeTakeFirstOrThrow();
|
||||
expect(row).toMatchObject({ status: "failed", reason: "scrape_canceled" });
|
||||
expect(row.finished_at).not.toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user