aboutsummaryrefslogtreecommitdiff
path: root/app/ui/runview.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-28 03:16:14 -0400
committerhistoria <historiavg@proton.me>2026-08-28 03:16:14 -0400
commitd487996281f71ccd3034dd708fa099057e528b42 (patch)
tree989d9f40f5c2dbb03e1cf570f2c463f43cf5ac76 /app/ui/runview.py
parent5c3db8f500ff206f3a675d8f4184cb0d61f94804 (diff)
downloadtts-audiobook-generator-d487996281f71ccd3034dd708fa099057e528b42.tar.gz
feat: unified logging with logging_kit.py
Diffstat (limited to 'app/ui/runview.py')
-rw-r--r--app/ui/runview.py51
1 files changed, 48 insertions, 3 deletions
diff --git a/app/ui/runview.py b/app/ui/runview.py
index 9448c06..b3a1ab0 100644
--- a/app/ui/runview.py
+++ b/app/ui/runview.py
@@ -29,7 +29,6 @@ so the failure is never scrolled away.
"""
import contextlib
-import io
import threading
import time
from dataclasses import dataclass
@@ -59,6 +58,51 @@ _SERVER_STATES = {
_MONITOR_INTERVAL = 2.0
+class _LogAppender:
+ """A file-like that appends redirected console output to the run's log.
+
+ The run view owns the screen, so anything a conversion prints to
+ stdout/stderr outside the progress events would otherwise be swallowed
+ silently; this mirrors it line by line into the run's dated log file
+ (RunConfig.log_path, the audiobook_ day stream), prefixed with the same
+ timestamp format the converter's log records use. Best-effort: write
+ errors are swallowed, and an empty path disables logging.
+ """
+
+ def __init__(self, path: str):
+ self._path = path
+ self._buffer = ""
+
+ def write(self, text: str) -> int:
+ if not text:
+ return 0
+ self._buffer += text
+ while True:
+ cut = self._buffer.find("\n")
+ if cut < 0:
+ break
+ line, self._buffer = self._buffer[:cut], self._buffer[cut + 1:]
+ self._append(line)
+ return len(text)
+
+ def flush(self) -> None:
+ if self._buffer:
+ self._append(self._buffer)
+ self._buffer = ""
+
+ def isatty(self) -> bool:
+ return False
+
+ def _append(self, line: str) -> None:
+ if not self._path or not line.strip():
+ return
+ try:
+ with open(self._path, "a", encoding="utf-8") as logf:
+ logf.write(f"{datetime.now():%Y-%m-%d %H:%M:%S} - {line}\n")
+ except OSError:
+ pass
+
+
@dataclass
class RunConfig:
"""Everything the run view needs to execute one conversion.
@@ -232,7 +276,7 @@ class RunView(ScreenView):
import audiobook
config = self.config
try:
- with contextlib.redirect_stdout(io.StringIO()):
+ with contextlib.redirect_stdout(_LogAppender(config.log_path)):
if config.autostart_spec is not None:
if config.restart_first:
# The managed qwen server hosts another model than
@@ -401,7 +445,8 @@ class RunView(ScreenView):
def _stop() -> None:
try:
- with contextlib.redirect_stdout(io.StringIO()):
+ with contextlib.redirect_stdout(
+ _LogAppender(self.config.log_path)):
servers.stop(name)
finally:
self._queue.put({"kind": "server_stopped"})