diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index af1d03f..c3f289c 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -3465,7 +3465,7 @@ export class DownloadManager extends EventEmitter { folderCandidates.push(extra); } } - const targetBaseName = buildAutoRenameBaseNameFromFoldersWithOptions(folderCandidates, sourceBaseName, { + let targetBaseName = buildAutoRenameBaseNameFromFoldersWithOptions(folderCandidates, sourceBaseName, { forceEpisodeForSeasonFolder: true }); const resolveRenameItem = (...extra: Array): { item: DownloadItem | null; matchedBy: string | null } => { @@ -3474,6 +3474,60 @@ export class DownloadManager extends EventEmitter { } return this.inferItemForMediaLog(pkg, sourcePath, sourceName, folderCandidates.join(" "), targetBaseName || "", ...extra); }; + // SAFETY NET: Never strip a valid SxxExx token from the source filename. + // If the source already has an episode token but the computed target lost it + // (e.g. malformed package name "S01GERMAN" with no separator), preserve the + // episode by either inserting it into the target or skipping the rename entirely. + // Without this guard, all episodes from a malformed pack collapse to one name + // and collide with (2)(3)(4) suffixes in the MKV library. + const sourceEpisodeToken = extractEpisodeToken(sourceBaseName); + if (targetBaseName && sourceEpisodeToken) { + const targetEpisodeToken = extractEpisodeToken(targetBaseName); + if (!targetEpisodeToken) { + // Try to insert the source's episode token: replace "Sxx" with "SxxExx." + const insertedEpisode = targetBaseName.replace( + /(^|[._\-\s])(s\d{1,2})(?=[A-Za-z0-9])/i, + `$1${sourceEpisodeToken}.` + ); + if (insertedEpisode !== targetBaseName && extractEpisodeToken(insertedEpisode)) { + logger.info(`Auto-Rename Safety: Episode-Token in Target eingefuegt: ${targetBaseName} -> ${insertedEpisode}`); + targetBaseName = insertedEpisode; + } else { + const repaired = applyEpisodeTokenToFolderName(targetBaseName, sourceEpisodeToken); + if (repaired && extractEpisodeToken(repaired)) { + logger.info(`Auto-Rename Safety: Episode-Token via applyToken: ${targetBaseName} -> ${repaired}`); + targetBaseName = repaired; + } else { + logger.warn(`Auto-Rename Safety: Skipping rename - target wuerde Episode-Token verlieren (source=${sourceBaseName}, target=${targetBaseName})`); + if (pkg) { + const resolved = resolveRenameItem(); + this.logRenameProcess(pkg, "WARN", "auto-rename", "Auto-Rename uebersprungen: Episode-Token wuerde verloren gehen", { + sourcePath, + sourceName, + sourceEpisodeToken, + targetBaseName + }, resolved.item, resolved.matchedBy); + } + continue; + } + } + } else if (targetEpisodeToken !== sourceEpisodeToken) { + // Target has a DIFFERENT episode token than source — that's a clear sign + // the rename is wrong (would mislabel the episode). Skip to be safe. + logger.warn(`Auto-Rename Safety: Skipping rename - Episode-Token Mismatch (source=${sourceEpisodeToken}, target=${targetEpisodeToken})`); + if (pkg) { + const resolved = resolveRenameItem(); + this.logRenameProcess(pkg, "WARN", "auto-rename", "Auto-Rename uebersprungen: Episode-Token Mismatch", { + sourcePath, + sourceName, + sourceEpisodeToken, + targetEpisodeToken, + targetBaseName + }, resolved.item, resolved.matchedBy); + } + continue; + } + } if (!targetBaseName) { if (pkg) { this.logPackageForPackage(pkg, "WARN", "Auto-Rename übersprungen: kein Zielname", { diff --git a/tests/auto-rename.test.ts b/tests/auto-rename.test.ts index a0da6ab..1a4087b 100644 --- a/tests/auto-rename.test.ts +++ b/tests/auto-rename.test.ts @@ -762,4 +762,25 @@ describe("buildAutoRenameBaseNameFromFolders", () => { ); expect(result).toBe("9JKL.S01E14.GERMAN.720p.WEB.x264-WvF"); }); + + it("documents malformed package name (S01GERMAN) limitation", () => { + // Real-world: "Drei.Meter.ueber.dem.Himmel.S01GERMAN.DL.720P.WEB.X264-WAYNE" + // is malformed (no separator between S01 and GERMAN). SCENE_SEASON_ONLY_RE + // doesn't match this, so the helper falls back to the package name as-is. + // The download-manager autoRenameExtractedVideoFiles safety net repairs + // this at runtime by inserting the source's episode token. + 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 } + ); + // Helper limitation: returns the malformed folder name unchanged. + // The download-manager safety net catches this at runtime. + if (result !== null) { + expect(typeof result).toBe("string"); + } + }); });