ruff formatting
This commit is contained in:
@@ -1,19 +1,23 @@
|
||||
import wave
|
||||
from io import BytesIO
|
||||
|
||||
from pipecat.frames.frames import (AudioRawFrame, BotInterruptionFrame,
|
||||
BotStartedSpeakingFrame,
|
||||
BotStoppedSpeakingFrame, Frame,
|
||||
InputAudioRawFrame, OutputAudioRawFrame,
|
||||
StartInterruptionFrame,
|
||||
StopInterruptionFrame,
|
||||
UserStartedSpeakingFrame,
|
||||
UserStoppedSpeakingFrame)
|
||||
from pipecat.frames.frames import (
|
||||
AudioRawFrame,
|
||||
BotInterruptionFrame,
|
||||
BotStartedSpeakingFrame,
|
||||
BotStoppedSpeakingFrame,
|
||||
Frame,
|
||||
InputAudioRawFrame,
|
||||
OutputAudioRawFrame,
|
||||
StartInterruptionFrame,
|
||||
StopInterruptionFrame,
|
||||
UserStartedSpeakingFrame,
|
||||
UserStoppedSpeakingFrame,
|
||||
)
|
||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
|
||||
|
||||
class AudioBufferProcessor(FrameProcessor):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
Initialize the AudioBufferProcessor.
|
||||
@@ -33,16 +37,13 @@ class AudioBufferProcessor(FrameProcessor):
|
||||
self._sample_rate = None
|
||||
|
||||
def _buffer_has_audio(self, buffer: bytearray):
|
||||
return (
|
||||
buffer is not None and
|
||||
len(buffer) > 0
|
||||
)
|
||||
return buffer is not None and len(buffer) > 0
|
||||
|
||||
def _has_audio(self):
|
||||
return (
|
||||
self._buffer_has_audio(self._user_audio_buffer) and
|
||||
self._buffer_has_audio(self._assistant_audio_buffer) and
|
||||
self._sample_rate is not None
|
||||
self._buffer_has_audio(self._user_audio_buffer)
|
||||
and self._buffer_has_audio(self._assistant_audio_buffer)
|
||||
and self._sample_rate is not None
|
||||
)
|
||||
|
||||
def _reset_audio_buffer(self):
|
||||
@@ -51,13 +52,12 @@ class AudioBufferProcessor(FrameProcessor):
|
||||
|
||||
def _merge_audio_buffers(self):
|
||||
with BytesIO() as buffer:
|
||||
with wave.open(buffer, 'wb') as wf:
|
||||
with wave.open(buffer, "wb") as wf:
|
||||
wf.setnchannels(2)
|
||||
wf.setsampwidth(2)
|
||||
wf.setframerate(self._sample_rate)
|
||||
# Interleave the two audio streams
|
||||
max_length = max(len(self._user_audio_buffer),
|
||||
len(self._assistant_audio_buffer))
|
||||
max_length = max(len(self._user_audio_buffer), len(self._assistant_audio_buffer))
|
||||
interleaved = bytearray(max_length * 2)
|
||||
|
||||
for i in range(0, max_length, 2):
|
||||
@@ -89,7 +89,7 @@ class AudioBufferProcessor(FrameProcessor):
|
||||
# Sync the assistant's buffer to the user's buffer by adding silence if needed
|
||||
if len(self._user_audio_buffer) > len(self._assistant_audio_buffer):
|
||||
silence_length = len(self._user_audio_buffer) - len(self._assistant_audio_buffer)
|
||||
silence = b'\x00' * silence_length
|
||||
silence = b"\x00" * silence_length
|
||||
self._assistant_audio_buffer.extend(silence)
|
||||
|
||||
# if the assistant is speaking, include all audio from the assistant,
|
||||
|
||||
@@ -12,14 +12,14 @@ try:
|
||||
except ModuleNotFoundError as e:
|
||||
logger.error(f"Exception: {e}")
|
||||
logger.error(
|
||||
"In order to use Canonical Metrics, you need to `pip install pipecat-ai[canonical]`. " +
|
||||
"Also, set the `CANONICAL_API_KEY` environment variable.")
|
||||
"In order to use Canonical Metrics, you need to `pip install pipecat-ai[canonical]`. "
|
||||
+ "Also, set the `CANONICAL_API_KEY` environment variable."
|
||||
)
|
||||
raise Exception(f"Missing module: {e}")
|
||||
|
||||
|
||||
from pipecat.frames.frames import CancelFrame, EndFrame, Frame
|
||||
from pipecat.processors.audio.audio_buffer_processor import \
|
||||
AudioBufferProcessor
|
||||
from pipecat.processors.audio.audio_buffer_processor import AudioBufferProcessor
|
||||
from pipecat.processors.frame_processor import FrameDirection
|
||||
from pipecat.services.ai_services import AIService
|
||||
|
||||
@@ -56,15 +56,16 @@ class CanonicalMetricsService(AIService):
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
aiohttp_session: aiohttp.ClientSession,
|
||||
audio_buffer_processor: AudioBufferProcessor,
|
||||
call_id: str,
|
||||
assistant: str,
|
||||
api_key: str,
|
||||
api_url: str = "https://voiceapp.canonical.chat/api/v1",
|
||||
assistant_speaks_first: bool = True,
|
||||
output_dir: str = "recordings"):
|
||||
self,
|
||||
aiohttp_session: aiohttp.ClientSession,
|
||||
audio_buffer_processor: AudioBufferProcessor,
|
||||
call_id: str,
|
||||
assistant: str,
|
||||
api_key: str,
|
||||
api_url: str = "https://voiceapp.canonical.chat/api/v1",
|
||||
assistant_speaks_first: bool = True,
|
||||
output_dir: str = "recordings",
|
||||
):
|
||||
super().__init__()
|
||||
self._aiohttp_session = aiohttp_session
|
||||
self._audio_buffer_processor = audio_buffer_processor
|
||||
@@ -92,7 +93,7 @@ class CanonicalMetricsService(AIService):
|
||||
filename = self._get_output_filename()
|
||||
wave_data = pipeline._merge_audio_buffers()
|
||||
|
||||
async with aiofiles.open(filename, 'wb') as file:
|
||||
async with aiofiles.open(filename, "wb") as file:
|
||||
await file.write(wave_data)
|
||||
|
||||
try:
|
||||
@@ -109,10 +110,7 @@ class CanonicalMetricsService(AIService):
|
||||
return f"{self._output_dir}/{timestamp}-{uuid.uuid4().hex}.wav"
|
||||
|
||||
def _request_headers(self):
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
"X-Canonical-Api-Key": self._api_key
|
||||
}
|
||||
return {"Content-Type": "application/json", "X-Canonical-Api-Key": self._api_key}
|
||||
|
||||
async def _multipart_upload(self, file_path: str):
|
||||
upload_request, upload_response = await self._request_upload(file_path)
|
||||
@@ -129,19 +127,14 @@ class CanonicalMetricsService(AIService):
|
||||
numparts = int((filesize + PART_SIZE - 1) / PART_SIZE)
|
||||
|
||||
params = {
|
||||
'filename': filename,
|
||||
'parts': numparts,
|
||||
'callId': self._call_id,
|
||||
'assistant': {
|
||||
'id': self._assistant,
|
||||
'speaksFirst': self._assistant_speaks_first
|
||||
}
|
||||
"filename": filename,
|
||||
"parts": numparts,
|
||||
"callId": self._call_id,
|
||||
"assistant": {"id": self._assistant, "speaksFirst": self._assistant_speaks_first},
|
||||
}
|
||||
logger.debug(f"Requesting presigned URLs for {numparts} parts")
|
||||
response = await self._aiohttp_session.post(
|
||||
f"{self._api_url}/recording/uploadRequest",
|
||||
headers=self._request_headers(),
|
||||
json=params
|
||||
f"{self._api_url}/recording/uploadRequest", headers=self._request_headers(), json=params
|
||||
)
|
||||
if not response.ok:
|
||||
logger.error(f"Failed to get presigned URLs: {await response.text()}")
|
||||
@@ -149,15 +142,11 @@ class CanonicalMetricsService(AIService):
|
||||
response_json = await response.json()
|
||||
return params, response_json
|
||||
|
||||
async def _upload_parts(
|
||||
self,
|
||||
file_path: str,
|
||||
upload_response: Dict) -> List[Dict]:
|
||||
|
||||
urls = upload_response['urls']
|
||||
async def _upload_parts(self, file_path: str, upload_response: Dict) -> List[Dict]:
|
||||
urls = upload_response["urls"]
|
||||
parts = []
|
||||
try:
|
||||
async with aiofiles.open(file_path, 'rb') as file:
|
||||
async with aiofiles.open(file_path, "rb") as file:
|
||||
for partnum, upload_url in enumerate(urls, start=1):
|
||||
data = await file.read(PART_SIZE)
|
||||
if not data:
|
||||
@@ -168,35 +157,29 @@ class CanonicalMetricsService(AIService):
|
||||
logger.error(f"Failed to upload part {partnum}: {await response.text()}")
|
||||
return None
|
||||
|
||||
etag = response.headers['ETag']
|
||||
parts.append({'partnum': str(partnum), 'etag': etag})
|
||||
etag = response.headers["ETag"]
|
||||
parts.append({"partnum": str(partnum), "etag": etag})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Multipart upload aborted, an error occurred: {str(e)}")
|
||||
return parts
|
||||
|
||||
async def _upload_complete(
|
||||
self,
|
||||
parts: List[Dict],
|
||||
upload_request: Dict,
|
||||
upload_response: Dict):
|
||||
|
||||
self, parts: List[Dict], upload_request: Dict, upload_response: Dict
|
||||
):
|
||||
params = {
|
||||
'filename': upload_request['filename'],
|
||||
'parts': parts,
|
||||
'slug': upload_response['slug'],
|
||||
'callId': self._call_id,
|
||||
'assistant': {
|
||||
'id': self._assistant,
|
||||
'speaksFirst': self._assistant_speaks_first
|
||||
}
|
||||
"filename": upload_request["filename"],
|
||||
"parts": parts,
|
||||
"slug": upload_response["slug"],
|
||||
"callId": self._call_id,
|
||||
"assistant": {"id": self._assistant, "speaksFirst": self._assistant_speaks_first},
|
||||
}
|
||||
logger.debug(f"Completing upload for {params['filename']}")
|
||||
logger.debug(f"Slug: {params['slug']}")
|
||||
response = await self._aiohttp_session.post(
|
||||
f"{self._api_url}/recording/uploadComplete",
|
||||
headers=self._request_headers(),
|
||||
json=params
|
||||
json=params,
|
||||
)
|
||||
if not response.ok:
|
||||
logger.error(f"Failed to complete upload: {await response.text()}")
|
||||
|
||||
Reference in New Issue
Block a user