fix(02): Alpine libmpv/libsndfile symlinks, upload endpoint, browser file picker

- Dockerfile: add libmpv.so + libsndfile.so symlinks (Alpine/musl has no ldconfig)
- engine.py: monkey-patch ctypes.util.find_library as fallback for Alpine
- audio.py: add /upload (multipart) and /files endpoints
- pyproject.toml: add python-multipart
- frontend: replace path input with upload button + file selector dropdown
This commit is contained in:
Claude
2026-04-06 15:34:11 +00:00
parent 7f9efee47a
commit 9239988d38
7 changed files with 197 additions and 9 deletions

View File

@@ -224,10 +224,55 @@ if (seekBar) {
});
}
// Load audio button
// Populate file selector
async function refreshFileList(selectValue) {
const sel = document.getElementById('audio-select');
if (!sel) return;
try {
const res = await fetch('/api/audio/files');
if (!res.ok) return;
const { files } = await res.json();
const prev = selectValue ?? sel.value;
sel.innerHTML = '<option value="">— select file —</option>';
files.forEach(name => {
const opt = document.createElement('option');
opt.value = `/app/shows/${name}`;
opt.textContent = name;
if (opt.value === prev) opt.selected = true;
sel.appendChild(opt);
});
} catch (e) {
console.error('[audio] file list error:', e);
}
}
refreshFileList();
// Upload audio file
document.getElementById('audio-upload')?.addEventListener('change', async (e) => {
const file = e.target.files?.[0];
if (!file) return;
const form = new FormData();
form.append('file', file);
try {
const res = await fetch('/api/audio/upload', { method: 'POST', body: form });
if (!res.ok) {
const err = await res.json();
console.error('[audio] upload failed:', err.detail);
return;
}
const { path } = await res.json();
await refreshFileList(path);
setTimeout(loadWaveform, 500);
} catch (err) {
console.error('[audio] upload error:', err);
}
e.target.value = '';
});
// Load selected audio file
document.getElementById('btn-load')?.addEventListener('click', async () => {
const pathInput = document.getElementById('audio-path');
const path = pathInput?.value.trim();
const sel = document.getElementById('audio-select');
const path = sel?.value;
if (!path) return;
try {
const res = await fetch('/api/audio/load', {
@@ -240,7 +285,6 @@ document.getElementById('btn-load')?.addEventListener('click', async () => {
console.error('[audio] load failed:', err.detail);
return;
}
// Wait briefly for mpv to initialize, then fetch waveform
setTimeout(loadWaveform, 500);
} catch (err) {
console.error('[audio] load error:', err);