Merge branch 'search4'
Co-Authored-By: Claude Fable 5.1 <[email protected]> # Conflicts: # docs/plan/00-first-version-plan.md
This commit is contained in:
commit
511ad93077
20 files changed
+773
-94
No files matched your search
@@ -12,7 +12,10 @@ export type FacetCount = z.infer<typeof FacetCount>;
|
||||
* 快速筛选参数 qf:`维度:值,值;维度:值`,值经 encodeURIComponent。
|
||||
* 维度:resolution(2160p/1080p/720p/other)、videoCodec(H.265/H.264/AV1/other)、
|
||||
* releaseType(disc/remux/encode/webdl/hdtv/other)、site(站点 ID)、promotion(free/2x/50%/other/none)、
|
||||
* hdr(DV/HDR10/HDR10+/other/SDR)、hr(yes/no/unknown)、releaseGroup(制作组名,不分大小写)、season(季号)、episode(集号 / pack 整季)。
|
||||
* hdr(DV/HDR10/HDR10+/other/SDR)、hr(yes/no/unknown)、releaseGroup(制作组名,不分大小写)、season(季号)、episode(集号 / pack 整季);
|
||||
* SEARCH-4 起:audioCodec(Atmos/TrueHD/DTS:X/DTS-HD/DTS/DDP/DD/AAC/FLAC/LPCM/other/unknown)、
|
||||
* subtitle(zh/zh-Hans/zh-Hant/en/ja/ko/unknown)、dub(cmn/yue/en/unknown)、feature(10bit/hfr/cut)、
|
||||
* year(年份)、category(站点分类名)、size(lt1/1-5/5-10/10-20/20-50/50+/unknown,GiB)、seeders(0/1-9/10-49/50+/unknown)。
|
||||
*/
|
||||
export const QuickFilterParam = z.string().max(4000).optional();
|
||||
|
||||
@@ -31,5 +34,19 @@ export const ReleaseQuickFacets = z.object({
|
||||
/** 没有剧集资源时为空 */
|
||||
season: z.array(FacetCount),
|
||||
episode: z.array(FacetCount),
|
||||
/** 音频编码(一条可属于多个;没写的归「未标注」) */
|
||||
audioCodec: z.array(FacetCount),
|
||||
/** 字幕:任何中文字幕都算 zh,简 / 繁另列 */
|
||||
subtitle: z.array(FacetCount),
|
||||
/** 配音:国语 / 粤语(MP 的 CNVOI / HKVOI) */
|
||||
dub: z.array(FacetCount),
|
||||
/** 特性:10bit / 50 帧以上 / 特别剪辑版;没有的不计入任何档 */
|
||||
feature: z.array(FacetCount),
|
||||
/** 年份,新的在前 */
|
||||
year: z.array(FacetCount),
|
||||
/** 站点分类名,计数最多的前 20 个 + 已选的 */
|
||||
category: z.array(FacetCount),
|
||||
size: z.array(FacetCount),
|
||||
seeders: z.array(FacetCount),
|
||||
});
|
||||
export type ReleaseQuickFacets = z.infer<typeof ReleaseQuickFacets>;
|
||||
@@ -79,6 +79,16 @@ export const SearchReleaseParsed = z.object({
|
||||
videoCodec: z.string().nullable(),
|
||||
hdr: z.array(z.string()),
|
||||
releaseGroup: z.string().nullable(),
|
||||
/** 音轨(`TrueHD`、`Atmos 7.1`…,SEARCH-4 快速筛选用);空 = 标题没写 */
|
||||
audio: z.array(z.string()).optional(),
|
||||
/** 字幕语言 zh / zh-Hans / zh-Hant / en / ja / ko;null = 标题没写 */
|
||||
subtitles: z.array(z.string()).nullable().optional(),
|
||||
/** 配音语言 cmn 国语 / yue 粤语 / en 英语;null = 标题没写 */
|
||||
audioLangs: z.array(z.string()).nullable().optional(),
|
||||
bitDepth: z.number().int().nullable().optional(),
|
||||
fps: z.number().nullable().optional(),
|
||||
/** 剪辑版(加长版、导演剪辑版…) */
|
||||
cut: z.string().nullable().optional(),
|
||||
});
|
||||
export type SearchReleaseParsed = z.infer<typeof SearchReleaseParsed>;
|
||||
|
||||
|
||||
@@ -16,6 +16,15 @@ export const FACET_DIMS = [
|
||||
"releaseGroup",
|
||||
"season",
|
||||
"episode",
|
||||
// SEARCH-4:对齐 MP 过滤规则里的音频 / 中字 / 国语粤语配音 / 60 帧,再加年份、分类、大小、做种
|
||||
"audioCodec",
|
||||
"subtitle",
|
||||
"dub",
|
||||
"feature",
|
||||
"year",
|
||||
"category",
|
||||
"size",
|
||||
"seeders",
|
||||
] as const;
|
||||
export type FacetDim = (typeof FACET_DIMS)[number];
|
||||
|
||||
@@ -49,6 +58,21 @@ export interface FacetInput {
|
||||
};
|
||||
/** H&R 三态;缺省 / null 按未知 */
|
||||
hr?: boolean | "unknown" | null;
|
||||
/** 解析出的音轨(`TrueHD`、`Atmos 7.1`、`DDP 5.1`…);空 = 标题没写 */
|
||||
audio?: readonly string[];
|
||||
/** 字幕语言(zh / zh-Hans / zh-Hant / en / ja / ko);缺省 / 空 = 标题没写 */
|
||||
subtitles?: readonly string[] | null;
|
||||
/** 配音语言(cmn / yue / en);缺省 / 空 = 标题没写 */
|
||||
audioLangs?: readonly string[] | null;
|
||||
bitDepth?: number | null;
|
||||
fps?: number | null;
|
||||
/** 剪辑版(加长版、导演剪辑版、IMAX…) */
|
||||
cut?: string | null;
|
||||
year?: number | null;
|
||||
/** 站点分类名 */
|
||||
category?: string | null;
|
||||
sizeBytes?: number | null;
|
||||
seeders?: number | null;
|
||||
}
|
||||
|
||||
/** 固定档位(顺序即展示顺序);"other" 类档位只在有资源时列出 */
|
||||
@@ -92,10 +116,62 @@ const FIXED: Partial<Record<FacetDim, readonly { value: string; label: string; h
|
||||
{ value: "no", label: "无" },
|
||||
{ value: "unknown", label: "未知", hideEmpty: true },
|
||||
],
|
||||
audioCodec: [
|
||||
{ value: "Atmos", label: "Atmos" },
|
||||
{ value: "TrueHD", label: "TrueHD" },
|
||||
{ value: "DTS:X", label: "DTS:X", hideEmpty: true },
|
||||
{ value: "DTS-HD", label: "DTS-HD MA" },
|
||||
{ value: "DTS", label: "DTS" },
|
||||
{ value: "DDP", label: "DD+" },
|
||||
{ value: "DD", label: "DD" },
|
||||
{ value: "AAC", label: "AAC" },
|
||||
{ value: "FLAC", label: "FLAC", hideEmpty: true },
|
||||
{ value: "LPCM", label: "LPCM", hideEmpty: true },
|
||||
{ value: "other", label: "其他", hideEmpty: true },
|
||||
{ value: "unknown", label: "未标注", hideEmpty: true },
|
||||
],
|
||||
subtitle: [
|
||||
{ value: "zh", label: "中文字幕" },
|
||||
{ value: "zh-Hans", label: "简体" },
|
||||
{ value: "zh-Hant", label: "繁体" },
|
||||
{ value: "en", label: "英文" },
|
||||
{ value: "ja", label: "日文", hideEmpty: true },
|
||||
{ value: "ko", label: "韩文", hideEmpty: true },
|
||||
{ value: "unknown", label: "未标注", hideEmpty: true },
|
||||
],
|
||||
dub: [
|
||||
{ value: "cmn", label: "国语" },
|
||||
{ value: "yue", label: "粤语" },
|
||||
{ value: "en", label: "英语", hideEmpty: true },
|
||||
{ value: "unknown", label: "未标注", hideEmpty: true },
|
||||
],
|
||||
feature: [
|
||||
{ value: "10bit", label: "10bit", hideEmpty: true },
|
||||
{ value: "hfr", label: "50 帧以上", hideEmpty: true },
|
||||
{ value: "cut", label: "特别剪辑版", hideEmpty: true },
|
||||
],
|
||||
size: [
|
||||
{ value: "lt1", label: "1 GB 以下" },
|
||||
{ value: "1-5", label: "1–5 GB" },
|
||||
{ value: "5-10", label: "5–10 GB" },
|
||||
{ value: "10-20", label: "10–20 GB" },
|
||||
{ value: "20-50", label: "20–50 GB" },
|
||||
{ value: "50+", label: "50 GB 以上" },
|
||||
{ value: "unknown", label: "未知", hideEmpty: true },
|
||||
],
|
||||
seeders: [
|
||||
{ value: "0", label: "无人做种" },
|
||||
{ value: "1-9", label: "1–9" },
|
||||
{ value: "10-49", label: "10–49" },
|
||||
{ value: "50+", label: "50 以上" },
|
||||
{ value: "unknown", label: "未知", hideEmpty: true },
|
||||
],
|
||||
};
|
||||
|
||||
/** 制作组只列计数最多的前 N 个(已选的总会列出) */
|
||||
export const RELEASE_GROUP_TOP = 20;
|
||||
/** 分类只列计数最多的前 N 个(已选的总会列出) */
|
||||
export const CATEGORY_TOP = 20;
|
||||
/** 一条资源最多展开多少集(防止异常集号范围撑爆计数) */
|
||||
const MAX_EPISODES_PER_RELEASE = 200;
|
||||
const MAX_SEASONS_PER_RELEASE = 50;
|
||||
@@ -155,6 +231,71 @@ export function hrBucket(hr: FacetInput["hr"]): string {
|
||||
return hr === true ? "yes" : hr === false ? "no" : "unknown";
|
||||
}
|
||||
|
||||
const AUDIO_BUCKET: Record<string, string> = {
|
||||
Atmos: "Atmos",
|
||||
TrueHD: "TrueHD",
|
||||
"DTS:X": "DTS:X",
|
||||
"DTS-HD MA": "DTS-HD",
|
||||
"DTS-HD HR": "DTS-HD",
|
||||
"DTS-HD": "DTS-HD",
|
||||
DTS: "DTS",
|
||||
DDP: "DDP",
|
||||
"E-AC3": "DDP",
|
||||
DD: "DD",
|
||||
AC3: "DD",
|
||||
AAC: "AAC",
|
||||
FLAC: "FLAC",
|
||||
LPCM: "LPCM",
|
||||
PCM: "LPCM",
|
||||
};
|
||||
|
||||
/** 音频编码档位(一条可有多条音轨);声道(`5.1`)、音轨数(`2Audios`)不算编码 */
|
||||
export function audioBuckets(audio: readonly string[] | undefined): string[] {
|
||||
const out = new Set<string>();
|
||||
for (const a of audio ?? []) {
|
||||
const codec = a.replace(/\s*\d\.\d$/, "").trim();
|
||||
if (!codec || /^\d+Audios?$/i.test(codec)) continue;
|
||||
out.add(AUDIO_BUCKET[codec] ?? "other");
|
||||
}
|
||||
return out.size ? [...out] : ["unknown"];
|
||||
}
|
||||
|
||||
/** 字幕档位:任何中文字幕都算「中文字幕」,简 / 繁另列 */
|
||||
export function subtitleBuckets(subs: readonly string[] | null | undefined): string[] {
|
||||
if (!subs?.length) return ["unknown"];
|
||||
const out = new Set<string>();
|
||||
for (const s of subs) {
|
||||
if (s.startsWith("zh")) out.add("zh");
|
||||
if (s === "zh-Hans" || s === "zh-Hant" || s === "en" || s === "ja" || s === "ko") out.add(s);
|
||||
}
|
||||
return out.size ? [...out] : ["unknown"];
|
||||
}
|
||||
|
||||
export function dubBuckets(langs: readonly string[] | null | undefined): string[] {
|
||||
const out = (langs ?? []).filter((l) => l === "cmn" || l === "yue" || l === "en");
|
||||
return out.length ? [...new Set(out)] : ["unknown"];
|
||||
}
|
||||
|
||||
export function featureBuckets(r: Pick<FacetInput, "bitDepth" | "fps" | "cut">): string[] {
|
||||
const out: string[] = [];
|
||||
if (r.bitDepth && r.bitDepth >= 10) out.push("10bit");
|
||||
if (r.fps && r.fps >= 50) out.push("hfr");
|
||||
if (r.cut) out.push("cut");
|
||||
return out;
|
||||
}
|
||||
|
||||
const GIB = 1024 ** 3;
|
||||
export function sizeBucket(bytes: number | null | undefined): string {
|
||||
if (bytes === null || bytes === undefined || !Number.isFinite(bytes) || bytes < 0) return "unknown";
|
||||
const g = bytes / GIB;
|
||||
return g < 1 ? "lt1" : g < 5 ? "1-5" : g < 10 ? "5-10" : g < 20 ? "10-20" : g < 50 ? "20-50" : "50+";
|
||||
}
|
||||
|
||||
export function seedersBucket(n: number | null | undefined): string {
|
||||
if (n === null || n === undefined || !Number.isFinite(n) || n < 0) return "unknown";
|
||||
return n === 0 ? "0" : n < 10 ? "1-9" : n < 50 ? "10-49" : "50+";
|
||||
}
|
||||
|
||||
function seasonValues(r: FacetInput): string[] {
|
||||
if (r.season === null || r.season === undefined) return [];
|
||||
const end =
|
||||
@@ -188,6 +329,14 @@ export function facetValues(r: FacetInput, now: number): Record<FacetDim, string
|
||||
releaseGroup: r.releaseGroup ? [r.releaseGroup] : [],
|
||||
season: seasonValues(r),
|
||||
episode: episodeValues(r),
|
||||
audioCodec: audioBuckets(r.audio),
|
||||
subtitle: subtitleBuckets(r.subtitles),
|
||||
dub: dubBuckets(r.audioLangs),
|
||||
feature: featureBuckets(r),
|
||||
year: r.year ? [String(r.year)] : [],
|
||||
category: r.category?.trim() ? [r.category.trim()] : [],
|
||||
size: [sizeBucket(r.sizeBytes)],
|
||||
seeders: [seedersBucket(r.seeders)],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -290,9 +439,11 @@ function sortDynamic(
|
||||
const n = (v: string) => (v === "pack" ? -1 : Number(v));
|
||||
return options.sort((a, b) => n(a.value) - n(b.value)).map(strip);
|
||||
}
|
||||
// 年份新的在前
|
||||
if (dim === "year") return options.sort((a, b) => Number(b.value) - Number(a.value)).map(strip);
|
||||
const sorted = options.sort((a, b) => b.total - a.total || a.label.localeCompare(b.label));
|
||||
if (dim !== "releaseGroup") return sorted.map(strip);
|
||||
const head = sorted.slice(0, top);
|
||||
if (dim !== "releaseGroup" && dim !== "category") return sorted.map(strip);
|
||||
const head = sorted.slice(0, dim === "category" ? CATEGORY_TOP : top);
|
||||
const extra = sorted.slice(top).filter((o) => chosen.has(norm(dim, o.value)));
|
||||
return [...head, ...extra].map(strip);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export { cnToInt } from "./cn-number.ts";
|
||||
export { evalEpisodeOffset, normalizeOffsetExpression, OffsetExpressionError } from "./offset.ts";
|
||||
export { type CnMarkers, confidenceOf, fileKindOf, parseRelease, scanCnMarkers } from "./parse.ts";
|
||||
export { type CnMarkers, confidenceOf, fileKindOf, parseRelease, scanAudioLangs, scanCnMarkers } from "./parse.ts";
|
||||
export { mergeParsed, type PathSegments, parseReleasePath, splitReleasePath } from "./path.ts";
|
||||
export { buildGroupRegex, RELEASE_GROUP_SEED } from "./release-groups.ts";
|
||||
export * from "./types.ts";
|
||||
|
||||
@@ -188,6 +188,7 @@ export interface CnMarkers {
|
||||
contentType?: ContentType;
|
||||
variantTags: string[];
|
||||
subtitles?: string[];
|
||||
audioLangs?: string[];
|
||||
evidence: string[];
|
||||
}
|
||||
|
||||
@@ -204,6 +205,18 @@ const SUB_KEYWORDS =
|
||||
/字幕|中字|双字|雙字|内封|內封|内嵌|內嵌|外挂|外掛|简繁|簡繁|简中|繁中|简体|简日|繁日|繁体|繁體|簡體|简英|繁英|中英|双语|雙語/u;
|
||||
const AUDIO_LANG_RE = /国粤双语|國粵雙語|国英双语|國英雙語|国语配音|國語配音|国配|國配|国语|國語|粤语|粵語/gu;
|
||||
|
||||
/** 配音语言标记(国语 / 粤语 / 国英双语…)→ cmn / yue / en */
|
||||
export function scanAudioLangs(text: string): string[] {
|
||||
const out: string[] = [];
|
||||
for (const m of text.matchAll(AUDIO_LANG_RE)) {
|
||||
const t = m[0];
|
||||
if (/[国國]/u.test(t) && !out.includes("cmn")) out.push("cmn");
|
||||
if (/[粤粵]/u.test(t) && !out.includes("yue")) out.push("yue");
|
||||
if (/英/u.test(t) && !out.includes("en")) out.push("en");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function scanSubtitleLangs(run: string): string[] {
|
||||
const langs: string[] = [];
|
||||
const add = (l: string) => {
|
||||
@@ -261,6 +274,11 @@ export function scanCnMarkers(input: string): { s: string } & CnMarkers {
|
||||
out.totalEpisodes = cnToInt((m[1] ?? m[2])!);
|
||||
out.evidence.push("total:cn");
|
||||
});
|
||||
const dubs = scanAudioLangs(s);
|
||||
if (dubs.length) {
|
||||
out.audioLangs = dubs;
|
||||
out.evidence.push("audio_lang:cn");
|
||||
}
|
||||
s = s.replace(AUDIO_LANG_RE, " ");
|
||||
for (const [re, ct, ev] of CONTENT_RULES) {
|
||||
take(re, () => {
|
||||
@@ -1097,6 +1115,10 @@ function applyCnMarkers(d: Draft, cm: CnMarkers, prefix: string): void {
|
||||
d.subtitles = [...new Set([...(d.subtitles ?? []), ...cm.subtitles])];
|
||||
note("subtitle");
|
||||
}
|
||||
if (cm.audioLangs) {
|
||||
d.audioLangs = [...new Set([...(d.audioLangs ?? []), ...cm.audioLangs])];
|
||||
note("audio_lang");
|
||||
}
|
||||
}
|
||||
|
||||
function finalize(d: Draft, opts: ParseOptions): ParsedRelease {
|
||||
|
||||
@@ -109,6 +109,8 @@ export interface ParsedRelease {
|
||||
part?: string;
|
||||
/** 字幕语言(zh / zh-Hans / zh-Hant / en / ja / ko);undefined = 标题无信息(未知),不是"无字幕" */
|
||||
subtitles?: string[];
|
||||
/** 配音语言(cmn 国语 / yue 粤语 / en 英语,来自「国语」「国粤双语」等标记);undefined = 标题无信息 */
|
||||
audioLangs?: string[];
|
||||
sizeBytes?: number;
|
||||
contentType: ContentType;
|
||||
segment?: Segment;
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
applyFacets,
|
||||
audioBuckets,
|
||||
type FacetInput,
|
||||
formatFacetParam,
|
||||
parseFacetParam,
|
||||
parseRelease,
|
||||
promotionBuckets,
|
||||
releaseTypeBucket,
|
||||
seedersBucket,
|
||||
sizeBucket,
|
||||
subtitleBuckets,
|
||||
} from "../src/index.ts";
|
||||
|
||||
// SEARCH-3 快速筛选:档位、计数(按其它维度已选条件)、同维度 OR / 跨维度 AND、qf 编解码
|
||||
@@ -186,4 +191,115 @@ describe("release-facets", () => {
|
||||
expect(parseFacetParam(s)).toEqual(sel);
|
||||
expect(parseFacetParam("bogus:1;hdr:;hdr:DV,DV")).toEqual({ hdr: ["DV"] });
|
||||
});
|
||||
|
||||
// ── SEARCH-4:对齐 MP 后新增的维度 ──
|
||||
|
||||
const GB = 1024 ** 3;
|
||||
const rich = [
|
||||
mk("a", {
|
||||
audio: ["TrueHD", "Atmos 7.1"],
|
||||
subtitles: ["zh-Hans", "en"],
|
||||
audioLangs: ["cmn"],
|
||||
bitDepth: 10,
|
||||
year: 2024,
|
||||
category: "电影",
|
||||
sizeBytes: 60 * GB,
|
||||
seeders: 80,
|
||||
}),
|
||||
mk("b", {
|
||||
audio: ["DDP 5.1"],
|
||||
subtitles: ["zh-Hant"],
|
||||
audioLangs: ["cmn", "yue"],
|
||||
fps: 60,
|
||||
year: 2023,
|
||||
category: "电影",
|
||||
sizeBytes: 8 * GB,
|
||||
seeders: 0,
|
||||
}),
|
||||
mk("c", { audio: ["DTS-HD MA 5.1", "2Audios"], year: 2024, category: "剧集", sizeBytes: 3 * GB, seeders: 12 }),
|
||||
mk("d", { audio: [], cut: "Extended", sizeBytes: null, seeders: null }),
|
||||
];
|
||||
const run2 = (sel: Parameters<typeof applyFacets>[2]) => applyFacets(rich, (x) => x, sel, { now: NOW });
|
||||
|
||||
it("新维度档位与计数:音频 / 字幕 / 配音 / 特性 / 年份 / 分类 / 大小 / 做种", () => {
|
||||
const r = run2({});
|
||||
expect(counts(r.facets.audioCodec)).toEqual({
|
||||
Atmos: 1,
|
||||
TrueHD: 1,
|
||||
"DTS-HD": 1,
|
||||
DTS: 0,
|
||||
DDP: 1,
|
||||
DD: 0,
|
||||
AAC: 0,
|
||||
unknown: 1,
|
||||
});
|
||||
expect(r.facets.audioCodec.find((o) => o.value === "DDP")?.label).toBe("DD+");
|
||||
expect(counts(r.facets.subtitle)).toEqual({ zh: 2, "zh-Hans": 1, "zh-Hant": 1, en: 1, unknown: 2 });
|
||||
expect(counts(r.facets.dub)).toEqual({ cmn: 2, yue: 1, unknown: 2 });
|
||||
expect(counts(r.facets.feature)).toEqual({ "10bit": 1, hfr: 1, cut: 1 });
|
||||
// 年份新的在前;分类按条数
|
||||
expect(r.facets.year.map((o) => [o.value, o.count])).toEqual([
|
||||
["2024", 2],
|
||||
["2023", 1],
|
||||
]);
|
||||
expect(r.facets.category.map((o) => [o.label, o.count])).toEqual([
|
||||
["电影", 2],
|
||||
["剧集", 1],
|
||||
]);
|
||||
expect(counts(r.facets.size)).toEqual({
|
||||
lt1: 0,
|
||||
"1-5": 1,
|
||||
"5-10": 1,
|
||||
"10-20": 0,
|
||||
"20-50": 0,
|
||||
"50+": 1,
|
||||
unknown: 1,
|
||||
});
|
||||
expect(counts(r.facets.seeders)).toEqual({ "0": 1, "1-9": 0, "10-49": 1, "50+": 1, unknown: 1 });
|
||||
});
|
||||
|
||||
it("新维度组合:同维度 OR、跨维度 AND,计数按其它维度已选条件", () => {
|
||||
// 中文字幕 AND 国语 → a、b;再加 Atmos → 只剩 a
|
||||
expect(ids(run2({ subtitle: ["zh"], dub: ["cmn"] }))).toEqual(["a", "b"]);
|
||||
const r = run2({ subtitle: ["zh"], dub: ["cmn"], audioCodec: ["Atmos"] });
|
||||
expect(ids(r)).toEqual(["a"]);
|
||||
// 音频维度自己的计数不受已选 Atmos 影响:DD+ 仍可多出 b
|
||||
expect(counts(r.facets.audioCodec).DDP).toBe(1);
|
||||
// 同维度 OR:Atmos 或 DTS-HD
|
||||
expect(ids(run2({ audioCodec: ["Atmos", "DTS-HD"] }))).toEqual(["a", "c"]);
|
||||
// 年份 × 分类 × 做种
|
||||
expect(ids(run2({ year: ["2024"], category: ["电影", "剧集"], seeders: ["10-49", "50+"] }))).toEqual(["a", "c"]);
|
||||
expect(ids(run2({ size: ["unknown"], feature: ["cut"] }))).toEqual(["d"]);
|
||||
// 已选但当前没有资源的年份列出、计数 0
|
||||
expect(run2({ year: ["1999"] }).facets.year.find((o) => o.value === "1999")).toEqual({
|
||||
value: "1999",
|
||||
label: "1999",
|
||||
count: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it("新维度的归档函数", () => {
|
||||
expect(audioBuckets(["E-AC3", "AC3 2.0", "PCM", "Opus", "5.1"])).toEqual(["DDP", "DD", "LPCM", "other"]);
|
||||
expect(audioBuckets(["2Audios"])).toEqual(["unknown"]);
|
||||
expect(subtitleBuckets(["zh"])).toEqual(["zh"]);
|
||||
expect(subtitleBuckets(null)).toEqual(["unknown"]);
|
||||
expect([0.5 * GB, GB, 49.9 * GB, 50 * GB, null, -1].map(sizeBucket)).toEqual([
|
||||
"lt1",
|
||||
"1-5",
|
||||
"20-50",
|
||||
"50+",
|
||||
"unknown",
|
||||
"unknown",
|
||||
]);
|
||||
expect([0, 9, 10, 50, null].map(seedersBucket)).toEqual(["0", "1-9", "10-49", "50+", "unknown"]);
|
||||
});
|
||||
|
||||
it("解析器补齐配音语言(MP 的 CNVOI / HKVOI)", () => {
|
||||
expect(parseRelease("Movie 2020 1080p 国语中字 WEB-DL").audioLangs).toEqual(["cmn"]);
|
||||
expect(parseRelease("某剧 第01集 国粤双语 1080p").audioLangs).toEqual(["cmn", "yue"]);
|
||||
expect(parseRelease("Movie.2020.1080p.BluRay.x264-GRP").audioLangs).toBeUndefined();
|
||||
expect(
|
||||
parseRelease("Movie.2020.2160p.BluRay.x265-GRP", { subtitle: "电影 | 国英双语 简繁字幕" }).audioLangs,
|
||||
).toEqual(["cmn", "en"]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user