From 02aef9da581f00cc82e43444ba0d7ea15e7023c9 Mon Sep 17 00:00:00 2001
From: Freddy Boulton <41651716+freddyaboulton@users.noreply.github.com>
Date: Wed, 23 Apr 2025 16:01:54 -0400
Subject: [PATCH] Add ability to Hide Title in Built-in UI + llama 4 cartesia
tweaks (#299)
* merge title
* Fix
---
backend/fastrtc/__init__.py | 7 +-
backend/fastrtc/stream.py | 164 +++++++++++----------
backend/fastrtc/text_to_speech/__init__.py | 8 +-
backend/fastrtc/text_to_speech/tts.py | 5 +-
demo/talk_to_llama4/AV_Huggy.png | Bin 0 -> 46888 bytes
demo/talk_to_llama4/app.py | 44 ++++--
6 files changed, 131 insertions(+), 97 deletions(-)
create mode 100644 demo/talk_to_llama4/AV_Huggy.png
diff --git a/backend/fastrtc/__init__.py b/backend/fastrtc/__init__.py
index 43f61ec..2e08399 100644
--- a/backend/fastrtc/__init__.py
+++ b/backend/fastrtc/__init__.py
@@ -17,7 +17,11 @@ from .reply_on_pause import AlgoOptions, ReplyOnPause
from .reply_on_stopwords import ReplyOnStopWords
from .speech_to_text import MoonshineSTT, get_stt_model
from .stream import Stream, UIArgs
-from .text_to_speech import KokoroTTSOptions, get_tts_model
+from .text_to_speech import (
+ CartesiaTTSOptions,
+ KokoroTTSOptions,
+ get_tts_model,
+)
from .tracks import (
AsyncAudioVideoStreamHandler,
AsyncStreamHandler,
@@ -87,4 +91,5 @@ __all__ = [
"VideoStreamHandler",
"CloseStream",
"get_current_context",
+ "CartesiaTTSOptions",
]
diff --git a/backend/fastrtc/stream.py b/backend/fastrtc/stream.py
index d1b53ac..1a2acdc 100644
--- a/backend/fastrtc/stream.py
+++ b/backend/fastrtc/stream.py
@@ -59,6 +59,8 @@ class UIArgs(TypedDict):
If "submit", the input will be sent when the submit event is triggered by the user.
If "change", the input will be sent whenever the user changes the input value.
"""
+ hide_title: NotRequired[bool]
+ """If True, the title and subtitle will not be displayed."""
class Stream(WebRTCConnectionMixin):
@@ -339,21 +341,22 @@ class Stream(WebRTCConnectionMixin):
same_components.append(component)
if self.modality == "video" and self.mode == "receive":
with gr.Blocks() as demo:
- gr.HTML(
- f"""
-
- {ui_args.get("title", "Video Streaming (Powered by FastRTC ⚡️)")}
-
- """
- )
- if ui_args.get("subtitle"):
- gr.Markdown(
+ if not ui_args.get("hide_title"):
+ gr.HTML(
f"""
-
- {ui_args.get("subtitle")}
-
- """
+
+ {ui_args.get("title", "Video Streaming (Powered by FastRTC ⚡️)")}
+
+ """
)
+ if ui_args.get("subtitle"):
+ gr.Markdown(
+ f"""
+
+ {ui_args.get("subtitle")}
+
+ """
+ )
with gr.Row():
with gr.Column():
if additional_input_components:
@@ -391,21 +394,22 @@ class Stream(WebRTCConnectionMixin):
)
elif self.modality == "video" and self.mode == "send":
with gr.Blocks() as demo:
- gr.HTML(
- f"""
-
- {ui_args.get("title", "Video Streaming (Powered by FastRTC ⚡️)")}
-
- """
- )
- if ui_args.get("subtitle"):
- gr.Markdown(
+ if not ui_args.get("hide_title"):
+ gr.HTML(
f"""
-
- {ui_args.get("subtitle")}
-
- """
+
+ {ui_args.get("title", "Video Streaming (Powered by FastRTC ⚡️)")}
+
+ """
)
+ if ui_args.get("subtitle"):
+ gr.Markdown(
+ f"""
+
+ {ui_args.get("subtitle")}
+
+ """
+ )
with gr.Row():
if additional_input_components:
with gr.Column():
@@ -494,21 +498,22 @@ class Stream(WebRTCConnectionMixin):
)
elif self.modality == "audio" and self.mode == "receive":
with gr.Blocks() as demo:
- gr.HTML(
- f"""
-
- {ui_args.get("title", "Audio Streaming (Powered by FastRTC ⚡️)")}
-
- """
- )
- if ui_args.get("subtitle"):
- gr.Markdown(
+ if not ui_args.get("hide_title"):
+ gr.HTML(
f"""
-
- {ui_args.get("subtitle")}
-
- """
+
+ {ui_args.get("title", "Audio Streaming (Powered by FastRTC ⚡️)")}
+
+ """
)
+ if ui_args.get("subtitle"):
+ gr.Markdown(
+ f"""
+
+ {ui_args.get("subtitle")}
+
+ """
+ )
with gr.Row():
with gr.Column():
for component in additional_input_components:
@@ -549,21 +554,22 @@ class Stream(WebRTCConnectionMixin):
)
elif self.modality == "audio" and self.mode == "send":
with gr.Blocks() as demo:
- gr.HTML(
- f"""
-
- {ui_args.get("title", "Audio Streaming (Powered by FastRTC ⚡️)")}
-
- """
- )
- if ui_args.get("subtitle"):
- gr.Markdown(
+ if not ui_args.get("hide_title"):
+ gr.HTML(
f"""
-
- {ui_args.get("subtitle")}
-
- """
+
+ {ui_args.get("title", "Audio Streaming (Powered by FastRTC ⚡️)")}
+
+ """
)
+ if ui_args.get("subtitle"):
+ gr.Markdown(
+ f"""
+
+ {ui_args.get("subtitle")}
+
+ """
+ )
with gr.Row():
with gr.Column():
with gr.Group():
@@ -604,21 +610,22 @@ class Stream(WebRTCConnectionMixin):
)
elif self.modality == "audio" and self.mode == "send-receive":
with gr.Blocks() as demo:
- gr.HTML(
- f"""
-
- {ui_args.get("title", "Audio Streaming (Powered by FastRTC ⚡️)")}
-
- """
- )
- if ui_args.get("subtitle"):
- gr.Markdown(
+ if not ui_args.get("hide_title"):
+ gr.HTML(
f"""
-
- {ui_args.get("subtitle")}
-
- """
+
+ {ui_args.get("title", "Audio Streaming (Powered by FastRTC ⚡️)")}
+
+ """
)
+ if ui_args.get("subtitle"):
+ gr.Markdown(
+ f"""
+
+ {ui_args.get("subtitle")}
+
+ """
+ )
with gr.Row():
with gr.Column():
with gr.Group():
@@ -662,21 +669,22 @@ class Stream(WebRTCConnectionMixin):
css = """.my-group {max-width: 600px !important; max-height: 600 !important;}
.my-column {display: flex !important; justify-content: center !important; align-items: center !important};"""
with gr.Blocks(css=css) as demo:
- gr.HTML(
- f"""
-
- {ui_args.get("title", "Audio Video Streaming (Powered by FastRTC ⚡️)")}
-
- """
- )
- if ui_args.get("subtitle"):
- gr.Markdown(
+ if not ui_args.get("hide_title"):
+ gr.HTML(
f"""
-
- {ui_args.get("subtitle")}
-
- """
+
+ {ui_args.get("title", "Audio Video Streaming (Powered by FastRTC ⚡️)")}
+
+ """
)
+ if ui_args.get("subtitle"):
+ gr.Markdown(
+ f"""
+
+ {ui_args.get("subtitle")}
+
+ """
+ )
with gr.Row():
with gr.Column(elem_classes=["my-column"]):
with gr.Group(elem_classes=["my-group"]):
diff --git a/backend/fastrtc/text_to_speech/__init__.py b/backend/fastrtc/text_to_speech/__init__.py
index 2cc082a..0d55538 100644
--- a/backend/fastrtc/text_to_speech/__init__.py
+++ b/backend/fastrtc/text_to_speech/__init__.py
@@ -1,3 +1,7 @@
-from .tts import KokoroTTSOptions, get_tts_model
+from .tts import (
+ CartesiaTTSOptions,
+ KokoroTTSOptions,
+ get_tts_model,
+)
-__all__ = ["get_tts_model", "KokoroTTSOptions"]
+__all__ = ["get_tts_model", "KokoroTTSOptions", "CartesiaTTSOptions"]
diff --git a/backend/fastrtc/text_to_speech/tts.py b/backend/fastrtc/text_to_speech/tts.py
index 37743be..f800e4b 100644
--- a/backend/fastrtc/text_to_speech/tts.py
+++ b/backend/fastrtc/text_to_speech/tts.py
@@ -2,7 +2,7 @@ import asyncio
import importlib.util
import re
from collections.abc import AsyncGenerator, Generator
-from dataclasses import dataclass
+from dataclasses import dataclass, field
from functools import lru_cache
from typing import Literal, Protocol, TypeVar
@@ -153,10 +153,11 @@ class KokoroTTSModel(TTSModel):
break
+@dataclass
class CartesiaTTSOptions(TTSOptions):
voice: str = "71a7ad14-091c-4e8e-a314-022ece01c121"
language: str = "en"
- emotion: list[str] = []
+ emotion: list[str] = field(default_factory=list)
cartesia_version: str = "2024-06-10"
model: str = "sonic-2"
sample_rate: int = 22_050
diff --git a/demo/talk_to_llama4/AV_Huggy.png b/demo/talk_to_llama4/AV_Huggy.png
new file mode 100644
index 0000000000000000000000000000000000000000..6a9fb741f3c9050b6d89f898a71c56095b193eaf
GIT binary patch
literal 46888
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