diff --git a/frontend/src/components/audiobook/BookDetails.jsx b/frontend/src/components/audiobook/BookDetails.jsx
new file mode 100644
index 00000000..156b1d4d
--- /dev/null
+++ b/frontend/src/components/audiobook/BookDetails.jsx
@@ -0,0 +1,89 @@
+import { Image as ImageIcon, X } from 'lucide-react';
+
+import { Button } from '../../ui';
+import { buttonVariants } from '@/components/ui/button.tsx';
+
+const META_FIELDS = ['title', 'author', 'narrator', 'year', 'genre'];
+
+/**
+ * Cover picker + the embedded-metadata fields for the Audiobook tab. Extracted
+ * from AudiobookTab so the tab stays under the max-lines lint and the block can
+ * live inside a collapsible Section. Behaviour-identical to the inline version:
+ * same inputs, same aria-labels, same store-backed `meta` / `setMetaField`.
+ */
+export default function BookDetails({
+ t,
+ coverPreview,
+ onCoverPick,
+ clearCover,
+ meta,
+ setMetaField,
+}) {
+ return (
+
+
+ {coverPreview ? (
+ <>
+
+
+ >
+ ) : (
+
+ )}
+
+
+ {META_FIELDS.map((k) => (
+
+ ))}
+
+
+
+ );
+}
diff --git a/frontend/src/components/audiobook/LexiconEditor.jsx b/frontend/src/components/audiobook/LexiconEditor.jsx
new file mode 100644
index 00000000..690dbb44
--- /dev/null
+++ b/frontend/src/components/audiobook/LexiconEditor.jsx
@@ -0,0 +1,52 @@
+import { Plus, X } from 'lucide-react';
+
+import { Button } from '../../ui';
+
+/**
+ * Pronunciation lexicon editor — the editable {word → respelling} rows plus the
+ * Add button. Extracted from AudiobookTab so the block can live inside a
+ * collapsible Section and the tab stays under the max-lines lint. Behaviour is
+ * identical: the parent still owns the `lex` rows and the row mutators.
+ */
+export default function LexiconEditor({ t, lex, setLexRow, addLexRow, removeLexRow }) {
+ return (
+ <>
+ {lex.map((row, i) => (
+
+
+
+
+
+ ))}
+ }
+ style={{ alignSelf: 'flex-start' }}
+ >
+ {t('audiobook.lex_add')}
+
+ >
+ );
+}
diff --git a/frontend/src/components/audiobook/Section.jsx b/frontend/src/components/audiobook/Section.jsx
new file mode 100644
index 00000000..d4ea264a
--- /dev/null
+++ b/frontend/src/components/audiobook/Section.jsx
@@ -0,0 +1,29 @@
+import { ChevronDown } from 'lucide-react';
+
+/**
+ * Collapsible settings group for the Audiobook tab — one consistent affordance
+ * for the secondary controls (Output, Book details, Pronunciation, Markup) so
+ * the right-hand column stops sprawling. Native `/` gives
+ * keyboard operability + a labelled disclosure for free; the chrome mirrors the
+ * AudiobookOverrides header (small muted mono label + a chevron that rotates
+ * when open) so every group header reads as one family.
+ *
+ * `defaultOpen` sets the initial state only (uncontrolled, like the overrides
+ * panel) — the user's toggle is theirs to keep for the session.
+ */
+export default function Section({ title, icon = null, defaultOpen = false, children }) {
+ return (
+
+
+ {icon}
+ {title}
+
+
+
{children}
+
+ );
+}
diff --git a/frontend/src/i18n/locales/ar.json b/frontend/src/i18n/locales/ar.json
index 3eb2ca53..62f120f3 100644
--- a/frontend/src/i18n/locales/ar.json
+++ b/frontend/src/i18n/locales/ar.json
@@ -2067,6 +2067,7 @@
"export_error": "تعذر تصدير القاموس."
},
"audiobook": {
+ "output": "الإخراج",
"title": "كتاب مسموع",
"subtitle": "قم بتحويل البرنامج النصي المحدد بفصل إلى كتاب صوتي m4b مقسم.",
"script": "البرنامج النصي",
diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json
index 73498164..d6580e79 100644
--- a/frontend/src/i18n/locales/de.json
+++ b/frontend/src/i18n/locales/de.json
@@ -2067,6 +2067,7 @@
"export_error": "Das Wörterbuch konnte nicht exportiert werden."
},
"audiobook": {
+ "output": "Ausgabe",
"title": "Hörbuch",
"subtitle": "Verwandeln Sie ein durch Kapitel getrenntes Skript in ein in Kapitel unterteiltes m4b-Hörbuch.",
"script": "Skript",
diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json
index 43861be3..1c6904c9 100644
--- a/frontend/src/i18n/locales/en.json
+++ b/frontend/src/i18n/locales/en.json
@@ -155,6 +155,7 @@
"flip_rail": "Flip rail side"
},
"audiobook": {
+ "output": "Output",
"title": "Audiobook",
"subtitle": "Turn a chapter-delimited script into a chapterized m4b audiobook.",
"script": "Script",
diff --git a/frontend/src/i18n/locales/es.json b/frontend/src/i18n/locales/es.json
index 49131d55..d617ce5d 100644
--- a/frontend/src/i18n/locales/es.json
+++ b/frontend/src/i18n/locales/es.json
@@ -2067,6 +2067,7 @@
"export_error": "No se pudo exportar el diccionario."
},
"audiobook": {
+ "output": "Salida",
"title": "Audiolibro",
"subtitle": "Convierta un guión delimitado por capítulos en un audiolibro m4b dividido en capítulos.",
"script": "Guión",
diff --git a/frontend/src/i18n/locales/fr.json b/frontend/src/i18n/locales/fr.json
index 889de698..4dfc1748 100644
--- a/frontend/src/i18n/locales/fr.json
+++ b/frontend/src/i18n/locales/fr.json
@@ -2067,6 +2067,7 @@
"export_error": "Impossible d'exporter le dictionnaire."
},
"audiobook": {
+ "output": "Sortie",
"title": "Livre audio",
"subtitle": "Transformez un script délimité par des chapitres en un livre audio m4b chapitré.",
"script": "Scénario",
diff --git a/frontend/src/i18n/locales/hi.json b/frontend/src/i18n/locales/hi.json
index 52e6acca..ae1db80d 100644
--- a/frontend/src/i18n/locales/hi.json
+++ b/frontend/src/i18n/locales/hi.json
@@ -2067,6 +2067,7 @@
"export_error": "शब्दकोश निर्यात नहीं हो सका।"
},
"audiobook": {
+ "output": "आउटपुट",
"title": "ऑडियोबुक",
"subtitle": "एक अध्याय-सीमांकित स्क्रिप्ट को एक अध्यायबद्ध m4b ऑडियोबुक में बदलें।",
"script": "स्क्रिप्ट",
diff --git a/frontend/src/i18n/locales/id.json b/frontend/src/i18n/locales/id.json
index 357fd405..3403c75c 100644
--- a/frontend/src/i18n/locales/id.json
+++ b/frontend/src/i18n/locales/id.json
@@ -2067,6 +2067,7 @@
"export_error": "Kamus tidak dapat diekspor."
},
"audiobook": {
+ "output": "Keluaran",
"title": "Buku Audio",
"subtitle": "Ubah skrip yang dibatasi bab menjadi buku audio m4b yang diberi bab.",
"script": "naskah",
diff --git a/frontend/src/i18n/locales/it.json b/frontend/src/i18n/locales/it.json
index 6ea6c01f..e0963193 100644
--- a/frontend/src/i18n/locales/it.json
+++ b/frontend/src/i18n/locales/it.json
@@ -2067,6 +2067,7 @@
"export_error": "Impossibile esportare il dizionario."
},
"audiobook": {
+ "output": "Uscita",
"title": "Audiolibro",
"subtitle": "Trasforma uno script delimitato da capitoli in un audiolibro m4b suddiviso in capitoli.",
"script": "Copione",
diff --git a/frontend/src/i18n/locales/ja.json b/frontend/src/i18n/locales/ja.json
index 073a2aab..26b27c8a 100644
--- a/frontend/src/i18n/locales/ja.json
+++ b/frontend/src/i18n/locales/ja.json
@@ -2067,6 +2067,7 @@
"export_error": "辞書をエクスポートできませんでした。"
},
"audiobook": {
+ "output": "出力",
"title": "オーディオブック",
"subtitle": "チャプター区切りのスクリプトをチャプター化された m4b オーディオブックに変換します。",
"script": "スクリプト",
diff --git a/frontend/src/i18n/locales/ko.json b/frontend/src/i18n/locales/ko.json
index d86c5e87..94b68238 100644
--- a/frontend/src/i18n/locales/ko.json
+++ b/frontend/src/i18n/locales/ko.json
@@ -2067,6 +2067,7 @@
"export_error": "사전을 내보낼 수 없습니다."
},
"audiobook": {
+ "output": "출력",
"title": "Audiobook",
"subtitle": "장으로 구분된 스크립트를 장으로 구분된 m4b 오디오북으로 바꿔보세요.",
"script": "스크립트",
diff --git a/frontend/src/i18n/locales/nl.json b/frontend/src/i18n/locales/nl.json
index 38e95bac..ce3293eb 100644
--- a/frontend/src/i18n/locales/nl.json
+++ b/frontend/src/i18n/locales/nl.json
@@ -2067,6 +2067,7 @@
"export_error": "Kan het woordenboek niet exporteren."
},
"audiobook": {
+ "output": "Uitvoer",
"title": "Audioboek",
"subtitle": "Verander een door hoofdstukken gescheiden script in een m4b-audioboek in hoofdstukken.",
"script": "Script",
diff --git a/frontend/src/i18n/locales/pl.json b/frontend/src/i18n/locales/pl.json
index 9d3e8f4a..cc78cd36 100644
--- a/frontend/src/i18n/locales/pl.json
+++ b/frontend/src/i18n/locales/pl.json
@@ -2067,6 +2067,7 @@
"export_error": "Nie udało się wyeksportować słownika."
},
"audiobook": {
+ "output": "Wyjście",
"title": "Książka audio",
"subtitle": "Zamień skrypt podzielony na rozdziały w audiobook m4b podzielony na rozdziały.",
"script": "Skrypt",
diff --git a/frontend/src/i18n/locales/pt.json b/frontend/src/i18n/locales/pt.json
index dc216bd6..32331697 100644
--- a/frontend/src/i18n/locales/pt.json
+++ b/frontend/src/i18n/locales/pt.json
@@ -2067,6 +2067,7 @@
"export_error": "Não foi possível exportar o dicionário."
},
"audiobook": {
+ "output": "Saída",
"title": "Audiolivro",
"subtitle": "Transforme um script delimitado por capítulos em um audiolivro m4b com capítulos.",
"script": "Roteiro",
diff --git a/frontend/src/i18n/locales/ru.json b/frontend/src/i18n/locales/ru.json
index 991900fd..f186b104 100644
--- a/frontend/src/i18n/locales/ru.json
+++ b/frontend/src/i18n/locales/ru.json
@@ -2067,6 +2067,7 @@
"export_error": "Не удалось экспортировать словарь."
},
"audiobook": {
+ "output": "Вывод",
"title": "Аудиокнига",
"subtitle": "Превратите сценарий с разделением глав в аудиокнигу m4b с разделением на главы.",
"script": "Скрипт",
diff --git a/frontend/src/i18n/locales/sv.json b/frontend/src/i18n/locales/sv.json
index efb2d887..8a25a30d 100644
--- a/frontend/src/i18n/locales/sv.json
+++ b/frontend/src/i18n/locales/sv.json
@@ -2067,6 +2067,7 @@
"export_error": "Kunde inte exportera ordboken."
},
"audiobook": {
+ "output": "Utdata",
"title": "Ljudbok",
"subtitle": "Förvandla ett kapitelavgränsat manus till en kapitelinställd m4b-ljudbok.",
"script": "Manus",
diff --git a/frontend/src/i18n/locales/th.json b/frontend/src/i18n/locales/th.json
index 5f1cc49f..3e8d9644 100644
--- a/frontend/src/i18n/locales/th.json
+++ b/frontend/src/i18n/locales/th.json
@@ -2067,6 +2067,7 @@
"export_error": "ไม่สามารถส่งออกพจนานุกรมได้"
},
"audiobook": {
+ "output": "เอาต์พุต",
"title": "หนังสือเสียง",
"subtitle": "เปลี่ยนสคริปต์ที่คั่นด้วยบทให้เป็นหนังสือเสียงแบบแบ่งบท m4b",
"script": "สคริปต์",
diff --git a/frontend/src/i18n/locales/tr.json b/frontend/src/i18n/locales/tr.json
index ae2ec809..3b0c42c8 100644
--- a/frontend/src/i18n/locales/tr.json
+++ b/frontend/src/i18n/locales/tr.json
@@ -2067,6 +2067,7 @@
"export_error": "Sözlük dışa aktarılamadı."
},
"audiobook": {
+ "output": "Çıktı",
"title": "Sesli kitap",
"subtitle": "Bölümle ayrılmış bir komut dosyasını bölümlere ayrılmış bir m4b sesli kitaba dönüştürün.",
"script": "Senaryo",
diff --git a/frontend/src/i18n/locales/uk.json b/frontend/src/i18n/locales/uk.json
index dad115eb..f9af5340 100644
--- a/frontend/src/i18n/locales/uk.json
+++ b/frontend/src/i18n/locales/uk.json
@@ -2067,6 +2067,7 @@
"export_error": "Не вдалося експортувати словник."
},
"audiobook": {
+ "output": "Вивід",
"title": "Аудіокнига",
"subtitle": "Перетворіть розділений розділами сценарій на розділену на розділи аудіокнигу m4b.",
"script": "Сценарій",
diff --git a/frontend/src/i18n/locales/vi.json b/frontend/src/i18n/locales/vi.json
index f2a876be..880baa3d 100644
--- a/frontend/src/i18n/locales/vi.json
+++ b/frontend/src/i18n/locales/vi.json
@@ -2067,6 +2067,7 @@
"export_error": "Không thể xuất từ điển."
},
"audiobook": {
+ "output": "Đầu ra",
"title": "Sách nói",
"subtitle": "Biến tập lệnh được phân cách theo chương thành sách nói m4b được chia theo chương.",
"script": "kịch bản",
diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json
index f7a039d7..e60a12f1 100644
--- a/frontend/src/i18n/locales/zh-CN.json
+++ b/frontend/src/i18n/locales/zh-CN.json
@@ -2074,6 +2074,7 @@
"export_error": "无法导出词典。"
},
"audiobook": {
+ "output": "输出",
"title": "有声读物",
"subtitle": "将章节分隔的脚本转换为章节化的 m4b 有声读物。",
"script": "脚本",
diff --git a/frontend/src/i18n/locales/zh-TW.json b/frontend/src/i18n/locales/zh-TW.json
index 14b99636..4adedd95 100644
--- a/frontend/src/i18n/locales/zh-TW.json
+++ b/frontend/src/i18n/locales/zh-TW.json
@@ -2067,6 +2067,7 @@
"export_error": "無法匯出字典。"
},
"audiobook": {
+ "output": "輸出",
"title": "有聲書",
"subtitle": "將章節分隔的腳本轉換為章節化的 m4b 有聲書。",
"script": "腳本",
diff --git a/frontend/src/pages/AudiobookTab.jsx b/frontend/src/pages/AudiobookTab.jsx
index a0452fc3..537b9c3d 100644
--- a/frontend/src/pages/AudiobookTab.jsx
+++ b/frontend/src/pages/AudiobookTab.jsx
@@ -3,13 +3,14 @@ import { useTranslation } from 'react-i18next';
import {
BookMarked,
BookOpen,
+ BookText,
+ Code,
Download,
Loader,
- Image as ImageIcon,
- X,
Play,
+ SlidersHorizontal,
+ SpellCheck,
Upload,
- Plus,
} from 'lucide-react';
import {
@@ -26,6 +27,9 @@ import { useAppStore } from '../store';
import VoiceSelector from '../components/VoiceSelector';
import SearchableSelect from '../components/SearchableSelect';
import AudiobookOverrides, { overridesToRequest } from '../components/audiobook/AudiobookOverrides';
+import Section from '../components/audiobook/Section';
+import BookDetails from '../components/audiobook/BookDetails';
+import LexiconEditor from '../components/audiobook/LexiconEditor';
import { SAMPLE_AUDIOBOOK_SCRIPT } from '../data/sampleAudiobook';
import ALL_LANGUAGES from '../languages.json';
import { POPULAR_LANGS } from '../utils/constants';
@@ -388,187 +392,67 @@ export default function AudiobookTab({ profiles = [] }) {
emotionSupported={emotionSupported}
/>
-