Strip every comment from the source (parsed with the TypeScript compiler so strings, template literals, regex literals and JSX are never touched), and drop internal/working artifacts that do not belong in the public repository (design mockups, internal analysis docs, a stray backup file and an old log). No functional change: build is green, the full test suite passes.
1090 lines
43 KiB
TypeScript
1090 lines
43 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import {
|
|
extractEpisodeToken,
|
|
applyEpisodeTokenToFolderName,
|
|
sourceHasRpToken,
|
|
ensureRepackToken,
|
|
buildAutoRenameBaseName,
|
|
buildAutoRenameBaseNameFromFolders,
|
|
buildAutoRenameBaseNameFromFoldersWithOptions,
|
|
hasMeaningfulSeriesPrefix,
|
|
looksLikeObfuscatedSceneFileName,
|
|
decideAutoRenameBaseName,
|
|
isBonusContent
|
|
} from "../src/main/download-manager";
|
|
|
|
describe("decideAutoRenameBaseName (shared naming decision — used by auto-rename AND mkv-collect)", () => {
|
|
|
|
it("derives the clean name for a Herzflimmern episode from the per-episode folder (S07E12 — the reported failure)", () => {
|
|
const source = "tvarchiv.herzflimmern.die.klinik.am.see.s07e12-720.mkv";
|
|
const folders = [
|
|
"Herzflimmern.Die.Klinik.am.See.S07E12.German.720p.Webrip.x264-TVARCHiV",
|
|
"Herzflimmern.Die.Klinik.am.See.S07.German.720p.Webrip.x264-TVARCHiV"
|
|
];
|
|
const decision = decideAutoRenameBaseName(
|
|
folders,
|
|
source,
|
|
"tvarchiv.herzflimmern.die.klinik.am.see.s07e12-720",
|
|
folders[0],
|
|
folders[1]
|
|
);
|
|
expect(decision.kind).toBe("rename");
|
|
expect(decision.kind === "rename" && decision.baseName).toBe("Herzflimmern.Die.Klinik.am.See.S07E12.German.720p.Webrip.x264-TVARCHiV");
|
|
});
|
|
|
|
it("derives the clean name from a SEASON-only folder by injecting the source episode token (Herzflimmern S03E14)", () => {
|
|
const source = "tvarchiv.herzflimmern.die.klinik.am.see.s03e14-720.mkv";
|
|
const seasonFolder = "Herzflimmern.die.Klinik.am.See.S03.German.720p.Webrip.x264-TVARCHiV";
|
|
const decision = decideAutoRenameBaseName(
|
|
[seasonFolder],
|
|
source,
|
|
"tvarchiv.herzflimmern.die.klinik.am.see.s03e14-720",
|
|
seasonFolder,
|
|
seasonFolder
|
|
);
|
|
expect(decision.kind).toBe("rename");
|
|
expect(decision.kind === "rename" && decision.baseName).toBe("Herzflimmern.die.Klinik.am.See.S03E14.German.720p.Webrip.x264-TVARCHiV");
|
|
});
|
|
|
|
it("derives the clean name for the Fritzie S04 files that sat raw in Downloader Unfertig (4sf- scene group, season folder)", () => {
|
|
const source = "4sf-fritzie.himmel.muss.warten.web.7p-s04e01.mkv";
|
|
const seasonFolder = "Fritzie.-.Der.Himmel.muss.warten.S04.GERMAN.720p.WEB.AVC-4SF";
|
|
const decision = decideAutoRenameBaseName(
|
|
[seasonFolder],
|
|
source,
|
|
"4sf-fritzie.himmel.muss.warten.web.7p-s04e01",
|
|
seasonFolder,
|
|
seasonFolder
|
|
);
|
|
expect(decision.kind).toBe("rename");
|
|
expect(decision.kind === "rename" && decision.baseName).toBe("Fritzie.-.Der.Himmel.muss.warten.S04E01.GERMAN.720p.WEB.AVC-4SF");
|
|
});
|
|
|
|
it("is idempotent: an already-clean file in its clean folder derives to the same name (no worse-than-now)", () => {
|
|
const clean = "Herzflimmern.Die.Klinik.am.See.S07E02.German.720p.Webrip.x264-TVARCHiV";
|
|
const decision = decideAutoRenameBaseName(
|
|
[clean, "Herzflimmern.Die.Klinik.am.See.S07.German.720p.Webrip.x264-TVARCHiV"],
|
|
`${clean}.mkv`,
|
|
clean,
|
|
clean,
|
|
"Herzflimmern.Die.Klinik.am.See.S07.German.720p.Webrip.x264-TVARCHiV"
|
|
);
|
|
expect(decision.kind).toBe("rename");
|
|
expect(decision.kind === "rename" && decision.baseName).toBe(clean);
|
|
});
|
|
|
|
it("GUARD: lets the parent folder token override an OBFUSCATED source filename (anti-piracy scramble)", () => {
|
|
const decision = decideAutoRenameBaseName(
|
|
["Die.Thundermans.S02E01.Der.Thunder.Van.GERMAN.x264-aWake"],
|
|
"awa-diethundermans02e16hd.mkv",
|
|
"awa-diethundermans02e16hd",
|
|
"Die.Thundermans.S02E01.Der.Thunder.Van.GERMAN.x264-aWake",
|
|
"Die.Thundermans.S02.GERMAN.x264-aWake"
|
|
);
|
|
expect(decision.kind).toBe("rename");
|
|
expect(decision.kind === "rename" && decision.baseName).toContain("S02E01");
|
|
});
|
|
|
|
it("GUARD: a CLEAN scene source is NEVER overridden by a mismatching folder token (folder is wrong, not the file)", () => {
|
|
const decision = decideAutoRenameBaseName(
|
|
["The.Royals.2015.S01E08.German.DL.720p.BluRay.x264-iNTENTiON"],
|
|
"the.royals.2015.s01e09.german.dl.720p.bluray.x264-j4f.mkv",
|
|
"the.royals.2015.s01e09.german.dl.720p.bluray.x264-j4f",
|
|
"The.Royals.2015.S01E08.German.DL.720p.BluRay.x264-iNTENTiON",
|
|
"The.Royals.2015.S01.German.DL.720p.BluRay.x264-iNTENTiON"
|
|
);
|
|
expect(decision.kind).toBe("skip");
|
|
expect(decision.kind === "skip" && decision.reason).toBe("token-mismatch");
|
|
});
|
|
|
|
it("skips (no-target) when no folder candidate yields a usable scene name", () => {
|
|
const decision = decideAutoRenameBaseName(
|
|
["random user folder", "another plain dir"],
|
|
"some.file.mkv",
|
|
"some.file",
|
|
"random user folder",
|
|
"another plain dir"
|
|
);
|
|
expect(decision.kind).toBe("skip");
|
|
});
|
|
|
|
it("uses the CLEAN per-episode folder (scene group WITH underscore, e.g. -idTV_iNT) — not the obfuscated package folder", () => {
|
|
const epFolder = "Castle.S08E02.GERMAN.DL.720p.WEB.H264-idTV_iNT";
|
|
const decision = decideAutoRenameBaseName(
|
|
[epFolder, "scn2-cstl7"],
|
|
"castle.s08e02.german.dl.720p.web.h264-idtv_int.mkv",
|
|
"castle.s08e02.german.dl.720p.web.h264-idtv_int",
|
|
epFolder,
|
|
"scn2-cstl7"
|
|
);
|
|
expect(decision.kind).toBe("rename");
|
|
expect(decision.kind === "rename" && decision.baseName).toBe(epFolder);
|
|
});
|
|
|
|
it("uses the complete per-episode folder when the SOURCE has no SxxExx token (bare 'Folge 01' format)", () => {
|
|
const folders = [
|
|
"Kreuzfahrt.ins.Glueck.01.Hochzeitsreise.nach.Burma.2007.German.720p.HDTV.x264-BET",
|
|
"kig.hdtv.7p-001",
|
|
"Kreuzfahrt ins Glück S01"
|
|
];
|
|
const decision = decideAutoRenameBaseName(folders, "bet_kig_01_hdt.mkv", "bet_kig_01_hdt", folders[0], folders[2]);
|
|
expect(decision.kind).toBe("rename");
|
|
expect(decision.kind === "rename" && decision.baseName).toBe(folders[0]);
|
|
});
|
|
|
|
it("complete-folder fallback fires on CODEC alone (no resolution token — DVDRip/XviD class)", () => {
|
|
const folders = [
|
|
"Kreuzfahrt.ins.Glueck.01.Hochzeitsreise.nach.Burma.2007.German.DVDRip.x264-BET",
|
|
"Kreuzfahrt ins Glück S01"
|
|
];
|
|
const decision = decideAutoRenameBaseName(folders, "bet_kig_01.mkv", "bet_kig_01", folders[0], folders[1]);
|
|
expect(decision.kind).toBe("rename");
|
|
expect(decision.kind === "rename" && decision.baseName).toBe(folders[0]);
|
|
});
|
|
|
|
it("complete-folder fallback does NOT fire when the source HAS an episode token (generic pack stays no-target)", () => {
|
|
const decision = decideAutoRenameBaseName(
|
|
["Mega-Direct-Pack"],
|
|
"Direct.Show.S01E01.German.1080p.WEB.x264-DIRECT.mkv",
|
|
"Direct.Show.S01E01.German.1080p.WEB.x264-DIRECT",
|
|
"Mega-Direct-Pack",
|
|
"Mega-Direct-Pack"
|
|
);
|
|
expect(decision.kind).toBe("skip");
|
|
});
|
|
});
|
|
|
|
describe("hasMeaningfulSeriesPrefix", () => {
|
|
it("recognizes a real series name before the season token", () => {
|
|
expect(hasMeaningfulSeriesPrefix("Desperate.Housewives.S01.Synced.DL.720p.WEB-DL.AC3.h264")).toBe(true);
|
|
expect(hasMeaningfulSeriesPrefix("Die.Thundermans.S02E06.Tickets.und.Shreddy.GERMAN.WS.720p.HDTV.x264-aWake")).toBe(true);
|
|
expect(hasMeaningfulSeriesPrefix("Mistresses.2013.S02.GERMAN.DL.720p.WEB.x264-TSCC")).toBe(true);
|
|
expect(hasMeaningfulSeriesPrefix("show.name.s01e01.720p")).toBe(true);
|
|
});
|
|
|
|
it("rejects generic season-label folders without a series name", () => {
|
|
expect(hasMeaningfulSeriesPrefix("S01 Complete")).toBe(false);
|
|
expect(hasMeaningfulSeriesPrefix("S02")).toBe(false);
|
|
expect(hasMeaningfulSeriesPrefix("S01E01 Complete")).toBe(false);
|
|
expect(hasMeaningfulSeriesPrefix(".S01.bla")).toBe(false);
|
|
});
|
|
|
|
it("returns false when there is no season token at all", () => {
|
|
expect(hasMeaningfulSeriesPrefix("Some Random Folder")).toBe(false);
|
|
expect(hasMeaningfulSeriesPrefix("")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("looksLikeObfuscatedSceneFileName", () => {
|
|
it("flags hoster-obfuscated names with no scene markers as obfuscated", () => {
|
|
expect(looksLikeObfuscatedSceneFileName("awa-diethundermans02e16hd.mkv")).toBe(true);
|
|
expect(looksLikeObfuscatedSceneFileName("scn-dthund7-S02E06.mkv")).toBe(true);
|
|
expect(looksLikeObfuscatedSceneFileName("4sj-blue-bloods-s08e21-720p.mkv")).toBe(true);
|
|
});
|
|
|
|
it("treats clean scene releases with multiple markers as NOT obfuscated", () => {
|
|
expect(looksLikeObfuscatedSceneFileName("the.royals.2015.s01e09.german.dl.720p.bluray.x264-j4f.mkv")).toBe(false);
|
|
expect(looksLikeObfuscatedSceneFileName("Die.Thundermans.S02E06.Tickets.und.Shreddy.GERMAN.WS.720p.HDTV.x264-aWake.mkv")).toBe(false);
|
|
expect(looksLikeObfuscatedSceneFileName("Desperate.Housewives.S01E01.German.Synced.DL.720p.WEB-DL.AC3.h264.mkv")).toBe(false);
|
|
});
|
|
|
|
it("handles edge cases (empty, very short)", () => {
|
|
expect(looksLikeObfuscatedSceneFileName("")).toBe(true);
|
|
expect(looksLikeObfuscatedSceneFileName("a.mkv")).toBe(true);
|
|
});
|
|
|
|
it("treats long dotted names as scene-style even with few markers", () => {
|
|
expect(looksLikeObfuscatedSceneFileName("Some.Show.With.Many.Tokens.S01E01.mkv")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("extractEpisodeToken (extended formats)", () => {
|
|
it("recognizes the older xX format (capped at 2 episode digits)", () => {
|
|
expect(extractEpisodeToken("show.1x01.720p.mkv")).toBe("S01E01");
|
|
expect(extractEpisodeToken("show-2x05-hdtv.mkv")).toBe("S02E05");
|
|
expect(extractEpisodeToken("Show.Name.10x99.mkv")).toBe("S10E99");
|
|
expect(extractEpisodeToken("Show.Name.10x100.mkv")).toBeNull();
|
|
expect(extractEpisodeToken("Show.Name.S10E100.mkv")).toBe("S10E100");
|
|
});
|
|
|
|
it("does not falsely match resolution tokens like 1080x720", () => {
|
|
expect(extractEpisodeToken("show.1080p.mkv")).toBeNull();
|
|
expect(extractEpisodeToken("show.S01E01.1080p.mkv")).toBe("S01E01");
|
|
});
|
|
|
|
it("does not falsely match codec tokens like x264 / x265 (caps episode digits)", () => {
|
|
expect(extractEpisodeToken("Movie.x264-GROUP.mkv")).toBeNull();
|
|
expect(extractEpisodeToken("Movie.5x265.x265.mkv")).toBeNull();
|
|
expect(extractEpisodeToken("Show.S01E01.x265-GROUP.mkv")).toBe("S01E01");
|
|
});
|
|
|
|
it("does not falsely match common aspect ratios like 1920x1080", () => {
|
|
expect(extractEpisodeToken("Movie.1920x1080.mkv")).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("extractEpisodeToken", () => {
|
|
it("extracts S01E01 from standard scene format", () => {
|
|
expect(extractEpisodeToken("show.name.s01e01.720p")).toBe("S01E01");
|
|
});
|
|
|
|
it("extracts episode with dot separators", () => {
|
|
expect(extractEpisodeToken("Show.S02E15.1080p")).toBe("S02E15");
|
|
});
|
|
|
|
it("extracts episode with dash separators", () => {
|
|
expect(extractEpisodeToken("show-s3e5-720p")).toBe("S03E05");
|
|
});
|
|
|
|
it("extracts episode with underscore separators", () => {
|
|
expect(extractEpisodeToken("show_s10e100_hdtv")).toBe("S10E100");
|
|
});
|
|
|
|
it("extracts episode with space separators", () => {
|
|
expect(extractEpisodeToken("Show Name s1e2 720p")).toBe("S01E02");
|
|
});
|
|
|
|
it("pads single-digit season and episode to 2 digits", () => {
|
|
expect(extractEpisodeToken("show.s1e3.720p")).toBe("S01E03");
|
|
});
|
|
|
|
it("handles 3-digit episode numbers", () => {
|
|
expect(extractEpisodeToken("show.s01e123")).toBe("S01E123");
|
|
});
|
|
|
|
it("returns null for no episode token", () => {
|
|
expect(extractEpisodeToken("some.random.file.720p")).toBeNull();
|
|
});
|
|
|
|
it("returns null for season-only pattern (no episode)", () => {
|
|
expect(extractEpisodeToken("show.s01.720p")).toBeNull();
|
|
});
|
|
|
|
it("returns null for empty string", () => {
|
|
expect(extractEpisodeToken("")).toBeNull();
|
|
});
|
|
|
|
it("is case-insensitive", () => {
|
|
expect(extractEpisodeToken("Show.S05E10.1080p")).toBe("S05E10");
|
|
expect(extractEpisodeToken("show.s05e10.1080p")).toBe("S05E10");
|
|
});
|
|
|
|
it("extracts from episode token at start of string", () => {
|
|
expect(extractEpisodeToken("s01e01.720p")).toBe("S01E01");
|
|
});
|
|
|
|
it("extracts from episode token at end of string", () => {
|
|
expect(extractEpisodeToken("show.s02e03")).toBe("S02E03");
|
|
});
|
|
|
|
it("extracts double episode token s01e01e02", () => {
|
|
expect(extractEpisodeToken("tvr-mammon-s01e01e02-720p")).toBe("S01E01E02");
|
|
});
|
|
|
|
it("extracts double episode with dot separators", () => {
|
|
expect(extractEpisodeToken("Show.S01E03E04.720p")).toBe("S01E03E04");
|
|
});
|
|
|
|
it("extracts double episode at end of string", () => {
|
|
expect(extractEpisodeToken("show.s02e05e06")).toBe("S02E05E06");
|
|
});
|
|
|
|
it("extracts double episode with single-digit numbers", () => {
|
|
expect(extractEpisodeToken("show-s1e1e2-720p")).toBe("S01E01E02");
|
|
});
|
|
|
|
it("extracts episode when title and season token are joined", () => {
|
|
expect(extractEpisodeToken("mdgp-carters02e01-720p")).toBe("S02E01");
|
|
});
|
|
});
|
|
|
|
describe("applyEpisodeTokenToFolderName", () => {
|
|
it("replaces existing episode token in folder name", () => {
|
|
expect(applyEpisodeTokenToFolderName("Show.S01E01.720p-4sf", "S02E05")).toBe("Show.S02E05.720p-4sf");
|
|
});
|
|
|
|
it("replaces season-only token when no episode in folder", () => {
|
|
expect(applyEpisodeTokenToFolderName("Show.S01.720p-4sf", "S01E03")).toBe("Show.S01E03.720p-4sf");
|
|
});
|
|
|
|
it("inserts before -4sf suffix when no season/episode in folder", () => {
|
|
expect(applyEpisodeTokenToFolderName("Show.720p-4sf", "S01E05")).toBe("Show.720p.S01E05-4sf");
|
|
});
|
|
|
|
it("inserts before -4sj suffix", () => {
|
|
expect(applyEpisodeTokenToFolderName("Show.720p-4sj", "S01E05")).toBe("Show.720p.S01E05-4sj");
|
|
});
|
|
|
|
it("appends episode token when no recognized pattern", () => {
|
|
expect(applyEpisodeTokenToFolderName("SomeFolder", "S01E01")).toBe("SomeFolder.S01E01");
|
|
});
|
|
|
|
it("returns episode token when folder name is empty", () => {
|
|
expect(applyEpisodeTokenToFolderName("", "S01E01")).toBe("S01E01");
|
|
});
|
|
|
|
it("handles folder with existing multi-digit episode", () => {
|
|
expect(applyEpisodeTokenToFolderName("Show.S01E99.720p-4sf", "S01E05")).toBe("Show.S01E05.720p-4sf");
|
|
});
|
|
|
|
it("is case-insensitive for -4SF/-4SJ suffix", () => {
|
|
expect(applyEpisodeTokenToFolderName("Show.720p-4SF", "S01E01")).toBe("Show.720p.S01E01-4SF");
|
|
});
|
|
|
|
it("applies double episode token to season-only folder", () => {
|
|
expect(applyEpisodeTokenToFolderName("Mammon.S01.German.1080P.Bluray.x264-SMAHD", "S01E01E02"))
|
|
.toBe("Mammon.S01E01E02.German.1080P.Bluray.x264-SMAHD");
|
|
});
|
|
|
|
it("replaces existing double episode in folder with new token", () => {
|
|
expect(applyEpisodeTokenToFolderName("Show.S01E01E02.720p-4sf", "S01E03E04"))
|
|
.toBe("Show.S01E03E04.720p-4sf");
|
|
});
|
|
|
|
it("replaces existing single episode in folder with double episode token", () => {
|
|
expect(applyEpisodeTokenToFolderName("Show.S01E01.720p-4sf", "S01E01E02"))
|
|
.toBe("Show.S01E01E02.720p-4sf");
|
|
});
|
|
});
|
|
|
|
describe("sourceHasRpToken", () => {
|
|
it("detects .rp. in filename", () => {
|
|
expect(sourceHasRpToken("show.s01e01.rp.720p")).toBe(true);
|
|
});
|
|
|
|
it("detects -rp- in filename", () => {
|
|
expect(sourceHasRpToken("show-s01e01-rp-720p")).toBe(true);
|
|
});
|
|
|
|
it("detects _rp_ in filename", () => {
|
|
expect(sourceHasRpToken("show_s01e01_rp_720p")).toBe(true);
|
|
});
|
|
|
|
it("detects rp at end of string", () => {
|
|
expect(sourceHasRpToken("show.s01e01.rp")).toBe(true);
|
|
});
|
|
|
|
it("does not match rp inside a word", () => {
|
|
expect(sourceHasRpToken("enterprise.s01e01")).toBe(false);
|
|
});
|
|
|
|
it("returns false for empty string", () => {
|
|
expect(sourceHasRpToken("")).toBe(false);
|
|
});
|
|
|
|
it("is case-insensitive", () => {
|
|
expect(sourceHasRpToken("show.RP.720p")).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("ensureRepackToken", () => {
|
|
it("inserts REPACK before quality token", () => {
|
|
expect(ensureRepackToken("Show.S01E01.1080p-4sf")).toBe("Show.S01E01.REPACK.1080p-4sf");
|
|
});
|
|
|
|
it("inserts REPACK before 720p", () => {
|
|
expect(ensureRepackToken("Show.S01E01.720p-4sf")).toBe("Show.S01E01.REPACK.720p-4sf");
|
|
});
|
|
|
|
it("inserts REPACK before 2160p", () => {
|
|
expect(ensureRepackToken("Show.S01E01.2160p-4sf")).toBe("Show.S01E01.REPACK.2160p-4sf");
|
|
});
|
|
|
|
it("inserts REPACK before -4sf when no quality token", () => {
|
|
expect(ensureRepackToken("Show.S01E01-4sf")).toBe("Show.S01E01.REPACK-4sf");
|
|
});
|
|
|
|
it("inserts REPACK before -4sj when no quality token", () => {
|
|
expect(ensureRepackToken("Show.S01E01-4sj")).toBe("Show.S01E01.REPACK-4sj");
|
|
});
|
|
|
|
it("appends REPACK when no recognized insertion point", () => {
|
|
expect(ensureRepackToken("Show.S01E01")).toBe("Show.S01E01.REPACK");
|
|
});
|
|
|
|
it("does not double-add REPACK if already present", () => {
|
|
expect(ensureRepackToken("Show.S01E01.REPACK.1080p-4sf")).toBe("Show.S01E01.REPACK.1080p-4sf");
|
|
});
|
|
|
|
it("does not double-add repack (case-insensitive)", () => {
|
|
expect(ensureRepackToken("Show.s01e01.repack.720p-4sf")).toBe("Show.s01e01.repack.720p-4sf");
|
|
});
|
|
});
|
|
|
|
describe("buildAutoRenameBaseName", () => {
|
|
it("renames with episode token from source file", () => {
|
|
const result = buildAutoRenameBaseName("Show.S01.720p-4sf", "show.s01e05.720p.mkv");
|
|
expect(result).toBe("Show.S01E05.720p-4sf");
|
|
});
|
|
|
|
it("works with -4sj suffix", () => {
|
|
const result = buildAutoRenameBaseName("Show.S01.720p-4sj", "show.s01e03.720p.mkv");
|
|
expect(result).toBe("Show.S01E03.720p-4sj");
|
|
});
|
|
|
|
it("renames generic scene folder with group suffix", () => {
|
|
const result = buildAutoRenameBaseName("Show.S01.720p-GROUP", "show.s01e05.720p.mkv");
|
|
expect(result).toBe("Show.S01.720p-GROUP");
|
|
});
|
|
|
|
it("returns null when source has no episode token", () => {
|
|
const result = buildAutoRenameBaseName("Show.S01.720p-4sf", "random.file.720p.mkv");
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("adds REPACK when source has rp token", () => {
|
|
const result = buildAutoRenameBaseName("Show.S01.720p-4sf", "show.s01e05.rp.720p.mkv");
|
|
expect(result).toBe("Show.S01E05.REPACK.720p-4sf");
|
|
});
|
|
|
|
it("handles folder with existing episode that gets replaced", () => {
|
|
const result = buildAutoRenameBaseName("Show.S01E01.720p-4sf", "show.s01e10.720p.mkv");
|
|
expect(result).toBe("Show.S01E10.720p-4sf");
|
|
});
|
|
|
|
it("inserts episode before -4sf when folder has no season/episode", () => {
|
|
const result = buildAutoRenameBaseName("Show.720p-4sf", "show.s01e05.720p.mkv");
|
|
expect(result).toBe("Show.720p.S01E05-4sf");
|
|
});
|
|
|
|
it("handles case-insensitive 4SF suffix", () => {
|
|
const result = buildAutoRenameBaseName("Show.S01.720p-4SF", "show.s01e02.720p.mkv");
|
|
expect(result).toBe("Show.S01E02.720p-4SF");
|
|
});
|
|
|
|
it("handles rp + no quality token in folder", () => {
|
|
const result = buildAutoRenameBaseName("Show.S01-4sf", "show.s01e05.rp.mkv");
|
|
expect(result).toBe("Show.S01E05.REPACK-4sf");
|
|
});
|
|
|
|
it("returns null for empty folder name", () => {
|
|
const result = buildAutoRenameBaseName("", "show.s01e01.mkv");
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("returns null for empty source file name", () => {
|
|
const result = buildAutoRenameBaseName("Show.S01-4sf", "");
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("handles 2160p quality token", () => {
|
|
const result = buildAutoRenameBaseName("Show.S01.2160p-4sf", "show.s01e01.rp.2160p.mkv");
|
|
expect(result).toBe("Show.S01E01.REPACK.2160p-4sf");
|
|
});
|
|
|
|
it("handles 480p quality token", () => {
|
|
const result = buildAutoRenameBaseName("Show.S01.480p-4sf", "show.s01e07.480p.mkv");
|
|
expect(result).toBe("Show.S01E07.480p-4sf");
|
|
});
|
|
|
|
it("does not trigger on folders ending with similar but wrong suffix", () => {
|
|
expect(buildAutoRenameBaseName("Show.S01-4sfx", "show.s01e01.mkv")).toBeNull();
|
|
expect(buildAutoRenameBaseName("Show.S01-x4sf", "show.s01e01.mkv")).toBeNull();
|
|
});
|
|
|
|
it("handles high season and episode numbers", () => {
|
|
const result = buildAutoRenameBaseName("Show.S99.720p-4sf", "show.s99e999.720p.mkv");
|
|
expect(result).not.toBeNull();
|
|
expect(result!).toContain("S99E999");
|
|
});
|
|
|
|
it("real-world: German series with dots", () => {
|
|
const result = buildAutoRenameBaseName(
|
|
"Der.Bergdoktor.S18.German.720p.WEB.x264-4SJ",
|
|
"der.bergdoktor.s18e01.german.720p.web.x264"
|
|
);
|
|
expect(result).toBe("Der.Bergdoktor.S18E01.German.720p.WEB.x264-4SJ");
|
|
});
|
|
|
|
it("real-world: English series with rp token", () => {
|
|
const result = buildAutoRenameBaseName(
|
|
"The.Last.of.Us.S02.1080p.WEB-4SF",
|
|
"the.last.of.us.s02e03.rp.1080p.web"
|
|
);
|
|
expect(result).toBe("The.Last.of.Us.S02E03.REPACK.1080p.WEB-4SF");
|
|
});
|
|
|
|
it("real-world: multiple dots in name", () => {
|
|
const result = buildAutoRenameBaseName(
|
|
"Grey.s.Anatomy.S21.German.DL.720p.WEB.x264-4SJ",
|
|
"grey.s.anatomy.s21e08.german.dl.720p.web.x264"
|
|
);
|
|
expect(result).toBe("Grey.s.Anatomy.S21E08.German.DL.720p.WEB.x264-4SJ");
|
|
});
|
|
|
|
it("real-world: 4K content", () => {
|
|
const result = buildAutoRenameBaseName(
|
|
"Severance.S02.2160p.ATVP.WEB-DL.DDP5.1.DV.H.265-4SF",
|
|
"severance.s02e07.2160p.atvp.web-dl.ddp5.1.dv.h.265"
|
|
);
|
|
expect(result).toBe("Severance.S02E07.2160p.ATVP.WEB-DL.DDP5.1.DV.H.265-4SF");
|
|
});
|
|
|
|
it("real-world: Britannia release keeps folder base name", () => {
|
|
const result = buildAutoRenameBaseName(
|
|
"Britannia.S02.GERMAN.720p.WEBRiP.x264-LAW",
|
|
"law-britannia.s02e01.720p.webrip"
|
|
);
|
|
expect(result).toBe("Britannia.S02.GERMAN.720p.WEBRiP.x264-LAW");
|
|
});
|
|
|
|
it("real-world: Britannia repack injects REPACK", () => {
|
|
const result = buildAutoRenameBaseName(
|
|
"Britannia.S02.GERMAN.720p.WEBRiP.x264-LAW",
|
|
"law-britannia.s02e09.720p.webrip.repack"
|
|
);
|
|
expect(result).toBe("Britannia.S02.GERMAN.REPACK.720p.WEBRiP.x264-LAW");
|
|
});
|
|
|
|
it("adds REPACK when folder name carries RP hint", () => {
|
|
const result = buildAutoRenameBaseName(
|
|
"Banshee.S02E01.German.RP.720p.BluRay.x264-RIPLEY",
|
|
"r-banshee.s02e01-720p"
|
|
);
|
|
expect(result).toBe("Banshee.S02E01.German.REPACK.720p.BluRay.x264-RIPLEY");
|
|
});
|
|
|
|
it("real-world: folder already has wrong episode", () => {
|
|
const result = buildAutoRenameBaseName(
|
|
"Cobra.Kai.S06E01.720p.NF.WEB-DL.DDP5.1.x264-4SF",
|
|
"cobra.kai.s06e14.720p.nf.web-dl.ddp5.1.x264"
|
|
);
|
|
expect(result).toBe("Cobra.Kai.S06E14.720p.NF.WEB-DL.DDP5.1.x264-4SF");
|
|
});
|
|
|
|
it("source filename extension is not included in episode detection", () => {
|
|
const result = buildAutoRenameBaseName("Show.S01-4sf", "show.s01e01.mkv");
|
|
expect(result).not.toBeNull();
|
|
expect(result!).toContain("S01E01");
|
|
});
|
|
|
|
it("does not match episode-like patterns in codec strings", () => {
|
|
const token = extractEpisodeToken("show.s01e01.h.265");
|
|
expect(token).toBe("S01E01");
|
|
});
|
|
|
|
it("handles folder with dash separators throughout", () => {
|
|
const result = buildAutoRenameBaseName(
|
|
"Show-Name-S01-720p-4sf",
|
|
"show-name-s01e05-720p"
|
|
);
|
|
expect(result).toBe("Show-Name-S01E05-720p-4sf");
|
|
});
|
|
|
|
it("does not duplicate episode when folder already has the same episode", () => {
|
|
const result = buildAutoRenameBaseName(
|
|
"Show.S01E05.720p-4sf",
|
|
"show.s01e05.720p"
|
|
);
|
|
expect(result).toBe("Show.S01E05.720p-4sf");
|
|
});
|
|
|
|
it("handles folder with only -4sf suffix (edge case)", () => {
|
|
const result = buildAutoRenameBaseName("-4sf", "show.s01e01.mkv");
|
|
expect(result).not.toBeNull();
|
|
expect(result!).toContain("S01E01");
|
|
expect(result!).toContain("-4sf");
|
|
expect(result!).not.toContain(".S01E01.S01E01");
|
|
});
|
|
|
|
it("sanitizes special characters from result", () => {
|
|
const result = buildAutoRenameBaseName("Show:Name.S01-4sf", "show.s01e01.mkv");
|
|
expect(result).not.toBeNull();
|
|
expect(result!).not.toContain(":");
|
|
});
|
|
});
|
|
|
|
describe("buildAutoRenameBaseNameFromFolders", () => {
|
|
it("uses parent folder when current folder is not a scene template", () => {
|
|
const result = buildAutoRenameBaseNameFromFolders(
|
|
[
|
|
"Episode 01",
|
|
"Banshee.S02.German.720p.BluRay.x264-RIPLEY"
|
|
],
|
|
"r-banshee.s02e01-720p"
|
|
);
|
|
expect(result).toBe("Banshee.S02.German.720p.BluRay.x264-RIPLEY");
|
|
});
|
|
|
|
it("uses nested scene subfolder directly", () => {
|
|
const result = buildAutoRenameBaseNameFromFolders(
|
|
[
|
|
"Banshee.S02E01.German.720p.BluRay.x264-RIPLEY",
|
|
"Banshee.S02.German.720p.BluRay.x264-RIPLEY"
|
|
],
|
|
"r-banshee.s02e01-720p"
|
|
);
|
|
expect(result).toBe("Banshee.S02E01.German.720p.BluRay.x264-RIPLEY");
|
|
});
|
|
|
|
it("injects REPACK when parent folder carries repack hint", () => {
|
|
const result = buildAutoRenameBaseNameFromFolders(
|
|
[
|
|
"Banshee.S02E01.German.720p.BluRay.x264-RIPLEY",
|
|
"Banshee.S02.German.RP.720p.BluRay.x264-RIPLEY"
|
|
],
|
|
"r-banshee.s02e01-720p"
|
|
);
|
|
expect(result).toBe("Banshee.S02E01.German.REPACK.720p.BluRay.x264-RIPLEY");
|
|
});
|
|
|
|
it("uses nested Arrow episode folder with title", () => {
|
|
const result = buildAutoRenameBaseNameFromFolders(
|
|
[
|
|
"Arrow.S04E01.Green.Arrow.German.DL.720p.BluRay.x264-RSG",
|
|
"Arrow.S04.German.DL.720p.BluRay.x264-RSG"
|
|
],
|
|
"rsg-arrow-s04e01-720p"
|
|
);
|
|
expect(result).toBe("Arrow.S04E01.Green.Arrow.German.DL.720p.BluRay.x264-RSG");
|
|
});
|
|
|
|
it("adds REPACK for Arrow when source contains rp token", () => {
|
|
const result = buildAutoRenameBaseNameFromFolders(
|
|
[
|
|
"Arrow.S04E01.Green.Arrow.German.DL.720p.BluRay.x264-RSG",
|
|
"Arrow.S04.German.DL.720p.BluRay.x264-RSG"
|
|
],
|
|
"rsg-arrow-s04e01.rp.720p"
|
|
);
|
|
expect(result).toBe("Arrow.S04E01.Green.Arrow.German.DL.REPACK.720p.BluRay.x264-RSG");
|
|
});
|
|
|
|
it("converts Teil token to episode using parent season", () => {
|
|
const result = buildAutoRenameBaseNameFromFolders(
|
|
[
|
|
"Last.Impact.Der.Einschlag.Teil1.GERMAN.DL.720p.WEB.H264-SunDry",
|
|
"Last.Impact.Der.Einschlag.S01.GERMAN.DL.720p.WEB.H264-SunDry"
|
|
],
|
|
"sundry-last.impact.der.einschlag.teil1.720p.web.h264"
|
|
);
|
|
expect(result).toBe("Last.Impact.Der.Einschlag.S01E01.GERMAN.DL.720p.WEB.H264-SunDry");
|
|
});
|
|
|
|
it("converts Teil token to episode with REPACK", () => {
|
|
const result = buildAutoRenameBaseNameFromFolders(
|
|
[
|
|
"Last.Impact.Der.Einschlag.Teil1.GERMAN.DL.720p.WEB.H264-SunDry",
|
|
"Last.Impact.Der.Einschlag.S01.GERMAN.DL.720p.WEB.H264-SunDry"
|
|
],
|
|
"sundry-last.impact.der.einschlag.teil1.rp.720p.web.h264"
|
|
);
|
|
expect(result).toBe("Last.Impact.Der.Einschlag.S01E01.GERMAN.DL.REPACK.720p.WEB.H264-SunDry");
|
|
});
|
|
|
|
it("forces episode insertion for flat season folder when many files share directory", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
[
|
|
"Arrow.S08.GERMAN.DUBBED.DL.720p.BluRay.x264-TMSF"
|
|
],
|
|
"tmsf-arrow-s08e03-720p",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Arrow.S08E03.GERMAN.DUBBED.DL.720p.BluRay.x264-TMSF");
|
|
});
|
|
|
|
it("forces episode insertion plus REPACK for flat season folder", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
[
|
|
"Gotham.S05.GERMAN.DUBBED.720p.BLURAY.x264-ZZGtv"
|
|
],
|
|
"zzgtv-gotham-s05e02.rp",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Gotham.S05E02.GERMAN.DUBBED.REPACK.720p.BLURAY.x264-ZZGtv");
|
|
});
|
|
|
|
it("uses nested episode title folder for Gotham TvR style", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
[
|
|
"Gotham.S04E01.Pax.Penguina.GERMAN.5.1.DL.AC3.720p.BDRiP.x264-TvR",
|
|
"Gotham.S04.GERMAN.5.1.DL.AC3.720p.BDRiP.x264-TvR"
|
|
],
|
|
"tvr-gotham-s04e01-720p",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Gotham.S04E01.Pax.Penguina.GERMAN.5.1.DL.AC3.720p.BDRiP.x264-TvR");
|
|
});
|
|
|
|
it("uses nested title folder for Britannia TV4A style", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
[
|
|
"Britannia.S01E01.Die.Landung.German.DL.720p.BluRay.x264-TV4A",
|
|
"Britannia.S01.German.DL.720p.BluRay.x264-TV4A"
|
|
],
|
|
"tv4a-britannia.s01e01-720p",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Britannia.S01E01.Die.Landung.German.DL.720p.BluRay.x264-TV4A");
|
|
});
|
|
|
|
it("handles odd source token style 101 by using nested Agent X folder", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
[
|
|
"Agent.X.S01E01.Pilot.German.DD51.Dubbed.DL.720p.iTunesHD.x264-TVS",
|
|
"Agent.X.S01.German.DD51.Dubbed.DL.720p.iTunesHD.x264-TVS"
|
|
],
|
|
"tvs-agent-x-dd51-ded-dl-7p-ithd-x264-101",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Agent.X.S01E01.Pilot.German.DD51.Dubbed.DL.720p.iTunesHD.x264-TVS");
|
|
});
|
|
|
|
it("maps compact code 301 to S03E01 for nested Legion folder", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
[
|
|
"Legion.S03E01.Kapitel.20.German.DD51.Dubbed.DL.720p.AmazonHD.AVC-TVS",
|
|
"Legion.S03.German.DD51.Dubbed.DL.720p.AmazonHD.AVC-TVS"
|
|
],
|
|
"tvs-legion-dd51-ded-dl-7p-azhd-avc-301",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Legion.S03E01.Kapitel.20.German.DD51.Dubbed.DL.720p.AmazonHD.AVC-TVS");
|
|
});
|
|
|
|
it("maps compact code 211 in flat season folder", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
[
|
|
"Lethal.Weapon.S02.German.DD51.Dubbed.DL.720p.AmazonHD.x264-TVS"
|
|
],
|
|
"tvs-lethal-weapon-dd51-ded-dl-7p-azhd-x264-211",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Lethal.Weapon.S02E11.German.DD51.Dubbed.DL.720p.AmazonHD.x264-TVS");
|
|
});
|
|
|
|
it("maps compact code 319a to episode 19 in season 3 folder", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
[
|
|
"Die.Bergpolizei.-.Ganz.nah.am.Himmel.S03.GERMAN.AC3.720p.HDTV.x264-hrs"
|
|
],
|
|
"hrs-bpol.hdtv.7p-319a",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Die.Bergpolizei.-.Ganz.nah.am.Himmel.S03E19.GERMAN.AC3.720p.HDTV.x264-hrs");
|
|
});
|
|
|
|
it("maps compact code 319b to next episode in season 3 folder", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
[
|
|
"Die.Bergpolizei.-.Ganz.nah.am.Himmel.S03.GERMAN.AC3.720p.HDTV.x264-hrs"
|
|
],
|
|
"hrs-bpol.hdtv.7p-319b",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Die.Bergpolizei.-.Ganz.nah.am.Himmel.S03E20.GERMAN.AC3.720p.HDTV.x264-hrs");
|
|
});
|
|
|
|
it("maps episode-only token e01 via season folder hint and keeps REPACK", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
[
|
|
"Cheat.der.Betrug.S01.GERMAN.720p.WEB.h264-TMSF"
|
|
],
|
|
"tmsf-cheatderbetrug-e01-720p-repack",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Cheat.der.Betrug.S01E01.GERMAN.REPACK.720p.WEB.h264-TMSF");
|
|
});
|
|
|
|
it("maps episode-only token e02 via season folder hint", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
[
|
|
"Cheat.der.Betrug.S01.GERMAN.720p.WEB.h264-TMSF"
|
|
],
|
|
"tmsf-cheatderbetrug-e02-720p",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Cheat.der.Betrug.S01E02.GERMAN.720p.WEB.h264-TMSF");
|
|
});
|
|
|
|
it("keeps renaming for odd source order like 4sf-bs-720p-s01e05", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
[
|
|
"Cheat.der.Betrug.S01.GERMAN.720p.WEB.h264-TMSF"
|
|
],
|
|
"4sf-bs-720p-s01e05",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Cheat.der.Betrug.S01E05.GERMAN.720p.WEB.h264-TMSF");
|
|
});
|
|
|
|
it("accepts lowercase scene group suffixes", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
[
|
|
"Cheat.der.Betrug.S01.GERMAN.720p.WEB.h264-tmsf"
|
|
],
|
|
"tmsf-cheatderbetrug-e01-720p",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Cheat.der.Betrug.S01E01.GERMAN.720p.WEB.h264-tmsf");
|
|
});
|
|
|
|
it("renames double episode file into season folder (Mammon style)", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
[
|
|
"Mammon.S01.German.1080P.Bluray.x264-SMAHD"
|
|
],
|
|
"tvr-mammon-s01e01e02-720p",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Mammon.S01E01E02.German.1080P.Bluray.x264-SMAHD");
|
|
});
|
|
|
|
it("renames second double episode file correctly", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
[
|
|
"Mammon.S01.German.1080P.Bluray.x264-SMAHD"
|
|
],
|
|
"tvr-mammon-s01e03e04-720p",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Mammon.S01E03E04.German.1080P.Bluray.x264-SMAHD");
|
|
});
|
|
|
|
it("renames third double episode file correctly", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
[
|
|
"Mammon.S01.German.1080P.Bluray.x264-SMAHD"
|
|
],
|
|
"tvr-mammon-s01e05e06-720p",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Mammon.S01E05E06.German.1080P.Bluray.x264-SMAHD");
|
|
});
|
|
|
|
it("renames when folder has season but no scene group suffix (Mystery Road case)", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
["Mystery Road S02"],
|
|
"myst.road.de.dl.hdtv.7p-s02e05",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Mystery Road S02E05");
|
|
});
|
|
|
|
it("renames with season-only folder and custom name without dots", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
["Meine Serie S03"],
|
|
"meine-serie-s03e10-720p",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Meine Serie S03E10");
|
|
});
|
|
|
|
it("prefers scene-group folder over season-only fallback", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
[
|
|
"Mystery Road S02",
|
|
"Mystery.Road.S02.GERMAN.DL.AC3.720p.HDTV.x264-hrs"
|
|
],
|
|
"myst.road.de.dl.hdtv.7p-s02e05",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Mystery.Road.S02E05.GERMAN.DL.AC3.720p.HDTV.x264-hrs");
|
|
});
|
|
|
|
it("does not use season-only fallback when forceEpisodeForSeasonFolder is false", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
["Mystery Road S02"],
|
|
"myst.road.de.dl.hdtv.7p-s02e05",
|
|
{ forceEpisodeForSeasonFolder: false }
|
|
);
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("renames Riviera S02 with single-digit episode s02e2", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
["Riviera.S02.GERMAN.DUBBED.DL.720p.WebHD.x264-TVP"],
|
|
"tvp-riviera-s02e2-720p",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Riviera.S02E02.GERMAN.DUBBED.DL.720p.WebHD.x264-TVP");
|
|
});
|
|
|
|
it("renames Room 104 abbreviated source r104.de.dl.web.7p-s04e02", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
["Room.104.S04.GERMAN.DL.720p.WEBRiP.x264-LAW"],
|
|
"r104.de.dl.web.7p-s04e02",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Room.104.S04E02.GERMAN.DL.720p.WEBRiP.x264-LAW");
|
|
});
|
|
|
|
it("renames Room 104 wayne source with episode", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
["Room.104.S04.GERMAN.DL.720p.WEBRiP.x264-LAW"],
|
|
"room.104.s04e01.german.dl.720p.web.h264-wayne",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Room.104.S04E01.GERMAN.DL.720p.WEBRiP.x264-LAW");
|
|
});
|
|
|
|
it("renames Carter when source joins title and season token", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
["Carter.S02.GERMAN.DL.720p.HDTV.x264-MDGP"],
|
|
"mdgp-carters02e01-720p",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Carter.S02E01.GERMAN.DL.720p.HDTV.x264-MDGP");
|
|
});
|
|
|
|
it("renames abbreviated source bupr.de.dl.web.7p-s01e03 via season folder", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
["Burning.Promise.S01.GERMAN.DL.720p.WEB.H264-WvF"],
|
|
"bupr.de.dl.web.7p-s01e03",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("Burning.Promise.S01E03.GERMAN.DL.720p.WEB.H264-WvF");
|
|
});
|
|
|
|
it("renames abbreviated 4SF source amilllt.de.dl.web.7p-s03e10 via season folder", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
["A.Million.Little.Things.S03.GERMAN.DL.720p.WEB.H264-4SF"],
|
|
"4sf-amilllt.de.dl.web.7p-s03e10",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("A.Million.Little.Things.S03E10.GERMAN.DL.720p.WEB.H264-4SF");
|
|
});
|
|
|
|
it("renames abbreviated source jkl.web.7p-s01e13 via season folder", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
["9JKL.S01.GERMAN.720p.WEB.x264-WvF"],
|
|
"jkl.web.7p-s01e13",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("9JKL.S01E13.GERMAN.720p.WEB.x264-WvF");
|
|
});
|
|
|
|
it("renames abbreviated source jkl.web.7p-s01e14 via season folder", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
["9JKL.S01.GERMAN.720p.WEB.x264-WvF"],
|
|
"jkl.web.7p-s01e14",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
expect(result).toBe("9JKL.S01E14.GERMAN.720p.WEB.x264-WvF");
|
|
});
|
|
|
|
it("documents malformed package name (S01GERMAN) limitation", () => {
|
|
const result = buildAutoRenameBaseNameFromFoldersWithOptions(
|
|
[
|
|
"3MH.web.7p-101",
|
|
"Drei.Meter.ueber.dem.Himmel.S01GERMAN.DL.720P.WEB.X264-WAYNE"
|
|
],
|
|
"Drei.Meter.ueber.dem.Himmel.S01E01.GERMAN.DL.720P.WEB.X264-WAYNE",
|
|
{ forceEpisodeForSeasonFolder: true }
|
|
);
|
|
if (result !== null) {
|
|
expect(typeof result).toBe("string");
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("isBonusContent (numbered episodes are never bonus)", () => {
|
|
const pkgDir = "/pkg/Show.S04.GERMAN.DL.720p.WEB.x264-GRP";
|
|
|
|
it("does NOT treat a numbered episode as bonus even when its TITLE is a bonus word", () => {
|
|
const name = "Revenge.2011.S04E19.Interview.GERMAN.DL.720p.WEB.x264-TSCC";
|
|
const fp = `${pkgDir}/${name}/${name}.mkv`;
|
|
expect(isBonusContent(fp, pkgDir, name)).toBe(false);
|
|
});
|
|
|
|
it("covers further bonus-word episode titles with a token", () => {
|
|
for (const title of ["Special", "Featurette", "Outtakes", "Bloopers", "Making.Of"]) {
|
|
const name = `Show.S04E07.${title}.GERMAN.720p.WEB.x264-GRP`;
|
|
expect(isBonusContent(`${pkgDir}/${name}.mkv`, pkgDir, name)).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("STILL treats genuine extras WITHOUT an episode token as bonus", () => {
|
|
for (const name of [
|
|
"Show.Making.Of.GERMAN.720p.WEB.x264-GRP",
|
|
"Show.Behind.The.Scenes.GERMAN-GRP",
|
|
"Some.Interview.With.Cast"
|
|
]) {
|
|
expect(isBonusContent(`${pkgDir}/${name}.mkv`, pkgDir, name)).toBe(true);
|
|
}
|
|
});
|
|
|
|
it("a token-bearing file inside an Extras subfolder is still kept (numbered episode wins)", () => {
|
|
const name = "Show.S04E19.Interview.GROUP";
|
|
const fp = `${pkgDir}/Extras/${name}/${name}.mkv`;
|
|
expect(isBonusContent(fp, pkgDir, name)).toBe(false);
|
|
});
|
|
|
|
it("a token-less file inside an Extras subfolder is bonus", () => {
|
|
const fp = `${pkgDir}/Extras/Making.Of.mkv`;
|
|
expect(isBonusContent(fp, pkgDir, "Making.Of")).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("complete episode folder WITHOUT group suffix (codec/resolution only)", () => {
|
|
const hash = "c284d9d9072eaf3ac314d05f951dd115";
|
|
|
|
it("uses the clean folder name when it has an episode token + codec but no -GROUP (safari S04E08a)", () => {
|
|
const folder = "Fluss-Monster.S04E08a.Am.Essequibo.Teil.1.German.DOKU.SATRiP.XviD";
|
|
const decision = decideAutoRenameBaseName([folder, hash], "safari-fm-s04e08a.avi", "safari-fm-s04e08a", hash, hash);
|
|
expect(decision).toEqual({ kind: "rename", baseName: folder });
|
|
});
|
|
|
|
it("keeps multi-part letters a/b distinct (Teil.1 vs Teil.2 do NOT collide)", () => {
|
|
const fa = "Fluss-Monster.S04E08a.Am.Essequibo.Teil.1.German.DOKU.SATRiP.XviD";
|
|
const fb = "Fluss-Monster.S04E08b.Am.Essequibo.Teil.2.German.DOKU.SATRiP.XviD";
|
|
const da = decideAutoRenameBaseName([fa, hash], "safari-fm-s04e08a.avi", "safari-fm-s04e08a", hash, hash);
|
|
const db = decideAutoRenameBaseName([fb, hash], "safari-fm-s04e08b.avi", "safari-fm-s04e08b", hash, hash);
|
|
expect(da).toEqual({ kind: "rename", baseName: fa });
|
|
expect(db).toEqual({ kind: "rename", baseName: fb });
|
|
expect((da as any).baseName).not.toBe((db as any).baseName);
|
|
});
|
|
|
|
it("the previously-working group-suffix folder still works (no regression)", () => {
|
|
const folder = "Fluss-Monster.S01E02.Auf.der.Suche.nach.dem.Killer-Wels.German.DOKU.SATRiP.XviD-SAFARi";
|
|
const decision = decideAutoRenameBaseName([folder, hash], "safari-fm-s01e02.avi", "safari-fm-s01e02", hash, hash);
|
|
expect(decision).toEqual({ kind: "rename", baseName: folder });
|
|
});
|
|
|
|
it("does NOT use a bare episode folder WITHOUT any codec/resolution marker (stays conservative)", () => {
|
|
const decision = decideAutoRenameBaseName(["Show.S01E01", hash], "abc-s01e01.avi", "abc-s01e01", hash, hash);
|
|
expect(decision.kind).toBe("skip");
|
|
});
|
|
|
|
it("does NOT fabricate a name from a token-LESS folder (Mega-Direct guard intact)", () => {
|
|
const decision = decideAutoRenameBaseName(["Mega-Direct-Pack", hash], "Direct.Show.S01E01.DIRECT.mkv", "Direct.Show.S01E01.DIRECT", hash, hash);
|
|
expect(decision.kind).toBe("skip");
|
|
});
|
|
});
|
|
|
|
describe("collect must not mangle an already-clean SxxExx name via an episode-title folder", () => {
|
|
const hash = "c284d9d9072eaf3ac314d05f951dd115";
|
|
const epFolder = "Steven.Spielbergs.Taken.E01.Hinter.dem.Himmel.German.720p.HDTV.x264-GTVG";
|
|
const pkgFolder = "Steven.Spielbergs.Taken.S01.German.720p.HDTV.x264-GTVG";
|
|
const cleanSource = "Steven.Spielbergs.Taken.S01E01.German.720p.HDTV.x264-GTVG";
|
|
|
|
it("keeps the clean source (skip) instead of appending the token to the episode-title folder", () => {
|
|
const decision = decideAutoRenameBaseName([epFolder, pkgFolder], cleanSource + ".mkv", cleanSource, epFolder, pkgFolder);
|
|
expect(decision.kind).toBe("skip");
|
|
expect(JSON.stringify(decision)).not.toContain("GTVG.S01E01");
|
|
});
|
|
|
|
it("still cleans a JUNK/obfuscated source via an episode-title folder (append path intact, no skip)", () => {
|
|
const epFolder = "Show.E05.Die.Sache.German.720p.HDTV.x264-GRP";
|
|
const seasonFolder = "Show.S01.German.720p.HDTV.x264-GRP";
|
|
const decision = decideAutoRenameBaseName([epFolder, seasonFolder], "scn-show7-S01E05.mkv", "scn-show7-S01E05", epFolder, seasonFolder);
|
|
expect(decision.kind).toBe("rename");
|
|
expect(extractEpisodeToken((decision as any).baseName)).toBe("S01E05");
|
|
});
|
|
|
|
it("does NOT affect a folder that already carries an SxxExx token (safari S04E08a stays a rename)", () => {
|
|
const folder = "Fluss-Monster.S04E08a.Am.Essequibo.Teil.1.German.DOKU.SATRiP.XviD";
|
|
const decision = decideAutoRenameBaseName([folder, hash], "safari-fm-s04e08a.avi", "safari-fm-s04e08a", hash, hash);
|
|
expect(decision).toEqual({ kind: "rename", baseName: folder });
|
|
});
|
|
|
|
it("keeps a clean SHORT-prefix series source (ER) instead of the crippled token append", () => {
|
|
const epFolder = "ER.E01.Tag.und.Nacht.German.720p.HDTV.x264-GROUP";
|
|
const seasonFolder = "ER.S01.German.720p.HDTV.x264-GROUP";
|
|
const cleanSource = "ER.S01E01.German.720p.HDTV.x264-GROUP";
|
|
const decision = decideAutoRenameBaseName([epFolder, seasonFolder], cleanSource + ".mkv", cleanSource, epFolder, seasonFolder);
|
|
expect(decision.kind).toBe("skip");
|
|
expect(JSON.stringify(decision)).not.toContain("GROUP.S01E01");
|
|
});
|
|
});
|