diff --git a/packages/cli/src/studio-server.ts b/packages/cli/src/studio-server.ts
index 1ba43ae..cb22e72 100644
--- a/packages/cli/src/studio-server.ts
+++ b/packages/cli/src/studio-server.ts
@@ -1388,7 +1388,7 @@ export async function startStudioServer(ctx: CliContext, port: number): Promise<
return res.end('missing ?path');
}
const safe = resolve(p);
- if (!safe.includes('/.html-video/projects/')) {
+ if (!safe.replace(/\\/g, '/').includes('/.html-video/projects/')) {
res.writeHead(403);
return res.end('forbidden');
}
diff --git a/packages/project-studio/public/app.js b/packages/project-studio/public/app.js
index 4f43bcf..89115b5 100644
--- a/packages/project-studio/public/app.js
+++ b/packages/project-studio/public/app.js
@@ -801,6 +801,7 @@ function renderMain() {
${t('soundtrack.gen_music')}
+
@@ -842,13 +843,13 @@ function renderMain() {
${t('soundtrack.gen_narration')}
+
${t('soundtrack.clear')}
-
@@ -925,7 +926,8 @@ function wireSoundtrackPanel() {
const clearBtn = document.getElementById('btn-st-clear');
const musicStatusEl = document.getElementById('st-music-status');
const narrationStatusEl = document.getElementById('st-narration-status');
- const previewEl = document.getElementById('st-preview');
+ const musicPreviewEl = document.getElementById('st-music-preview');
+ const narrationPreviewEl = document.getElementById('st-narration-preview');
const draftFrameBtn = document.getElementById('btn-st-draft-frame');
const draftAllBtn = document.getElementById('btn-st-draft-all');
const whichEl = document.getElementById('st-narration-which');
@@ -1079,7 +1081,8 @@ function wireSoundtrackPanel() {
await fetch(`/api/projects/${state.selected.id}/soundtrack`, { method: 'DELETE' });
musicPrompt.value = '';
narrationText.value = '';
- previewEl.innerHTML = '';
+ if (musicPreviewEl) musicPreviewEl.innerHTML = '';
+ if (narrationPreviewEl) narrationPreviewEl.innerHTML = '';
if (musicStatusEl) musicStatusEl.textContent = '';
if (narrationStatusEl) narrationStatusEl.textContent = '';
if (state.selected) delete state.selected.soundtrack;
@@ -1165,19 +1168,34 @@ function wireSoundtrackPanel() {
}
function renderSoundtrackPreview(soundtrack) {
- const previewEl = document.getElementById('st-preview');
- if (!previewEl || !soundtrack || !state.selected) return;
+ if (!soundtrack || !state.selected) return;
const assets = state.selected.assets || [];
const srcFor = (id) => {
const a = assets.find((x) => x.id === id);
return a?.path ? `/asset?path=${encodeURIComponent(a.path)}` : null;
};
- const blocks = [];
+ const musicPreviewEl = document.getElementById('st-music-preview');
+ const narrationPreviewEl = document.getElementById('st-narration-preview');
+ const legacyPreviewEl = document.getElementById('st-preview');
const musicSrc = soundtrack.musicAssetId && srcFor(soundtrack.musicAssetId);
const narrSrc = soundtrack.narrationAssetId && srcFor(soundtrack.narrationAssetId);
- if (musicSrc) blocks.push(`${t('soundtrack.music_ready')} `);
- if (narrSrc) blocks.push(`${t('soundtrack.narration_ready')} `);
- previewEl.innerHTML = blocks.join('');
+ if (musicPreviewEl) {
+ musicPreviewEl.innerHTML = musicSrc
+ ? `${t('soundtrack.music_ready')} `
+ : '';
+ }
+ if (narrationPreviewEl) {
+ narrationPreviewEl.innerHTML = narrSrc
+ ? `${t('soundtrack.narration_ready')} `
+ : '';
+ }
+ // Compatibility: if the old single preview container still exists, render both.
+ if (legacyPreviewEl) {
+ const blocks = [];
+ if (musicSrc) blocks.push(`${t('soundtrack.music_ready')} `);
+ if (narrSrc) blocks.push(`${t('soundtrack.narration_ready')} `);
+ legacyPreviewEl.innerHTML = blocks.join('');
+ }
}
// ============== composer attachments ==============