wip: video image frames
This commit is contained in:
@@ -18,6 +18,7 @@ from dailyai.pipeline.frames import (
|
||||
Frame,
|
||||
TextFrame,
|
||||
TranscriptionQueueFrame,
|
||||
VisionFrame
|
||||
)
|
||||
|
||||
from abc import abstractmethod
|
||||
@@ -133,6 +134,22 @@ class STTService(AIService):
|
||||
yield TranscriptionQueueFrame(text, "", str(time.time()))
|
||||
|
||||
|
||||
class VisionService(AIService):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# Renders the image. Returns an Image object.
|
||||
# TODO-CB: return type
|
||||
@abstractmethod
|
||||
async def run_vision(self, prompt: str, image: bytes):
|
||||
pass
|
||||
|
||||
async def process_frame(self, frame: Frame) -> AsyncGenerator[Frame, None]:
|
||||
if isinstance(frame, VisionFrame):
|
||||
async for frame in self.run_vision(frame.prompt, frame.image):
|
||||
yield frame
|
||||
|
||||
|
||||
class FrameLogger(AIService):
|
||||
def __init__(self, prefix="Frame", **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@@ -90,7 +90,8 @@ class BaseTransportService:
|
||||
self._vad_stop_s = kwargs.get("vad_stop_s") or 0.8
|
||||
self._context = kwargs.get("context") or []
|
||||
self._vad_enabled = kwargs.get("vad_enabled") or False
|
||||
|
||||
self._receive_video = kwargs.get("receive_video") or False
|
||||
self._receive_video_fps = kwargs.get("receive_video_fps") or 1.0
|
||||
if self._vad_enabled and self._speaker_enabled:
|
||||
raise Exception(
|
||||
"Sorry, you can't use speaker_enabled and vad_enabled at the same time. Please set one to False."
|
||||
|
||||
@@ -2,6 +2,7 @@ import asyncio
|
||||
import inspect
|
||||
import logging
|
||||
import signal
|
||||
import time
|
||||
import threading
|
||||
import types
|
||||
|
||||
@@ -11,6 +12,7 @@ from typing import Any
|
||||
from dailyai.pipeline.frames import (
|
||||
ReceivedAppMessageFrame,
|
||||
TranscriptionQueueFrame,
|
||||
VideoImageFrame
|
||||
)
|
||||
|
||||
from threading import Event
|
||||
@@ -62,6 +64,7 @@ class DailyTransportService(BaseTransportService, EventHandler):
|
||||
|
||||
self._other_participant_has_joined = False
|
||||
self._my_participant_id = None
|
||||
self._participant_frame_times = {}
|
||||
|
||||
self.transcription_settings = {
|
||||
"language": "en",
|
||||
@@ -204,11 +207,12 @@ class DailyTransportService(BaseTransportService, EventHandler):
|
||||
)
|
||||
self._my_participant_id = self.client.participants()["local"]["id"]
|
||||
|
||||
self.client.update_subscription_profiles({
|
||||
"base": {
|
||||
"camera": "unsubscribed",
|
||||
}
|
||||
})
|
||||
if not self._receive_video:
|
||||
self.client.update_subscription_profiles({
|
||||
"base": {
|
||||
"camera": "unsubscribed",
|
||||
}
|
||||
})
|
||||
|
||||
if self._token and self._start_transcription:
|
||||
self.client.start_transcription(self.transcription_settings)
|
||||
@@ -225,6 +229,16 @@ class DailyTransportService(BaseTransportService, EventHandler):
|
||||
self.client.leave()
|
||||
self.client.release()
|
||||
|
||||
def _handle_video_frame(self, participant_id, video_frame):
|
||||
# TODO-CB: What about multiple participants?
|
||||
if (not participant_id in self._participant_frame_times) or (time.time() > self._participant_frame_times[participant_id] + 1.0/self._receive_video_fps):
|
||||
print(f"### sending frame now")
|
||||
self._participant_frame_times[participant_id] = time.time()
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
self.receive_queue.put(
|
||||
VideoImageFrame(participant_id, video_frame)), self._loop
|
||||
)
|
||||
|
||||
def on_first_other_participant_joined(self):
|
||||
pass
|
||||
|
||||
@@ -248,6 +262,9 @@ class DailyTransportService(BaseTransportService, EventHandler):
|
||||
if not self._other_participant_has_joined and participant["id"] != self._my_participant_id:
|
||||
self._other_participant_has_joined = True
|
||||
self.on_first_other_participant_joined()
|
||||
if self._receive_video:
|
||||
self.client.set_video_renderer(
|
||||
participant["id"], self._handle_video_frame)
|
||||
|
||||
def on_participant_left(self, participant, reason):
|
||||
if len(self.client.participants()) < self._min_others_count + 1:
|
||||
|
||||
@@ -2,13 +2,21 @@ import aiohttp
|
||||
from PIL import Image
|
||||
import io
|
||||
import time
|
||||
from openai import AsyncOpenAI
|
||||
import base64
|
||||
from openai import AsyncOpenAI, AsyncStream
|
||||
|
||||
import json
|
||||
from collections.abc import AsyncGenerator
|
||||
|
||||
from dailyai.services.ai_services import LLMService, ImageGenService
|
||||
from openai.types.chat import (
|
||||
ChatCompletion,
|
||||
ChatCompletionChunk,
|
||||
ChatCompletionMessageParam,
|
||||
)
|
||||
|
||||
from dailyai.services.ai_services import LLMService, ImageGenService, VisionService
|
||||
from dailyai.services.openai_api_llm_service import BaseOpenAILLMService
|
||||
from dailyai.pipeline.frames import TextFrame
|
||||
|
||||
|
||||
class OpenAILLMService(BaseOpenAILLMService):
|
||||
@@ -50,3 +58,41 @@ class OpenAIImageGenService(ImageGenService):
|
||||
image_stream = io.BytesIO(await response.content.read())
|
||||
image = Image.open(image_stream)
|
||||
return (image_url, image.tobytes())
|
||||
|
||||
|
||||
class OpenAIVisionService(VisionService):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
model="gpt-4-vision-preview",
|
||||
api_key,
|
||||
):
|
||||
self._model = model
|
||||
self._client = AsyncOpenAI(api_key=api_key)
|
||||
|
||||
async def run_vision(self, prompt: str, image: bytes):
|
||||
base64_image = base64.b64encode(image).decode('utf-8')
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": prompt},
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": f"data:image/jpeg;base64,{base64_image}"
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
chunks: AsyncStream[ChatCompletionChunk] = (
|
||||
await self._client.chat.completions.create(
|
||||
model=self._model,
|
||||
stream=True,
|
||||
messages=messages,
|
||||
)
|
||||
)
|
||||
async for chunk in chunks:
|
||||
print(f"!!! chunk: {chunk}")
|
||||
yield TextFrame(chunk)
|
||||
|
||||
Reference in New Issue
Block a user