- H&R becomes a quick-filter facet (yes/no/unknown, with counts) on search results and the site resources tab; drop the old free/H&R selects, keep size range and sort on the same row (mobile: drawer), single clear button - legacy free/hr URL params migrate into quick filters; stored selections keep the same localStorage key - search timing and query terms move into TechInfo - library episode versions: click the whole row to expand, versions listed as sibling rows, details only under the selected version, raw values in TechInfo - slot comparison gaps get reason codes (matchGapText in contracts reasons.ts): "可换成 2160p", "缺 HDR 版本" Co-Authored-By: Claude Opus 5.5 <[email protected]>
205 lines
8.0 KiB
TypeScript
205 lines
8.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
audioCodecOf,
|
|
channelsLabel,
|
|
deliveryOfSpec,
|
|
type LocalVersion,
|
|
languageOf,
|
|
matchVersions,
|
|
type ProbeLike,
|
|
resolutionFromDims,
|
|
type SlotSpec,
|
|
sidecarLanguageOf,
|
|
specFromDelivery,
|
|
summarizeVersions,
|
|
VERSION_SPEC_VERSION,
|
|
versionSpecOf,
|
|
} from "../src/index.ts";
|
|
|
|
const GB = 1e9;
|
|
const DV_FILE = "人生切割术 (2022)/Season 1/Severance.S01E01.2160p.ATVP.WEB-DL.DDP5.1.Atmos.DV.HDR.H.265-FLUX.mkv";
|
|
const BD_FILE = "人生切割术 (2022)/Season 1/Severance.S01E01.1080p.BluRay.x264.DTS-HD.MA.5.1-CHD.mkv";
|
|
|
|
const PROBE_DV: ProbeLike = {
|
|
status: "ok",
|
|
durationSec: 3421.6,
|
|
video: [{ codec: "hevc", width: 3840, height: 1606, pixFmt: "yuv420p10le", hdr: "DV" }],
|
|
audio: [
|
|
{
|
|
codec: "eac3",
|
|
profile: "Dolby Digital Plus + Dolby Atmos",
|
|
channels: 6,
|
|
channelLayout: "5.1(side)",
|
|
language: "eng",
|
|
},
|
|
{ codec: "aac", channels: 2, language: "chi", title: "国语" },
|
|
],
|
|
subtitles: [
|
|
{ codec: "subrip", language: "chi", title: "简体" },
|
|
{ codec: "subrip", language: "chi", title: "繁體 cht" },
|
|
{ codec: "subrip", language: "eng" },
|
|
],
|
|
};
|
|
|
|
describe("版本规格:文件名 + 探测", () => {
|
|
it("探测优先给技术事实,来源 / 制作组来自文件名", () => {
|
|
const s = versionSpecOf({ path: DV_FILE, probe: PROBE_DV });
|
|
expect(s).toMatchObject({
|
|
resolution: "2160p",
|
|
source: "WEB-DL",
|
|
isEncode: false,
|
|
videoCodec: "H.265",
|
|
hdr: ["DV", "HDR10"],
|
|
bitDepth: 10,
|
|
releaseGroup: "FLUX",
|
|
durationSec: 3422,
|
|
specFrom: ["name", "probe"],
|
|
});
|
|
expect(s.audio).toEqual([
|
|
{ codec: "DDP Atmos", channels: "5.1", language: "en" },
|
|
{ codec: "AAC", channels: "2.0", language: "zh" },
|
|
]);
|
|
expect(s.subtitleLanguages).toEqual(["zh-Hans", "zh-Hant", "en"]);
|
|
});
|
|
|
|
it("没有探测结果只用文件名;外挂字幕按文件名记语言", () => {
|
|
const s = versionSpecOf({
|
|
path: BD_FILE,
|
|
sidecarSubtitles: ["Severance.S01E01.chs.srt", "Severance.S01E01.eng.ass"],
|
|
});
|
|
expect(s).toMatchObject({
|
|
resolution: "1080p",
|
|
source: "BluRay",
|
|
isEncode: true,
|
|
videoCodec: "H.264",
|
|
hdr: [],
|
|
releaseGroup: "CHD",
|
|
specFrom: ["name"],
|
|
});
|
|
expect(s.audio).toEqual([{ codec: "DTS-HD MA", channels: "5.1" }]);
|
|
expect(s.subtitleLanguages).toEqual(["zh-Hans", "en"]);
|
|
expect(s.durationSec).toBeUndefined();
|
|
});
|
|
|
|
it("裁切过的 4K(3840x1600)仍算 2160p;小工具函数", () => {
|
|
expect(resolutionFromDims(3840, 1600)).toBe("2160p");
|
|
expect(resolutionFromDims(1920, 800)).toBe("1080p");
|
|
expect(resolutionFromDims(1280, 536)).toBe("720p");
|
|
expect(resolutionFromDims()).toBeUndefined();
|
|
expect(audioCodecOf("truehd", undefined, "TrueHD Atmos 7.1")).toBe("TrueHD Atmos");
|
|
expect(audioCodecOf("dts", "DTS-HD MA + DTS:X")).toBe("DTS:X");
|
|
expect(audioCodecOf("dts", "DTS-HD MA")).toBe("DTS-HD MA");
|
|
expect(channelsLabel(8)).toBe("7.1");
|
|
expect(channelsLabel(undefined, "stereo")).toBe("2.0");
|
|
expect(languageOf("und")).toBeUndefined();
|
|
expect(languageOf("jpn")).toBe("ja");
|
|
expect(sidecarLanguageOf("x.zh-Hant.ass")).toBe("zh-Hant");
|
|
});
|
|
|
|
it("写回 delivery 与读回互为逆;旧口径返回 null", () => {
|
|
const s = versionSpecOf({ path: DV_FILE, probe: PROBE_DV });
|
|
const d = deliveryOfSpec(s);
|
|
expect(d.specVersion).toBe(VERSION_SPEC_VERSION);
|
|
expect(d.sourceType).toBe("WEB-DL");
|
|
expect(d.audioCodecs).toEqual(["DDP Atmos", "AAC"]);
|
|
expect(specFromDelivery(JSON.parse(JSON.stringify(d)))).toEqual(s);
|
|
expect(specFromDelivery({ resolution: "2160p", sourceType: "WEB-DL" })).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("汇总", () => {
|
|
it("按 分辨率 + 来源 + HDR 分组,DV 带 HDR10 兜底只算 DV", () => {
|
|
const dv = versionSpecOf({ path: DV_FILE, probe: PROBE_DV });
|
|
const bd = versionSpecOf({ path: BD_FILE });
|
|
const sum = summarizeVersions([
|
|
{ spec: dv, sizeBytes: 14.2 * GB },
|
|
{ spec: dv, sizeBytes: 13 * GB },
|
|
{ spec: bd, sizeBytes: 8 * GB },
|
|
]);
|
|
expect(sum.versionCount).toBe(3);
|
|
expect(sum.sizeBytes).toBe(35.2 * GB);
|
|
expect(sum.specs).toEqual([
|
|
{ resolution: "2160p", source: "WEB-DL", hdr: ["DV"], count: 2 },
|
|
{ resolution: "1080p", source: "BluRay", hdr: [], count: 1 },
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("与版本目标比对", () => {
|
|
const v = (id: string, path: string, probe?: ProbeLike, size = 10 * GB): LocalVersion => ({
|
|
assetId: id,
|
|
path,
|
|
spec: versionSpecOf({ path, probe: probe ?? null }),
|
|
sizeBytes: size,
|
|
unitCount: 1,
|
|
});
|
|
const target: SlotSpec = {
|
|
id: "slot-4k",
|
|
name: "4K",
|
|
hard: { resolutions: ["2160p"], minSeeders: 5, sites: ["only-site"], include: ["FLUX"], maxSizeBytes: 1 },
|
|
};
|
|
|
|
it("没有版本 = missing", () => {
|
|
expect(matchVersions(target, [], { isEpisode: true })).toEqual({
|
|
slotId: "slot-4k",
|
|
state: "missing",
|
|
assetId: null,
|
|
reasons: [],
|
|
codes: [],
|
|
});
|
|
});
|
|
|
|
it("两个版本里有一个达标 = satisfied,指向达标的那个;站点/做种/关键词/整包体积条件不参与", () => {
|
|
const m = matchVersions(target, [v("bd", BD_FILE), v("dv", DV_FILE, PROBE_DV)], { isEpisode: true });
|
|
expect(m).toMatchObject({ state: "satisfied", assetId: "dv" });
|
|
});
|
|
|
|
it("都不达标 = upgradeable,附不达标原因", () => {
|
|
const m = matchVersions(target, [v("bd", BD_FILE)], { isEpisode: true });
|
|
expect(m.state).toBe("upgradeable");
|
|
expect(m.assetId).toBe("bd");
|
|
expect(m.reasons.join(" ")).toMatch(/1080p/);
|
|
expect(m.codes).toEqual(["version_resolution:2160p"]);
|
|
});
|
|
|
|
it("差距原因码:HDR / 字幕 / 来源带目标参数", () => {
|
|
const hdr: SlotSpec = { id: "h", name: "h", hard: { hdr: { anyOf: ["DV", "HDR10"] } } };
|
|
expect(matchVersions(hdr, [v("bd", BD_FILE)], { isEpisode: true }).codes).toEqual(["version_hdr"]);
|
|
const dvOnly: SlotSpec = { id: "d", name: "d", hard: { hdr: { anyOf: ["DV"] } } };
|
|
expect(matchVersions(dvOnly, [v("bd", BD_FILE)], { isEpisode: true }).codes).toEqual(["version_hdr:DV"]);
|
|
const remux: SlotSpec = { id: "r", name: "r", hard: { sources: ["REMUX", "BluRay-Disc"] } };
|
|
expect(matchVersions(remux, [v("dv", DV_FILE, PROBE_DV)], { isEpisode: true }).codes).toEqual([
|
|
"version_source:REMUX",
|
|
]);
|
|
});
|
|
|
|
it("达标但升级策略还有余量 = upgradeable;到顶 = satisfied", () => {
|
|
const up: SlotSpec = { id: "s", name: "x", hard: {}, upgrade: { enabled: true } };
|
|
const web = matchVersions(up, [v("dv", DV_FILE, PROBE_DV)], { isEpisode: true });
|
|
expect(web).toMatchObject({
|
|
state: "upgradeable",
|
|
assetId: "dv",
|
|
reasons: ["来源还能升级"],
|
|
codes: ["version_source_upgrade"],
|
|
});
|
|
const capped: SlotSpec = { ...up, upgrade: { enabled: true, ceilingTier: 2 } };
|
|
expect(matchVersions(capped, [v("dv", DV_FILE, PROBE_DV)], { isEpisode: true }).state).toBe("satisfied");
|
|
const onlyWeb: SlotSpec = { ...up, hard: { sources: ["WEB-DL"] } };
|
|
expect(matchVersions(onlyWeb, [v("dv", DV_FILE, PROBE_DV)], { isEpisode: true }).state).toBe("satisfied");
|
|
});
|
|
|
|
it("只凭文件名时字幕未知不判不达标;探测过且缺中字则不达标", () => {
|
|
const needZh: SlotSpec = { id: "z", name: "中字", hard: { requiredSubtitles: ["zh"] } };
|
|
expect(matchVersions(needZh, [v("bd", BD_FILE)], { isEpisode: true }).state).toBe("satisfied");
|
|
const noZh: ProbeLike = { ...PROBE_DV, subtitles: [{ language: "eng" }] };
|
|
expect(matchVersions(needZh, [v("dv", DV_FILE, noZh)], { isEpisode: true }).state).toBe("upgradeable");
|
|
});
|
|
|
|
it("电影保留整片体积条件", () => {
|
|
const small: SlotSpec = { id: "m", name: "m", hard: { maxSizeBytes: 5 * GB } };
|
|
const movie = "Oppenheimer.2023.2160p.UHD.BluRay.REMUX.HDR10.HEVC.TrueHD.7.1.Atmos-FraMeSToR.mkv";
|
|
expect(matchVersions(small, [v("r", movie, undefined, 80 * GB)], { isEpisode: false }).state).toBe("upgradeable");
|
|
expect(matchVersions(small, [v("r", movie, undefined, 80 * GB)], { isEpisode: true }).state).toBe("satisfied");
|
|
});
|
|
});
|