wip: untested
This commit is contained in:
@@ -3,45 +3,35 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: BSD 2-Clause License
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
#
|
#
|
||||||
|
import base64
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import aiohttp
|
|
||||||
import base64
|
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
from typing import AsyncGenerator, List, Literal
|
from typing import AsyncGenerator, List, Literal
|
||||||
|
|
||||||
from pipecat.frames.frames import (
|
import aiohttp
|
||||||
ErrorFrame,
|
|
||||||
Frame,
|
|
||||||
LLMFullResponseEndFrame,
|
|
||||||
LLMFullResponseStartFrame,
|
|
||||||
LLMMessagesFrame,
|
|
||||||
LLMResponseEndFrame,
|
|
||||||
LLMResponseStartFrame,
|
|
||||||
TextFrame,
|
|
||||||
URLImageRawFrame,
|
|
||||||
VisionImageRawFrame
|
|
||||||
)
|
|
||||||
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext, OpenAILLMContextFrame
|
|
||||||
from pipecat.processors.frame_processor import FrameDirection
|
|
||||||
from pipecat.services.ai_services import LLMService, ImageGenService
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
from openai import OpenAI
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from pipecat.frames.frames import (AudioRawFrame, ErrorFrame, Frame,
|
||||||
|
LLMFullResponseEndFrame,
|
||||||
|
LLMFullResponseStartFrame, LLMMessagesFrame,
|
||||||
|
LLMResponseEndFrame, LLMResponseStartFrame,
|
||||||
|
TextFrame, URLImageRawFrame,
|
||||||
|
VisionImageRawFrame)
|
||||||
|
from pipecat.processors.aggregators.openai_llm_context import (
|
||||||
|
OpenAILLMContext, OpenAILLMContextFrame)
|
||||||
|
from pipecat.processors.frame_processor import FrameDirection
|
||||||
|
from pipecat.services.ai_services import (ImageGenService, LLMService,
|
||||||
|
TTSService)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from openai import AsyncOpenAI, AsyncStream
|
from openai import AsyncOpenAI, AsyncStream
|
||||||
|
from openai.types.chat import (ChatCompletion, ChatCompletionChunk,
|
||||||
from openai.types.chat import (
|
ChatCompletionFunctionMessageParam,
|
||||||
ChatCompletion,
|
ChatCompletionMessageParam,
|
||||||
ChatCompletionChunk,
|
ChatCompletionToolParam)
|
||||||
ChatCompletionFunctionMessageParam,
|
|
||||||
ChatCompletionMessageParam,
|
|
||||||
ChatCompletionToolParam
|
|
||||||
)
|
|
||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
logger.error(f"Exception: {e}")
|
logger.error(f"Exception: {e}")
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -272,3 +262,40 @@ class OpenAIImageGenService(ImageGenService):
|
|||||||
image = Image.open(image_stream)
|
image = Image.open(image_stream)
|
||||||
frame = URLImageRawFrame(image_url, image.tobytes(), image.size, image.format)
|
frame = URLImageRawFrame(image_url, image.tobytes(), image.size, image.format)
|
||||||
yield frame
|
yield frame
|
||||||
|
|
||||||
|
|
||||||
|
class WhisperTTSService(TTSService):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
api_key: str | None,
|
||||||
|
voice: Literal["alloy", "echo", "fable", "onyx", "nova", "shimmer"] = "alloy",
|
||||||
|
response_format: Literal["mp3", "opus", "flac", "pcm"] = "pcm",
|
||||||
|
model: Literal["tts-1", "tts-1-hd"] = "tts-1",
|
||||||
|
**kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self._voice = voice
|
||||||
|
self._model = model
|
||||||
|
self._response_format = response_format
|
||||||
|
|
||||||
|
self._client = AsyncOpenAI(api_key=api_key)
|
||||||
|
|
||||||
|
async def run_tts(self, text: str) -> AsyncGenerator[Frame, None]:
|
||||||
|
logger.debug(f"Generating TTS: [{text}]")
|
||||||
|
|
||||||
|
async with self._client.audio.speech.with_streaming_response.create(
|
||||||
|
input=text,
|
||||||
|
model=self._model,
|
||||||
|
voice=self._voice,
|
||||||
|
response_format=self._response_format
|
||||||
|
) as r:
|
||||||
|
if r.status != 200:
|
||||||
|
error = await r.text()
|
||||||
|
logger.error(f"Error getting audio (status: {r.status}, error: {error})")
|
||||||
|
yield ErrorFrame(f"Error getting audio (status: {r.status}, error: {error})")
|
||||||
|
return
|
||||||
|
for chunk in r.iter_bytes(1024):
|
||||||
|
if len(chunk) > 0:
|
||||||
|
frame = AudioRawFrame(chunk, 16000, 1)
|
||||||
|
yield frame
|
||||||
|
|||||||
14
tests/test_whisper.py
Normal file
14
tests/test_whisper.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from pipecat.services.openai import WhisperTTSService
|
||||||
|
|
||||||
|
|
||||||
|
class TestWhisperOpenAIService(unittest.IsolatedAsyncioTestCase):
|
||||||
|
async def test_whisper_tts(self):
|
||||||
|
tts = WhisperTTSService()
|
||||||
|
# tts_response = await tts.run_tts("Hello, world")
|
||||||
|
await tts.say("Hi! If you want to talk to me, just say 'Hey Robot'.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user