Skip to content

Commit 0855c64

Browse files
committed
Initial Checkin
Samples for delaying audio by a few seconds and showing an image
1 parent 7bfed83 commit 0855c64

File tree

6 files changed

+379
-2
lines changed

6 files changed

+379
-2
lines changed

README.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,26 @@
1-
# bc-python
2-
Python api
1+
# Project Name
2+
bc-python
3+
4+
This project requires Python 3.12 and specific dependencies listed in `requirements.txt`. Follow the instructions below to set up your development environment using Conda.
5+
6+
## Prerequisites
7+
8+
- [Anaconda](https://www.anaconda.com/products/distribution#download-section) or [Miniconda](https://docs.conda.io/en/latest/miniconda.html) should be installed on your system.
9+
10+
## Setting Up the Environment
11+
12+
1. **Create a New Conda Environment**
13+
14+
Open your terminal or command prompt and create a new Conda environment with Python 3.12:
15+
16+
```bash
17+
conda create --name birdconv python=3.12
18+
19+
20+
2. **Activate birdconv
21+
```bash
22+
conda activate birdconv
23+
24+
3. **Install requirements
25+
```bash
26+
pip install -r requirements.txt

bird.png

7.01 MB
Loading

birdconv.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import requests
2+
import json
3+
4+
5+
# Grab your uid from the python section of https://birdcallauth.web.app
6+
api_request = {"uid": "API KEY HERE"}
7+
host = "https://birdcallauth.web.app"
8+
9+
# if you are running the birdconv server locally, use this instead
10+
# host = "http://localhost:5173"
11+
12+
active_path = '/api/active'
13+
room_path = '/api/room'
14+
token_path = '/api/token_monitor'
15+
headers = {'Content-Type': 'application/json'}
16+
17+
18+
# choose printIt=True to see the request and response from the api
19+
def send_request( path, api_request, printit=True) :
20+
21+
if printit:
22+
print("Request to " + path)
23+
print(json.dumps(api_request, indent=4))
24+
25+
response = requests.post( host + path, headers=headers, json=api_request)
26+
if response.ok:
27+
res = response.json()
28+
else:
29+
print(f"Error: {response.status_code}, {response.text}")
30+
31+
if printit:
32+
print("Response:")
33+
print(json.dumps(res, indent=4))
34+
return res
35+
36+
# Get a list of active devices in birdconv
37+
def active():
38+
return send_request(active_path, api_request, printit=False)
39+
40+
# create a room with a subset of the active devices
41+
def create_room( devices , recording=False, expiryTime=1):
42+
roomRequest = {
43+
"devices" : devices, # A subset of the array of devices returned by the /active endpoint
44+
"audioOnly" : False, # this value is ignored,
45+
"recording" : recording, # set to True to record
46+
"expiryTime" : expiryTime*60, # how long the call will be active, 1 min * 60 sec
47+
}
48+
roomRequest.update(api_request)
49+
50+
return send_request(room_path, roomRequest, printit=False)
51+
52+
# get a token for this program to join a room
53+
def create_token( room , username):
54+
55+
device = {
56+
"username" : username, # here is your python program's name
57+
"species" : "computer", # use computer
58+
"deviceId" : "", # !! leave blank !!
59+
"fcm_token" : "", # !! leave blank !!
60+
}
61+
device.update(api_request)
62+
63+
token_request = {
64+
"device" : device,
65+
"eavesdrop" : False # this way you are allowed to send video and audio!
66+
}
67+
token_request.update(room)
68+
token_request.update(api_request)
69+
70+
return send_request(token_path, token_request, printit=False)

delay_audio.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import threading
2+
import queue
3+
import time
4+
import birdconv
5+
from daily import *
6+
7+
8+
BUFFER = 2 # 2 second of buffering
9+
10+
SAMPLE_RATE = 16000
11+
NUM_CHANNELS = 1
12+
BYTES_PER_SAMPLE =2
13+
14+
15+
class AudioApp:
16+
def __init__(self, sample_rate, num_channels):
17+
18+
self._sample_rate = sample_rate
19+
self._num_channels = num_channels
20+
self._client = CallClient()
21+
self._client.update_subscription_profiles({
22+
"base": {
23+
"camera": "unsubscribed",
24+
"microphone": "subscribed"
25+
}
26+
})
27+
28+
self._mic_device = Daily.create_microphone_device("my-mic" ,sample_rate=sample_rate,channels=num_channels)
29+
self._speaker_device = Daily.create_speaker_device("my-speaker",sample_rate=sample_rate,channels=num_channels)
30+
Daily.select_speaker_device("my-speaker")
31+
32+
self.client_settings = {
33+
"inputs": {
34+
"camera": False,
35+
"microphone": {
36+
"isEnabled": True,
37+
"settings": {
38+
"deviceId": "my-mic"
39+
}
40+
}
41+
}
42+
}
43+
44+
self._app_quit = False
45+
self._app_error = None
46+
47+
self._buffer_queue = queue.Queue()
48+
49+
self._start_receive_event = threading.Event()
50+
self._thread_receive = threading.Thread(target=self.receive_audio)
51+
self._thread_receive.start()
52+
53+
self._start_send_event = threading.Event()
54+
self._thread_send = threading.Thread(target=self.send_audio)
55+
self._thread_send.start()
56+
57+
58+
def on_joined(self, data, error):
59+
if error:
60+
print(f"Unable to join meeting: {error}")
61+
self._app_error = error
62+
self._start_receive_event.set()
63+
self._start_send_event.set()
64+
65+
def run(self, meeting):
66+
meeting_url = meeting["url"]
67+
meeting_token = meeting["token"]
68+
self._client.join(meeting_url, meeting_token=meeting_token, client_settings=self.client_settings, completion=self.on_joined)
69+
70+
# wait until both threads terrminate
71+
self._thread_receive.join()
72+
self._thread_send.join()
73+
74+
def leave(self):
75+
self._app_quit = True
76+
self._thread_receive.join()
77+
self._thread_send.join()
78+
self._client.leave()
79+
self._client.release()
80+
81+
82+
def receive_audio(self):
83+
self._start_receive_event.wait()
84+
print("starting receive audio thread")
85+
86+
if self._app_error:
87+
print("Unable to receive audio!")
88+
return
89+
90+
while not self._app_quit:
91+
# Read BUFFER seconds worth of audio frames.
92+
num_frames = int(self._sample_rate * BUFFER)
93+
buffer = self._speaker_device.read_frames(num_frames)
94+
95+
if buffer:
96+
self._buffer_queue.put(buffer)
97+
98+
def send_audio(self):
99+
self._start_send_event.wait()
100+
print("starting send audio thread")
101+
102+
103+
if self._app_error:
104+
print("Unable to send audio!")
105+
return
106+
107+
while not self._app_quit:
108+
try:
109+
# Wait for BUFFER seconds before sending audio
110+
buffer = self._buffer_queue.get(timeout=.5) # wait for buffer with timeout to avoid blocking indefinitely
111+
if buffer:
112+
self._mic_device.write_frames(buffer)
113+
except queue.Empty:
114+
print("Buffer queue is empty, continuing...")
115+
continue
116+
117+
def main():
118+
119+
# set api_key
120+
# Get it from the website under python.
121+
birdconv.api_request["uid"] = "API KEY HERE"
122+
123+
# get the list of birdconv devices
124+
devices = birdconv.active()
125+
126+
# pick the devices that you want to place in a call
127+
my_device = None
128+
for device in devices :
129+
if device["username"] == "Alistair":
130+
my_device = device
131+
break
132+
133+
# place 1 or more selected devices on a call
134+
room = birdconv.create_room( [my_device] )
135+
136+
# get a token so that this python program can join the call
137+
# FYI : don't use "-" in a username, use an "_"
138+
meeting = birdconv.create_token( room , "Delay_Audio")
139+
print("Meeting URL: " + meeting["url"])
140+
# print("Meeting Token: " + meeting["token"])
141+
142+
print("sleeping for 1 second")
143+
time.sleep(1)
144+
145+
Daily.init()
146+
147+
app = AudioApp(SAMPLE_RATE, NUM_CHANNELS)
148+
149+
try:
150+
app.run(meeting)
151+
except KeyboardInterrupt:
152+
print("Ctrl-C detected. Exiting!")
153+
finally:
154+
app.leave()
155+
156+
157+
if __name__ == '__main__':
158+
main()

requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
boto3
2+
requests
3+
daily-python
4+
pillow

show_image.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import requests
2+
import json
3+
from daily import *
4+
5+
from PIL import Image
6+
import time
7+
import threading
8+
import birdconv
9+
10+
11+
12+
13+
class Participant:
14+
def __init__(self, image_file, framerate):
15+
self.__image = Image.open(image_file)
16+
self.__framerate = framerate
17+
18+
self.__camera = Daily.create_camera_device("my-camera",
19+
width=self.__image.width,
20+
height=self.__image.height,
21+
color_format="RGB")
22+
23+
self.__client = CallClient()
24+
25+
self.__client.update_subscription_profiles({
26+
"base": {
27+
"camera": "unsubscribed",
28+
"microphone": "unsubscribed"
29+
}
30+
})
31+
32+
self.__app_quit = False
33+
self.__app_error = None
34+
35+
self.__start_event = threading.Event()
36+
self.__thread = threading.Thread(target=self.send_image)
37+
self.__thread.start()
38+
39+
def on_joined(self, data, error):
40+
if error:
41+
print(f"Unable to join meeting: {error}")
42+
self.__app_error = error
43+
self.__start_event.set()
44+
45+
def run(self, meeting):
46+
self.__client.join(meeting["url"], meeting_token=meeting["token"], client_settings={
47+
"inputs": {
48+
"camera": {
49+
"isEnabled": True,
50+
"settings": { "deviceId": "my-camera"}
51+
},
52+
"microphone": False
53+
}
54+
}, completion=self.on_joined)
55+
self.__thread.join()
56+
57+
def leave(self):
58+
self.__app_quit = True
59+
self.__thread.join()
60+
self.__client.leave()
61+
self.__client.release()
62+
63+
def send_image(self):
64+
self.__start_event.wait()
65+
66+
if self.__app_error:
67+
print(f"Unable to send!")
68+
return
69+
70+
sleep_time = 1.0 / self.__framerate
71+
image_bytes = self.__image.tobytes()
72+
73+
while not self.__app_quit:
74+
self.__camera.write_frame(image_bytes)
75+
time.sleep(sleep_time)
76+
77+
78+
79+
def main():
80+
81+
82+
# set api_key
83+
birdconv.api_request["uid"] = "0dtTMNNV6tYiNoSco8fdL3lzyYp2"
84+
85+
# get the list of birdconv devices
86+
devices = birdconv.active()
87+
88+
# pick the devices that you want to place in a call
89+
my_device = None
90+
for device in devices :
91+
if device["username"] == "Alistair":
92+
my_device = device
93+
break
94+
95+
# place 1 or more selected devices on a call
96+
room = birdconv.create_room( [my_device] )
97+
98+
# get a token so that this python program can join the call
99+
meeting = birdconv.create_token( room , "show_image.py" )
100+
print("Meeting URL: " + meeting["url"])
101+
# print("Meeting Token: " + meeting["token"])
102+
103+
print("sleeping for 5 seconds")
104+
time.sleep(5)
105+
106+
print("connecting...")
107+
Daily.init()
108+
109+
print("showing a picture of a bird")
110+
participant = Participant("bird.png", 30)
111+
112+
try:
113+
participant.run(meeting)
114+
except KeyboardInterrupt:
115+
print("Ctrl-C detected. Exiting!")
116+
finally:
117+
participant.leave()
118+
119+
120+
if __name__ == "__main__":
121+
main()

0 commit comments

Comments
 (0)