diff --git a/backend/package-lock.json b/backend/package-lock.json
index 5f93a3c..958e829 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "ripster-backend",
- "version": "0.10.2-7",
+ "version": "0.10.2-8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ripster-backend",
- "version": "0.10.2-7",
+ "version": "0.10.2-8",
"dependencies": {
"archiver": "^7.0.1",
"cors": "^2.8.5",
diff --git a/backend/package.json b/backend/package.json
index b3dfc43..1a741f0 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,6 +1,6 @@
{
"name": "ripster-backend",
- "version": "0.10.2-7",
+ "version": "0.10.2-8",
"private": true,
"type": "commonjs",
"scripts": {
diff --git a/backend/src/services/pipelineService.js b/backend/src/services/pipelineService.js
index 83594d0..c9bb8f8 100644
--- a/backend/src/services/pipelineService.js
+++ b/backend/src/services/pipelineService.js
@@ -11562,6 +11562,7 @@ class PipelineService extends EventEmitter {
{
contextPatch: {
outputPath: incompleteOutputPath,
+ completedChapterCount: index,
currentChapter: {
index: index + 1,
total: outputFiles.length,
@@ -11618,6 +11619,24 @@ class PipelineService extends EventEmitter {
chapterTitle,
outputPath: entry.outputPath
});
+
+ await this.updateProgress(
+ 'ENCODING',
+ endPercent,
+ null,
+ `Audiobook-Encoding Kapitel ${index + 1}/${outputFiles.length} abgeschlossen`,
+ jobId,
+ {
+ contextPatch: {
+ completedChapterCount: index + 1,
+ currentChapter: {
+ index: index + 1,
+ total: outputFiles.length,
+ title: chapterTitle
+ }
+ }
+ }
+ );
}
ffmpegRunInfo = {
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 0fa20fb..016bacf 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "ripster-frontend",
- "version": "0.10.2-7",
+ "version": "0.10.2-8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ripster-frontend",
- "version": "0.10.2-7",
+ "version": "0.10.2-8",
"dependencies": {
"primeicons": "^7.0.0",
"primereact": "^10.9.2",
diff --git a/frontend/package.json b/frontend/package.json
index 5989d71..5754c45 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "ripster-frontend",
- "version": "0.10.2-7",
+ "version": "0.10.2-8",
"private": true,
"type": "module",
"scripts": {
diff --git a/frontend/src/components/AudiobookConfigPanel.jsx b/frontend/src/components/AudiobookConfigPanel.jsx
index eb28ad7..59b345d 100644
--- a/frontend/src/components/AudiobookConfigPanel.jsx
+++ b/frontend/src/components/AudiobookConfigPanel.jsx
@@ -158,8 +158,13 @@ export default function AudiobookConfigPanel({
const schema = AUDIOBOOK_FORMAT_SCHEMAS[format] || AUDIOBOOK_FORMAT_SCHEMAS.mp3;
const canStart = Boolean(jobId) && (state === 'READY_TO_START' || state === 'ERROR' || state === 'CANCELLED');
const isRunning = state === 'ENCODING';
+ const isFinished = state === 'FINISHED';
const progress = Number.isFinite(Number(pipeline?.progress)) ? Math.max(0, Math.min(100, Number(pipeline.progress))) : 0;
const outputPath = String(context?.outputPath || '').trim() || null;
+ const isSplitOutput = format === 'mp3' || format === 'flac';
+ const currentChapter = context?.currentChapter && typeof context.currentChapter === 'object' ? context.currentChapter : null;
+ const completedChapterCount = Number(context?.completedChapterCount ?? -1);
+ const chapterTotal = Number(currentChapter?.total || editableChapters.length || 0);
const statusLabel = getStatusLabel(state);
const statusSeverity = getStatusSeverity(state);
const description = String(metadata?.description || '').trim();
@@ -290,7 +295,29 @@ export default function AudiobookConfigPanel({
{isRunning ? (
-
{Math.round(progress)}%
+
{Math.round(progress)}%{currentChapter ? ` | Kapitel ${currentChapter.index}/${currentChapter.total}: ${currentChapter.title}` : ''}
+
+ ) : null}
+
+ {isSplitOutput && (isRunning || isFinished) && editableChapters.length > 0 ? (
+
+
Kapitel-Status ({completedChapterCount >= 0 ? completedChapterCount : (isFinished ? editableChapters.length : 0)}/{chapterTotal || editableChapters.length} fertig)
+
+ {editableChapters.map((chapter, idx) => {
+ const chIdx = chapter.index || idx + 1;
+ const isDone = isFinished || (completedChapterCount >= 0 && chIdx <= completedChapterCount);
+ const isActive = !isDone && currentChapter?.index === chIdx;
+ const statusIcon = isDone ? 'pi pi-check-circle' : isActive ? 'pi pi-spin pi-spinner' : 'pi pi-circle';
+ const statusClass = isDone ? 'chapter-status-done' : isActive ? 'chapter-status-active' : 'chapter-status-pending';
+ return (
+
+
+ #{String(chIdx).padStart(2, '0')}
+ {chapter.title}
+
+ );
+ })}
+
) : null}
diff --git a/frontend/src/pages/DashboardPage.jsx b/frontend/src/pages/DashboardPage.jsx
index 3085dae..59bce91 100644
--- a/frontend/src/pages/DashboardPage.jsx
+++ b/frontend/src/pages/DashboardPage.jsx
@@ -866,6 +866,7 @@ export default function DashboardPage({
const [insertWaitSeconds, setInsertWaitSeconds] = useState(30);
const toastRef = useRef(null);
const audiobookFileUploadRef = useRef(null);
+ const [audiobookUploadStatusVisible, setAudiobookUploadStatusVisible] = useState(false);
const state = String(pipeline?.state || 'IDLE').trim().toUpperCase();
const currentPipelineJobId = normalizeJobId(pipeline?.activeJobId || pipeline?.context?.jobId);
@@ -1123,6 +1124,19 @@ export default function DashboardPage({
setExpandedJobId(normalizeJobId(dashboardJobs[0]?.id));
}, [dashboardJobs, expandedJobId, currentPipelineJobId]);
+ useEffect(() => {
+ if (audiobookUploadPhase === 'idle') {
+ setAudiobookUploadStatusVisible(false);
+ return undefined;
+ }
+ setAudiobookUploadStatusVisible(true);
+ if (audiobookUploadPhase === 'completed') {
+ const timer = setTimeout(() => setAudiobookUploadStatusVisible(false), 5000);
+ return () => clearTimeout(timer);
+ }
+ return undefined;
+ }, [audiobookUploadPhase]);
+
useEffect(() => {
if (!currentPipelineJobId || !isProcessing) {
setLiveJobLog('');
@@ -2531,14 +2545,6 @@ export default function DashboardPage({
{file.name}
{options.formatSize}
-