0.12.0-18 Tests and Converter Fixes

This commit is contained in:
2026-03-31 12:51:32 +00:00
parent aea8fa6772
commit 5aa65ad4c1
24 changed files with 2281 additions and 249 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "ripster-backend",
"version": "0.12.0-17",
"version": "0.12.0-18",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ripster-backend",
"version": "0.12.0-17",
"version": "0.12.0-18",
"dependencies": {
"archiver": "^7.0.1",
"cors": "^2.8.5",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ripster-backend",
"version": "0.12.0-17",
"version": "0.12.0-18",
"private": true,
"type": "commonjs",
"scripts": {
+43 -11
View File
@@ -339,7 +339,13 @@ class ConverterPlugin extends SourcePlugin {
cmd: handBrakeConfig.cmd, args: handBrakeConfig.args,
jobId: job?.id,
progressParser: parseHandBrakeProgress,
onProgress: (pct, statusText) => ctx.emitProgress(Math.round(pct), statusText || `Encoding ${pct}%`),
onProgress: (pct, statusText, eta) => ctx.emitProgress(
Math.round(pct),
statusText || `Encoding ${pct}%`,
eta ?? null
),
appendLogLine: ctx?.extra?.appendLogLine,
logSource: 'HANDBRAKE',
isCancelled,
onProcessHandle,
context: { jobId: job?.id, source: 'ConverterPlugin.encode', stage: 'ENCODING' }
@@ -451,6 +457,8 @@ class ConverterPlugin extends SourcePlugin {
await _spawnAndWait({
cmd: encodeConfig.cmd, args: encodeConfig.args,
jobId: job?.id,
appendLogLine: ctx?.extra?.appendLogLine,
logSource: 'FFMPEG',
isCancelled,
onProcessHandle,
context: { jobId: job?.id, source: 'ConverterPlugin.encode', stage: 'ENCODING' }
@@ -504,6 +512,8 @@ class ConverterPlugin extends SourcePlugin {
await _spawnAndWait({
cmd: encodeConfig.cmd, args: encodeConfig.args,
jobId: job?.id,
appendLogLine: ctx?.extra?.appendLogLine,
logSource: 'FFMPEG',
isCancelled,
onProcessHandle,
context: { jobId: job?.id, source: 'ConverterPlugin.encode', stage: 'ENCODING' }
@@ -536,6 +546,8 @@ class ConverterPlugin extends SourcePlugin {
await _spawnAndWait({
cmd: encodeConfig.cmd, args: encodeConfig.args,
jobId: job?.id,
appendLogLine: ctx?.extra?.appendLogLine,
logSource: 'FFMPEG',
isCancelled,
onProcessHandle,
context: { jobId: job?.id, source: 'ConverterPlugin.encode', stage: 'ENCODING' }
@@ -591,25 +603,45 @@ class ConverterPlugin extends SourcePlugin {
}
}
async function _spawnAndWait({ cmd, args, jobId, progressParser, onProgress, isCancelled, onProcessHandle, context }) {
async function _spawnAndWait({
cmd,
args,
jobId,
progressParser,
onProgress,
appendLogLine,
logSource = 'PROCESS',
isCancelled,
onProcessHandle,
context
}) {
if (typeof isCancelled === 'function' && isCancelled()) {
const err = new Error('Job wurde vom Benutzer abgebrochen.');
err.statusCode = 409;
throw err;
}
const handleLine = (stream, line) => {
if (progressParser && typeof onProgress === 'function') {
const progress = progressParser(line);
if (progress?.percent != null) {
onProgress(progress.percent, progress.detail || null, progress.eta ?? null);
}
}
if (typeof appendLogLine === 'function') {
try {
appendLogLine(logSource, line, stream);
} catch (_error) {
// ignore log append errors in process stream
}
}
};
const handle = spawnTrackedProcess({
cmd,
args,
onStdoutLine: () => {},
onStderrLine: (line) => {
if (progressParser && typeof onProgress === 'function') {
const progress = progressParser(line);
if (progress?.percent != null) {
onProgress(progress.percent, progress.detail || null);
}
}
},
onStdoutLine: (line) => handleLine('stdout', line),
onStderrLine: (line) => handleLine('stderr', line),
context: context || { jobId }
});
+4 -1
View File
@@ -280,6 +280,7 @@ router.post(
const selectedPreEncodeChainIds = req.body?.selectedPreEncodeChainIds;
const skipPipelineStateUpdate = Boolean(req.body?.skipPipelineStateUpdate);
const selectedUserPresetId = req.body?.selectedUserPresetId ?? null;
const selectedHandBrakePreset = req.body?.selectedHandBrakePreset ?? null;
logger.info('post:confirm-encode', {
reqId: req.reqId,
jobId,
@@ -287,6 +288,7 @@ router.post(
selectedTrackSelectionProvided: Boolean(selectedTrackSelection),
skipPipelineStateUpdate,
selectedUserPresetId,
selectedHandBrakePreset,
selectedPostEncodeScriptIdsCount: Array.isArray(selectedPostEncodeScriptIds)
? selectedPostEncodeScriptIds.length
: 0,
@@ -308,7 +310,8 @@ router.post(
selectedPostEncodeChainIds,
selectedPreEncodeChainIds,
skipPipelineStateUpdate,
selectedUserPresetId
selectedUserPresetId,
selectedHandBrakePreset
});
res.json({ job });
})
+58 -6
View File
@@ -22,7 +22,7 @@ function parseJsonSafe(raw, fallback = null) {
const PROCESS_LOG_TAIL_MAX_BYTES = 1024 * 1024;
const processLogStreams = new Map();
const PROFILE_PATH_SUFFIXES = ['bluray', 'dvd', 'cd', 'audiobook', 'other'];
const PROFILE_PATH_SUFFIXES = ['bluray', 'dvd', 'cd', 'audiobook', 'converter', 'other'];
const RAW_INCOMPLETE_PREFIX = 'Incomplete_';
const RAW_RIP_COMPLETE_PREFIX = 'Rip_Complete_';
const PROCESS_LOG_FILE_PATTERN = /^job-(\d+)\.process\.log$/i;
@@ -329,7 +329,27 @@ function inferMediaType(job, makemkvInfo, mediainfoInfo, encodePlan, handbrakeIn
// Converter jobs: detected via media_type field (plan.mediaProfile = 'converter')
const rawMediaType = normalizeMediaTypeValue(job?.media_type);
const rawPlanProfile = String(plan?.mediaProfile || '').trim().toLowerCase();
if (rawPlanProfile === 'converter' || rawMediaType === 'converter') {
const converterMediaTypeHint = String(plan?.converterMediaType || '').trim().toLowerCase();
const hasConverterPathHint = (value) => {
const normalized = String(value || '')
.trim()
.replace(/\\/g, '/')
.toLowerCase();
if (!normalized) {
return false;
}
return /(^|\/)converter(\/|$)/.test(normalized);
};
if (
rawPlanProfile === 'converter'
|| rawMediaType === 'converter'
|| converterMediaTypeHint === 'video'
|| converterMediaTypeHint === 'audio'
|| converterMediaTypeHint === 'iso'
|| hasConverterPathHint(rawPath)
|| hasConverterPathHint(encodeInputPath)
|| hasConverterPathHint(plan?.inputPath)
) {
return 'converter';
}
@@ -630,12 +650,17 @@ function enrichJobRow(job, settings = null, options = {}) {
const mediainfoInfo = resolvedPaths.mediainfoInfo;
const encodePlan = resolvedPaths.encodePlan;
const mediaType = resolvedPaths.mediaType;
const normalizedJobStatus = String(job?.status || '').trim().toUpperCase();
const ripSuccessful = Number(job?.rip_successful || 0) === 1
|| String(makemkvInfo?.status || '').trim().toUpperCase() === 'SUCCESS';
const backupSuccess = ripSuccessful;
const finishedWithOutput = normalizedJobStatus === 'FINISHED'
&& (includeFsChecks ? Boolean(outputStatus?.exists) : true);
const encodeSuccess = directoryLikeOutput
? (String(job?.status || '').trim().toUpperCase() === 'FINISHED' && Boolean(outputStatus?.exists))
: String(handbrakeInfo?.status || '').trim().toUpperCase() === 'SUCCESS';
? finishedWithOutput
: (mediaType === 'converter'
? finishedWithOutput
: String(handbrakeInfo?.status || '').trim().toUpperCase() === 'SUCCESS');
return {
...job,
@@ -3067,7 +3092,33 @@ class HistoryService {
`
SELECT *
FROM jobs
WHERE status IN ('ANALYZING', 'RIPPING', 'ENCODING', 'MEDIAINFO_CHECK', 'READY_TO_ENCODE', 'CD_ANALYZING', 'CD_READY_TO_RIP', 'CD_RIPPING', 'CD_ENCODING')
WHERE status IN ('ANALYZING', 'RIPPING', 'ENCODING', 'MEDIAINFO_CHECK', 'CD_ANALYZING', 'CD_RIPPING', 'CD_ENCODING')
ORDER BY updated_at ASC, id ASC
`
),
settingsService.getSettingsMap()
]);
return rows.map((job) => ({
...enrichJobRow(job, settings),
log_count: hasProcessLogFile(job.id) ? 1 : 0
}));
}
async getQueueIdleJobs() {
const db = await getDb();
const [rows, settings] = await Promise.all([
db.all(
`
SELECT *
FROM jobs
WHERE status IN (
'METADATA_SELECTION',
'WAITING_FOR_USER_DECISION',
'READY_TO_START',
'READY_TO_ENCODE',
'CD_METADATA_SELECTION',
'CD_READY_TO_RIP'
)
ORDER BY updated_at ASC, id ASC
`
),
@@ -4041,6 +4092,7 @@ class HistoryService {
}
const artifactMoviePathSet = new Set(artifactMoviePaths);
const includeMovieParentCandidate = resolvedPaths?.mediaType !== 'converter';
for (const outputPath of explicitMoviePaths) {
addCandidate(
movieCandidates,
@@ -4050,7 +4102,7 @@ class HistoryService {
movieAllowedPaths
);
const parentDir = toNormalizedPath(path.dirname(outputPath));
if (parentDir && !movieRoots.includes(parentDir)) {
if (includeMovieParentCandidate && parentDir && !movieRoots.includes(parentDir)) {
addCandidate(
movieCandidates,
'movie',
File diff suppressed because it is too large Load Diff