From bf7c73e57cbb3a2adbdb0bc03361a8f02bfa4125 Mon Sep 17 00:00:00 2001 From: bdim404 Date: Tue, 10 Mar 2026 17:56:46 +0800 Subject: [PATCH] feat(audiobook): change audio format from MP3 to WAV for project downloads and merging --- qwen3-tts-backend/api/audiobook.py | 8 +++--- qwen3-tts-backend/core/audiobook_service.py | 2 +- qwen3-tts-frontend/src/pages/Audiobook.tsx | 28 ++++++++++++++------- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/qwen3-tts-backend/api/audiobook.py b/qwen3-tts-backend/api/audiobook.py index 8d772ec..aef84db 100644 --- a/qwen3-tts-backend/api/audiobook.py +++ b/qwen3-tts-backend/api/audiobook.py @@ -461,19 +461,19 @@ async def download_project( if chapter is not None: output_path = str( - Path(settings.OUTPUT_DIR) / "audiobook" / str(project_id) / "chapters" / f"chapter_{chapter}.mp3" + Path(settings.OUTPUT_DIR) / "audiobook" / str(project_id) / "chapters" / f"chapter_{chapter}.wav" ) else: output_path = str( - Path(settings.OUTPUT_DIR) / "audiobook" / str(project_id) / "full.mp3" + Path(settings.OUTPUT_DIR) / "audiobook" / str(project_id) / "full.wav" ) if not Path(output_path).exists(): from core.audiobook_service import merge_audio_files merge_audio_files(audio_paths, output_path) - filename = f"chapter_{chapter}.mp3" if chapter is not None else f"{project.title}.mp3" - return FileResponse(output_path, media_type="audio/mpeg", filename=filename) + filename = f"chapter_{chapter}.wav" if chapter is not None else f"{project.title}.wav" + return FileResponse(output_path, media_type="audio/wav", filename=filename) @router.delete("/projects/{project_id}", status_code=status.HTTP_204_NO_CONTENT) diff --git a/qwen3-tts-backend/core/audiobook_service.py b/qwen3-tts-backend/core/audiobook_service.py index 3cc976e..ba0f860 100644 --- a/qwen3-tts-backend/core/audiobook_service.py +++ b/qwen3-tts-backend/core/audiobook_service.py @@ -542,4 +542,4 @@ def merge_audio_files(audio_paths: list[str], output_path: str) -> None: if combined: Path(output_path).parent.mkdir(parents=True, exist_ok=True) - combined.export(output_path, format="mp3") + combined.export(output_path, format="wav") diff --git a/qwen3-tts-frontend/src/pages/Audiobook.tsx b/qwen3-tts-frontend/src/pages/Audiobook.tsx index 3fb1d0f..910b999 100644 --- a/qwen3-tts-frontend/src/pages/Audiobook.tsx +++ b/qwen3-tts-frontend/src/pages/Audiobook.tsx @@ -321,6 +321,8 @@ function ProjectCard({ project, onRefresh }: { project: AudiobookProject; onRefr const [editFields, setEditFields] = useState({ name: '', description: '', instruct: '' }) const [sequentialPlayingId, setSequentialPlayingId] = useState(null) const [turbo, setTurbo] = useState(false) + const [charsCollapsed, setCharsCollapsed] = useState(false) + const [chaptersCollapsed, setChaptersCollapsed] = useState(false) const prevStatusRef = useRef(project.status) const autoExpandedRef = useRef(new Set()) @@ -589,10 +591,14 @@ function ProjectCard({ project, onRefresh }: { project: AudiobookProject; onRefr
{detail && detail.characters.length > 0 && (
-
+
-
+ + {!charsCollapsed &&
{detail.characters.map(char => (
{editingCharId === char.id ? ( @@ -643,7 +649,7 @@ function ProjectCard({ project, onRefresh }: { project: AudiobookProject; onRefr )}
))} -
+
} {status === 'characters_ready' && (
+ {detail.chapters.some(c => ['pending', 'error', 'ready'].includes(c.status)) && (
-
+ {!chaptersCollapsed &&
{detail.chapters.map(ch => { const chSegs = segments.filter(s => s.chapter_index === ch.chapter_index) const chDone = chSegs.filter(s => s.status === 'done').length @@ -729,8 +739,8 @@ function ProjectCard({ project, onRefresh }: { project: AudiobookProject; onRefr
) })} -
- {doneCount > 0 && ( + } + {!chaptersCollapsed && doneCount > 0 && (