aboutsummaryrefslogtreecommitdiff
path: root/app/ui/runview.py
diff options
context:
space:
mode:
Diffstat (limited to 'app/ui/runview.py')
-rw-r--r--app/ui/runview.py64
1 files changed, 57 insertions, 7 deletions
diff --git a/app/ui/runview.py b/app/ui/runview.py
index 759fcfb..21ac08c 100644
--- a/app/ui/runview.py
+++ b/app/ui/runview.py
@@ -161,6 +161,7 @@ class RunView(ScreenView):
self.chunk_total = 0
self.book_results: List[tuple] = [] # (name, ok, files, error, model)
self.error_message = ""
+ self._book_error = "" # current book's failure reason (results row)
self.started_server = False # cancelled/cancelling/finished_at: base
self.boot_started: Optional[float] = None
self.convert_started: Optional[float] = None
@@ -215,6 +216,11 @@ class RunView(ScreenView):
self.chapter = None
self.chunk_done = 0
self.chunk_total = 0
+ # A new book starts with a clean slate: a failure message from
+ # the previous book (an earlier model of an "All" run) must not
+ # linger under this one's progress.
+ self.error_message = ""
+ self._book_error = ""
self.convert_started = self.convert_started or self._now()
if self.server == "ready":
self.server = "processing"
@@ -231,15 +237,30 @@ class RunView(ScreenView):
if self.server in ("ready", "processing"):
self.server = "processing"
elif kind == "chunk_failed":
- self.error_message = (f"chunk {event.get('chunk')}/"
- f"{event.get('total')} failed")
+ # The converter emits book_done(ok=False) for a chunk failure
+ # with no error of its own, so remember the reason here for
+ # that results row. The live message names the model (an "All"
+ # run stamps its events) and the server's error detail.
+ detail = event.get("error") or ""
+ self._book_error = detail or (f"chunk {event.get('chunk')}/"
+ f"{event.get('total')} failed")
+ message = self._book_error
+ if detail:
+ message = (f"chunk {event.get('chunk')}/"
+ f"{event.get('total')} failed — {detail}")
+ if event.get("model"):
+ message = f"{event['model']}: {message}"
+ self.error_message = message
if self.server in ("ready", "processing"):
self.server = "ready"
elif kind == "book_done":
self.book_results.append((event.get("name") or "?",
bool(event.get("ok")),
list(event.get("files") or []),
- "", event.get("model")))
+ "" if event.get("ok")
+ else (event.get("error")
+ or self._book_error),
+ event.get("model")))
elif kind == "book_failed":
self.book_results.append((event.get("name") or "?", False,
list(event.get("files") or []),
@@ -256,7 +277,8 @@ class RunView(ScreenView):
elif total and ok >= total and not self.error_message:
self._finish("done")
else:
- self.error_message = self.error_message or \
+ self.error_message = self._failure_summary(total) or \
+ self.error_message or \
f"{total - ok} of {total} book(s) failed"
self._finish("error")
elif kind == "error":
@@ -510,6 +532,26 @@ class RunView(ScreenView):
f"{self.config.log_path}")
return "\n".join(lines)
+ def _failure_summary(self, total: int) -> str:
+ """The run-level failure line for the terminal "done" screen.
+
+ Names every book that produced no audiobook (the generating model
+ on an "All" run, the book file otherwise) instead of leaving a
+ stale per-chunk message as the run's headline. Capped so the two
+ detail lines stay readable; the [FAIL] result rows below carry the
+ full list with each failure's reason. Empty when the results say
+ every book succeeded (the count comes from the events, not the
+ rows — see the caller's fallback).
+ """
+ failed = [(model or name) for name, ok, _files, _error, model
+ in self.book_results if not ok]
+ if not failed:
+ return ""
+ shown = ", ".join(failed[:5])
+ if len(failed) > 5:
+ shown += f", … +{len(failed) - 5} more"
+ return f"{len(failed)} of {total} book(s) failed: {shown}"
+
_server_stopped_confirmed = False
# ------------------------------------------------------------------
@@ -665,16 +707,24 @@ class RunView(ScreenView):
_text(scr, theme, y, value_x, _fit(result, value_w),
theme.get(kind, theme["body"]))
y += 1
- for name, ok, _files, _error, model in self.book_results[:5]:
+ # Failed rows first (stable sort keeps each group in completion
+ # order): with an "All" run's dozens of results the failures must
+ # not require scrolling to find. A failed row carries its reason
+ # (the server's error, remembered from the chunk_failed event).
+ rows = sorted(self.book_results,
+ key=lambda result: 1 if result[1] else 0)
+ for name, ok, _files, error, model in rows[:5]:
mark = "[OK] " if ok else "[FAIL]"
label = f"{name} — {model}" if model else name
+ if not ok and error:
+ label = f"{label}: {error}"
_text(scr, theme, y, value_x,
_fit(f"{mark} {label}", value_w),
theme["ok"] if ok else theme["err"])
y += 1
- if len(self.book_results) > 5:
+ if len(rows) > 5:
_text(scr, theme, y, value_x,
- _fit(f"... and {len(self.book_results) - 5} more",
+ _fit(f"... and {len(rows) - 5} more",
value_w), theme["dim"])
y += 1
if self.phase == "error":