1+ #!/usr/bin/env python3
2+
3+ import asyncio
4+ import os
5+ import logging
6+ import argparse
7+ from uuid import uuid4
8+ from dotenv import load_dotenv
9+ from aiortc .contrib .media import MediaPlayer
10+
11+ from examples .utils import create_user
12+ from getstream .stream import Stream
13+ from getstream .video import rtc
14+ from getstream .video .rtc .recording import RecordingType
15+
16+ # Configure logging for the Stream SDK
17+ logging .basicConfig (level = logging .WARN )
18+
19+ # Sample audio files - update these paths to your actual audio files
20+ AUDIO_FILES = [
21+ "/path/to/audio/input1.mp3" ,
22+ "/path/to/audio/input2.mp3" ,
23+ "/path/to/audio/input3.mp3" ,
24+ ]
25+
26+
27+ async def create_bot_with_audio (client , bot_name : str , audio_file : str ):
28+ """Create a bot user and prepare its audio player."""
29+ if not os .path .exists (audio_file ):
30+ print (f"Audio file not found: { audio_file } , skipping bot { bot_name } " )
31+ return None
32+
33+ # Create bot user
34+ bot_user_id = f"bot-{ bot_name .lower ()} -{ str (uuid4 ())[:8 ]} "
35+
36+ # Create the user in Stream first using the utility function
37+ create_user (client , bot_user_id , f"Bot { bot_name } " )
38+
39+ try :
40+ # Create media player from audio file
41+ player = MediaPlayer (audio_file , loop = True )
42+
43+ if player .audio :
44+ print (f"Bot { bot_name } created with audio: { os .path .basename (audio_file )} " )
45+ return bot_user_id , player
46+
47+ print (f"No audio track found in { audio_file } " )
48+ return None
49+
50+ except Exception as e :
51+ print (f"Error creating bot { bot_name } : { e } " )
52+ return None
53+
54+
55+ async def print_recording_status (connection ):
56+ """Print the current recording status."""
57+ status = connection .get_recording_status ()
58+ print (f"Recording Status:" )
59+ print (f" • Recording Active: { status ['recording_enabled' ]} " )
60+ print (f" • Recording Types: { status ['recording_types' ]} " )
61+ print (f" • Output Directory: { status ['output_directory' ]} " )
62+ if 'user_id_filter' in status :
63+ print (f" • User Filter: { status ['user_id_filter' ]} " )
64+
65+ async def record (connection , recording_type , output_dir , user_id_filter = None ):
66+ """Perform recording with the specified parameters."""
67+ print (f"\n Starting { recording_type } recording..." )
68+
69+ # Start recording with specified parameters
70+ recording_params = {
71+ "recording_types" : [recording_type ],
72+ "output_dir" : output_dir
73+ }
74+ if user_id_filter :
75+ recording_params ["user_ids" ] = user_id_filter
76+
77+ await connection .start_recording (** recording_params )
78+
79+ # Show recording status
80+ await print_recording_status (connection )
81+
82+ # Record for 20 seconds
83+ print (f"\n Recording for 20 seconds..." )
84+ await asyncio .sleep (20 )
85+
86+ # Stop recording
87+ print (f"\n Stopping recording..." )
88+ await connection .stop_recording ()
89+
90+ # Show final results
91+ final_status = connection .get_recording_status ()
92+ print (f"\n Recording Complete!" )
93+ print (f" • Output Directory: { final_status ['output_directory' ]} " )
94+
95+ async def record_composite (call , recorder_connection , bot_connections , players ):
96+ """Record composite audio from all participants."""
97+ await record (
98+ recorder_connection ,
99+ RecordingType .COMPOSITE ,
100+ "recordings/composite"
101+ )
102+
103+ async def record_tracks (call , recorder_connection , bot_connections , players ):
104+ """Record individual tracks from each participant."""
105+ await record (
106+ recorder_connection ,
107+ RecordingType .TRACK ,
108+ "recordings/tracks" ,
109+ user_id_filter = bot_connections [0 ].user_id # Record only the first bot's audio
110+ )
111+
112+ async def setup_bots (client , bot_names , audio_files ):
113+ """Set up bot users with audio files."""
114+ players = []
115+ bot_user_ids = []
116+
117+ print ("\n Creating bot users with audio files..." )
118+
119+ # Create all users first
120+ for bot_name , audio_file in zip (bot_names , audio_files ):
121+ result = await create_bot_with_audio (client , bot_name , audio_file )
122+ if result :
123+ bot_user_id , player = result
124+ bot_user_ids .append (bot_user_id )
125+ players .append (player )
126+
127+ if not bot_user_ids :
128+ print ("No bots could be created. Please check your audio file paths." )
129+ return None , None
130+
131+ print (f"Created { len (bot_user_ids )} bot users" )
132+ return bot_user_ids , players
133+
134+ async def main ():
135+ """Main recording example with multiple audio file inputs."""
136+ # Parse command line arguments
137+ parser = argparse .ArgumentParser (description = 'Stream Video Recording Example' )
138+ parser .add_argument ('--type' , choices = ['composite' , 'track' ], required = True ,
139+ help = 'Type of recording to perform (composite or track)' )
140+ args = parser .parse_args ()
141+
142+ # Load environment variables
143+ load_dotenv ()
144+
145+ print (f"Audio Recording Example - { args .type .title ()} Recording" )
146+ print ("=" * 50 )
147+
148+ # Initialize Stream client
149+ client = Stream .from_env ()
150+
151+ # Create a unique call
152+ call_id = f"recording-example-{ str (uuid4 ())} "
153+ call = client .video .call ("default" , call_id )
154+ print (f"Call ID: { call_id } " )
155+
156+ # Create the call
157+ call .get_or_create (data = {"created_by_id" : "recording-example" })
158+
159+ try :
160+ # Set up bots
161+ bot_names = ["Alice" , "Bob" , "Charlie" ]
162+ bot_user_ids , players = await setup_bots (client , bot_names , AUDIO_FILES )
163+ if not bot_user_ids :
164+ return
165+
166+ # Create recorder user
167+ recorder_user_id = f"bot-recorder-{ str (uuid4 ())[:8 ]} "
168+ create_user (client , recorder_user_id , "Recording Bot" )
169+
170+ # Join all bots first and add their tracks
171+ async with (
172+ await rtc .join (call , bot_user_ids [0 ]) as bot1_connection ,
173+ await rtc .join (call , bot_user_ids [1 ]) as bot2_connection ,
174+ await rtc .join (call , bot_user_ids [2 ]) as bot3_connection ,
175+ ):
176+ # Add all audio tracks
177+ for connection , player in zip ([bot1_connection , bot2_connection , bot3_connection ], players ):
178+ if player .audio :
179+ await connection .add_tracks (audio = player .audio )
180+
181+ # Now join with recorder and start recording
182+ async with await rtc .join (call , recorder_user_id ) as recorder_connection :
183+ print (f"Recorder joined: { recorder_user_id } " )
184+
185+ # Run the selected recording type
186+ if args .type == 'composite' :
187+ await record_composite (call , recorder_connection ,
188+ [bot1_connection , bot2_connection , bot3_connection ],
189+ players )
190+ else : # track recording
191+ await record_tracks (call , recorder_connection ,
192+ [bot1_connection , bot2_connection , bot3_connection ],
193+ players )
194+
195+ for connection in [bot1_connection , bot2_connection , bot3_connection ]:
196+ await connection .leave ()
197+ await asyncio .sleep (1.0 )
198+ except Exception as e :
199+ print (f"Error during recording: { e } " )
200+ finally :
201+ # Delete created users
202+ print ("Deleting created users..." )
203+ try :
204+ # Delete bot users
205+ client .delete_users (bot_user_ids + [recorder_user_id ])
206+ print (f"Deleted bot users" )
207+ except Exception as e :
208+ print (f"Error during user cleanup: { e } " )
209+
210+ print ("Done" )
211+
212+
213+ if __name__ == "__main__" :
214+ print ("Before running this example:" )
215+ print (" 1. Update the AUDIO_FILES list with paths to your actual audio files" )
216+ print (" 2. Make sure you have a .env file with STREAM_API_KEY and STREAM_API_SECRET" )
217+ print (" 3. Audio files should be in a format supported by aiortc (WAV, MP3, MP4, etc.)" )
218+ print (" 4. Run with --type composite or --type track to specify recording type" )
219+ print ()
220+
221+ asyncio .run (main ())
0 commit comments