import aiohttp import asyncio import io from openai import AsyncAzureOpenAI from collections.abc import AsyncGenerator from dailyai.services.ai_services import TTSService, ImageGenService from PIL import Image # See .env.example for Azure configuration needed try: from azure.cognitiveservices.speech import ( SpeechSynthesizer, SpeechConfig, ResultReason, CancellationReason, ) except ModuleNotFoundError as e: print(f"Exception: {e}") print( "In order to use Azure TTS, you need to `pip install dailyai[azure]`. Also, set `AZURE_SPEECH_API_KEY` and `AZURE_SPEECH_REGION` environment variables.") raise Exception(f"Missing module: {e}") from dailyai.services.openai_api_llm_service import BaseOpenAILLMService class AzureTTSService(TTSService): def __init__(self, *, api_key, region, voice="en-US-SaraNeural"): super().__init__() self.speech_config = SpeechConfig(subscription=api_key, region=region) self.speech_synthesizer = SpeechSynthesizer( speech_config=self.speech_config, audio_config=None ) self._voice = voice async def run_tts(self, sentence) -> AsyncGenerator[bytes, None]: self.logger.info("Running azure tts") ssml = ( "" f"" "" "" "" f"{sentence}" " ") result = await asyncio.to_thread(self.speech_synthesizer.speak_ssml, (ssml)) self.logger.info("Got azure tts result") if result.reason == ResultReason.SynthesizingAudioCompleted: self.logger.info("Returning result") # azure always sends a 44-byte header. Strip it off. yield result.audio_data[44:] elif result.reason == ResultReason.Canceled: cancellation_details = result.cancellation_details self.logger.info( "Speech synthesis canceled: {}".format( cancellation_details.reason)) if cancellation_details.reason == CancellationReason.Error: self.logger.info( "Error details: {}".format( cancellation_details.error_details)) class AzureLLMService(BaseOpenAILLMService): def __init__( self, *, api_key, endpoint, api_version="2023-12-01-preview", model): self._endpoint = endpoint self._api_version = api_version super().__init__(api_key=api_key, model=model) self._model: str = model def create_client(self, api_key=None, base_url=None): self._client = AsyncAzureOpenAI( api_key=api_key, azure_endpoint=self._endpoint, api_version=self._api_version, ) class AzureImageGenServiceREST(ImageGenService): def __init__( self, *, api_version="2023-06-01-preview", image_size: str, aiohttp_session: aiohttp.ClientSession, api_key, endpoint, model, ): super().__init__() self._api_key = api_key self._azure_endpoint = endpoint self._api_version = api_version self._model = model self._aiohttp_session = aiohttp_session self._image_size = image_size async def run_image_gen(self, prompt: str) -> tuple[str, bytes, tuple[int, int]]: 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": prompt, "size": self._image_size, "n": 1, } async with self._aiohttp_session.post( url, headers=headers, json=body ) as submission: # We never get past this line, because this header isn't # defined on a 429 response, but something is eating our # exceptions! operation_location = submission.headers["operation-location"] status = "" attempts_left = 120 json_response = None while status != "succeeded": attempts_left -= 1 if attempts_left == 0: raise Exception("Image generation timed out") await asyncio.sleep(1) response = await self._aiohttp_session.get( operation_location, headers=headers ) json_response = await response.json() status = json_response["status"] image_url = ( json_response["result"]["data"][0]["url"] if json_response else None) if not image_url: raise Exception("Image generation failed") # Load the image from the url async with self._aiohttp_session.get(image_url) as response: image_stream = io.BytesIO(await response.content.read()) image = Image.open(image_stream) return (image_url, image.tobytes(), image.size)