Merge branch 'fleet/story-d1-webm-remux'

This commit is contained in:
Giancarmine Salucci
2026-07-08 01:18:41 +02:00
5 changed files with 296 additions and 0 deletions
+3
View File
@@ -90,6 +90,9 @@ async function runJob(
} else {
rawAudioPath = await saveUploadedFile(input.buffer, input.filename, jobId);
title = input.filename.replace(/\.[^.]+$/, '');
// Remux webm files (MediaRecorder output) to fix Cues/duration
const { remuxUpload } = await import('./remux.js');
rawAudioPath = await remuxUpload(rawAudioPath, jobId);
}
updateJob({ id: jobId, title });
+69
View File
@@ -0,0 +1,69 @@
import { execFile } from 'child_process';
import { unlink, rename, mkdir } from 'fs/promises';
import { join, extname } from 'path';
import { existsSync } from 'fs';
const REMUX_DIR = join(process.env.DATA_DIR ?? '/tmp/.whisper-pwa', 'remux');
async function ensureTmpDir() {
if (!existsSync(REMUX_DIR)) await mkdir(REMUX_DIR, { recursive: true });
}
/** Wrap execFile in a promise without using util.promisify */
function execFileAsync(cmd: string, args: string[]): Promise<{ stdout: string; stderr: string }> {
return new Promise((resolve, reject) => {
execFile(cmd, args, (err, stdout, stderr) => {
if (err) reject(err);
else resolve({ stdout, stderr });
});
});
}
/**
* Remux an uploaded webm file to fix Cues/duration metadata.
*
* Browser MediaRecorder produces opus-in-webm output without finalized
* Cues (seeking index) and with unset/streaming duration. Downstream
* ffmpeg audio-prep (prepareAudio) fails on such files with "duration
* unknown" or corrupt-container errors.
*
* This function uses ffmpeg stream copy (no re-encode) to rewrite
* container headers, generating proper Cues and finalizing duration.
* Non-webm files pass through unchanged.
*
* @param inputPath - Path to the uploaded file
* @param jobId - Job ID for temp file naming
* @returns The (possibly remuxed) path — same as inputPath if remuxed in-place
*/
export async function remuxUpload(inputPath: string, jobId: string): Promise<string> {
const ext = extname(inputPath).toLowerCase();
// Only remux webm files — other formats pass through
if (ext !== '.webm') {
return inputPath;
}
await ensureTmpDir();
const tmpPath = join(REMUX_DIR, `${jobId}.webm`);
try {
await execFileAsync('ffmpeg', [
'-y',
'-i', inputPath,
'-c', 'copy',
'-fflags', '+genpts',
'-f', 'webm',
tmpPath
]);
// Replace original with remuxed file
await unlink(inputPath);
await rename(tmpPath, inputPath);
return inputPath;
} catch (err) {
// Clean up temp file on failure
await unlink(tmpPath).catch(() => {});
throw err;
}
}