diff --git a/package-lock.json b/package-lock.json index 3970b44..29fe034 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "siteforge", - "version": "2.17.0", + "version": "2.17.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "siteforge", - "version": "2.17.0", + "version": "2.17.1", "dependencies": { "@aws-sdk/client-s3": "^3.1135.0", "@dnd-kit/core": "^6.3.1", diff --git a/package.json b/package.json index 1a7835e..129f9f7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "siteforge", "private": true, - "version": "2.17.0", + "version": "2.17.1", "type": "module", "scripts": { "dev": "vite", diff --git a/server/routes/nuggets-workflow.js b/server/routes/nuggets-workflow.js index 22ce471..9241e84 100644 --- a/server/routes/nuggets-workflow.js +++ b/server/routes/nuggets-workflow.js @@ -15,6 +15,15 @@ import { const WORKFLOW_FILE = path.join(DATA_DIR, 'nuggets-workflow.json') +const AUDIO_TYPES = { + '.mp3': 'audio/mpeg', + '.wav': 'audio/wav', + '.m4a': 'audio/mp4', + '.aac': 'audio/aac', + '.ogg': 'audio/ogg', + '.flac': 'audio/flac', +} + export const WORKFLOW_STEPS = ['listen', 'coldOpen', 'recordColdOpen', 'edit', 'spotify'] async function loadWorkflow() { @@ -100,6 +109,7 @@ export function register(app) { { headers: { Authorization: `Token ${SEAFILE_TOKEN}` }, signal: AbortSignal.timeout(10_000) }, ) if (!linkRes.ok) { + console.error(`[nuggets-workflow] Seafile link lookup for "${filePath}" failed: ${linkRes.status}`) res.status(404).json({ message: 'Audio file not found in Seafile.' }) return } @@ -109,21 +119,26 @@ export function register(app) { if (req.headers.range) headers.Range = req.headers.range const upstream = await fetch(downloadUrl, { headers }) if (!upstream.ok && upstream.status !== 206) { + console.error(`[nuggets-workflow] Seafile download failed: ${upstream.status}`) res.status(502).json({ message: 'Could not fetch audio from Seafile.' }) return } res.status(upstream.status) - for (const h of ['content-type', 'content-length', 'content-range', 'accept-ranges']) { + for (const h of ['content-length', 'content-range', 'accept-ranges']) { const v = upstream.headers.get(h) if (v) res.setHeader(h, v) } - if (!upstream.headers.get('content-type')) res.setHeader('content-type', 'audio/mpeg') + // Seafile serves downloads as application/octet-stream, which browsers refuse to play + // (the site sends nosniff), so set the audio type from the file extension. + const ext = path.extname(entry.filename).toLowerCase() + res.setHeader('content-type', AUDIO_TYPES[ext] ?? 'audio/mpeg') + if (!res.getHeader('accept-ranges')) res.setHeader('accept-ranges', 'bytes') const stream = Readable.fromWeb(upstream.body) req.on('close', () => stream.destroy()) stream.on('error', () => res.end()) stream.pipe(res) } catch (err) { - console.error('[nuggets-workflow] audio error:', err.message) + console.error('[nuggets-workflow] audio error:', err.message, err.cause?.message ?? '') if (!res.headersSent) res.status(500).json({ message: 'Audio stream failed.' }) } }) diff --git a/src/AdminPage.tsx b/src/AdminPage.tsx index f4997e0..29f5cd7 100644 --- a/src/AdminPage.tsx +++ b/src/AdminPage.tsx @@ -1751,7 +1751,19 @@ function NuggetsWorkflowAdmin() { {s.key === 'listen' && (
-
)} {s.key === 'coldOpen' && (