Auto-Rename Folder-Override: nur bei OBFUSKIERTEM Source-Filename

Regression in v1.7.147: der Folder-Override (parentEpisodeToken
ueberschreibt sourceEpisodeToken bei Mismatch) ist zu aggressiv. Bei
sauberen Scene-Releases die zufaellig im falschen Folder liegen wuerde
das den Episodennamen FALSCH umschreiben.

Beispiel aus Production-Log:
  Folder: The.Royals.S01E08.Der.Grosse.stuerzt.German.DL.720p.BluRay.x264-J4F
  File:   the.royals.2015.s01e09.german.dl.720p.bluray.x264-j4f.mkv

Beide haben sauberes Scene-Format. Datei sagt klar S01E09 — Folder ist
falsch beschriftet (Hoster-Fehler). Die Datei zu S01E08 umbenennen
waere Daten-Korruption.

Fix: neue Helper-Funktion looksLikeObfuscatedSceneFileName() prueft ob
ein Filename Scene-Marker hat (720p/1080p, german/english, bluray/web/
hdtv, x264/x265, ac3/aac/dts) ODER 5+ Punkte als Scene-Struktur. Wenn
2+ Marker oder 5+ Punkte → KEIN Override (Source ist authoritativ).
Wenn weniger → Source ist obfuskiert, Folder gewinnt.

Beispiele aus Production:
- "awa-diethundermans02e16hd.mkv" (0 Marker, 0 Punkte) → obfuskiert,
  Folder Die.Thundermans.S02E01... gewinnt → korrekt umbenannt
- "the.royals.2015.s01e09.german.dl.720p.bluray.x264-j4f.mkv"
  (5 Marker) → sauber, Source bleibt → Skip statt Falsch-Rename
- "Desperate.Housewives.S01E01.German.Synced.DL.720p.WEB-DL.AC3.h264.mkv"
  (5+ Marker) → sauber, kein Override

4 neue Unit-Tests fuer looksLikeObfuscatedSceneFileName, 581/581 gruen.
This commit is contained in:
Sucukdeluxe 2026-04-22 01:29:32 +02:00
parent b1291d2e3c
commit 834da04b45
2 changed files with 78 additions and 1 deletions

View File

@ -866,6 +866,46 @@ export function hasMeaningfulSeriesPrefix(name: string): boolean {
const alphaChars = (prefix.match(/[A-Za-z]/g) || []).length;
return alphaChars >= 3;
}
/** Heuristic: returns true if the file name LOOKS LIKE an obfuscated /
* scrambled hoster name (e.g. "awa-diethundermans02e16hd.mkv") rather than
* a clean scene release ("the.royals.2015.s01e09.german.dl.720p.bluray.x264-j4f.mkv").
*
* Used as a guard before we override a source-derived episode token with
* a folder-derived one. A clean scene file's embedded SxxExx is
* authoritative overriding it would mislabel the episode. Only files
* that lack the usual scene markers (quality, language, codec, source/
* format) are treated as obfuscated and let the folder win.
*
* Threshold: < 2 scene markers AND no proper dot-separated scene
* structure considered obfuscated. */
export function looksLikeObfuscatedSceneFileName(name: string): boolean {
const text = String(name || "").toLowerCase();
if (!text) {
return true;
}
let markers = 0;
if (/(?:^|[._\-\s])(?:480|540|576|720|1080|1440|2160|4320)p(?:[._\-\s]|$)/.test(text)) markers += 1;
if (/(?:^|[._\-\s])(?:german|english|french|italian|spanish|dutch|nordic|multi|ger|eng|ita|fre|spa)(?:[._\-\s]|$)/.test(text)) markers += 1;
if (/(?:^|[._\-\s])(?:bluray|brrip|bdrip|webrip|web-?dl|web|hdtv|dvdrip|amazonhd|amzn|nflx|nf|hulu|dsnp)(?:[._\-\s]|$)/.test(text)) markers += 1;
if (/(?:^|[._\-\s])(?:x264|x265|h264|h265|hevc|xvid|divx|avc)(?:[._\-\s]|$)/.test(text)) markers += 1;
if (/(?:^|[._\-\s])(?:ac3|aac|dd5\.?1|dd51|dts|eac3|atmos|truehd|flac)(?:[._\-\s]|$)/.test(text)) markers += 1;
// 2+ scene markers → definitely a clean scene file, not obfuscated
if (markers >= 2) {
return false;
}
// No markers AND looks like glued/short hoster code → obfuscated
// Check for typical hoster pattern: short prefix + glued lowercase + episode digits
// e.g. "awa-diethundermans02e16hd", "scn-dthund7-S02E06"
const dotCount = (text.match(/\./g) || []).length;
// A clean scene file usually has 6+ dot-separated tokens (excluding extension)
// An obfuscated file usually has 0-2 dots (mostly using - or no separators).
if (dotCount >= 5) {
// Many dots usually means scene-style structure even with few markers
return false;
}
return true;
}
const SCENE_SEASON_CAPTURE_RE = /(?:^|[._\-\s])s(\d{1,2})(?=[._\-\s]|$)/i;
const SCENE_EPISODE_ONLY_RE = /(?:^|[._\-\s])e(?:p(?:isode)?)?\s*0*(\d{1,3})(?:[._\-\s]|$)/i;
const SCENE_PART_TOKEN_RE = /(?:^|[._\-\s])(?:teil|part)\s*0*(\d{1,3})(?=[._\-\s]|$)/i;
@ -3787,10 +3827,20 @@ export class DownloadManager extends EventEmitter {
// misleading source token.
const parentFolderName = path.basename(path.dirname(sourcePath));
const parentEpisodeToken = extractEpisodeToken(parentFolderName);
// GUARD: only let the folder override the source token when the
// source filename actually LOOKS obfuscated (no scene markers like
// 720p / german / x264 / bluray, no dot-separated structure).
// A clean scene release filename — e.g. "the.royals.2015.s01e09.
// german.dl.720p.bluray.x264-j4f.mkv" — must NEVER be overridden,
// because a one-off folder/file mismatch with a clean source means
// the FOLDER is wrong, not the file. Renaming a real S01E09 to
// S01E08 because the folder happens to say E08 would corrupt data.
const sourceLooksObfuscated = looksLikeObfuscatedSceneFileName(sourceName);
const folderIsAuthoritative = Boolean(
parentEpisodeToken
&& parentEpisodeToken === targetEpisodeToken
&& parentFolderName.toLowerCase() !== path.basename(extractDir).toLowerCase()
&& sourceLooksObfuscated
);
if (folderIsAuthoritative) {
logger.info(`Auto-Rename: source-Token ${sourceEpisodeToken} ignoriert, Folder-Token ${targetEpisodeToken} ist authoritativ (vermutlich obfuskierter Dateiname in ${parentFolderName})`);

View File

@ -7,7 +7,8 @@ import {
buildAutoRenameBaseName,
buildAutoRenameBaseNameFromFolders,
buildAutoRenameBaseNameFromFoldersWithOptions,
hasMeaningfulSeriesPrefix
hasMeaningfulSeriesPrefix,
looksLikeObfuscatedSceneFileName
} from "../src/main/download-manager";
describe("hasMeaningfulSeriesPrefix", () => {
@ -31,6 +32,32 @@ describe("hasMeaningfulSeriesPrefix", () => {
});
});
describe("looksLikeObfuscatedSceneFileName", () => {
it("flags hoster-obfuscated names with no scene markers as obfuscated", () => {
// No 720p / german / x264 / bluray, no dot-separated structure
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", () => {
// Has 720p + german + bluray + x264 — clearly a clean scene file
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", () => {
// 6+ dots → looks like scene structure even without quality/codec markers
expect(looksLikeObfuscatedSceneFileName("Some.Show.With.Many.Tokens.S01E01.mkv")).toBe(false);
});
});
describe("extractEpisodeToken", () => {
it("extracts S01E01 from standard scene format", () => {
expect(extractEpisodeToken("show.name.s01e01.720p")).toBe("S01E01");