Compare commits
2 Commits
74aec6f056
...
7b39b33cc7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b39b33cc7 | ||
|
|
339c46bdd2 |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "real-debrid-downloader",
|
||||
"version": "1.7.181",
|
||||
"version": "1.7.182",
|
||||
"description": "Desktop downloader",
|
||||
"main": "build/main/main/main.js",
|
||||
"author": "Sucukdeluxe",
|
||||
|
||||
@ -946,6 +946,10 @@ const SCENE_COMPACT_EPISODE_CODE_RE = /(?:^|[._\-\s])(\d{3,4})([a-z])?(?=$|[._\-
|
||||
const SCENE_RP_TOKEN_RE = /(?:^|[._\-\s])rp(?:[._\-\s]|$)/i;
|
||||
const SCENE_REPACK_TOKEN_RE = /(?:^|[._\-\s])repack(?:[._\-\s]|$)/i;
|
||||
const SCENE_QUALITY_TOKEN_RE = /([._\-\s])((?:4320|2160|1440|1080|720|576|540|480|360)p)(?=[._\-\s]|$)/i;
|
||||
// Marker, dass ein Name eine vollstaendige Release-Benennung ist (Aufloesung ODER Codec).
|
||||
// Bewusst inkl. xvid/divx — alte deutsche Dokus/Serien sind oft XviD ohne Aufloesungs-Tag.
|
||||
const SCENE_RESOLUTION_MARKER_RE = /\b(?:480|540|576|720|1080|1440|2160|4320)[pi]\b/i;
|
||||
const SCENE_CODEC_MARKER_RE = /\b(?:x264|x265|x266|h\.?264|h\.?265|h\.?266|hevc|avc|vvc|av1|xvid|divx)\b/i;
|
||||
const SCENE_GROUP_SUFFIX_FALLBACK_RE = /-([A-Za-z0-9]{2,})$/;
|
||||
const SCENE_FLEXIBLE_GROUP_SUFFIX_RE = /-([A-Za-z0-9]+(?:_[A-Za-z0-9]+)*)$/;
|
||||
const SCENE_MIXED_GROUP_SUFFIX_RE = /-[^-]*[\/\\|\u2044\u2215][^-]*$/;
|
||||
@ -1306,7 +1310,16 @@ export function buildAutoRenameBaseName(folderName: string, sourceFileName: stri
|
||||
|
||||
const isLegacy4sf4sjFolder = SCENE_RELEASE_FOLDER_RE.test(normalizedFolderName);
|
||||
const isSceneGroupFolder = hasSceneGroupSuffix(normalizedFolderName);
|
||||
if (!isLegacy4sf4sjFolder && !isSceneGroupFolder) {
|
||||
// Auch einen vollstaendigen Episoden-Ordnernamen OHNE Gruppen-Suffix akzeptieren: hat der
|
||||
// Ordner einen echten SxxExx-Token UND einen Codec-/Aufloesungs-Marker, ist es eine saubere
|
||||
// Release-Benennung (z.B. alte deutsche Dokus ohne -GROUP, Ordner endet auf ".XviD":
|
||||
// "Fluss-Monster.S04E08a.Am.Essequibo.Teil.1.German.DOKU.SATRiP.XviD"). Ohne diese Klausel
|
||||
// lieferte der Helper null → "kein Zielname" → die Folge blieb mit rohem Hoster-Namen
|
||||
// ("safari-fm-s04e08a.avi") in der Library liegen. Part-Buchstaben (a/b) bleiben erhalten,
|
||||
// weil der Ordnername unveraendert als Zielname dient (nur der RANGE-Zweig schreibt Token um).
|
||||
const isCompleteEpisodeFolder = Boolean(extractEpisodeToken(normalizedFolderName))
|
||||
&& (SCENE_RESOLUTION_MARKER_RE.test(normalizedFolderName) || SCENE_CODEC_MARKER_RE.test(normalizedFolderName));
|
||||
if (!isLegacy4sf4sjFolder && !isSceneGroupFolder && !isCompleteEpisodeFolder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -1323,7 +1336,7 @@ export function buildAutoRenameBaseName(folderName: string, sourceFileName: stri
|
||||
// range with the source's specific episode token. Without this, all
|
||||
// episodes in a range-named folder share the same target name, producing
|
||||
// (2)(3)(4) suffixes during MKV collection.
|
||||
if (!isLegacy4sf4sjFolder && isSceneGroupFolder) {
|
||||
if (!isLegacy4sf4sjFolder && (isSceneGroupFolder || isCompleteEpisodeFolder)) {
|
||||
const episodeRangeRe = /(^|[._\-\s])s\d{1,2}e\d{1,3}[-]e?\d{1,3}(?=[._\-\s]|$)/i;
|
||||
if (episodeRangeRe.test(normalizedFolderName)) {
|
||||
next = applyEpisodeTokenToFolderName(normalizedFolderName, episodeToken);
|
||||
@ -1537,14 +1550,12 @@ export function decideAutoRenameBaseName(
|
||||
// Greift NUR ohne Quell-Episode-Token → schliesst sich mit dem Fabrikations-Guard oben aus
|
||||
// (Mega-Direct hat einen Quell-Token und kommt nie hierher).
|
||||
if (!extractEpisodeToken(sourceBaseName)) {
|
||||
const RESOLUTION_RE = /\b(?:480|540|576|720|1080|1440|2160|4320)[pi]\b/i;
|
||||
const CODEC_RE = /\b(?:x264|x265|x266|h\.?264|h\.?265|h\.?266|hevc|avc|vvc|av1|xvid|divx)\b/i;
|
||||
for (const folderName of folderCandidates) {
|
||||
const f = sanitizeFilename(String(folderName || "").trim());
|
||||
if (!f) {
|
||||
continue;
|
||||
}
|
||||
if (hasSceneGroupSuffix(f) && (RESOLUTION_RE.test(f) || CODEC_RE.test(f)) && !SCENE_SEASON_ONLY_RE.test(f)) {
|
||||
if (hasSceneGroupSuffix(f) && (SCENE_RESOLUTION_MARKER_RE.test(f) || SCENE_CODEC_MARKER_RE.test(f)) && !SCENE_SEASON_ONLY_RE.test(f)) {
|
||||
return { kind: "rename", baseName: f, note: "folder-as-is" };
|
||||
}
|
||||
}
|
||||
|
||||
@ -289,3 +289,21 @@ im Bundle. Per Code bewiesen: finaler Deferred-Collect laeuft fuer jedes fertige
|
||||
completed-Items, Z.11904) mit `deferFreshFiles=false` → faengt Frische-Defers. Also Frische orphan't NICHT;
|
||||
Bonus schon (Filter ignoriert deferFreshFiles, skippt in JEDEM Pass inkl. final). Lehre: bevor man „X ist
|
||||
Orphan" behauptet, pruefen ob der GEGENBEWEIS (Move) im verfuegbaren Log ueberhaupt sichtbar WAERE.
|
||||
|
||||
## 2026-06-05 — Folge bleibt ROH: vollstaendiger Episoden-Ordner OHNE -GROUP-Suffix
|
||||
**Symptom (rename-session 2026-06-04):** `safari-fm-s04e08a.avi` / `...b.avi` landeten ROH in der Library
|
||||
(entpackt2). Log: `Auto-Rename übersprungen: kein Zielname`. Funktionierende S01E02 hatte Ordner
|
||||
`...XviD-SAFARi` (Gruppe), die kaputten S04E08a/b hatten `...SATRiP.XviD` (KEIN -GROUP).
|
||||
**Root Cause (Wegwerf-Diagnose, NICHT geraten):** Erste Hypothese „a/b-Token nicht erkannt" war FALSCH —
|
||||
`extractEpisodeToken("...s04e08a")`="S04E08" (das Lookahead `(?!\d)` verbietet nur Ziffern, nicht Buchstaben).
|
||||
Echte Ursache: das Gate in `buildAutoRenameBaseName` (`isLegacy4sf || isSceneGroupFolder`) lehnt einen
|
||||
vollstaendigen Episoden-Ordner OHNE -GROUP ab (endet auf bare Codec `.XviD`). Die QUELLE hat aber einen
|
||||
Token → der v1.7.180-Fallback (greift NUR ohne Quell-Token) feuert nicht → no-target → roh gemoved.
|
||||
**Fix:** Gate um `isCompleteEpisodeFolder` erweitert = echter Episoden-Token IM Ordner UND Codec-/
|
||||
Aufloesungs-Marker (neue Module-Consts `SCENE_RESOLUTION_MARKER_RE` / `SCENE_CODEC_MARKER_RE`, inkl.
|
||||
xvid/divx). Part-Buchstabe a/b bleibt erhalten (Ordnername dient unveraendert als Zielname; nur der
|
||||
RANGE-Zweig schreibt Token um, und a/b ist kein Range). Konservativ: bare „Show.S01E01" ohne Marker bleibt
|
||||
abgelehnt (kein Over-Firing). v1.7.180-Fallback nutzt jetzt dieselben Module-Consts (DRY). Greift in
|
||||
Auto-Rename UND Collect (beide via decideAutoRenameBaseName). 5 Unit- + 1 Collect-Integrationstest.
|
||||
**Methodik-Lektion:** Die naheliegende Hypothese (a/b-Suffix) per Diagnose-Test widerlegt, BEVOR gefixt —
|
||||
das Lookahead genau gelesen statt angenommen. Spart einen Fix am falschen Ort.
|
||||
|
||||
@ -1061,3 +1061,45 @@ describe("isBonusContent (numbered episodes are never bonus)", () => {
|
||||
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)", () => {
|
||||
// Echter Bug (rename-session 2026-06-04): alte deutsche Doku ohne Gruppen-Suffix.
|
||||
// Ordner endet auf ".XviD" (kein "-GROUP") -> buildAutoRenameBaseName lieferte null ->
|
||||
// "kein Zielname" -> Datei landete roh als "safari-fm-s04e08a.avi" in der Library.
|
||||
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 });
|
||||
// Verschiedene Zielnamen -> keine "(2)"-Kollision beim Sammeln.
|
||||
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)", () => {
|
||||
// "Show.S01E01" allein (keine Qualitaets-/Codec-Info, kein -GROUP) ist mehrdeutig ->
|
||||
// weiterhin kein Ableiten (kein Over-Firing auf generische Ordner).
|
||||
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");
|
||||
});
|
||||
});
|
||||
|
||||
@ -9686,6 +9686,72 @@ describe("download manager", () => {
|
||||
void manager;
|
||||
}, 20000);
|
||||
|
||||
it("collect CLEANS a raw .avi whose folder is a complete episode name WITHOUT a -GROUP suffix (safari S04E08a)", async () => {
|
||||
// Echter Bug (rename-session 2026-06-04): alte deutsche Doku ohne Gruppen-Suffix
|
||||
// (Ordner endet ".XviD", kein "-GROUP"). buildAutoRenameBaseName lieferte null -> die
|
||||
// Folge landete ROH als "safari-fm-s04e08a.avi" in der Library. Der Part-Buchstabe a/b
|
||||
// muss erhalten bleiben (Teil 1 vs Teil 2 duerfen nicht kollidieren).
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
|
||||
tempDirs.push(root);
|
||||
|
||||
const packageName = "c284d9d9072eaf3ac314d05f951dd115";
|
||||
const outputDir = path.join(root, "downloads", packageName);
|
||||
const extractDir = path.join(root, "extract", packageName);
|
||||
const mk = (folder: string, raw: string) => {
|
||||
const epDir = path.join(extractDir, folder);
|
||||
fs.mkdirSync(epDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(epDir, raw), Buffer.alloc(4096, 8));
|
||||
};
|
||||
const folderA = "Fluss-Monster.S04E08a.Am.Essequibo.Teil.1.German.DOKU.SATRiP.XviD";
|
||||
const folderB = "Fluss-Monster.S04E08b.Am.Essequibo.Teil.2.German.DOKU.SATRiP.XviD";
|
||||
mk(folderA, "safari-fm-s04e08a.avi");
|
||||
mk(folderB, "safari-fm-s04e08b.avi");
|
||||
|
||||
const session = emptySession();
|
||||
const packageId = `${packageName}-pkg`;
|
||||
const createdAt = Date.now() - 60_000;
|
||||
session.packageOrder = [packageId];
|
||||
session.packages[packageId] = {
|
||||
id: packageId,
|
||||
name: packageName,
|
||||
outputDir,
|
||||
extractDir,
|
||||
status: "completed",
|
||||
itemIds: [],
|
||||
cancelled: false,
|
||||
enabled: true,
|
||||
createdAt,
|
||||
updatedAt: createdAt
|
||||
};
|
||||
|
||||
const mkvLibraryDir = path.join(root, "mkv-library");
|
||||
const manager = new DownloadManager(
|
||||
{
|
||||
...defaultSettings(),
|
||||
outputDir: path.join(root, "downloads"),
|
||||
extractDir: path.join(root, "extract"),
|
||||
autoExtract: true,
|
||||
autoRename4sf4sj: true,
|
||||
collectMkvToLibrary: true,
|
||||
mkvLibraryDir,
|
||||
enableIntegrityCheck: false,
|
||||
cleanupMode: "none"
|
||||
},
|
||||
session,
|
||||
createStoragePaths(path.join(root, "state"))
|
||||
);
|
||||
|
||||
await (manager as any).collectMkvFilesToLibrary(packageId, session.packages[packageId], undefined, false);
|
||||
|
||||
// Beide Folgen sauber benannt in der Library, Part a/b distinkt, KEINE rohen Namen.
|
||||
expect(fs.existsSync(path.join(mkvLibraryDir, `${folderA}.avi`))).toBe(true);
|
||||
expect(fs.existsSync(path.join(mkvLibraryDir, `${folderB}.avi`))).toBe(true);
|
||||
expect(fs.existsSync(path.join(mkvLibraryDir, "safari-fm-s04e08a.avi"))).toBe(false);
|
||||
expect(fs.existsSync(path.join(mkvLibraryDir, "safari-fm-s04e08b.avi"))).toBe(false);
|
||||
|
||||
void manager;
|
||||
}, 20000);
|
||||
|
||||
it("deferred final pass renames fresh files before collecting them (no scene names in library)", async () => {
|
||||
// Folge-Fund zu 18eada9 (verifiziert via Advisor-Gate): 18eada9 schloss den
|
||||
// "frische Datei landet unbenannt"-Bug nur fuer den HYBRID-Pfad (deferFreshFiles=true
|
||||
|
||||
Loading…
Reference in New Issue
Block a user