Fix Nuggets workflow audio: serve with audio content type (nosniff blocked octet-stream), show playback errors; v2.17.1
Publish Container / build (push) Successful in 16m34s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-12 08:56:41 -04:00
1 parent 326838e5bc
commit 11f3d76e9b
4 files changed
+34 -7

No files matched your search

+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "siteforge", "name": "siteforge",
"version": "2.17.0", "version": "2.17.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "siteforge", "name": "siteforge",
"version": "2.17.0", "version": "2.17.1",
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.1135.0", "@aws-sdk/client-s3": "^3.1135.0",
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "siteforge", "name": "siteforge",
"private": true, "private": true,
"version": "2.17.0", "version": "2.17.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+18 -3
View File
@@ -15,6 +15,15 @@ import {
const WORKFLOW_FILE = path.join(DATA_DIR, 'nuggets-workflow.json') 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'] export const WORKFLOW_STEPS = ['listen', 'coldOpen', 'recordColdOpen', 'edit', 'spotify']
async function loadWorkflow() { async function loadWorkflow() {
@@ -100,6 +109,7 @@ export function register(app) {
{ headers: { Authorization: `Token ${SEAFILE_TOKEN}` }, signal: AbortSignal.timeout(10_000) }, { headers: { Authorization: `Token ${SEAFILE_TOKEN}` }, signal: AbortSignal.timeout(10_000) },
) )
if (!linkRes.ok) { 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.' }) res.status(404).json({ message: 'Audio file not found in Seafile.' })
return return
} }
@@ -109,21 +119,26 @@ export function register(app) {
if (req.headers.range) headers.Range = req.headers.range if (req.headers.range) headers.Range = req.headers.range
const upstream = await fetch(downloadUrl, { headers }) const upstream = await fetch(downloadUrl, { headers })
if (!upstream.ok && upstream.status !== 206) { 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.' }) res.status(502).json({ message: 'Could not fetch audio from Seafile.' })
return return
} }
res.status(upstream.status) 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) const v = upstream.headers.get(h)
if (v) res.setHeader(h, v) 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) const stream = Readable.fromWeb(upstream.body)
req.on('close', () => stream.destroy()) req.on('close', () => stream.destroy())
stream.on('error', () => res.end()) stream.on('error', () => res.end())
stream.pipe(res) stream.pipe(res)
} catch (err) { } 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.' }) if (!res.headersSent) res.status(500).json({ message: 'Audio stream failed.' })
} }
}) })
+13 -1
View File
@@ -1751,7 +1751,19 @@ function NuggetsWorkflowAdmin() {
{s.key === 'listen' && ( {s.key === 'listen' && (
<div style={{ marginTop: 8 }}> <div style={{ marginTop: 8 }}>
<audio controls preload="none" style={{ width: '100%' }} src={`/api/admin/nuggets-workflow/${selected.id}/audio`} /> <audio
key={selected.id}
controls
preload="none"
style={{ width: '100%' }}
src={`/api/admin/nuggets-workflow/${selected.id}/audio`}
onError={async () => {
const r = await fetch(`/api/admin/nuggets-workflow/${selected.id}/audio`, { headers: { Range: 'bytes=0-0' } }).catch(() => null)
if (!r) setError('Audio request failed (network).')
else if (r.ok) setError('The server sent the audio but the browser could not play it. The file may be in an unsupported format.')
else setError(`Audio unavailable (${r.status}): ${(await r.json().catch(() => ({}))).message ?? 'unknown error'}`)
}}
/>
</div> </div>
)} )}
{s.key === 'coldOpen' && ( {s.key === 'coldOpen' && (