Use URL-based logos, env-based WebGUI title, apstrom.ca link, update README
This commit is contained in:
@@ -1,28 +1,42 @@
|
|||||||
# ScrAIbe – LocalAI-Backed Transcription and Summarization
|
# ScrAIbe – LocalAI-Backed Transcription and Summarization
|
||||||
|
|
||||||
ScrAIbe is a lightweight transcription and summarization client that:
|
ScrAIbe is a transcription and summarization service that:
|
||||||
|
|
||||||
- Sends audio to a LocalAI server running vibevoice.cpp for transcription and speaker diarization.
|
- Sends audio to a LocalAI server running vibevoice.cpp for transcription and speaker diarization.
|
||||||
- Optionally uses a second LLM to generate a detailed, structured summary of the conversation.
|
- Optionally uses a second LLM to generate a detailed, structured summary.
|
||||||
|
- Provides:
|
||||||
|
- A web GUI for uploading audio and receiving transcripts via email.
|
||||||
|
- A CLI and Python API for direct integration.
|
||||||
|
|
||||||
No local speech models or heavy dependencies are required. ScrAIbe is designed to be run as a thin client in front of your own AI services.
|
No local speech models or heavy dependencies are required. ScrAIbe is designed as a thin client in front of your own AI services.
|
||||||
|
|
||||||
|
For more information: https://apstrom.ca
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Transcription with speaker diarization via LocalAI:
|
- Transcription with speaker diarization via LocalAI:
|
||||||
- Uses the `/v1/audio/diarization` endpoint.
|
- Uses the /v1/audio/diarization endpoint.
|
||||||
- Compatible with vibevoice.cpp and other diarization-capable backends.
|
- Compatible with vibevoice.cpp and other diarization-capable backends.
|
||||||
- Optional AI-powered summarization:
|
- Optional AI-powered summarization:
|
||||||
- Task: `transcript_and_summarize`
|
- Task: transcript_and_summarize
|
||||||
- Chunks long transcripts, summarizes each chunk, then generates a final comprehensive summary.
|
- Highlights:
|
||||||
- Summary highlights:
|
|
||||||
- Main topics and discussion points
|
- Main topics and discussion points
|
||||||
- Key decisions and outcomes
|
- Key decisions and outcomes
|
||||||
- Action items and responsibilities
|
- Action items and responsibilities
|
||||||
- Open issues and risks
|
- Open issues and risks
|
||||||
|
- Async web GUI:
|
||||||
|
- Upload audio via browser.
|
||||||
|
- Jobs are queued and processed in the background (Celery + Redis).
|
||||||
|
- Emails:
|
||||||
|
- Immediate confirmation with queue position.
|
||||||
|
- Final transcript (TXT + JSON) when ready.
|
||||||
|
- Summary as MD file (if requested).
|
||||||
|
- Error notification if processing fails.
|
||||||
|
- Customizable branding:
|
||||||
|
- Web GUI title, logo, and email logo via environment variables.
|
||||||
- CLI and Python API:
|
- CLI and Python API:
|
||||||
- Simple command-line interface.
|
- Simple command-line interface.
|
||||||
- Drop-in `Scraibe` class for integration into other tools.
|
- Drop-in Scraibe class for integration into other tools.
|
||||||
- Docker-ready:
|
- Docker-ready:
|
||||||
- Lightweight container, configured via environment variables.
|
- Lightweight container, configured via environment variables.
|
||||||
|
|
||||||
@@ -38,6 +52,31 @@ No local speech models or heavy dependencies are required. ScrAIbe is designed t
|
|||||||
- Transcript assembly
|
- Transcript assembly
|
||||||
- Chunked summarization
|
- Chunked summarization
|
||||||
- Output formatting (e.g., .md with transcript + summary)
|
- Output formatting (e.g., .md with transcript + summary)
|
||||||
|
- Runs:
|
||||||
|
- Web GUI (Gradio)
|
||||||
|
- Celery worker (async processing)
|
||||||
|
- Redis (in-container by default)
|
||||||
|
|
||||||
|
## Quick Start (Web GUI in Docker)
|
||||||
|
|
||||||
|
Run the container with your LocalAI and summarizer endpoints:
|
||||||
|
|
||||||
|
- docker run -d \
|
||||||
|
-p 7860:7860 \
|
||||||
|
-e LOCALAI_API_URL=http://localai:8080 \
|
||||||
|
-e SUMMARIZER_API_URL=http://llm:8080 \
|
||||||
|
-e EMAIL_SMTP_HOST=smtp.your-domain.com \
|
||||||
|
-e EMAIL_SMTP_PORT=587 \
|
||||||
|
-e EMAIL_SMTP_USER=transcribe@your-domain.com \
|
||||||
|
-e EMAIL_SMTP_PASSWORD=your_password \
|
||||||
|
-e EMAIL_FROM_ADDRESS="ScrAIbe <transcribe@your-domain.com>" \
|
||||||
|
-e EMAIL_CONTACT_ADDRESS=support@your-domain.com \
|
||||||
|
-e WEBUI_TITLE="Your Transcription Service" \
|
||||||
|
-e WEBUI_LOGO_URL="https://your-domain.com/logo.png" \
|
||||||
|
-e EMAIL_LOGO_URL="https://your-domain.com/logo.png" \
|
||||||
|
scraibe:latest
|
||||||
|
|
||||||
|
Then open: http://<host>:7860
|
||||||
|
|
||||||
## Quick Start (CLI)
|
## Quick Start (CLI)
|
||||||
|
|
||||||
@@ -101,7 +140,7 @@ Other options (e.g., --language, --num-speakers) are accepted and forwarded wher
|
|||||||
|
|
||||||
ScrAIbe is designed to run in Docker as a client to your LocalAI and summarizer LLM.
|
ScrAIbe is designed to run in Docker as a client to your LocalAI and summarizer LLM.
|
||||||
|
|
||||||
### Basic run (transcribe)
|
### Basic run (transcribe via CLI)
|
||||||
|
|
||||||
- docker run -it \
|
- docker run -it \
|
||||||
-e LOCALAI_API_URL=http://localai:8080 \
|
-e LOCALAI_API_URL=http://localai:8080 \
|
||||||
@@ -109,7 +148,7 @@ ScrAIbe is designed to run in Docker as a client to your LocalAI and summarizer
|
|||||||
scraibe:latest \
|
scraibe:latest \
|
||||||
-f /audio/meeting.wav -o /audio/output -of txt
|
-f /audio/meeting.wav -o /audio/output -of txt
|
||||||
|
|
||||||
### Basic run (transcribe + summarize)
|
### Basic run (transcribe + summarize via CLI)
|
||||||
|
|
||||||
- docker run -it \
|
- docker run -it \
|
||||||
-e LOCALAI_API_URL=http://localai:8080 \
|
-e LOCALAI_API_URL=http://localai:8080 \
|
||||||
@@ -148,6 +187,46 @@ Summarization LLM:
|
|||||||
- Optional (default: llama-3.1-8b-instruct).
|
- Optional (default: llama-3.1-8b-instruct).
|
||||||
- Model name used for summarization.
|
- Model name used for summarization.
|
||||||
|
|
||||||
|
Web GUI and branding:
|
||||||
|
|
||||||
|
- WEBUI_TITLE:
|
||||||
|
- Title shown in the web GUI (default: A.P.Strom Transcription).
|
||||||
|
- WEBUI_LOGO_URL:
|
||||||
|
- URL of the logo displayed in the web GUI header.
|
||||||
|
- Example: https://your-domain.com/logo.png
|
||||||
|
|
||||||
|
Async processing (Celery + Redis):
|
||||||
|
|
||||||
|
- CELERY_BROKER_URL:
|
||||||
|
- Redis broker URL (default: redis://localhost:6379/0).
|
||||||
|
- CELERY_RESULT_BACKEND:
|
||||||
|
- Redis backend URL (default: redis://localhost:6379/0).
|
||||||
|
- SCRAIBE_UPLOAD_DIR:
|
||||||
|
- Directory where uploaded audio is stored (default: /tmp/scraibe_uploads).
|
||||||
|
|
||||||
|
Email configuration:
|
||||||
|
|
||||||
|
- EMAIL_SMTP_HOST:
|
||||||
|
- SMTP server host.
|
||||||
|
- EMAIL_SMTP_PORT:
|
||||||
|
- SMTP server port (e.g., 587).
|
||||||
|
- EMAIL_SMTP_USER:
|
||||||
|
- SMTP username.
|
||||||
|
- EMAIL_SMTP_PASSWORD:
|
||||||
|
- SMTP password.
|
||||||
|
- EMAIL_SMTP_USE_TLS:
|
||||||
|
- Use TLS (true/false; default: true).
|
||||||
|
- EMAIL_FROM_ADDRESS:
|
||||||
|
- Sender address (e.g., "ScrAIbe <transcribe@your-domain.com>").
|
||||||
|
- EMAIL_CONTACT_ADDRESS:
|
||||||
|
- Support contact address shown in email templates.
|
||||||
|
- EMAIL_LOGO_URL:
|
||||||
|
- URL of the logo used in emails (preferred).
|
||||||
|
- EMAIL_LOGO_PATH:
|
||||||
|
- Fallback local path for email logo (default: /app/src/misc/logo1.png).
|
||||||
|
- EMAIL_CSS_PATH:
|
||||||
|
- Path to the CSS used in emails (default: /app/src/misc/mail_style.css).
|
||||||
|
|
||||||
All of these can also be overridden from the CLI when needed (e.g., --localai-api-url, --summarizer-api-url).
|
All of these can also be overridden from the CLI when needed (e.g., --localai-api-url, --summarizer-api-url).
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
@@ -158,6 +237,9 @@ Core runtime dependencies:
|
|||||||
- httpx
|
- httpx
|
||||||
- numpy
|
- numpy
|
||||||
- tqdm
|
- tqdm
|
||||||
|
- gradio
|
||||||
|
- celery[redis]
|
||||||
|
- redis
|
||||||
- ffmpeg (for audio preprocessing)
|
- ffmpeg (for audio preprocessing)
|
||||||
|
|
||||||
No local Whisper, PyTorch, or Pyannote models are required.
|
No local Whisper, PyTorch, or Pyannote models are required.
|
||||||
|
|||||||
+9
-9
@@ -56,14 +56,14 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #333;
|
color: #333;
|
||||||
}}
|
}}
|
||||||
.github-section {{
|
.brand-section {{
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}}
|
}}
|
||||||
.github-icon a {{
|
.brand-icon a {{
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -75,15 +75,15 @@
|
|||||||
color: white;
|
color: white;
|
||||||
transition: background-color 0.3s ease, transform 0.3s ease;
|
transition: background-color 0.3s ease, transform 0.3s ease;
|
||||||
}}
|
}}
|
||||||
.github-icon i {{
|
.brand-icon i {{
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}}
|
}}
|
||||||
.github-icon a:hover, .github-icon a:focus {{
|
.brand-icon a:hover, .brand-icon a:focus {{
|
||||||
background-color: #50AF31;
|
background-color: #50AF31;
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}}
|
}}
|
||||||
.github-icon a, .github-icon a:hover, .github-icon a:active, .github-icon a:visited {{
|
.brand-icon a, .brand-icon a:hover, .brand-icon a:active, .brand-icon a:visited {{
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}}
|
}}
|
||||||
.build-version {{
|
.build-version {{
|
||||||
@@ -105,10 +105,10 @@
|
|||||||
<h2 style="font-weight: bold; color: #7C6DA0;">Data retention</h2>
|
<h2 style="font-weight: bold; color: #7C6DA0;">Data retention</h2>
|
||||||
<p>Audio or video files uploaded to this application are only retained for the time that it takes to complete the transcription. All transcripts are deleted after they are transmitted to the user.</p>
|
<p>Audio or video files uploaded to this application are only retained for the time that it takes to complete the transcription. All transcripts are deleted after they are transmitted to the user.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="github-section">
|
<div class="brand-section">
|
||||||
<div class="github-icon">
|
<div class="brand-icon">
|
||||||
<a href="https://github.com/JSchmie/ScrAIbe-WebUI" aria-label="GitHub">
|
<a href="https://apstrom.ca" aria-label="A.P.Strom">
|
||||||
<i class="fab fa-github"></i>
|
<i class="fas fa-globe"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+3
-3
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>A.P.Strom Transcription</title>
|
<title>{webui_title}</title>
|
||||||
|
|
||||||
<!-- Importing Cormorant Garamond font from Google Fonts -->
|
<!-- Importing Cormorant Garamond font from Google Fonts -->
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;700&display=swap" rel="stylesheet">
|
||||||
@@ -66,10 +66,10 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="header-container">
|
<div class="header-container">
|
||||||
<h1 class="header-title">A.P.Strom Transcription</h1>
|
<h1 class="header-title">{webui_title}</h1>
|
||||||
<div class="logo-container">
|
<div class="logo-container">
|
||||||
<a href="{header_logo_url}">
|
<a href="{header_logo_url}">
|
||||||
<img src="./gradio_api/file={header_logo_src}" alt="A.P.Strom Logo" class="logo">
|
<img src="{header_logo_src}" alt="{webui_title}" class="logo">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+16
-7
@@ -64,11 +64,19 @@ def _load_css(path: str) -> str:
|
|||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
def _logo1_inline_html() -> str:
|
def _email_logo_html() -> str:
|
||||||
"""
|
"""
|
||||||
Return an inline <img> tag for logo1.png as base64.
|
Return logo HTML for emails.
|
||||||
Falls back to empty string if not found.
|
|
||||||
|
Priority:
|
||||||
|
1) EMAIL_LOGO_URL (direct URL to logo image)
|
||||||
|
2) EMAIL_LOGO_PATH (local file; embedded as base64)
|
||||||
|
3) empty string if neither is set.
|
||||||
"""
|
"""
|
||||||
|
logo_url = os.getenv("EMAIL_LOGO_URL")
|
||||||
|
if logo_url:
|
||||||
|
return f'<img src="{logo_url}" alt="Logo" style="max-width:180px; display:block; margin:0 auto 10px auto;"/>'
|
||||||
|
|
||||||
logo_path = os.getenv("EMAIL_LOGO_PATH", "/app/src/misc/logo1.png")
|
logo_path = os.getenv("EMAIL_LOGO_PATH", "/app/src/misc/logo1.png")
|
||||||
if not os.path.exists(logo_path):
|
if not os.path.exists(logo_path):
|
||||||
return ""
|
return ""
|
||||||
@@ -76,7 +84,7 @@ def _logo1_inline_html() -> str:
|
|||||||
try:
|
try:
|
||||||
with open(logo_path, "rb") as f:
|
with open(logo_path, "rb") as f:
|
||||||
b64 = base64.b64encode(f.read()).decode("utf-8")
|
b64 = base64.b64encode(f.read()).decode("utf-8")
|
||||||
return f'<img src="data:image/png;base64,{b64}" alt="A.P.Strom" style="max-width:180px; display:block; margin:0 auto 10px auto;"/>'
|
return f'<img src="data:image/png;base64,{b64}" alt="Logo" style="max-width:180px; display:block; margin:0 auto 10px auto;"/>'
|
||||||
except Exception:
|
except Exception:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@@ -90,14 +98,15 @@ def build_template_context(**runtime_kwargs: Any) -> Dict[str, Any]:
|
|||||||
Environment variables:
|
Environment variables:
|
||||||
- EMAIL_CONTACT_ADDRESS: value for {contact_email}
|
- EMAIL_CONTACT_ADDRESS: value for {contact_email}
|
||||||
- EMAIL_CSS_PATH: path to mail_style.css (optional; we inline it)
|
- EMAIL_CSS_PATH: path to mail_style.css (optional; we inline it)
|
||||||
- EMAIL_LOGO_PATH: path to logo1.png (default: /app/src/misc/logo1.png)
|
- EMAIL_LOGO_URL: URL for email logo (preferred)
|
||||||
|
- EMAIL_LOGO_PATH: fallback local path for email logo
|
||||||
"""
|
"""
|
||||||
# Load and inline mail_style.css for consistent email styling
|
# Load and inline mail_style.css for consistent email styling
|
||||||
css_path = os.getenv("EMAIL_CSS_PATH", "/app/src/misc/mail_style.css")
|
css_path = os.getenv("EMAIL_CSS_PATH", "/app/src/misc/mail_style.css")
|
||||||
css_text = _load_css(css_path)
|
css_text = _load_css(css_path)
|
||||||
|
|
||||||
# Build inline logo HTML
|
# Build logo HTML (URL or local fallback)
|
||||||
logo_html = _logo1_inline_html()
|
logo_html = _email_logo_html()
|
||||||
|
|
||||||
ctx: Dict[str, Any] = {
|
ctx: Dict[str, Any] = {
|
||||||
"contact_email": os.getenv("EMAIL_CONTACT_ADDRESS", "support@example.com"),
|
"contact_email": os.getenv("EMAIL_CONTACT_ADDRESS", "support@example.com"),
|
||||||
|
|||||||
+9
-7
@@ -82,15 +82,19 @@ def create_app():
|
|||||||
# Paths for assets
|
# Paths for assets
|
||||||
header_path = layout_cfg.get("header", "/app/src/misc/header.html")
|
header_path = layout_cfg.get("header", "/app/src/misc/header.html")
|
||||||
footer_path = layout_cfg.get("footer", "/app/src/misc/footer.html")
|
footer_path = layout_cfg.get("footer", "/app/src/misc/footer.html")
|
||||||
logo_path = layout_cfg.get("logo", "/app/src/misc/logo.png")
|
|
||||||
|
|
||||||
# Prepare header HTML with logo
|
# Configurable title and logo URL via environment
|
||||||
|
webui_title = os.getenv("WEBUI_TITLE", "A.P.Strom Transcription")
|
||||||
|
logo_url = os.getenv("WEBUI_LOGO_URL", "https://apstrom.ca")
|
||||||
|
|
||||||
|
# Prepare header HTML with logo URL
|
||||||
header_html = ""
|
header_html = ""
|
||||||
if os.path.exists(header_path):
|
if os.path.exists(header_path):
|
||||||
header_html = load_html_template(
|
header_html = load_html_template(
|
||||||
header_path,
|
header_path,
|
||||||
header_logo_url="https://www.apstrom.de/",
|
webui_title=webui_title,
|
||||||
header_logo_src=logo_path if os.path.exists(logo_path) else "",
|
header_logo_url=logo_url,
|
||||||
|
header_logo_src=logo_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Prepare footer HTML
|
# Prepare footer HTML
|
||||||
@@ -104,7 +108,7 @@ def create_app():
|
|||||||
|
|
||||||
# Build Gradio interface
|
# Build Gradio interface
|
||||||
with gr.Blocks(
|
with gr.Blocks(
|
||||||
title="A.P.Strom Transcription",
|
title=webui_title,
|
||||||
) as app:
|
) as app:
|
||||||
|
|
||||||
# Header
|
# Header
|
||||||
@@ -247,12 +251,10 @@ def create_app():
|
|||||||
# Launch options
|
# Launch options
|
||||||
server_name = launch_cfg.get("server_name", os.getenv("GRADIO_SERVER_NAME", "0.0.0.0"))
|
server_name = launch_cfg.get("server_name", os.getenv("GRADIO_SERVER_NAME", "0.0.0.0"))
|
||||||
server_port = launch_cfg.get("server_port", 7860)
|
server_port = launch_cfg.get("server_port", 7860)
|
||||||
favicon_path = logo_path if os.path.exists(logo_path) else None
|
|
||||||
|
|
||||||
app.launch(
|
app.launch(
|
||||||
server_name=str(server_name),
|
server_name=str(server_name),
|
||||||
server_port=int(server_port),
|
server_port=int(server_port),
|
||||||
favicon_path=favicon_path,
|
|
||||||
css="body { font-family: Arial, sans-serif; }",
|
css="body { font-family: Arial, sans-serif; }",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user