Release v1.5.93
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1218adf5f2
commit
bb8fd0646a
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.5.92",
|
"version": "1.5.93",
|
||||||
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -705,6 +705,23 @@ export function buildAutoRenameBaseNameFromFoldersWithOptions(
|
|||||||
return sanitizeFilename(target);
|
return sanitizeFilename(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Last-resort fallback: if no scene-group-suffix folder was found but a folder
|
||||||
|
// has a season token and the source has an episode token, inject the episode anyway.
|
||||||
|
// This handles user-renamed packages like "Mystery Road S02" where the folder has
|
||||||
|
// no scene group suffix but still contains enough info for a useful rename.
|
||||||
|
if (resolvedEpisode && forceEpisodeForSeasonFolder) {
|
||||||
|
for (const folderName of ordered) {
|
||||||
|
if (!SCENE_SEASON_ONLY_RE.test(folderName) || extractEpisodeToken(folderName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let target = applyEpisodeTokenToFolderName(folderName, resolvedEpisode.token);
|
||||||
|
if (globalRepackHint) {
|
||||||
|
target = ensureRepackToken(removeRpTokens(target));
|
||||||
|
}
|
||||||
|
return sanitizeFilename(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2029,7 +2046,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async autoRenameExtractedVideoFiles(extractDir: string): Promise<number> {
|
private async autoRenameExtractedVideoFiles(extractDir: string, pkg?: PackageEntry): Promise<number> {
|
||||||
if (!this.settings.autoRename4sf4sj) {
|
if (!this.settings.autoRename4sf4sj) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -2037,6 +2054,25 @@ export class DownloadManager extends EventEmitter {
|
|||||||
const videoFiles = await this.collectVideoFiles(extractDir);
|
const videoFiles = await this.collectVideoFiles(extractDir);
|
||||||
let renamed = 0;
|
let renamed = 0;
|
||||||
|
|
||||||
|
// Collect additional folder candidates from package metadata (outputDir, item filenames)
|
||||||
|
const packageExtraCandidates: string[] = [];
|
||||||
|
if (pkg) {
|
||||||
|
const outputBase = path.basename(pkg.outputDir || "");
|
||||||
|
if (outputBase) {
|
||||||
|
packageExtraCandidates.push(outputBase);
|
||||||
|
}
|
||||||
|
for (const itemId of pkg.itemIds) {
|
||||||
|
const item = this.session.items[itemId];
|
||||||
|
if (item?.fileName) {
|
||||||
|
const itemBase = path.basename(item.fileName, path.extname(item.fileName));
|
||||||
|
const stripped = itemBase.replace(/\.part\d+$/i, "").replace(/\.vol\d+[+\d]*$/i, "");
|
||||||
|
if (stripped) {
|
||||||
|
packageExtraCandidates.push(stripped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const sourcePath of videoFiles) {
|
for (const sourcePath of videoFiles) {
|
||||||
const sourceName = path.basename(sourcePath);
|
const sourceName = path.basename(sourcePath);
|
||||||
const sourceExt = path.extname(sourceName);
|
const sourceExt = path.extname(sourceName);
|
||||||
@ -2051,6 +2087,14 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
currentDir = parent;
|
currentDir = parent;
|
||||||
}
|
}
|
||||||
|
// Append package-level candidates that aren't already present
|
||||||
|
const seen = new Set(folderCandidates.map(c => c.toLowerCase()));
|
||||||
|
for (const extra of packageExtraCandidates) {
|
||||||
|
if (!seen.has(extra.toLowerCase())) {
|
||||||
|
seen.add(extra.toLowerCase());
|
||||||
|
folderCandidates.push(extra);
|
||||||
|
}
|
||||||
|
}
|
||||||
const targetBaseName = buildAutoRenameBaseNameFromFoldersWithOptions(folderCandidates, sourceBaseName, {
|
const targetBaseName = buildAutoRenameBaseNameFromFoldersWithOptions(folderCandidates, sourceBaseName, {
|
||||||
forceEpisodeForSeasonFolder: true
|
forceEpisodeForSeasonFolder: true
|
||||||
});
|
});
|
||||||
@ -5871,7 +5915,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
|
|
||||||
logger.info(`Hybrid-Extract Ende: pkg=${pkg.name}, extracted=${result.extracted}, failed=${result.failed}`);
|
logger.info(`Hybrid-Extract Ende: pkg=${pkg.name}, extracted=${result.extracted}, failed=${result.failed}`);
|
||||||
if (result.extracted > 0) {
|
if (result.extracted > 0) {
|
||||||
await this.autoRenameExtractedVideoFiles(pkg.extractDir);
|
await this.autoRenameExtractedVideoFiles(pkg.extractDir, pkg);
|
||||||
}
|
}
|
||||||
if (result.failed > 0) {
|
if (result.failed > 0) {
|
||||||
logger.warn(`Hybrid-Extract: ${result.failed} Archive fehlgeschlagen, wird beim finalen Durchlauf erneut versucht`);
|
logger.warn(`Hybrid-Extract: ${result.failed} Archive fehlgeschlagen, wird beim finalen Durchlauf erneut versucht`);
|
||||||
@ -6151,7 +6195,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
} else {
|
} else {
|
||||||
const hasExtractedOutput = await this.directoryHasAnyFiles(pkg.extractDir);
|
const hasExtractedOutput = await this.directoryHasAnyFiles(pkg.extractDir);
|
||||||
if (result.extracted > 0 || hasExtractedOutput) {
|
if (result.extracted > 0 || hasExtractedOutput) {
|
||||||
await this.autoRenameExtractedVideoFiles(pkg.extractDir);
|
await this.autoRenameExtractedVideoFiles(pkg.extractDir, pkg);
|
||||||
}
|
}
|
||||||
const sourceExists = await this.existsAsync(pkg.outputDir);
|
const sourceExists = await this.existsAsync(pkg.outputDir);
|
||||||
let finalStatusText = "";
|
let finalStatusText = "";
|
||||||
|
|||||||
@ -257,6 +257,9 @@ function normalizeLoadedSession(raw: unknown): SessionState {
|
|||||||
const status: DownloadStatus = VALID_DOWNLOAD_STATUSES.has(statusRaw) ? statusRaw : "queued";
|
const status: DownloadStatus = VALID_DOWNLOAD_STATUSES.has(statusRaw) ? statusRaw : "queued";
|
||||||
const providerRaw = asText(item.provider) as DebridProvider;
|
const providerRaw = asText(item.provider) as DebridProvider;
|
||||||
|
|
||||||
|
const onlineStatusRaw = asText(item.onlineStatus);
|
||||||
|
const validOnlineStatuses = new Set(["online", "offline", "checking"]);
|
||||||
|
|
||||||
itemsById[id] = {
|
itemsById[id] = {
|
||||||
id,
|
id,
|
||||||
packageId,
|
packageId,
|
||||||
@ -274,6 +277,7 @@ function normalizeLoadedSession(raw: unknown): SessionState {
|
|||||||
attempts: clampNumber(item.attempts, 0, 0, 10_000),
|
attempts: clampNumber(item.attempts, 0, 0, 10_000),
|
||||||
lastError: asText(item.lastError),
|
lastError: asText(item.lastError),
|
||||||
fullStatus: asText(item.fullStatus),
|
fullStatus: asText(item.fullStatus),
|
||||||
|
onlineStatus: validOnlineStatuses.has(onlineStatusRaw) ? onlineStatusRaw as "online" | "offline" | "checking" : undefined,
|
||||||
createdAt: clampNumber(item.createdAt, now, 0, Number.MAX_SAFE_INTEGER),
|
createdAt: clampNumber(item.createdAt, now, 0, Number.MAX_SAFE_INTEGER),
|
||||||
updatedAt: clampNumber(item.updatedAt, now, 0, Number.MAX_SAFE_INTEGER)
|
updatedAt: clampNumber(item.updatedAt, now, 0, Number.MAX_SAFE_INTEGER)
|
||||||
};
|
};
|
||||||
|
|||||||
@ -620,4 +620,45 @@ describe("buildAutoRenameBaseNameFromFolders", () => {
|
|||||||
);
|
);
|
||||||
expect(result).toBe("Mammon.S01E05E06.German.1080P.Bluray.x264-SMAHD");
|
expect(result).toBe("Mammon.S01E05E06.German.1080P.Bluray.x264-SMAHD");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Last-resort fallback: folder has season but no scene group suffix (user-renamed packages)
|
||||||
|
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 }
|
||||||
|
);
|
||||||
|
// Should use the scene-group folder (hrs), not the custom one
|
||||||
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user