Jon Taylor
fixed bot thread loop
4d3af55
raw
history blame
5.55 kB
import argparse
import queue
import time
import threading
import logging
import os
from PIL import Image
from typing import Any, Mapping
from daily import EventHandler, CallClient, Daily
from datetime import datetime
from dotenv import load_dotenv
from auth import get_meeting_token, get_room_name
load_dotenv()
class DailyVision(EventHandler):
def __init__(
self,
room_url,
room_name,
expiration,
bot_name="Daily Bot",
):
self.__client = CallClient(event_handler=self)
self.__pipeline = None
self.__camera = None
self.__time = time.time()
self.__queue = queue.Queue()
self.__app_quit = False
self.__bot_name = bot_name
self.__room_url = room_url
self.__room_name = room_name
self.__expiration = expiration
# Configure logger
FORMAT = f"%(asctime)s {self.__room_url} %(message)s"
logging.basicConfig(format=FORMAT)
self.logger = logging.getLogger("bot-instance")
self.logger.setLevel(logging.DEBUG)
self.logger.info(f"Expiration timer set to: {self.__expiration}")
# Start thread
self.__thread = threading.Thread(target = self.process_frames)
self.__thread.start()
def run(self, meeting_url, token):
# Join
self.logger.info(f"Connecting to room {meeting_url} as {self.__bot_name}")
self.__client.set_user_name(self.__bot_name)
self.__client.join(meeting_url, token, completion=self.on_joined)
#self.__participant_id = self.client.participants()["local"]["id"]
# Keep-alive on thread
self.__thread.join()
def leave(self):
self.logger.info(f"Leaving...")
self.__app_quit = True
self.__thread.join()
self.__client.leave()
def on_joined(self, join_data, client_error):
self.logger.info(f"call_joined: {join_data}, {client_error}")
def on_participant_joined(self, participant):
self.logger.info(f"Participant {participant['id']} joined, analyzing frames...")
self.__client.set_video_renderer(participant["id"], self.on_video_frame)
# Say hello
self.wave()
def setup_camera(self, video_frame):
if not self.__camera:
self.__camera = Daily.create_camera_device("camera",
width = video_frame.width,
height = video_frame.height,
color_format="RGB")
self.__client.update_inputs({
"camera": {
"isEnabled": True,
"settings": {
"deviceId": "camera"
}
}
})
def process_frames(self):
while not self.__app_quit:
# Check expiry timer
if time.time() > self.__expiration:
self.logger.info(f"Expiration timer exceeded. Exiting...")
self.__app_quit = True
return
try:
video_frame = self.__queue.get(timeout=5)
if video_frame:
image = Image.frombytes("RGBA", (video_frame.width, video_frame.height), video_frame.buffer)
result = self.__pipeline(image)
pil = Image.fromarray(result.render()[0], mode="RGB").tobytes()
self.__camera.write_frame(pil)
except queue.Empty:
pass
def on_video_frame(self, participant_id, video_frame):
# Process ~15 frames per second (considering incoming frames at 30fps).
if time.time() - self.__time > 0.05:
self.__time = time.time()
self.setup_camera(video_frame)
self.__queue.put(video_frame)
def wave(self, emoji="👋"):
self.__client.send_app_message(
{
"event": "sync-emoji-reaction",
"reaction": {
"emoji": emoji,
"room": "main-room",
"sessionId": "bot",
"id": time.time(),
},
}
)
def main():
parser = argparse.ArgumentParser(description="Daily Bot")
# Required args
parser.add_argument("-u", "--url", required=True, type=str, help="URL of the Daily room")
parser.add_argument("-k", "--api_key", required=True, type=str, help="Daily API key")
# Optional args
parser.add_argument("-t", "--private", type=bool, help="Is this room private?", default=True)
parser.add_argument("-n", "--bot-name", type=str, help="Name of the bot", default="Daily Bot")
parser.add_argument("-e", "--expiration", type=int, help="Duration of bot", default=os.getenv("BOT_MAX_DURATION", 300))
args = parser.parse_args()
Daily.init()
expiration = time.time() + args.expiration
room_name = get_room_name(args.url)
# Retrieve a meeting token, if not provided
#@TODO do room lookup to check privacy
if args.private:
token = get_meeting_token(room_name, args.api_key, expiration)
app = DailyVision(args.url, room_name, expiration, args.bot_name)
try :
app.run(args.url, token)
except KeyboardInterrupt:
print("Ctrl-C detected. Exiting!")
finally:
print("Bot loop completed. Exiting")
app.leave()
# Let leave finish
time.sleep(2)
if __name__ == '__main__':
main()