1.0.0-rc3 rc3
This commit is contained in:
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-backend",
|
"name": "ripster-backend",
|
||||||
"version": "1.0.0-rc2",
|
"version": "1.0.0-rc3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster-backend",
|
"name": "ripster-backend",
|
||||||
"version": "1.0.0-rc2",
|
"version": "1.0.0-rc3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-backend",
|
"name": "ripster-backend",
|
||||||
"version": "1.0.0-rc2",
|
"version": "1.0.0-rc3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -107,6 +107,16 @@ function nowIso() {
|
|||||||
return new Date().toISOString();
|
return new Date().toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function appendLinesInPlace(target, lines) {
|
||||||
|
if (!Array.isArray(target) || !Array.isArray(lines) || lines.length === 0) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
for (const line of lines) {
|
||||||
|
target.push(line);
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeLifecycleJobState(value) {
|
function normalizeLifecycleJobState(value) {
|
||||||
return String(value || '').trim().toUpperCase();
|
return String(value || '').trim().toUpperCase();
|
||||||
}
|
}
|
||||||
@@ -9637,8 +9647,6 @@ function applyPreviousSelectionDefaultsToReviewPlan(reviewPlan, previousPlan = n
|
|||||||
const postEncodeScriptIds = normalizeScriptIdList(previousPlan?.postEncodeScriptIds || []);
|
const postEncodeScriptIds = normalizeScriptIdList(previousPlan?.postEncodeScriptIds || []);
|
||||||
const preEncodeChainIds = normalizeChainIdList(previousPlan?.preEncodeChainIds || []);
|
const preEncodeChainIds = normalizeChainIdList(previousPlan?.preEncodeChainIds || []);
|
||||||
const postEncodeChainIds = normalizeChainIdList(previousPlan?.postEncodeChainIds || []);
|
const postEncodeChainIds = normalizeChainIdList(previousPlan?.postEncodeChainIds || []);
|
||||||
const userPreset = normalizeUserPresetForPlan(previousPlan?.userPreset || null);
|
|
||||||
|
|
||||||
nextPlan = {
|
nextPlan = {
|
||||||
...nextPlan,
|
...nextPlan,
|
||||||
preEncodeScriptIds,
|
preEncodeScriptIds,
|
||||||
@@ -9647,7 +9655,7 @@ function applyPreviousSelectionDefaultsToReviewPlan(reviewPlan, previousPlan = n
|
|||||||
postEncodeScripts: buildScriptDescriptorList(postEncodeScriptIds, previousPlan?.postEncodeScripts || []),
|
postEncodeScripts: buildScriptDescriptorList(postEncodeScriptIds, previousPlan?.postEncodeScripts || []),
|
||||||
preEncodeChainIds,
|
preEncodeChainIds,
|
||||||
postEncodeChainIds,
|
postEncodeChainIds,
|
||||||
userPreset,
|
userPreset: null,
|
||||||
reviewConfirmed: false,
|
reviewConfirmed: false,
|
||||||
reviewConfirmedAt: null,
|
reviewConfirmedAt: null,
|
||||||
prefilledFromPreviousRun: true,
|
prefilledFromPreviousRun: true,
|
||||||
@@ -9659,8 +9667,7 @@ function applyPreviousSelectionDefaultsToReviewPlan(reviewPlan, previousPlan = n
|
|||||||
|| preEncodeScriptIds.length > 0
|
|| preEncodeScriptIds.length > 0
|
||||||
|| postEncodeScriptIds.length > 0
|
|| postEncodeScriptIds.length > 0
|
||||||
|| preEncodeChainIds.length > 0
|
|| preEncodeChainIds.length > 0
|
||||||
|| postEncodeChainIds.length > 0
|
|| postEncodeChainIds.length > 0;
|
||||||
|| Boolean(userPreset);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plan: nextPlan,
|
plan: nextPlan,
|
||||||
@@ -9670,7 +9677,7 @@ function applyPreviousSelectionDefaultsToReviewPlan(reviewPlan, previousPlan = n
|
|||||||
postEncodeScriptCount: postEncodeScriptIds.length,
|
postEncodeScriptCount: postEncodeScriptIds.length,
|
||||||
preEncodeChainCount: preEncodeChainIds.length,
|
preEncodeChainCount: preEncodeChainIds.length,
|
||||||
postEncodeChainCount: postEncodeChainIds.length,
|
postEncodeChainCount: postEncodeChainIds.length,
|
||||||
userPresetApplied: Boolean(userPreset)
|
userPresetApplied: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18149,7 +18156,7 @@ class PipelineService extends EventEmitter {
|
|||||||
source: 'HANDBRAKE_SCAN',
|
source: 'HANDBRAKE_SCAN',
|
||||||
silent: !this.isPrimaryJob(jobId)
|
silent: !this.isPrimaryJob(jobId)
|
||||||
});
|
});
|
||||||
lines.push(...pluginScan.scanLines);
|
appendLinesInPlace(lines, pluginScan.scanLines);
|
||||||
runInfo = pluginScan.runInfo || null;
|
runInfo = pluginScan.runInfo || null;
|
||||||
sourceArg = pluginScan.sourceArg || null;
|
sourceArg = pluginScan.sourceArg || null;
|
||||||
reviewPluginExecution = this.mergePluginExecutionState(
|
reviewPluginExecution = this.mergePluginExecutionState(
|
||||||
@@ -18514,7 +18521,7 @@ class PipelineService extends EventEmitter {
|
|||||||
source: 'HANDBRAKE_SCAN_PLAYLIST_MAP',
|
source: 'HANDBRAKE_SCAN_PLAYLIST_MAP',
|
||||||
silent: !this.isPrimaryJob(jobId)
|
silent: !this.isPrimaryJob(jobId)
|
||||||
});
|
});
|
||||||
resolveScanLines.push(...pluginScan.scanLines);
|
appendLinesInPlace(resolveScanLines, pluginScan.scanLines);
|
||||||
reviewPluginExecution = this.mergePluginExecutionState(
|
reviewPluginExecution = this.mergePluginExecutionState(
|
||||||
reviewPluginExecution,
|
reviewPluginExecution,
|
||||||
pluginScan.pluginExecution
|
pluginScan.pluginExecution
|
||||||
@@ -18656,7 +18663,7 @@ class PipelineService extends EventEmitter {
|
|||||||
source: 'HANDBRAKE_SCAN_PLAYLIST_MAP',
|
source: 'HANDBRAKE_SCAN_PLAYLIST_MAP',
|
||||||
silent: !this.isPrimaryJob(jobId)
|
silent: !this.isPrimaryJob(jobId)
|
||||||
});
|
});
|
||||||
scanLines.push(...pluginScan.scanLines);
|
appendLinesInPlace(scanLines, pluginScan.scanLines);
|
||||||
reviewPluginExecution = this.mergePluginExecutionState(
|
reviewPluginExecution = this.mergePluginExecutionState(
|
||||||
reviewPluginExecution,
|
reviewPluginExecution,
|
||||||
pluginScan.pluginExecution
|
pluginScan.pluginExecution
|
||||||
@@ -19315,8 +19322,15 @@ class PipelineService extends EventEmitter {
|
|||||||
source: 'HANDBRAKE_SCAN_PLAYLIST_MAP',
|
source: 'HANDBRAKE_SCAN_PLAYLIST_MAP',
|
||||||
silent: !this.isPrimaryJob(jobId)
|
silent: !this.isPrimaryJob(jobId)
|
||||||
});
|
});
|
||||||
resolveScanLines.push(...pluginScan.scanLines);
|
appendLinesInPlace(resolveScanLines, pluginScan.scanLines);
|
||||||
handBrakeResolveRunInfo = pluginScan.runInfo || null;
|
handBrakeResolveRunInfo = pluginScan.runInfo || null;
|
||||||
|
const resolveScanStatus = String(handBrakeResolveRunInfo?.status || '').trim().toUpperCase();
|
||||||
|
if (resolveScanStatus === 'CANCELLED') {
|
||||||
|
const error = new Error('HandBrake Playlist-Mapping wurde abgebrochen.');
|
||||||
|
error.statusCode = 409;
|
||||||
|
error.runInfo = handBrakeResolveRunInfo;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
reviewPluginExecution = this.mergePluginExecutionState(
|
reviewPluginExecution = this.mergePluginExecutionState(
|
||||||
reviewPluginExecution,
|
reviewPluginExecution,
|
||||||
pluginScan.pluginExecution
|
pluginScan.pluginExecution
|
||||||
@@ -19328,6 +19342,16 @@ class PipelineService extends EventEmitter {
|
|||||||
error.runInfo = handBrakeResolveRunInfo;
|
error.runInfo = handBrakeResolveRunInfo;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
const knownPlaylists = listAvailableHandBrakePlaylists(resolveScanJson);
|
||||||
|
if (knownPlaylists.length === 0 && resolveScanStatus && resolveScanStatus !== 'SUCCESS') {
|
||||||
|
const error = new Error(
|
||||||
|
`HandBrake Playlist-Mapping unvollständig (${resolveScanStatus}). `
|
||||||
|
+ 'Scan enthält keine erkennbaren Playlist-IDs.'
|
||||||
|
);
|
||||||
|
error.statusCode = resolveScanStatus === 'CANCELLED' ? 409 : 502;
|
||||||
|
error.runInfo = handBrakeResolveRunInfo;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
resolvedHandBrakeTitleId = resolveHandBrakeTitleIdForPlaylist(resolveScanJson, resolvedPlaylistId, {
|
resolvedHandBrakeTitleId = resolveHandBrakeTitleIdForPlaylist(resolveScanJson, resolvedPlaylistId, {
|
||||||
expectedMakemkvTitleId: selectedTitleForReview,
|
expectedMakemkvTitleId: selectedTitleForReview,
|
||||||
@@ -19336,7 +19360,6 @@ class PipelineService extends EventEmitter {
|
|||||||
playlistAliases: playlistAliasesForResolve
|
playlistAliases: playlistAliasesForResolve
|
||||||
});
|
});
|
||||||
if (!resolvedHandBrakeTitleId) {
|
if (!resolvedHandBrakeTitleId) {
|
||||||
const knownPlaylists = listAvailableHandBrakePlaylists(resolveScanJson);
|
|
||||||
const error = new Error(
|
const error = new Error(
|
||||||
`Kein HandBrake-Titel für ${toPlaylistFile(resolvedPlaylistId)} gefunden.`
|
`Kein HandBrake-Titel für ${toPlaylistFile(resolvedPlaylistId)} gefunden.`
|
||||||
+ ` ${knownPlaylists.length > 0 ? `Scan-Playlists: ${knownPlaylists.map((id) => `${id}.mpls`).join(', ')}` : 'Scan enthält keine erkennbaren Playlist-IDs.'}`
|
+ ` ${knownPlaylists.length > 0 ? `Scan-Playlists: ${knownPlaylists.map((id) => `${id}.mpls`).join(', ')}` : 'Scan enthält keine erkennbaren Playlist-IDs.'}`
|
||||||
@@ -22781,6 +22804,10 @@ class PipelineService extends EventEmitter {
|
|||||||
: '';
|
: '';
|
||||||
const hasExplicitUserPresetSelection = !multipartSettingsLocked && explicitUserPresetId !== null;
|
const hasExplicitUserPresetSelection = !multipartSettingsLocked && explicitUserPresetId !== null;
|
||||||
const hasExplicitHandBrakePresetSelection = !multipartSettingsLocked && Boolean(rawHandBrakePreset);
|
const hasExplicitHandBrakePresetSelection = !multipartSettingsLocked && Boolean(rawHandBrakePreset);
|
||||||
|
const explicitPresetClearRequested = !multipartSettingsLocked
|
||||||
|
&& (hasUserPresetIdField || hasHandBrakePresetField)
|
||||||
|
&& explicitUserPresetId === null
|
||||||
|
&& !rawHandBrakePreset;
|
||||||
let resolvedUserPreset = null;
|
let resolvedUserPreset = null;
|
||||||
if (hasExplicitUserPresetSelection) {
|
if (hasExplicitUserPresetSelection) {
|
||||||
resolvedUserPreset = await userPresetService.getPresetById(explicitUserPresetId);
|
resolvedUserPreset = await userPresetService.getPresetById(explicitUserPresetId);
|
||||||
@@ -22799,7 +22826,7 @@ class PipelineService extends EventEmitter {
|
|||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
if (!resolvedUserPreset) {
|
if (!resolvedUserPreset && !explicitPresetClearRequested) {
|
||||||
resolvedUserPreset = normalizeUserPresetForPlan(
|
resolvedUserPreset = normalizeUserPresetForPlan(
|
||||||
planForConfirm?.userPreset || encodePlan?.userPreset || null
|
planForConfirm?.userPreset || encodePlan?.userPreset || null
|
||||||
);
|
);
|
||||||
@@ -22833,14 +22860,18 @@ class PipelineService extends EventEmitter {
|
|||||||
const requiresExplicitPreset = readyMediaProfile === 'dvd'
|
const requiresExplicitPreset = readyMediaProfile === 'dvd'
|
||||||
|| readyMediaProfile === 'bluray'
|
|| readyMediaProfile === 'bluray'
|
||||||
|| (readyMediaProfile === 'converter' && confirmedConverterMediaType !== 'audio');
|
|| (readyMediaProfile === 'converter' && confirmedConverterMediaType !== 'audio');
|
||||||
const planPresetName = String(
|
const planPresetName = explicitPresetClearRequested
|
||||||
|
? ''
|
||||||
|
: String(
|
||||||
planForConfirm?.userPreset?.handbrakePreset
|
planForConfirm?.userPreset?.handbrakePreset
|
||||||
|| encodePlan?.userPreset?.handbrakePreset
|
|| encodePlan?.userPreset?.handbrakePreset
|
||||||
|| planForConfirm?.selectors?.preset
|
|| planForConfirm?.selectors?.preset
|
||||||
|| encodePlan?.selectors?.preset
|
|| encodePlan?.selectors?.preset
|
||||||
|| ''
|
|| ''
|
||||||
).trim();
|
).trim();
|
||||||
const planExtraArgs = String(
|
const planExtraArgs = explicitPresetClearRequested
|
||||||
|
? ''
|
||||||
|
: String(
|
||||||
planForConfirm?.userPreset?.extraArgs
|
planForConfirm?.userPreset?.extraArgs
|
||||||
|| encodePlan?.userPreset?.extraArgs
|
|| encodePlan?.userPreset?.extraArgs
|
||||||
|| planForConfirm?.selectors?.extraArgs
|
|| planForConfirm?.selectors?.extraArgs
|
||||||
@@ -23800,7 +23831,7 @@ class PipelineService extends EventEmitter {
|
|||||||
source: 'HANDBRAKE_SCAN_DVD',
|
source: 'HANDBRAKE_SCAN_DVD',
|
||||||
silent: !this.isPrimaryJob(jobId)
|
silent: !this.isPrimaryJob(jobId)
|
||||||
});
|
});
|
||||||
dvdScanLines.push(...pluginScan.scanLines);
|
appendLinesInPlace(dvdScanLines, pluginScan.scanLines);
|
||||||
dvdScanRunInfo = pluginScan.runInfo || null;
|
dvdScanRunInfo = pluginScan.runInfo || null;
|
||||||
reviewPluginExecution = this.mergePluginExecutionState(
|
reviewPluginExecution = this.mergePluginExecutionState(
|
||||||
reviewPluginExecution,
|
reviewPluginExecution,
|
||||||
@@ -24548,7 +24579,7 @@ class PipelineService extends EventEmitter {
|
|||||||
source: 'HANDBRAKE_SCAN_DVD_TITLES',
|
source: 'HANDBRAKE_SCAN_DVD_TITLES',
|
||||||
silent: !this.isPrimaryJob(jobId)
|
silent: !this.isPrimaryJob(jobId)
|
||||||
});
|
});
|
||||||
dvdTitleScanLines.push(...pluginScan.scanLines);
|
appendLinesInPlace(dvdTitleScanLines, pluginScan.scanLines);
|
||||||
dvdTitleScanRunInfo = pluginScan.runInfo || null;
|
dvdTitleScanRunInfo = pluginScan.runInfo || null;
|
||||||
reviewPluginExecution = this.mergePluginExecutionState(
|
reviewPluginExecution = this.mergePluginExecutionState(
|
||||||
reviewPluginExecution,
|
reviewPluginExecution,
|
||||||
|
|||||||
@@ -308,12 +308,38 @@ function removeSelectionArgs(extraArgs) {
|
|||||||
|
|
||||||
const isAudioWithValue = AUDIO_SELECTION_KEYS_WITH_VALUE.has(key);
|
const isAudioWithValue = AUDIO_SELECTION_KEYS_WITH_VALUE.has(key);
|
||||||
const isAudioFlagOnly = AUDIO_SELECTION_KEYS_FLAG_ONLY.has(key);
|
const isAudioFlagOnly = AUDIO_SELECTION_KEYS_FLAG_ONLY.has(key);
|
||||||
const isSubtitleWithValue = SUBTITLE_SELECTION_KEYS_WITH_VALUE.has(key)
|
const isSubtitleSelectionWithValue = SUBTITLE_SELECTION_KEYS_WITH_VALUE.has(key);
|
||||||
|| SUBTITLE_FLAG_KEYS_WITH_VALUE.has(key);
|
const isSubtitleFlagWithValue = SUBTITLE_FLAG_KEYS_WITH_VALUE.has(key);
|
||||||
|
const isSubtitleWithValue = isSubtitleSelectionWithValue || isSubtitleFlagWithValue;
|
||||||
const isSubtitleFlagOnly = SUBTITLE_SELECTION_KEYS_FLAG_ONLY.has(key);
|
const isSubtitleFlagOnly = SUBTITLE_SELECTION_KEYS_FLAG_ONLY.has(key);
|
||||||
const isTitleWithValue = TITLE_SELECTION_KEYS_WITH_VALUE.has(key);
|
const isTitleWithValue = TITLE_SELECTION_KEYS_WITH_VALUE.has(key);
|
||||||
const skip = isAudioWithValue || isAudioFlagOnly || isSubtitleWithValue || isSubtitleFlagOnly || isTitleWithValue;
|
const skip = isAudioWithValue || isAudioFlagOnly || isSubtitleWithValue || isSubtitleFlagOnly || isTitleWithValue;
|
||||||
|
|
||||||
|
if (isSubtitleFlagWithValue) {
|
||||||
|
const inlineValue = token.includes('=')
|
||||||
|
? token.slice(token.indexOf('=') + 1)
|
||||||
|
: '';
|
||||||
|
const hasAttachedValue = token.includes('=');
|
||||||
|
const nextToken = String(args[i + 1] || '');
|
||||||
|
const hasSeparateValue = !hasAttachedValue && nextToken && !nextToken.startsWith('-');
|
||||||
|
const candidateValue = String(
|
||||||
|
hasAttachedValue
|
||||||
|
? inlineValue
|
||||||
|
: (hasSeparateValue ? nextToken : '')
|
||||||
|
).trim().toLowerCase();
|
||||||
|
|
||||||
|
// Keep explicit "none" subtitle flags from extra args.
|
||||||
|
// This allows users to intentionally clear subtitle burn/default/forced behavior.
|
||||||
|
if (candidateValue === 'none') {
|
||||||
|
filtered.push(token);
|
||||||
|
if (hasSeparateValue) {
|
||||||
|
filtered.push(nextToken);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!skip) {
|
if (!skip) {
|
||||||
filtered.push(token);
|
filtered.push(token);
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-frontend",
|
"name": "ripster-frontend",
|
||||||
"version": "1.0.0-rc2",
|
"version": "1.0.0-rc3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster-frontend",
|
"name": "ripster-frontend",
|
||||||
"version": "1.0.0-rc2",
|
"version": "1.0.0-rc3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-frontend",
|
"name": "ripster-frontend",
|
||||||
"version": "1.0.0-rc2",
|
"version": "1.0.0-rc3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -449,6 +449,11 @@ function App() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
const isUnknownRunningProgress = Boolean(
|
||||||
|
normalizedProgressJobId
|
||||||
|
&& incomingIsRunning
|
||||||
|
&& !hasKnownBinding
|
||||||
|
);
|
||||||
const allowRunningTransitionFromTerminal = Boolean(
|
const allowRunningTransitionFromTerminal = Boolean(
|
||||||
normalizedProgressJobId
|
normalizedProgressJobId
|
||||||
&& incomingIsRunning
|
&& incomingIsRunning
|
||||||
@@ -465,7 +470,7 @@ function App() {
|
|||||||
&& (
|
&& (
|
||||||
prevJobProgressIsTerminal
|
prevJobProgressIsTerminal
|
||||||
|| matchingCdDriveIsTerminal
|
|| matchingCdDriveIsTerminal
|
||||||
|| !hasKnownBinding
|
|| isUnknownRunningProgress
|
||||||
|| regressesStableJobState
|
|| regressesStableJobState
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -587,12 +587,37 @@ function removeSelectionArgs(extraArgs) {
|
|||||||
|
|
||||||
const isAudioWithValue = AUDIO_SELECTION_KEYS_WITH_VALUE.has(key);
|
const isAudioWithValue = AUDIO_SELECTION_KEYS_WITH_VALUE.has(key);
|
||||||
const isAudioFlagOnly = AUDIO_SELECTION_KEYS_FLAG_ONLY.has(key);
|
const isAudioFlagOnly = AUDIO_SELECTION_KEYS_FLAG_ONLY.has(key);
|
||||||
const isSubtitleWithValue = SUBTITLE_SELECTION_KEYS_WITH_VALUE.has(key)
|
const isSubtitleSelectionWithValue = SUBTITLE_SELECTION_KEYS_WITH_VALUE.has(key);
|
||||||
|| SUBTITLE_FLAG_KEYS_WITH_VALUE.has(key);
|
const isSubtitleFlagWithValue = SUBTITLE_FLAG_KEYS_WITH_VALUE.has(key);
|
||||||
|
const isSubtitleWithValue = isSubtitleSelectionWithValue || isSubtitleFlagWithValue;
|
||||||
const isSubtitleFlagOnly = SUBTITLE_SELECTION_KEYS_FLAG_ONLY.has(key);
|
const isSubtitleFlagOnly = SUBTITLE_SELECTION_KEYS_FLAG_ONLY.has(key);
|
||||||
const isTitleWithValue = TITLE_SELECTION_KEYS_WITH_VALUE.has(key);
|
const isTitleWithValue = TITLE_SELECTION_KEYS_WITH_VALUE.has(key);
|
||||||
const skip = isAudioWithValue || isAudioFlagOnly || isSubtitleWithValue || isSubtitleFlagOnly || isTitleWithValue;
|
const skip = isAudioWithValue || isAudioFlagOnly || isSubtitleWithValue || isSubtitleFlagOnly || isTitleWithValue;
|
||||||
|
|
||||||
|
if (isSubtitleFlagWithValue) {
|
||||||
|
const inlineValue = token.includes('=')
|
||||||
|
? token.slice(token.indexOf('=') + 1)
|
||||||
|
: '';
|
||||||
|
const hasAttachedValue = token.includes('=');
|
||||||
|
const nextToken = String(args[i + 1] || '');
|
||||||
|
const hasSeparateValue = !hasAttachedValue && nextToken && !nextToken.startsWith('-');
|
||||||
|
const candidateValue = String(
|
||||||
|
hasAttachedValue
|
||||||
|
? inlineValue
|
||||||
|
: (hasSeparateValue ? nextToken : '')
|
||||||
|
).trim().toLowerCase();
|
||||||
|
|
||||||
|
// Keep explicit "none" subtitle flags in preview, same as backend command builder.
|
||||||
|
if (candidateValue === 'none') {
|
||||||
|
filtered.push(token);
|
||||||
|
if (hasSeparateValue) {
|
||||||
|
filtered.push(nextToken);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!skip) {
|
if (!skip) {
|
||||||
filtered.push(token);
|
filtered.push(token);
|
||||||
continue;
|
continue;
|
||||||
@@ -709,9 +734,10 @@ function buildHandBrakeCommandPreview({
|
|||||||
subtitleLanguageOrder: selectedSubtitleLanguageOrder,
|
subtitleLanguageOrder: selectedSubtitleLanguageOrder,
|
||||||
fallbackSelectedSubtitleTrackIds: normalizedSelectedSubtitleTrackIds,
|
fallbackSelectedSubtitleTrackIds: normalizedSelectedSubtitleTrackIds,
|
||||||
fallbackSelectedSubtitleTrackIdsOrdered: normalizedSelectedSubtitleTrackIds,
|
fallbackSelectedSubtitleTrackIdsOrdered: normalizedSelectedSubtitleTrackIds,
|
||||||
hasExplicitVariantSelection: normalizedSubtitleSelectionMode === 'track_ids'
|
// Track-ID mode must use fallbackSelectedSubtitleTrackIds instead of forcing
|
||||||
? true
|
// variant-explicit mode, otherwise preview can incorrectly collapse to "-s none".
|
||||||
: hasExplicitSelectedVariantSelection
|
hasExplicitVariantSelection: normalizedSubtitleSelectionMode === 'variants'
|
||||||
|
&& hasExplicitSelectedVariantSelection
|
||||||
});
|
});
|
||||||
const subtitleTrackIds = resolvedSubtitleSelection.subtitleTrackIds.length > 0
|
const subtitleTrackIds = resolvedSubtitleSelection.subtitleTrackIds.length > 0
|
||||||
? resolvedSubtitleSelection.subtitleTrackIds
|
? resolvedSubtitleSelection.subtitleTrackIds
|
||||||
@@ -1894,6 +1920,17 @@ export default function MediaInfoReviewPanel({
|
|||||||
const isUserPresetActive = Boolean(selectedUserPreset);
|
const isUserPresetActive = Boolean(selectedUserPreset);
|
||||||
const trimmedHandBrakePreset = String(selectedHandBrakePreset || '').trim();
|
const trimmedHandBrakePreset = String(selectedHandBrakePreset || '').trim();
|
||||||
const isHandBrakePresetActive = !isUserPresetActive && Boolean(trimmedHandBrakePreset);
|
const isHandBrakePresetActive = !isUserPresetActive && Boolean(trimmedHandBrakePreset);
|
||||||
|
const settingsPresetFallback = String(review?.selectors?.preset || '').trim();
|
||||||
|
const settingsExtraArgsFallback = String(review?.selectors?.extraArgs || '').trim();
|
||||||
|
const usesSettingsFallbackOnly = !isUserPresetActive && !isHandBrakePresetActive;
|
||||||
|
const hasSettingsPresetFallback = Boolean(settingsPresetFallback);
|
||||||
|
const hasSettingsExtraArgsFallback = Boolean(settingsExtraArgsFallback);
|
||||||
|
const hasInvalidPresetConfig = usesSettingsFallbackOnly
|
||||||
|
&& !hasSettingsPresetFallback
|
||||||
|
&& !hasSettingsExtraArgsFallback;
|
||||||
|
const hasSettingsArgsOnlyConfig = usesSettingsFallbackOnly
|
||||||
|
&& !hasSettingsPresetFallback
|
||||||
|
&& hasSettingsExtraArgsFallback;
|
||||||
const effectivePresetOverride = isUserPresetActive
|
const effectivePresetOverride = isUserPresetActive
|
||||||
? { handbrakePreset: selectedUserPreset.handbrakePreset || '', extraArgs: selectedUserPreset.extraArgs || '' }
|
? { handbrakePreset: selectedUserPreset.handbrakePreset || '', extraArgs: selectedUserPreset.extraArgs || '' }
|
||||||
: (isHandBrakePresetActive ? { handbrakePreset: trimmedHandBrakePreset, extraArgs: '' } : null);
|
: (isHandBrakePresetActive ? { handbrakePreset: trimmedHandBrakePreset, extraArgs: '' } : null);
|
||||||
@@ -1940,7 +1977,7 @@ export default function MediaInfoReviewPanel({
|
|||||||
const audioCopyMaskSourceLabel = String(effectiveAudioSelector?.copyMaskSource || '').trim() || 'default';
|
const audioCopyMaskSourceLabel = String(effectiveAudioSelector?.copyMaskSource || '').trim() || 'default';
|
||||||
const audioFallbackSourceLabel = String(effectiveAudioSelector?.fallbackSource || '').trim() || 'default';
|
const audioFallbackSourceLabel = String(effectiveAudioSelector?.fallbackSource || '').trim() || 'default';
|
||||||
const presetDropdownOptions = (() => {
|
const presetDropdownOptions = (() => {
|
||||||
const options = [];
|
const options = [{ label: 'Kein Preset', value: 'none' }];
|
||||||
if (hasUserPresets) {
|
if (hasUserPresets) {
|
||||||
options.push({ label: 'User-Presets/', value: '__group__userpresets', disabled: true });
|
options.push({ label: 'User-Presets/', value: '__group__userpresets', disabled: true });
|
||||||
for (const preset of normalizedUserPresets) {
|
for (const preset of normalizedUserPresets) {
|
||||||
@@ -1990,7 +2027,7 @@ export default function MediaInfoReviewPanel({
|
|||||||
})();
|
})();
|
||||||
const selectedPresetToken = isUserPresetActive
|
const selectedPresetToken = isUserPresetActive
|
||||||
? `user:${selectedUserPreset.id}`
|
? `user:${selectedUserPreset.id}`
|
||||||
: (trimmedHandBrakePreset ? `hb:${trimmedHandBrakePreset}` : null);
|
: (trimmedHandBrakePreset ? `hb:${trimmedHandBrakePreset}` : 'none');
|
||||||
|
|
||||||
const scriptCatalog = (Array.isArray(availableScripts) ? availableScripts : [])
|
const scriptCatalog = (Array.isArray(availableScripts) ? availableScripts : [])
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
@@ -2221,6 +2258,15 @@ export default function MediaInfoReviewPanel({
|
|||||||
if (typeof onHandBrakePresetChange === 'function') {
|
if (typeof onHandBrakePresetChange === 'function') {
|
||||||
onHandBrakePresetChange(presetName);
|
onHandBrakePresetChange(presetName);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value === 'none') {
|
||||||
|
if (typeof onUserPresetChange === 'function') {
|
||||||
|
onUserPresetChange(null);
|
||||||
|
}
|
||||||
|
if (typeof onHandBrakePresetChange === 'function') {
|
||||||
|
onHandBrakePresetChange('');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
placeholder="Preset auswählen …"
|
placeholder="Preset auswählen …"
|
||||||
@@ -2242,6 +2288,20 @@ export default function MediaInfoReviewPanel({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{!compactTitleSelection && !isTitleSelectionMode && hasInvalidPresetConfig ? (
|
||||||
|
<div className="media-review-notes">
|
||||||
|
<small>
|
||||||
|
Warnung: Weder HandBrake-Preset noch Extra-Args sind in den Settings gesetzt. Der CLI-Command kann so nicht valide gebaut werden.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{!compactTitleSelection && !isTitleSelectionMode && hasSettingsArgsOnlyConfig ? (
|
||||||
|
<div className="media-review-notes">
|
||||||
|
<small>
|
||||||
|
Hinweis: Es wird kein Preset verwendet. Nur CLI-Parameter aus den Settings werden genutzt - bitte auf Korrektheit prüfen.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
{!compactTitleSelection && !isTitleSelectionMode ? (
|
{!compactTitleSelection && !isTitleSelectionMode ? (
|
||||||
<div className="media-review-meta">
|
<div className="media-review-meta">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -2342,8 +2342,11 @@ export default function PipelineStatusCard({
|
|||||||
const canConfirmReview = !reviewPlaylistDecisionRequired || hasSelectedEncodeTitle;
|
const canConfirmReview = !reviewPlaylistDecisionRequired || hasSelectedEncodeTitle;
|
||||||
const trimmedHandBrakePreset = String(selectedHandBrakePreset || '').trim();
|
const trimmedHandBrakePreset = String(selectedHandBrakePreset || '').trim();
|
||||||
const settingsPresetFallback = String(mediaInfoReview?.selectors?.preset || '').trim();
|
const settingsPresetFallback = String(mediaInfoReview?.selectors?.preset || '').trim();
|
||||||
|
const settingsExtraArgsFallback = String(mediaInfoReview?.selectors?.extraArgs || '').trim();
|
||||||
const hasSelectedPreset = Boolean(selectedUserPresetId) || Boolean(trimmedHandBrakePreset);
|
const hasSelectedPreset = Boolean(selectedUserPresetId) || Boolean(trimmedHandBrakePreset);
|
||||||
const hasEffectiveSelectedPreset = hasSelectedPreset || Boolean(settingsPresetFallback);
|
const hasEffectiveSelectedPreset = hasSelectedPreset
|
||||||
|
|| Boolean(settingsPresetFallback)
|
||||||
|
|| Boolean(settingsExtraArgsFallback);
|
||||||
const converterMediaType = String(mediaInfoReview?.converterMediaType || 'video').trim().toLowerCase() || 'video';
|
const converterMediaType = String(mediaInfoReview?.converterMediaType || 'video').trim().toLowerCase() || 'video';
|
||||||
const showConverterConfig = jobMediaProfile === 'converter' && converterMediaType !== 'audio';
|
const showConverterConfig = jobMediaProfile === 'converter' && converterMediaType !== 'audio';
|
||||||
const requiresPresetSelection = showConverterConfig || jobMediaProfile === 'dvd' || jobMediaProfile === 'bluray';
|
const requiresPresetSelection = showConverterConfig || jobMediaProfile === 'dvd' || jobMediaProfile === 'bluray';
|
||||||
@@ -3856,12 +3859,12 @@ export default function PipelineStatusCard({
|
|||||||
selectedPostEncodeChainIds: selectedPostChainIds,
|
selectedPostEncodeChainIds: selectedPostChainIds,
|
||||||
selectedPreEncodeChainIds: selectedPreChainIds
|
selectedPreEncodeChainIds: selectedPreChainIds
|
||||||
};
|
};
|
||||||
if (selectedUserPresetId !== null && selectedUserPresetId !== undefined) {
|
startPayload.selectedUserPresetId = (
|
||||||
startPayload.selectedUserPresetId = selectedUserPresetId;
|
selectedUserPresetId !== null && selectedUserPresetId !== undefined
|
||||||
}
|
? selectedUserPresetId
|
||||||
if (String(selectedHandBrakePreset || '').trim()) {
|
: null
|
||||||
|
);
|
||||||
startPayload.selectedHandBrakePreset = String(selectedHandBrakePreset || '').trim();
|
startPayload.selectedHandBrakePreset = String(selectedHandBrakePreset || '').trim();
|
||||||
}
|
|
||||||
await onStart(retryJobId, startPayload);
|
await onStart(retryJobId, startPayload);
|
||||||
}}
|
}}
|
||||||
loading={busy}
|
loading={busy}
|
||||||
|
|||||||
@@ -97,6 +97,10 @@ const HISTORY_WINDOWS = [
|
|||||||
{ label: '4d', hours: 96 }
|
{ label: '4d', hours: 96 }
|
||||||
];
|
];
|
||||||
const HISTORY_STACK_BREAKPOINT = 1700;
|
const HISTORY_STACK_BREAKPOINT = 1700;
|
||||||
|
const HISTORY_RESAMPLE_TARGET_POINTS = 720;
|
||||||
|
const HISTORY_INTERPOLATION_MIN_GAP_MS = 5 * 60 * 1000;
|
||||||
|
const HISTORY_INTERPOLATION_MAX_GAP_MS = 45 * 60 * 1000;
|
||||||
|
const HISTORY_INTERPOLATION_GAP_FACTOR = 6;
|
||||||
|
|
||||||
const CPU_SERIES_COLOR = '#c43d2f';
|
const CPU_SERIES_COLOR = '#c43d2f';
|
||||||
const GPU_SERIES_COLOR = '#c9961a';
|
const GPU_SERIES_COLOR = '#c9961a';
|
||||||
@@ -113,6 +117,15 @@ const historyChartOptions = {
|
|||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: {
|
||||||
display: false
|
display: false
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
title: (items) => {
|
||||||
|
const first = Array.isArray(items) ? items[0] : null;
|
||||||
|
const rawLabel = first?.label ?? first?.raw?.x ?? first?.parsed?.x ?? '';
|
||||||
|
return formatTickLabel(rawLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
@@ -120,7 +133,17 @@ const historyChartOptions = {
|
|||||||
ticks: {
|
ticks: {
|
||||||
autoSkip: true,
|
autoSkip: true,
|
||||||
maxTicksLimit: 8,
|
maxTicksLimit: 8,
|
||||||
color: '#6a4d38'
|
color: '#6a4d38',
|
||||||
|
callback: function (value, index, ticks) {
|
||||||
|
const resolvedLabel = typeof this?.getLabelForValue === 'function'
|
||||||
|
? this.getLabelForValue(value)
|
||||||
|
: (
|
||||||
|
Array.isArray(ticks) && ticks[index]
|
||||||
|
? (ticks[index].label ?? ticks[index].value ?? value)
|
||||||
|
: value
|
||||||
|
);
|
||||||
|
return formatTickLabel(resolvedLabel);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
color: 'rgba(111,57,34,0.08)'
|
color: 'rgba(111,57,34,0.08)'
|
||||||
@@ -152,6 +175,182 @@ function formatTickLabel(timestampIso) {
|
|||||||
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
|
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatDayMarkerLabel(timestampIso) {
|
||||||
|
const parsed = Date.parse(String(timestampIso || ''));
|
||||||
|
if (!Number.isFinite(parsed)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const date = new Date(parsed);
|
||||||
|
return `${String(date.getDate()).padStart(2, '0')}.${String(date.getMonth() + 1).padStart(2, '0')}.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toDayKeyFromTimestamp(timestampIso) {
|
||||||
|
const parsed = Date.parse(String(timestampIso || ''));
|
||||||
|
if (!Number.isFinite(parsed)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const date = new Date(parsed);
|
||||||
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseTimestampMs(value) {
|
||||||
|
const parsed = Date.parse(String(value || ''));
|
||||||
|
return Number.isFinite(parsed) ? parsed : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeHistoryPoints(points = []) {
|
||||||
|
return (Array.isArray(points) ? points : [])
|
||||||
|
.map((point) => {
|
||||||
|
const timestampMs = parseTimestampMs(point?.capturedAt);
|
||||||
|
if (!Number.isFinite(timestampMs)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
capturedAt: new Date(timestampMs).toISOString(),
|
||||||
|
timestampMs,
|
||||||
|
cpuUsagePercent: toNumberOrNull(point?.cpuUsagePercent),
|
||||||
|
ramUsagePercent: toNumberOrNull(point?.ramUsagePercent),
|
||||||
|
gpuUsagePercent: toNumberOrNull(point?.gpuUsagePercent),
|
||||||
|
cpuTemperatureC: toNumberOrNull(point?.cpuTemperatureC),
|
||||||
|
gpuTemperatureC: toNumberOrNull(point?.gpuTemperatureC)
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
.sort((left, right) => left.timestampMs - right.timestampMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
function interpolateMetric(prevPoint, nextPoint, metricKey, targetMs, maxGapMs) {
|
||||||
|
if (!prevPoint || !nextPoint) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (prevPoint.timestampMs === nextPoint.timestampMs) {
|
||||||
|
return toNumberOrNull(prevPoint[metricKey]);
|
||||||
|
}
|
||||||
|
const gapMs = nextPoint.timestampMs - prevPoint.timestampMs;
|
||||||
|
if (!Number.isFinite(gapMs) || gapMs <= 0 || gapMs > maxGapMs) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const prevValue = toNumberOrNull(prevPoint[metricKey]);
|
||||||
|
const nextValue = toNumberOrNull(nextPoint[metricKey]);
|
||||||
|
if (!Number.isFinite(prevValue) || !Number.isFinite(nextValue)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const ratio = (targetMs - prevPoint.timestampMs) / gapMs;
|
||||||
|
if (!Number.isFinite(ratio) || ratio < 0 || ratio > 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Number((prevValue + ((nextValue - prevValue) * ratio)).toFixed(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildResampledHistoryPoints(points = [], historyHours = 24) {
|
||||||
|
const normalized = normalizeHistoryPoints(points);
|
||||||
|
if (normalized.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const historyWindowMs = Math.max(1, Number(historyHours || 24)) * 60 * 60 * 1000;
|
||||||
|
const stepMsRaw = Math.round(historyWindowMs / HISTORY_RESAMPLE_TARGET_POINTS);
|
||||||
|
const stepMs = Math.max(60 * 1000, stepMsRaw);
|
||||||
|
const maxInterpolationGapMs = Math.max(
|
||||||
|
HISTORY_INTERPOLATION_MIN_GAP_MS,
|
||||||
|
Math.min(HISTORY_INTERPOLATION_MAX_GAP_MS, stepMs * HISTORY_INTERPOLATION_GAP_FACTOR)
|
||||||
|
);
|
||||||
|
|
||||||
|
const latestSourceMs = normalized[normalized.length - 1].timestampMs;
|
||||||
|
const windowEndMs = Math.max(Date.now(), latestSourceMs);
|
||||||
|
const windowStartMs = windowEndMs - historyWindowMs;
|
||||||
|
const gridStartMs = Math.floor(windowStartMs / stepMs) * stepMs;
|
||||||
|
const gridEndMs = Math.ceil(windowEndMs / stepMs) * stepMs;
|
||||||
|
const source = normalized.filter((point) => point.timestampMs >= (gridStartMs - maxInterpolationGapMs));
|
||||||
|
|
||||||
|
const keys = [
|
||||||
|
'cpuUsagePercent',
|
||||||
|
'ramUsagePercent',
|
||||||
|
'gpuUsagePercent',
|
||||||
|
'cpuTemperatureC',
|
||||||
|
'gpuTemperatureC'
|
||||||
|
];
|
||||||
|
|
||||||
|
let sourceIndex = 0;
|
||||||
|
const output = [];
|
||||||
|
for (let ts = gridStartMs; ts <= gridEndMs; ts += stepMs) {
|
||||||
|
while (sourceIndex < source.length && source[sourceIndex].timestampMs < ts) {
|
||||||
|
sourceIndex += 1;
|
||||||
|
}
|
||||||
|
const nextPoint = sourceIndex < source.length ? source[sourceIndex] : null;
|
||||||
|
const prevPoint = sourceIndex > 0 ? source[sourceIndex - 1] : null;
|
||||||
|
|
||||||
|
const row = {
|
||||||
|
capturedAt: new Date(ts).toISOString(),
|
||||||
|
timestampMs: ts
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const key of keys) {
|
||||||
|
const nextValue = nextPoint && nextPoint.timestampMs === ts
|
||||||
|
? toNumberOrNull(nextPoint[key])
|
||||||
|
: null;
|
||||||
|
const prevValue = prevPoint && prevPoint.timestampMs === ts
|
||||||
|
? toNumberOrNull(prevPoint[key])
|
||||||
|
: null;
|
||||||
|
row[key] = Number.isFinite(nextValue)
|
||||||
|
? nextValue
|
||||||
|
: (Number.isFinite(prevValue)
|
||||||
|
? prevValue
|
||||||
|
: interpolateMetric(prevPoint, nextPoint, key, ts, maxInterpolationGapMs));
|
||||||
|
}
|
||||||
|
output.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
const historyDayBoundaryPlugin = {
|
||||||
|
id: 'historyDayBoundary',
|
||||||
|
afterDatasetsDraw(chart, _args, options) {
|
||||||
|
if (options === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const xScale = chart?.scales?.x;
|
||||||
|
const area = chart?.chartArea;
|
||||||
|
const labels = Array.isArray(chart?.data?.labels) ? chart.data.labels : [];
|
||||||
|
if (!xScale || !area || labels.length < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = chart.ctx;
|
||||||
|
ctx.save();
|
||||||
|
ctx.strokeStyle = 'rgba(111,57,34,0.30)';
|
||||||
|
ctx.fillStyle = 'rgba(111,57,34,0.75)';
|
||||||
|
ctx.setLineDash([4, 4]);
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.font = '11px sans-serif';
|
||||||
|
ctx.textAlign = 'left';
|
||||||
|
ctx.textBaseline = 'top';
|
||||||
|
|
||||||
|
for (let index = 1; index < labels.length; index += 1) {
|
||||||
|
const prevDayKey = toDayKeyFromTimestamp(labels[index - 1]);
|
||||||
|
const nextDayKey = toDayKeyFromTimestamp(labels[index]);
|
||||||
|
if (!prevDayKey || !nextDayKey || prevDayKey === nextDayKey) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const xPrev = xScale.getPixelForValue(index - 1);
|
||||||
|
const xNext = xScale.getPixelForValue(index);
|
||||||
|
const x = (xPrev + xNext) / 2;
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, area.top);
|
||||||
|
ctx.lineTo(x, area.bottom);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
const markerLabel = formatDayMarkerLabel(labels[index]);
|
||||||
|
if (markerLabel) {
|
||||||
|
ctx.fillText(markerLabel, x + 4, area.top + 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function buildSeriesToggleButtonStyle(color, active) {
|
function buildSeriesToggleButtonStyle(color, active) {
|
||||||
if (active) {
|
if (active) {
|
||||||
return {
|
return {
|
||||||
@@ -258,6 +457,10 @@ export default function HardwarePage({ hardwareMonitoring }) {
|
|||||||
const ramTitle = String(memory?.name || memory?.vendor || memory?.model || '').trim() || 'Arbeitsspeicher';
|
const ramTitle = String(memory?.name || memory?.vendor || memory?.model || '').trim() || 'Arbeitsspeicher';
|
||||||
const gpuTitle = String(primaryGpu?.name || gpu?.name || gpu?.vendor || '').trim() || 'GPU';
|
const gpuTitle = String(primaryGpu?.name || gpu?.name || gpu?.vendor || '').trim() || 'GPU';
|
||||||
const historyPoints = Array.isArray(historyState.points) ? historyState.points : [];
|
const historyPoints = Array.isArray(historyState.points) ? historyState.points : [];
|
||||||
|
const resampledHistoryPoints = useMemo(
|
||||||
|
() => buildResampledHistoryPoints(historyPoints, historyHours),
|
||||||
|
[historyPoints, historyHours]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!monitoringState.enabled) {
|
if (!monitoringState.enabled) {
|
||||||
@@ -338,12 +541,12 @@ export default function HardwarePage({ hardwareMonitoring }) {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const historyUsageChartData = useMemo(() => ({
|
const historyUsageChartData = useMemo(() => ({
|
||||||
labels: historyPoints.map((point) => formatTickLabel(point?.capturedAt)),
|
labels: resampledHistoryPoints.map((point) => String(point?.capturedAt || '').trim()),
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'CPU',
|
label: 'CPU',
|
||||||
data: historyPoints.map((point) => toNumberOrNull(point?.cpuUsagePercent)),
|
data: resampledHistoryPoints.map((point) => toNumberOrNull(point?.cpuUsagePercent)),
|
||||||
borderColor: CPU_SERIES_COLOR,
|
borderColor: CPU_SERIES_COLOR,
|
||||||
backgroundColor: CPU_SERIES_COLOR,
|
backgroundColor: CPU_SERIES_COLOR,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
@@ -353,7 +556,7 @@ export default function HardwarePage({ hardwareMonitoring }) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'RAM',
|
label: 'RAM',
|
||||||
data: historyPoints.map((point) => toNumberOrNull(point?.ramUsagePercent)),
|
data: resampledHistoryPoints.map((point) => toNumberOrNull(point?.ramUsagePercent)),
|
||||||
borderColor: RAM_SERIES_COLOR,
|
borderColor: RAM_SERIES_COLOR,
|
||||||
backgroundColor: RAM_SERIES_COLOR,
|
backgroundColor: RAM_SERIES_COLOR,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
@@ -363,7 +566,7 @@ export default function HardwarePage({ hardwareMonitoring }) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'GPU',
|
label: 'GPU',
|
||||||
data: historyPoints.map((point) => toNumberOrNull(point?.gpuUsagePercent)),
|
data: resampledHistoryPoints.map((point) => toNumberOrNull(point?.gpuUsagePercent)),
|
||||||
borderColor: GPU_SERIES_COLOR,
|
borderColor: GPU_SERIES_COLOR,
|
||||||
backgroundColor: GPU_SERIES_COLOR,
|
backgroundColor: GPU_SERIES_COLOR,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
@@ -372,14 +575,14 @@ export default function HardwarePage({ hardwareMonitoring }) {
|
|||||||
tension: 0.22
|
tension: 0.22
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}), [historyPoints, usageSeriesVisible]);
|
}), [resampledHistoryPoints, usageSeriesVisible]);
|
||||||
|
|
||||||
const historyTempChartData = useMemo(() => ({
|
const historyTempChartData = useMemo(() => ({
|
||||||
labels: historyPoints.map((point) => formatTickLabel(point?.capturedAt)),
|
labels: resampledHistoryPoints.map((point) => String(point?.capturedAt || '').trim()),
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'CPU',
|
label: 'CPU',
|
||||||
data: historyPoints.map((point) => toNumberOrNull(point?.cpuTemperatureC)),
|
data: resampledHistoryPoints.map((point) => toNumberOrNull(point?.cpuTemperatureC)),
|
||||||
borderColor: CPU_SERIES_COLOR,
|
borderColor: CPU_SERIES_COLOR,
|
||||||
backgroundColor: CPU_SERIES_COLOR,
|
backgroundColor: CPU_SERIES_COLOR,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
@@ -389,7 +592,7 @@ export default function HardwarePage({ hardwareMonitoring }) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'GPU',
|
label: 'GPU',
|
||||||
data: historyPoints.map((point) => toNumberOrNull(point?.gpuTemperatureC)),
|
data: resampledHistoryPoints.map((point) => toNumberOrNull(point?.gpuTemperatureC)),
|
||||||
borderColor: GPU_SERIES_COLOR,
|
borderColor: GPU_SERIES_COLOR,
|
||||||
backgroundColor: GPU_SERIES_COLOR,
|
backgroundColor: GPU_SERIES_COLOR,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
@@ -398,7 +601,7 @@ export default function HardwarePage({ hardwareMonitoring }) {
|
|||||||
tension: 0.22
|
tension: 0.22
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}), [historyPoints, tempSeriesVisible]);
|
}), [resampledHistoryPoints, tempSeriesVisible]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="hardware-detail-page">
|
<div className="hardware-detail-page">
|
||||||
@@ -463,7 +666,7 @@ export default function HardwarePage({ hardwareMonitoring }) {
|
|||||||
{historyState.error ? <small className="error-text">{historyState.error}</small> : null}
|
{historyState.error ? <small className="error-text">{historyState.error}</small> : null}
|
||||||
{historyState.loading && historyPoints.length === 0 ? (
|
{historyState.loading && historyPoints.length === 0 ? (
|
||||||
<p>Lade Verlauf ...</p>
|
<p>Lade Verlauf ...</p>
|
||||||
) : historyPoints.length === 0 ? (
|
) : resampledHistoryPoints.length === 0 ? (
|
||||||
<p>Noch keine Verlaufsdaten vorhanden.</p>
|
<p>Noch keine Verlaufsdaten vorhanden.</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="hardware-history-grid">
|
<div className="hardware-history-grid">
|
||||||
@@ -502,6 +705,7 @@ export default function HardwarePage({ hardwareMonitoring }) {
|
|||||||
type="line"
|
type="line"
|
||||||
data={historyUsageChartData}
|
data={historyUsageChartData}
|
||||||
options={historyChartOptions}
|
options={historyChartOptions}
|
||||||
|
plugins={[historyDayBoundaryPlugin]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ChartLegendRow
|
<ChartLegendRow
|
||||||
@@ -539,6 +743,7 @@ export default function HardwarePage({ hardwareMonitoring }) {
|
|||||||
type="line"
|
type="line"
|
||||||
data={historyTempChartData}
|
data={historyTempChartData}
|
||||||
options={historyChartOptions}
|
options={historyChartOptions}
|
||||||
|
plugins={[historyDayBoundaryPlugin]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ChartLegendRow
|
<ChartLegendRow
|
||||||
|
|||||||
@@ -1476,22 +1476,27 @@ function buildPipelineFromJob(job, currentPipeline, currentPipelineJobId) {
|
|||||||
: null;
|
: null;
|
||||||
const liveState = String(liveJobProgress?.state || '').trim().toUpperCase();
|
const liveState = String(liveJobProgress?.state || '').trim().toUpperCase();
|
||||||
const isTerminalJobStatus = jobStatus === 'FINISHED' || jobStatus === 'ERROR' || jobStatus === 'CANCELLED';
|
const isTerminalJobStatus = jobStatus === 'FINISHED' || jobStatus === 'ERROR' || jobStatus === 'CANCELLED';
|
||||||
const ignoreStaleLiveProgress = isTerminalJobStatus && processingStates.includes(liveState);
|
const jobStatusIsRunning = processingStates.includes(jobStatus);
|
||||||
const mergedContext = liveContext
|
const ignoreStaleLiveProgress = (
|
||||||
|
(isTerminalJobStatus && processingStates.includes(liveState))
|
||||||
|
|| (!jobStatusIsRunning && jobStatus !== 'IDLE' && processingStates.includes(liveState))
|
||||||
|
);
|
||||||
|
const effectiveLiveContext = ignoreStaleLiveProgress ? null : liveContext;
|
||||||
|
const mergedContext = effectiveLiveContext
|
||||||
? {
|
? {
|
||||||
...computedContext,
|
...computedContext,
|
||||||
...liveContext,
|
...effectiveLiveContext,
|
||||||
// Prefer the DB plan when it has been confirmed but the live (jobProgress) plan hasn't —
|
// Prefer the DB plan when it has been confirmed but the live (jobProgress) plan hasn't —
|
||||||
// this happens when confirmEncodeReview is called with skipPipelineStateUpdate=true
|
// this happens when confirmEncodeReview is called with skipPipelineStateUpdate=true
|
||||||
// (always the case for queued jobs), leaving jobProgress with the pre-confirmation plan.
|
// (always the case for queued jobs), leaving jobProgress with the pre-confirmation plan.
|
||||||
mediaInfoReview: (computedContext.mediaInfoReview?.reviewConfirmedAt && !liveContext.mediaInfoReview?.reviewConfirmedAt)
|
mediaInfoReview: (computedContext.mediaInfoReview?.reviewConfirmedAt && !effectiveLiveContext.mediaInfoReview?.reviewConfirmedAt)
|
||||||
? computedContext.mediaInfoReview
|
? computedContext.mediaInfoReview
|
||||||
: (liveContext.mediaInfoReview || computedContext.mediaInfoReview),
|
: (effectiveLiveContext.mediaInfoReview || computedContext.mediaInfoReview),
|
||||||
tracks: (Array.isArray(liveContext.tracks) && liveContext.tracks.length > 0)
|
tracks: (Array.isArray(effectiveLiveContext.tracks) && effectiveLiveContext.tracks.length > 0)
|
||||||
? liveContext.tracks
|
? effectiveLiveContext.tracks
|
||||||
: computedContext.tracks,
|
: computedContext.tracks,
|
||||||
selectedMetadata: computedContext.selectedMetadata || liveContext.selectedMetadata,
|
selectedMetadata: computedContext.selectedMetadata || effectiveLiveContext.selectedMetadata,
|
||||||
cdRipConfig: liveContext.cdRipConfig || computedContext.cdRipConfig
|
cdRipConfig: effectiveLiveContext.cdRipConfig || computedContext.cdRipConfig
|
||||||
}
|
}
|
||||||
: computedContext;
|
: computedContext;
|
||||||
// The card always represents `job.id`; never let stale live context override it.
|
// The card always represents `job.id`; never let stale live context override it.
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster",
|
"name": "ripster",
|
||||||
"version": "1.0.0-rc2",
|
"version": "1.0.0-rc3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster",
|
"name": "ripster",
|
||||||
"version": "1.0.0-rc2",
|
"version": "1.0.0-rc3",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^9.1.2"
|
"concurrently": "^9.1.2"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster",
|
"name": "ripster",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0-rc2",
|
"version": "1.0.0-rc3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"",
|
"dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"",
|
||||||
"dev:backend": "npm run dev --prefix backend",
|
"dev:backend": "npm run dev --prefix backend",
|
||||||
|
|||||||
Reference in New Issue
Block a user