Files
UmbraAudio/scripts/qwen3_tts_demo_umbra_patch.patch
B.D. Murphy c86e323d7a Initial commit: UmbraAudio audiobook generation program
Quality-first multi-pass audiobook generation from English manuscripts.

Includes:
- umbraaudio/ core package (pipeline, config, voices, web review UI)
- scripts/ CLI utilities + model download script
- tts_server/ remote TTS server
- specs/ design docs, tests/, pyproject.toml + uv.lock
- Qwen3-TTS git submodule (pinned 022e286) with local demo.py UI patch
  saved at scripts/qwen3_tts_demo_umbra_patch.patch
- .env with runtime config/secrets

Excluded (see .gitignore): models/ (downloaded via scripts/download_models.sh),
output/, logs/, .venv, .umbra runtime data, and book projects
(DealHunterShip_*), which are committed separately.
2026-08-30 07:58:24 -07:00

112 lines
3.5 KiB
Diff

diff --git a/qwen_tts/cli/demo.py b/qwen_tts/cli/demo.py
index e267861..686c3f6 100644
--- a/qwen_tts/cli/demo.py
+++ b/qwen_tts/cli/demo.py
@@ -18,9 +18,11 @@ A gradio demo for Qwen3 TTS models.
"""
import argparse
+import base64
import os
import tempfile
from dataclasses import asdict
+from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
import gradio as gr
@@ -59,6 +61,31 @@ def _maybe(v):
return v if v is not None else gr.update()
+def _find_icon() -> Optional[Path]:
+ candidates = [
+ Path(os.environ.get("UMBRA_AUDIO_ICON", "")) if os.environ.get("UMBRA_AUDIO_ICON") else None,
+ Path("/Volumes/1TB_APL_Storage/UmbraAudio/UmbraAudio_icon.png"),
+ Path(__file__).resolve().parents[2] / "assets" / "logo.png",
+ Path("/Volumes/1TB_APL_Storage/UmbraAudio/UmbraAudio_icon.jpeg"),
+ ]
+ for c in candidates:
+ if c is not None and c.is_file():
+ return c
+ return None
+
+
+def _icon_data_uri() -> Optional[str]:
+ icon = _find_icon()
+ if icon is None:
+ return None
+ try:
+ data = base64.b64encode(icon.read_bytes()).decode("ascii")
+ mime = "image/png" if icon.suffix.lower() == ".png" else "image/jpeg"
+ return f"data:{mime};base64,{data}"
+ except OSError:
+ return None
+
+
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="qwen-tts-demo",
@@ -168,6 +195,18 @@ def build_parser() -> argparse.ArgumentParser:
return parser
+def _find_favicon() -> Optional[Path]:
+ for name in ("UmbraAudio_icon.png", "logo.png"):
+ p = Path(__file__).resolve().parents[3] / name
+ if p.is_file():
+ return p
+ alt = Path("/Volumes/1TB_APL_Storage/UmbraAudio") / name
+ if alt.is_file():
+ return alt
+ icon = _find_icon()
+ return icon if icon is not None and icon.suffix.lower() in (".png", ".gif", ".ico") else None
+
+
def _resolve_checkpoint(args: argparse.Namespace) -> str:
ckpt = args.checkpoint or args.checkpoint_pos
if not ckpt:
@@ -275,13 +314,30 @@ def build_demo(tts: Qwen3TTSModel, ckpt: str, gen_kwargs_default: Dict[str, Any]
css = ".gradio-container {max-width: none !important;}"
with gr.Blocks(theme=theme, css=css) as demo:
- gr.Markdown(
- f"""
+ icon_uri = _icon_data_uri()
+ if icon_uri:
+ with gr.Row():
+ with gr.Column(scale=1, min_width=120):
+ gr.HTML(
+ f'<img src="{icon_uri}" alt="UmbraAudio" '
+ 'style="max-height:96px; display:block; margin:0 auto;">'
+ )
+ with gr.Column(scale=4):
+ gr.Markdown(
+ f"""
+# UmbraAudio Voice Studio
+**Checkpoint:** `{ckpt}`
+**Model Type:** `{model_kind}`
+"""
+ )
+ else:
+ gr.Markdown(
+ f"""
# Qwen3 TTS Demo
**Checkpoint:** `{ckpt}`
**Model Type:** `{model_kind}`
"""
- )
+ )
if model_kind == "custom_voice":
with gr.Row():
@@ -621,6 +677,9 @@ def main(argv=None) -> int:
share=args.share,
ssl_verify=True if args.ssl_verify else False,
)
+ favicon = _find_favicon()
+ if favicon is not None:
+ launch_kwargs["favicon_path"] = str(favicon)
if args.ssl_certfile is not None:
launch_kwargs["ssl_certfile"] = args.ssl_certfile
if args.ssl_keyfile is not None: