Compare commits
2 Commits
74aec6f056
...
7b39b33cc7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b39b33cc7 | ||
|
|
339c46bdd2 |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.7.181",
|
"version": "1.7.182",
|
||||||
"description": "Desktop downloader",
|
"description": "Desktop downloader",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"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_RP_TOKEN_RE = /(?:^|[._\-\s])rp(?:[._\-\s]|$)/i;
|
||||||
const SCENE_REPACK_TOKEN_RE = /(?:^|[._\-\s])repack(?:[._\-\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;
|
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_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_FLEXIBLE_GROUP_SUFFIX_RE = /-([A-Za-z0-9]+(?:_[A-Za-z0-9]+)*)$/;
|
||||||
const SCENE_MIXED_GROUP_SUFFIX_RE = /-[^-]*[\/\\|\u2044\u2215][^-]*$/;
|
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 isLegacy4sf4sjFolder = SCENE_RELEASE_FOLDER_RE.test(normalizedFolderName);
|
||||||
const isSceneGroupFolder = hasSceneGroupSuffix(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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1323,7 +1336,7 @@ export function buildAutoRenameBaseName(folderName: string, sourceFileName: stri
|
|||||||
// range with the source's specific episode token. Without this, all
|
// range with the source's specific episode token. Without this, all
|
||||||
// episodes in a range-named folder share the same target name, producing
|
// episodes in a range-named folder share the same target name, producing
|
||||||
// (2)(3)(4) suffixes during MKV collection.
|
// (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;
|
const episodeRangeRe = /(^|[._\-\s])s\d{1,2}e\d{1,3}[-]e?\d{1,3}(?=[._\-\s]|$)/i;
|
||||||
if (episodeRangeRe.test(normalizedFolderName)) {
|
if (episodeRangeRe.test(normalizedFolderName)) {
|
||||||
next = applyEpisodeTokenToFolderName(normalizedFolderName, episodeToken);
|
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
|
// Greift NUR ohne Quell-Episode-Token → schliesst sich mit dem Fabrikations-Guard oben aus
|
||||||
// (Mega-Direct hat einen Quell-Token und kommt nie hierher).
|
// (Mega-Direct hat einen Quell-Token und kommt nie hierher).
|
||||||
if (!extractEpisodeToken(sourceBaseName)) {
|
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) {
|
for (const folderName of folderCandidates) {
|
||||||
const f = sanitizeFilename(String(folderName || "").trim());
|
const f = sanitizeFilename(String(folderName || "").trim());
|
||||||
if (!f) {
|
if (!f) {
|
||||||
continue;
|
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" };
|
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;
|
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
|
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.
|
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);
|
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;
|
void manager;
|
||||||
}, 20000);
|
}, 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 () => {
|
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
|
// Folge-Fund zu 18eada9 (verifiziert via Advisor-Gate): 18eada9 schloss den
|
||||||
// "frische Datei landet unbenannt"-Bug nur fuer den HYBRID-Pfad (deferFreshFiles=true
|
// "frische Datei landet unbenannt"-Bug nur fuer den HYBRID-Pfad (deferFreshFiles=true
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user