0.12.0-17 Misc fixes
Deploy Docs to GitHub Pages / Build Documentation (push) Has been cancelled
Deploy Docs to GitHub Pages / Deploy to GitHub Pages (push) Has been cancelled

This commit is contained in:
2026-03-30 19:15:23 +00:00
parent 5284dd2a43
commit c67ac593de
29 changed files with 1596 additions and 117 deletions
+132 -5
View File
@@ -5187,7 +5187,7 @@ class PipelineService extends EventEmitter {
...previousJobProgress,
state: previousJobProgress.state || this.snapshot.state,
progress: previousJobProgress.progress ?? this.snapshot.progress ?? 0,
eta: previousJobProgress.eta ?? this.snapshot.eta ?? null,
eta: previousJobProgress.eta ?? null,
statusText: previousJobProgress.statusText ?? this.snapshot.statusText ?? null,
context: {
...(previousJobProgress.context && typeof previousJobProgress.context === 'object'
@@ -5434,6 +5434,54 @@ class PipelineService extends EventEmitter {
return this.resolveCurrentRawPath(rawBaseDir, stored, rawExtraDirs);
}
async resolveUsableRawInputForReadyJob(jobId, job, encodePlan = null) {
const plan = encodePlan && typeof encodePlan === 'object'
? encodePlan
: this.safeParseJson(job?.encode_plan_json);
const mkInfo = this.safeParseJson(job?.makemkv_info_json);
const mediaProfile = this.resolveMediaProfileForJob(job, {
makemkvInfo: mkInfo,
encodePlan: plan,
rawPath: job?.raw_path
});
const settings = await settingsService.getEffectiveSettingsMap(mediaProfile);
const resolvedRawPath = this.resolveCurrentRawPathForSettings(
settings,
mediaProfile,
job?.raw_path
) || String(job?.raw_path || '').trim() || null;
if (!resolvedRawPath || !fs.existsSync(resolvedRawPath)) {
return {
mediaProfile,
resolvedRawPath,
inputPath: null,
hasUsableRawInput: false
};
}
let inputPath = null;
try {
if (hasBluRayBackupStructure(resolvedRawPath)) {
inputPath = resolvedRawPath;
} else {
const playlistDecision = this.resolvePlaylistDecisionForJob(jobId, job);
inputPath = findPreferredRawInput(resolvedRawPath, {
playlistAnalysis: playlistDecision.playlistAnalysis,
selectedPlaylistId: playlistDecision.selectedPlaylist
})?.path || null;
}
} catch (_error) {
inputPath = null;
}
return {
mediaProfile,
resolvedRawPath,
inputPath,
hasUsableRawInput: Boolean(inputPath)
};
}
async alignRawFolderJobId(rawPath, targetJobId) {
const sourceRawPath = String(rawPath || '').trim();
const normalizedTargetJobId = this.normalizeQueueJobId(targetJobId);
@@ -5856,6 +5904,13 @@ class PipelineService extends EventEmitter {
};
}
_broadcastPipelineStateChanged() {
const snapshotPayload = this.getSnapshot();
wsService.broadcast('PIPELINE_STATE_CHANGED', snapshotPayload);
this.emit('stateChanged', snapshotPayload);
return snapshotPayload;
}
_setCdDriveState(devicePath, updates) {
const existing = this.cdDrives.get(devicePath) || {
devicePath,
@@ -6059,6 +6114,7 @@ class PipelineService extends EventEmitter {
source: owner.source,
reason: owner.reason
});
this._broadcastPipelineStateChanged();
return true;
}
@@ -6080,6 +6136,7 @@ class PipelineService extends EventEmitter {
jobId: targetJobId,
reason: String(options?.reason || '').trim() || null
});
this._broadcastPipelineStateChanged();
return true;
}
@@ -7599,8 +7656,18 @@ class PipelineService extends EventEmitter {
async updateProgress(stage, percent, eta, statusText, jobIdOverride = null, options = {}) {
const effectiveJobId = jobIdOverride != null ? Number(jobIdOverride) : this.snapshot.activeJobId;
const previousJobProgress = effectiveJobId != null
? (this.jobProgress.get(effectiveJobId) || {})
: null;
const effectiveProgress = percent ?? this.snapshot.progress;
const effectiveEta = eta ?? this.snapshot.eta;
let effectiveEta;
if (eta !== undefined) {
effectiveEta = eta;
} else if (previousJobProgress && previousJobProgress.eta !== undefined) {
effectiveEta = previousJobProgress.eta ?? null;
} else {
effectiveEta = this.snapshot.eta ?? null;
}
const effectiveStatusText = statusText ?? this.snapshot.statusText;
const progressOptions = options && typeof options === 'object' ? options : {};
const contextPatch = progressOptions.contextPatch && typeof progressOptions.contextPatch === 'object'
@@ -7610,7 +7677,6 @@ class PipelineService extends EventEmitter {
// Update per-job progress so concurrent jobs don't overwrite each other.
if (effectiveJobId != null) {
const previousJobProgress = this.jobProgress.get(effectiveJobId) || {};
const mergedContext = contextPatch
? {
...(previousJobProgress.context && typeof previousJobProgress.context === 'object'
@@ -9926,7 +9992,13 @@ class PipelineService extends EventEmitter {
// Pre-rip jobs bypass the encode queue because the next step is a rip, not an encode.
const jobEncodePlan = this.safeParseJson(preloadedJob.encode_plan_json);
const jobMode = String(jobEncodePlan?.mode || '').trim().toLowerCase();
const willRipFirst = jobMode === 'pre_rip' || Boolean(jobEncodePlan?.preRip);
let willRipFirst = jobMode === 'pre_rip' || Boolean(jobEncodePlan?.preRip);
if (willRipFirst) {
const rawReuse = await this.resolveUsableRawInputForReadyJob(jobId, preloadedJob, jobEncodePlan);
if (rawReuse.hasUsableRawInput) {
willRipFirst = false;
}
}
if (willRipFirst) {
return this.startPreparedJob(jobId, { ...options, immediate: true });
}
@@ -9999,6 +10071,52 @@ class PipelineService extends EventEmitter {
}
if (isPreRipReadyState) {
const rawReuse = await this.resolveUsableRawInputForReadyJob(jobId, job, encodePlanForReadyState);
if (rawReuse.hasUsableRawInput) {
const updatePayload = {
status: 'ENCODING',
last_state: 'ENCODING',
error_message: null,
end_time: null
};
if (
rawReuse.resolvedRawPath
&& normalizeComparablePath(rawReuse.resolvedRawPath) !== normalizeComparablePath(job.raw_path)
) {
updatePayload.raw_path = rawReuse.resolvedRawPath;
}
if (
rawReuse.inputPath
&& normalizeComparablePath(rawReuse.inputPath) !== normalizeComparablePath(job.encode_input_path)
) {
updatePayload.encode_input_path = rawReuse.inputPath;
}
await historyService.updateJob(jobId, updatePayload);
await historyService.appendLog(
jobId,
'SYSTEM',
`Pre-Rip Auswahl erkannt, nutzbares RAW gefunden (${rawReuse.resolvedRawPath}). Starte Encode direkt aus RAW; Laufwerk wird nicht angesprochen.`
);
this.startEncodingFromPrepared(jobId).catch((error) => {
logger.error('startPreparedJob:encode-background-from-prerip-raw-failed', {
jobId,
error: errorToMeta(error)
});
if (error?.jobAlreadyFailed) {
return;
}
this.failJob(jobId, 'ENCODING', error).catch((failError) => {
logger.error('startPreparedJob:encode-background-from-prerip-raw-failJob-failed', {
jobId,
error: errorToMeta(failError)
});
});
});
return { started: true, stage: 'ENCODING', reusedRaw: true };
}
await historyService.updateJob(jobId, {
status: 'RIPPING',
last_state: 'RIPPING',
@@ -12864,6 +12982,15 @@ class PipelineService extends EventEmitter {
}
const ripPlugin = await this.resolveAnalyzePlugin({ mediaProfile }, 'rip').catch(() => null);
if (devicePath) {
const existingLock = this._getDriveLockByPath(devicePath);
const existingLockJobId = Number(existingLock?.jobId || existingLock?.owner?.jobId || 0) || null;
const lockedByOtherJob = (
(diskDetectionService.isDeviceLocked(devicePath) || Boolean(existingLock))
&& existingLockJobId !== Number(jobId)
);
if (lockedByOtherJob) {
throw this._buildDriveLockedError(devicePath, existingLock);
}
this._acquireDriveLockForJob(devicePath, jobId, {
stage: 'RIPPING',
source: 'MAKEMKV_RIP',
@@ -14420,7 +14547,7 @@ class PipelineService extends EventEmitter {
} else if (detail) {
const jobEntry = this.jobProgress.get(Number(normalizedJobId));
const currentProgress = jobEntry?.progress ?? Number(this.snapshot.progress || 0);
const currentEta = jobEntry?.eta ?? this.snapshot.eta;
const currentEta = jobEntry?.eta ?? runInfo.eta ?? null;
const statusText = composeStatusText(stage, currentProgress, runInfo.lastDetail);
void this.updateProgress(stage, currentProgress, currentEta, statusText, normalizedJobId);
}