0.12.0-2 Checkable Infrastructure

This commit is contained in:
2026-03-19 20:19:07 +00:00
parent 24955a956d
commit d9969dfbe5
20 changed files with 1207 additions and 278 deletions
+31 -14
View File
@@ -22,13 +22,16 @@ const DEFAULT_CD_OUTPUT_TEMPLATE = '{artist} - {album} ({year})/{trackNr} {artis
function parseToc(tocOutput) {
const lines = String(tocOutput || '').split(/\r?\n/);
const tracks = [];
const tocTimePattern = /[\(\[]\s*\d+:\d+(?:[.,:]\d+)?\s*[\)\]]/g;
const tocTablePattern =
/^\s*(\d+)\.?\s+(\d+)\s+[\(\[]\s*\d+:\d+(?:[.,:]\d+)?\s*[\)\]]\s+(\d+)\s+[\(\[]\s*\d+:\d+(?:[.,:]\d+)?\s*[\)\]]/i;
for (const line of lines) {
const trackMatch = line.match(/^\s*track\s+(\d+)\s*:\s*(.+)$/i);
if (trackMatch) {
const position = Number(trackMatch[1]);
const payloadWithoutTimes = String(trackMatch[2] || '')
.replace(/[\(\[]\s*\d+:\d+\.\d+\s*[\)\]]/g, ' ');
.replace(tocTimePattern, ' ');
const sectorValues = payloadWithoutTimes.match(/\d+/g) || [];
if (sectorValues.length < 2) {
continue;
@@ -58,9 +61,7 @@ function parseToc(tocOutput) {
// Alternative cdparanoia -Q table style:
// 1. 16503 [03:40.03] 0 [00:00.00] no no 2
// ^ length sectors ^ start sector
const tableMatch = line.match(
/^\s*(\d+)\.?\s+(\d+)\s+[\(\[]\d+:\d+\.\d+[\)\]]\s+(\d+)\s+[\(\[]\d+:\d+\.\d+[\)\]]/i
);
const tableMatch = line.match(tocTablePattern);
if (!tableMatch) {
continue;
}
@@ -395,6 +396,7 @@ async function runProcessTracked({
* @param {object[]} options.tracks - TOC track list [{position, durationMs, title}]
* @param {object} options.meta - album metadata {title, artist, year}
* @param {string} options.outputTemplate - template for relative output path without extension
* @param {boolean} options.skipEncode - true => rip only (no encode)
* @param {Function} options.onProgress - ({phase, trackIndex, trackTotal, percent, track}) => void
* @param {Function} options.onLog - (level, msg) => void
* @param {Function} options.onProcessHandle- called with spawned process handle for cancellation integration
@@ -419,10 +421,11 @@ async function ripAndEncode(options) {
onProcessHandle,
isCancelled,
context,
skipRip = false
skipRip = false,
skipEncode = false
} = options;
if (!SUPPORTED_FORMATS.has(format)) {
if (!skipEncode && !SUPPORTED_FORMATS.has(format)) {
throw new Error(`Unbekanntes Ausgabeformat: ${format}`);
}
@@ -435,7 +438,9 @@ async function ripAndEncode(options) {
}
await ensureDir(rawWavDir);
await ensureDir(outputDir);
if (!skipEncode) {
await ensureDir(outputDir);
}
logger.info('rip:start', {
jobId,
@@ -448,6 +453,9 @@ async function ripAndEncode(options) {
logger[level] && logger[level](msg, { jobId });
onLog && onLog(level, msg);
};
const ripPercentSpan = skipEncode ? 100 : 50;
const encodePercentStart = ripPercentSpan;
const encodePercentSpan = Math.max(0, 100 - encodePercentStart);
// ── Phase 1: Rip each selected track to WAV ──────────────────────────────
if (skipRip) {
@@ -472,7 +480,7 @@ async function ripAndEncode(options) {
trackTotal: tracksToRip.length,
trackPosition: track.position,
trackPercent: 0,
percent: (i / tracksToRip.length) * 50
percent: (i / tracksToRip.length) * ripPercentSpan
});
log('info', `Rippe Track ${track.position} von ${tracksToRip.length}`);
@@ -486,7 +494,7 @@ async function ripAndEncode(options) {
onStderrLine(line) {
const parsed = parseCdParanoiaProgress(line);
if (parsed && parsed.percent !== null) {
const overallPercent = ((i + parsed.percent / 100) / tracksToRip.length) * 50;
const overallPercent = ((i + parsed.percent / 100) / tracksToRip.length) * ripPercentSpan;
onProgress && onProgress({
phase: 'rip',
trackEvent: 'progress',
@@ -518,13 +526,22 @@ async function ripAndEncode(options) {
trackTotal: tracksToRip.length,
trackPosition: track.position,
trackPercent: 100,
percent: ((i + 1) / tracksToRip.length) * 50
percent: ((i + 1) / tracksToRip.length) * ripPercentSpan
});
log('info', `Track ${track.position} gerippt.`);
}
} // end if (!skipRip)
if (skipEncode) {
return {
outputDir: null,
format: 'raw',
trackCount: tracksToRip.length,
encodeResults: []
};
}
// ── Phase 2: Encode WAVs to target format ─────────────────────────────────
const encodeResults = [];
@@ -542,7 +559,7 @@ async function ripAndEncode(options) {
trackTotal: tracksToRip.length,
trackPosition: track.position,
trackPercent: 0,
percent: 50 + ((i / tracksToRip.length) * 50)
percent: encodePercentStart + ((i / tracksToRip.length) * encodePercentSpan)
});
ensureDir(path.dirname(outFile));
log('info', `Promptkette [Copy ${i + 1}/${tracksToRip.length}]: cp ${quoteShellArg(wavFile)} ${quoteShellArg(outFile)}`);
@@ -554,7 +571,7 @@ async function ripAndEncode(options) {
trackTotal: tracksToRip.length,
trackPosition: track.position,
trackPercent: 100,
percent: 50 + ((i + 1) / tracksToRip.length) * 50
percent: encodePercentStart + ((i + 1) / tracksToRip.length) * encodePercentSpan
});
log('info', `WAV für Track ${track.position} gespeichert.`);
encodeResults.push({ position: track.position, title: track.title || '', outputFile: path.basename(outFile), success: true });
@@ -581,7 +598,7 @@ async function ripAndEncode(options) {
trackTotal: tracksToRip.length,
trackPosition: track.position,
trackPercent: 0,
percent: 50 + ((i / tracksToRip.length) * 50)
percent: encodePercentStart + ((i / tracksToRip.length) * encodePercentSpan)
});
log('info', `Encodiere Track ${track.position}${outFilename}`);
@@ -628,7 +645,7 @@ async function ripAndEncode(options) {
trackTotal: tracksToRip.length,
trackPosition: track.position,
trackPercent: 100,
percent: 50 + ((i + 1) / tracksToRip.length) * 50
percent: encodePercentStart + ((i + 1) / tracksToRip.length) * encodePercentSpan
});
log('info', `Track ${track.position} encodiert.`);