use inference text in demo, clean up image generation
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -22,4 +22,6 @@ share/python-wheels/
|
|||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
MANIFEST
|
MANIFEST
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
|
from collections.abc import AsyncGenerator
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
@@ -13,18 +15,17 @@ class AIService:
|
|||||||
def close(self):
|
def close(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LLMService(AIService):
|
class LLMService(AIService):
|
||||||
# Generate a set of responses to a prompt. Yields a list of responses.
|
# Generate a set of responses to a prompt. Yields a list of responses.
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def run_llm_async(
|
async def run_llm_async(
|
||||||
self, messages
|
self, messages
|
||||||
) -> Generator[str, None, None]:
|
) -> AsyncGenerator[str, None, None]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Generate a responses to a prompt. Returns the response
|
# Generate a responses to a prompt. Returns the response
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def run_llm(
|
async def run_llm(
|
||||||
self, messages
|
self, messages
|
||||||
) -> str or None:
|
) -> str or None:
|
||||||
pass
|
pass
|
||||||
@@ -38,14 +39,14 @@ class TTSService(AIService):
|
|||||||
# Converts the sentence to audio. Yields a list of audio frames that can
|
# Converts the sentence to audio. Yields a list of audio frames that can
|
||||||
# be sent to the microphone device
|
# be sent to the microphone device
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def run_tts(self, sentence) -> Generator[bytes, None, None]:
|
async def run_tts(self, sentence) -> AsyncGenerator[bytes, None, None]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ImageGenService(AIService):
|
class ImageGenService(AIService):
|
||||||
# Renders the image. Returns an Image object.
|
# Renders the image. Returns an Image object.
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def run_image_gen(self, sentence) -> tuple[str, Image.Image]:
|
async def run_image_gen(self, sentence) -> tuple[str, Image.Image]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import json
|
import aiohttp
|
||||||
|
import asyncio
|
||||||
import io
|
import io
|
||||||
|
import json
|
||||||
from openai import AzureOpenAI
|
from openai import AzureOpenAI
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from typing import Generator
|
from collections.abc import AsyncGenerator
|
||||||
|
|
||||||
from dailyai.services.ai_services import LLMService, TTSService, ImageGenService
|
from dailyai.services.ai_services import LLMService, TTSService, ImageGenService
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
@@ -23,7 +25,7 @@ class AzureTTSService(TTSService):
|
|||||||
self.speech_config = SpeechConfig(subscription=speech_key, region=speech_region)
|
self.speech_config = SpeechConfig(subscription=speech_key, region=speech_region)
|
||||||
self.speech_synthesizer = SpeechSynthesizer(speech_config=self.speech_config, audio_config=None)
|
self.speech_synthesizer = SpeechSynthesizer(speech_config=self.speech_config, audio_config=None)
|
||||||
|
|
||||||
def run_tts(self, sentence) -> Generator[bytes, None, None]:
|
async def run_tts(self, sentence) -> AsyncGenerator[bytes, None, None]:
|
||||||
self.logger.info("Running azure tts")
|
self.logger.info("Running azure tts")
|
||||||
ssml = "<speak version='1.0' xml:lang='en-US' xmlns='http://www.w3.org/2001/10/synthesis' " \
|
ssml = "<speak version='1.0' xml:lang='en-US' xmlns='http://www.w3.org/2001/10/synthesis' " \
|
||||||
"xmlns:mstts='http://www.w3.org/2001/mstts'>" \
|
"xmlns:mstts='http://www.w3.org/2001/mstts'>" \
|
||||||
@@ -33,7 +35,7 @@ class AzureTTSService(TTSService):
|
|||||||
"<prosody rate='1.05'>" \
|
"<prosody rate='1.05'>" \
|
||||||
f"{sentence}" \
|
f"{sentence}" \
|
||||||
"</prosody></mstts:express-as></voice></speak> "
|
"</prosody></mstts:express-as></voice></speak> "
|
||||||
result = self.speech_synthesizer.speak_ssml(ssml)
|
result = await asyncio.to_thread(self.speech_synthesizer.speak_ssml, (ssml))
|
||||||
self.logger.info("Got azure tts result")
|
self.logger.info("Got azure tts result")
|
||||||
if result.reason == ResultReason.SynthesizingAudioCompleted:
|
if result.reason == ResultReason.SynthesizingAudioCompleted:
|
||||||
self.logger.info("Returning result")
|
self.logger.info("Returning result")
|
||||||
@@ -65,7 +67,7 @@ class AzureLLMService(LLMService):
|
|||||||
model=self.model,
|
model=self.model,
|
||||||
)
|
)
|
||||||
|
|
||||||
def run_llm_async(self, messages) -> Generator[str, None, None]:
|
async def run_llm_async(self, messages) -> AsyncGenerator[str, None, None]:
|
||||||
messages_for_log = json.dumps(messages)
|
messages_for_log = json.dumps(messages)
|
||||||
self.logger.debug(f"Generating chat via azure: {messages_for_log}")
|
self.logger.debug(f"Generating chat via azure: {messages_for_log}")
|
||||||
|
|
||||||
@@ -78,7 +80,7 @@ class AzureLLMService(LLMService):
|
|||||||
if chunk.choices[0].delta.content:
|
if chunk.choices[0].delta.content:
|
||||||
yield chunk.choices[0].delta.content
|
yield chunk.choices[0].delta.content
|
||||||
|
|
||||||
def run_llm(self, messages) -> str | None:
|
async def run_llm(self, messages) -> str | None:
|
||||||
messages_for_log = json.dumps(messages)
|
messages_for_log = json.dumps(messages)
|
||||||
self.logger.debug(f"Generating chat via azure: {messages_for_log}")
|
self.logger.debug(f"Generating chat via azure: {messages_for_log}")
|
||||||
|
|
||||||
@@ -88,6 +90,49 @@ class AzureLLMService(LLMService):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
class AzureImageGenServiceREST(ImageGenService):
|
||||||
|
|
||||||
|
def __init__(self, api_key=None, azure_endpoint=None, api_version=None, model=None):
|
||||||
|
super().__init__()
|
||||||
|
self.api_key = api_key or os.getenv("AZURE_DALLE_KEY")
|
||||||
|
self.azure_endpoint = azure_endpoint or os.getenv("AZURE_DALLE_ENDPOINT")
|
||||||
|
self.api_version = api_version or "2023-06-01-preview"
|
||||||
|
self.model = model or os.getenv("AZURE_DALLE_DEPLOYMENT_ID")
|
||||||
|
|
||||||
|
async def run_image_gen(self, sentence, size) -> tuple[str, Image.Image]:
|
||||||
|
# TODO hoist the session to app-level
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
url = f"{self.azure_endpoint}openai/images/generations:submit?api-version={self.api_version}"
|
||||||
|
headers= { "api-key": self.api_key, "Content-Type": "application/json" }
|
||||||
|
body = {
|
||||||
|
# Enter your prompt text here
|
||||||
|
"prompt": sentence,
|
||||||
|
"size": size,
|
||||||
|
"n": 1,
|
||||||
|
}
|
||||||
|
async with session.post(url, headers=headers, json=body) as submission:
|
||||||
|
operation_location = submission.headers['operation-location']
|
||||||
|
|
||||||
|
status = ""
|
||||||
|
attempts_left = 120
|
||||||
|
while status != "succeeded":
|
||||||
|
attempts_left -= 1
|
||||||
|
if attempts_left == 0:
|
||||||
|
raise Exception("Image generation timed out")
|
||||||
|
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
response = await session.get(operation_location, headers=headers)
|
||||||
|
json_response = await response.json()
|
||||||
|
status = json_response["status"]
|
||||||
|
|
||||||
|
image_url = json_response["result"]["data"][0]["url"]
|
||||||
|
|
||||||
|
# Load the image from the url
|
||||||
|
async with session.get(image_url) as response:
|
||||||
|
image_stream = io.BytesIO(await response.content.read())
|
||||||
|
image = Image.open(image_stream)
|
||||||
|
return (image_url, image.tobytes())
|
||||||
|
|
||||||
|
|
||||||
class AzureImageGenService(ImageGenService):
|
class AzureImageGenService(ImageGenService):
|
||||||
|
|
||||||
@@ -96,7 +141,7 @@ class AzureImageGenService(ImageGenService):
|
|||||||
|
|
||||||
api_key = api_key or os.getenv("AZURE_DALLE_KEY")
|
api_key = api_key or os.getenv("AZURE_DALLE_KEY")
|
||||||
azure_endpoint = azure_endpoint or os.getenv("AZURE_DALLE_ENDPOINT")
|
azure_endpoint = azure_endpoint or os.getenv("AZURE_DALLE_ENDPOINT")
|
||||||
api_version = api_version or "2023-12-01-preview"
|
api_version = api_version or "2023-06-01-preview"
|
||||||
self.model = model or os.getenv("AZURE_DALLE_DEPLOYMENT_ID")
|
self.model = model or os.getenv("AZURE_DALLE_DEPLOYMENT_ID")
|
||||||
|
|
||||||
self.client = AzureOpenAI(
|
self.client = AzureOpenAI(
|
||||||
@@ -105,7 +150,7 @@ class AzureImageGenService(ImageGenService):
|
|||||||
api_version=api_version,
|
api_version=api_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
def run_image_gen(self, sentence) -> tuple[str, Image.Image]:
|
async def run_image_gen(self, sentence) -> tuple[str, Image.Image]:
|
||||||
self.logger.info("Generating azure image", sentence)
|
self.logger.info("Generating azure image", sentence)
|
||||||
|
|
||||||
image = self.client.images.generate(
|
image = self.client.images.generate(
|
||||||
|
|||||||
267
src/dailyai/services/daily_transport_service.py
Normal file
267
src/dailyai/services/daily_transport_service.py
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
import inspect
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import types
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
|
from queue import Queue, Empty
|
||||||
|
|
||||||
|
from dailyai.output_queue import OutputQueueFrame, FrameType
|
||||||
|
|
||||||
|
from threading import Thread, Event, Timer
|
||||||
|
|
||||||
|
from daily import (
|
||||||
|
EventHandler,
|
||||||
|
CallClient,
|
||||||
|
Daily,
|
||||||
|
VirtualCameraDevice,
|
||||||
|
VirtualMicrophoneDevice,
|
||||||
|
VirtualSpeakerDevice,
|
||||||
|
)
|
||||||
|
|
||||||
|
class DailyTransportService(EventHandler):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
room_url: str,
|
||||||
|
token: str,
|
||||||
|
bot_name: str,
|
||||||
|
duration: float = 10,
|
||||||
|
):
|
||||||
|
super().__init__()
|
||||||
|
self.bot_name: str = bot_name
|
||||||
|
self.room_url: str = room_url
|
||||||
|
self.token: str = token
|
||||||
|
self.duration: float = duration
|
||||||
|
self.expiration = time.time() + duration * 60
|
||||||
|
|
||||||
|
self.output_queue = Queue()
|
||||||
|
self.is_interrupted = Event()
|
||||||
|
self.stop_threads = Event()
|
||||||
|
self.story_started = False
|
||||||
|
|
||||||
|
self.logger: logging.Logger = logging.getLogger("dailyai")
|
||||||
|
|
||||||
|
self.event_handlers = {}
|
||||||
|
|
||||||
|
def monkeypatch(self, event_name, *args):
|
||||||
|
for handler in self.event_handlers[event_name]:
|
||||||
|
handler(*args)
|
||||||
|
|
||||||
|
def add_event_handler(self, event_name: str, handler):
|
||||||
|
if not event_name.startswith("on_"):
|
||||||
|
raise Exception(f"Event handler {event_name} must start with 'on_'")
|
||||||
|
|
||||||
|
methods = inspect.getmembers(self, predicate=inspect.ismethod)
|
||||||
|
if event_name not in [method[0] for method in methods]:
|
||||||
|
raise Exception(f"Event handler {event_name} not found")
|
||||||
|
|
||||||
|
if not event_name in self.event_handlers:
|
||||||
|
self.event_handlers[event_name] = [getattr(self, event_name), types.MethodType(handler, self)]
|
||||||
|
setattr(self, event_name, partial(self.monkeypatch, event_name))
|
||||||
|
else:
|
||||||
|
self.event_handlers[event_name].append(types.MethodType(handler, self))
|
||||||
|
|
||||||
|
def configure_daily(self):
|
||||||
|
Daily.init()
|
||||||
|
self.client = CallClient(event_handler=self)
|
||||||
|
|
||||||
|
if self.mic_enabled:
|
||||||
|
self.mic: VirtualMicrophoneDevice = Daily.create_microphone_device(
|
||||||
|
"mic", sample_rate=self.mic_sample_rate, channels=1
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.camera_enabled:
|
||||||
|
self.camera: VirtualCameraDevice = Daily.create_camera_device(
|
||||||
|
"camera", width=self.camera_width, height=self.camera_height, color_format="RGB"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.speaker: VirtualSpeakerDevice = Daily.create_speaker_device(
|
||||||
|
"speaker", sample_rate=16000, channels=1
|
||||||
|
)
|
||||||
|
|
||||||
|
Daily.select_speaker_device("speaker")
|
||||||
|
|
||||||
|
self.client.set_user_name(self.bot_name)
|
||||||
|
self.client.join(self.room_url, self.token, completion=self.call_joined)
|
||||||
|
|
||||||
|
self.client.update_inputs(
|
||||||
|
{
|
||||||
|
"camera": {
|
||||||
|
"isEnabled": True,
|
||||||
|
"settings": {
|
||||||
|
"deviceId": "camera",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"microphone": {
|
||||||
|
"isEnabled": True,
|
||||||
|
"settings": {
|
||||||
|
"deviceId": "mic",
|
||||||
|
"customConstraints": {
|
||||||
|
"autoGainControl": {"exact": False},
|
||||||
|
"echoCancellation": {"exact": False},
|
||||||
|
"noiseSuppression": {"exact": False},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.client.update_publishing(
|
||||||
|
{
|
||||||
|
"camera": {
|
||||||
|
"sendSettings": {
|
||||||
|
"maxQuality": "low",
|
||||||
|
"encodings": {
|
||||||
|
"low": {
|
||||||
|
"maxBitrate": 250000,
|
||||||
|
"scaleResolutionDownBy": 1.333,
|
||||||
|
"maxFramerate": 8,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.my_participant_id = self.client.participants()["local"]["id"]
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
self.configure_daily()
|
||||||
|
self.running_thread = Thread(target=self.run_daily, daemon=True)
|
||||||
|
self.running_thread.start()
|
||||||
|
|
||||||
|
def run_daily(self):
|
||||||
|
# TODO: this loop could, I think, be replaced with a timer and an event
|
||||||
|
self.participant_left = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
participant_count: int = len(self.client.participants())
|
||||||
|
self.logger.info(f"{participant_count} participants in room")
|
||||||
|
while time.time() < self.expiration and not self.participant_left:
|
||||||
|
# all handling of incoming transcriptions happens in on_transcription_message
|
||||||
|
time.sleep(1)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Exception {e}")
|
||||||
|
finally:
|
||||||
|
self.client.leave()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.stop_threads.set()
|
||||||
|
self.camera_thread.join()
|
||||||
|
self.output_queue.put(OutputQueueFrame(FrameType.END_STREAM, None))
|
||||||
|
self.frame_consumer_thread.join()
|
||||||
|
self.client.leave()
|
||||||
|
|
||||||
|
def call_joined(self, join_data, client_error):
|
||||||
|
self.logger.info(f"Call_joined: {join_data}, {client_error}")
|
||||||
|
|
||||||
|
self.image: bytes | None = None
|
||||||
|
self.camera_thread = Thread(target=self.run_camera, daemon=True)
|
||||||
|
self.camera_thread.start()
|
||||||
|
|
||||||
|
self.logger.info("Starting frame consumer thread")
|
||||||
|
self.frame_consumer_thread = Thread(target=self.frame_consumer, daemon=True)
|
||||||
|
self.frame_consumer_thread.start()
|
||||||
|
|
||||||
|
if self.token:
|
||||||
|
self.client.start_transcription(
|
||||||
|
{
|
||||||
|
"language": "en",
|
||||||
|
"tier": "nova",
|
||||||
|
"model": "2-conversationalai",
|
||||||
|
"profanity_filter": True,
|
||||||
|
"redact": False,
|
||||||
|
"extra": {
|
||||||
|
"endpointing": True,
|
||||||
|
"punctuate": False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_participant_joined(self, participant):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_participant_left(self, participant, reason):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_app_message(self, message, sender):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_transcription_message(self, message):
|
||||||
|
with self.tracer.start_as_current_span(
|
||||||
|
"on_transcription_message", context=self.ctx
|
||||||
|
):
|
||||||
|
if message["session_id"] != self.my_participant_id:
|
||||||
|
self.handle_transcription_fragment(message["text"])
|
||||||
|
|
||||||
|
def on_transcription_stopped(self, stopped_by, stopped_by_error):
|
||||||
|
self.logger.info(f"Transcription stopped {stopped_by}, {stopped_by_error}")
|
||||||
|
|
||||||
|
def on_transcription_error(self, message):
|
||||||
|
self.logger.error(f"Transcription error {message}")
|
||||||
|
|
||||||
|
def on_transcription_started(self, status):
|
||||||
|
self.logger.info(f"Transcription started {status}")
|
||||||
|
|
||||||
|
def set_image(self, image: bytes):
|
||||||
|
self.image: bytes | None = image
|
||||||
|
|
||||||
|
def run_camera(self):
|
||||||
|
try:
|
||||||
|
while not self.stop_threads.is_set():
|
||||||
|
if self.image:
|
||||||
|
self.camera.write_frame(self.image)
|
||||||
|
|
||||||
|
time.sleep(1.0 / 8.0) # 8 fps
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Exception {e} in camera thread.")
|
||||||
|
|
||||||
|
def frame_consumer(self):
|
||||||
|
self.logger.info("🎬 Starting frame consumer thread")
|
||||||
|
b = bytearray()
|
||||||
|
smallest_write_size = 3200
|
||||||
|
all_audio_frames = bytearray()
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
frame: OutputQueueFrame = self.output_queue.get()
|
||||||
|
if frame.frame_type == FrameType.END_STREAM:
|
||||||
|
self.logger.info("Stopping frame consumer thread")
|
||||||
|
return
|
||||||
|
|
||||||
|
# if interrupted, we just pull frames off the queue and discard them
|
||||||
|
if not self.is_interrupted.is_set():
|
||||||
|
if frame:
|
||||||
|
if frame.frame_type == FrameType.AUDIO_FRAME:
|
||||||
|
chunk = frame.frame_data
|
||||||
|
|
||||||
|
all_audio_frames.extend(chunk)
|
||||||
|
|
||||||
|
b.extend(chunk)
|
||||||
|
l = len(b) - (len(b) % smallest_write_size)
|
||||||
|
if l:
|
||||||
|
self.mic.write_frames(bytes(b[:l]))
|
||||||
|
b = b[l:]
|
||||||
|
elif frame.frame_type == FrameType.IMAGE_FRAME:
|
||||||
|
self.set_image(frame.frame_data)
|
||||||
|
elif len(b):
|
||||||
|
self.mic.write_frames(bytes(b))
|
||||||
|
b = bytearray()
|
||||||
|
else:
|
||||||
|
if self.interrupt_time:
|
||||||
|
self.logger.info(
|
||||||
|
f"Lag to stop stream after interruption {time.perf_counter() - self.interrupt_time}"
|
||||||
|
)
|
||||||
|
self.interrupt_time = None
|
||||||
|
|
||||||
|
if frame.frame_type == FrameType.START_STREAM:
|
||||||
|
self.is_interrupted.clear()
|
||||||
|
|
||||||
|
self.output_queue.task_done()
|
||||||
|
except Empty:
|
||||||
|
try:
|
||||||
|
if len(b):
|
||||||
|
self.mic.write_frames(bytes(b))
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Exception in frame_consumer: {e}, {len(b)}")
|
||||||
|
|
||||||
|
b = bytearray()
|
||||||
60
src/samples/05-sync-speech-and-text.py
Normal file
60
src/samples/05-sync-speech-and-text.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
|
from dailyai.output_queue import OutputQueueFrame, FrameType
|
||||||
|
from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService, AzureImageGenServiceREST
|
||||||
|
from dailyai.services.daily_transport_service import DailyTransportService
|
||||||
|
|
||||||
|
async def main(room_url, token):
|
||||||
|
class Sample05Transport(DailyTransportService):
|
||||||
|
def on_participant_joined(self, participant):
|
||||||
|
super().on_participant_joined(participant)
|
||||||
|
|
||||||
|
meeting_duration_minutes = 4
|
||||||
|
transport = Sample05Transport(
|
||||||
|
room_url,
|
||||||
|
token,
|
||||||
|
"Simple Bot",
|
||||||
|
meeting_duration_minutes,
|
||||||
|
)
|
||||||
|
transport.mic_enabled = True
|
||||||
|
transport.camera_enabled = True
|
||||||
|
transport.mic_sample_rate = 16000
|
||||||
|
transport.camera_width = 1024
|
||||||
|
transport.camera_height = 1024
|
||||||
|
|
||||||
|
llm = AzureLLMService()
|
||||||
|
tts = AzureTTSService()
|
||||||
|
dalle = AzureImageGenServiceREST()
|
||||||
|
|
||||||
|
inference_text_process = llm.run_llm(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": f"Describe a nature photograph suitable for use in a calendar, for the month of January. Include only the image description with no preamble."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
transport.run()
|
||||||
|
|
||||||
|
inference_text = await inference_text_process
|
||||||
|
|
||||||
|
tts_iterator = tts.run_tts(inference_text)
|
||||||
|
(image, audio) = await asyncio.gather(
|
||||||
|
*[dalle.run_image_gen(inference_text, "1024x1024"), anext(tts_iterator)]
|
||||||
|
)
|
||||||
|
transport.output_queue.put(OutputQueueFrame(FrameType.IMAGE_FRAME, image[1]))
|
||||||
|
transport.output_queue.put(OutputQueueFrame(FrameType.AUDIO_FRAME, audio))
|
||||||
|
async for audio in tts_iterator:
|
||||||
|
transport.output_queue.put(
|
||||||
|
OutputQueueFrame(FrameType.AUDIO_FRAME, audio)
|
||||||
|
)
|
||||||
|
|
||||||
|
await asyncio.sleep(meeting_duration_minutes * 60)
|
||||||
|
finally:
|
||||||
|
transport.stop()
|
||||||
|
print("Done")
|
||||||
|
|
||||||
|
if __name__=="__main__":
|
||||||
|
asyncio.run(main("https://moishe.daily.co/Lettvins", None))
|
||||||
Reference in New Issue
Block a user