pygpt-net 2.6.30__py3-none-any.whl → 2.6.32__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. pygpt_net/CHANGELOG.txt +15 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +7 -1
  4. pygpt_net/app_core.py +3 -1
  5. pygpt_net/config.py +3 -1
  6. pygpt_net/controller/__init__.py +9 -2
  7. pygpt_net/controller/audio/audio.py +38 -1
  8. pygpt_net/controller/audio/ui.py +2 -2
  9. pygpt_net/controller/chat/audio.py +1 -8
  10. pygpt_net/controller/chat/common.py +23 -62
  11. pygpt_net/controller/chat/handler/__init__.py +0 -0
  12. pygpt_net/controller/chat/handler/stream_worker.py +1124 -0
  13. pygpt_net/controller/chat/output.py +8 -3
  14. pygpt_net/controller/chat/stream.py +3 -1071
  15. pygpt_net/controller/chat/text.py +3 -2
  16. pygpt_net/controller/kernel/kernel.py +11 -3
  17. pygpt_net/controller/kernel/reply.py +5 -1
  18. pygpt_net/controller/lang/custom.py +2 -2
  19. pygpt_net/controller/media/__init__.py +12 -0
  20. pygpt_net/controller/media/media.py +115 -0
  21. pygpt_net/controller/realtime/__init__.py +12 -0
  22. pygpt_net/controller/realtime/manager.py +53 -0
  23. pygpt_net/controller/realtime/realtime.py +293 -0
  24. pygpt_net/controller/ui/mode.py +23 -2
  25. pygpt_net/controller/ui/ui.py +19 -1
  26. pygpt_net/core/audio/audio.py +6 -1
  27. pygpt_net/core/audio/backend/native/__init__.py +12 -0
  28. pygpt_net/core/audio/backend/{native.py → native/native.py} +426 -127
  29. pygpt_net/core/audio/backend/native/player.py +139 -0
  30. pygpt_net/core/audio/backend/native/realtime.py +250 -0
  31. pygpt_net/core/audio/backend/pyaudio/__init__.py +12 -0
  32. pygpt_net/core/audio/backend/pyaudio/playback.py +194 -0
  33. pygpt_net/core/audio/backend/pyaudio/pyaudio.py +923 -0
  34. pygpt_net/core/audio/backend/pyaudio/realtime.py +312 -0
  35. pygpt_net/core/audio/backend/pygame/__init__.py +12 -0
  36. pygpt_net/core/audio/backend/{pygame.py → pygame/pygame.py} +130 -19
  37. pygpt_net/core/audio/backend/shared/__init__.py +38 -0
  38. pygpt_net/core/audio/backend/shared/conversions.py +211 -0
  39. pygpt_net/core/audio/backend/shared/envelope.py +38 -0
  40. pygpt_net/core/audio/backend/shared/player.py +137 -0
  41. pygpt_net/core/audio/backend/shared/rt.py +52 -0
  42. pygpt_net/core/audio/capture.py +5 -0
  43. pygpt_net/core/audio/output.py +14 -2
  44. pygpt_net/core/audio/whisper.py +6 -2
  45. pygpt_net/core/bridge/bridge.py +2 -1
  46. pygpt_net/core/bridge/worker.py +4 -1
  47. pygpt_net/core/dispatcher/dispatcher.py +37 -1
  48. pygpt_net/core/events/__init__.py +2 -1
  49. pygpt_net/core/events/realtime.py +55 -0
  50. pygpt_net/core/image/image.py +56 -5
  51. pygpt_net/core/realtime/__init__.py +0 -0
  52. pygpt_net/core/realtime/options.py +87 -0
  53. pygpt_net/core/realtime/shared/__init__.py +0 -0
  54. pygpt_net/core/realtime/shared/audio.py +213 -0
  55. pygpt_net/core/realtime/shared/loop.py +64 -0
  56. pygpt_net/core/realtime/shared/session.py +59 -0
  57. pygpt_net/core/realtime/shared/text.py +37 -0
  58. pygpt_net/core/realtime/shared/tools.py +276 -0
  59. pygpt_net/core/realtime/shared/turn.py +38 -0
  60. pygpt_net/core/realtime/shared/types.py +16 -0
  61. pygpt_net/core/realtime/worker.py +160 -0
  62. pygpt_net/core/render/web/body.py +24 -3
  63. pygpt_net/core/text/utils.py +54 -2
  64. pygpt_net/core/types/__init__.py +1 -0
  65. pygpt_net/core/types/image.py +54 -0
  66. pygpt_net/core/video/__init__.py +12 -0
  67. pygpt_net/core/video/video.py +290 -0
  68. pygpt_net/data/config/config.json +26 -5
  69. pygpt_net/data/config/models.json +221 -103
  70. pygpt_net/data/config/settings.json +244 -6
  71. pygpt_net/data/css/web-blocks.css +6 -0
  72. pygpt_net/data/css/web-chatgpt.css +6 -0
  73. pygpt_net/data/css/web-chatgpt_wide.css +6 -0
  74. pygpt_net/data/locale/locale.de.ini +35 -7
  75. pygpt_net/data/locale/locale.en.ini +56 -17
  76. pygpt_net/data/locale/locale.es.ini +35 -7
  77. pygpt_net/data/locale/locale.fr.ini +35 -7
  78. pygpt_net/data/locale/locale.it.ini +35 -7
  79. pygpt_net/data/locale/locale.pl.ini +38 -7
  80. pygpt_net/data/locale/locale.uk.ini +35 -7
  81. pygpt_net/data/locale/locale.zh.ini +31 -3
  82. pygpt_net/data/locale/plugin.audio_input.en.ini +4 -0
  83. pygpt_net/data/locale/plugin.audio_output.en.ini +4 -0
  84. pygpt_net/data/locale/plugin.cmd_web.en.ini +8 -0
  85. pygpt_net/item/model.py +22 -1
  86. pygpt_net/plugin/audio_input/plugin.py +37 -4
  87. pygpt_net/plugin/audio_input/simple.py +57 -8
  88. pygpt_net/plugin/cmd_files/worker.py +3 -0
  89. pygpt_net/provider/api/google/__init__.py +76 -7
  90. pygpt_net/provider/api/google/audio.py +8 -1
  91. pygpt_net/provider/api/google/chat.py +45 -6
  92. pygpt_net/provider/api/google/image.py +226 -86
  93. pygpt_net/provider/api/google/realtime/__init__.py +12 -0
  94. pygpt_net/provider/api/google/realtime/client.py +1945 -0
  95. pygpt_net/provider/api/google/realtime/realtime.py +186 -0
  96. pygpt_net/provider/api/google/video.py +364 -0
  97. pygpt_net/provider/api/openai/__init__.py +22 -2
  98. pygpt_net/provider/api/openai/realtime/__init__.py +12 -0
  99. pygpt_net/provider/api/openai/realtime/client.py +1828 -0
  100. pygpt_net/provider/api/openai/realtime/realtime.py +193 -0
  101. pygpt_net/provider/audio_input/google_genai.py +103 -0
  102. pygpt_net/provider/audio_output/google_genai_tts.py +229 -0
  103. pygpt_net/provider/audio_output/google_tts.py +0 -12
  104. pygpt_net/provider/audio_output/openai_tts.py +8 -5
  105. pygpt_net/provider/core/config/patch.py +241 -178
  106. pygpt_net/provider/core/model/patch.py +28 -2
  107. pygpt_net/provider/llms/google.py +8 -9
  108. pygpt_net/provider/web/duckduck_search.py +212 -0
  109. pygpt_net/ui/layout/toolbox/audio.py +55 -0
  110. pygpt_net/ui/layout/toolbox/footer.py +14 -42
  111. pygpt_net/ui/layout/toolbox/image.py +7 -13
  112. pygpt_net/ui/layout/toolbox/raw.py +52 -0
  113. pygpt_net/ui/layout/toolbox/split.py +48 -0
  114. pygpt_net/ui/layout/toolbox/toolbox.py +8 -8
  115. pygpt_net/ui/layout/toolbox/video.py +49 -0
  116. pygpt_net/ui/widget/option/combo.py +15 -1
  117. {pygpt_net-2.6.30.dist-info → pygpt_net-2.6.32.dist-info}/METADATA +46 -22
  118. {pygpt_net-2.6.30.dist-info → pygpt_net-2.6.32.dist-info}/RECORD +121 -73
  119. pygpt_net/core/audio/backend/pyaudio.py +0 -554
  120. {pygpt_net-2.6.30.dist-info → pygpt_net-2.6.32.dist-info}/LICENSE +0 -0
  121. {pygpt_net-2.6.30.dist-info → pygpt_net-2.6.32.dist-info}/WHEEL +0 -0
  122. {pygpt_net-2.6.30.dist-info → pygpt_net-2.6.32.dist-info}/entry_points.txt +0 -0
@@ -1,554 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- # ================================================== #
4
- # This file is a part of PYGPT package #
5
- # Website: https://pygpt.net #
6
- # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
- # MIT License #
8
- # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.08.27 07:00:00 #
10
- # ================================================== #
11
-
12
- import time
13
- from typing import List, Tuple
14
-
15
- import wave
16
- from PySide6.QtCore import QTimer
17
-
18
-
19
- class PyaudioBackend:
20
-
21
- MIN_FRAMES = 25 # minimum frames to start transcription
22
-
23
- def __init__(self, window=None):
24
- """
25
- Audio input capture core using PyAudio backend
26
- """
27
- self.window = window
28
- self.path = None
29
- self.frames = []
30
- self.loop = False
31
- self.stop_callback = None
32
- self.start_time = 0
33
- self.initialized = False
34
- self.pyaudio_instance = None
35
- self.pyaudio_instance_output = None
36
- self.stream = None
37
- self.stream_output = None
38
- self.mode = "input" # input|control
39
-
40
- # Get configuration values (use defaults if unavailable)
41
- if self.window is not None and hasattr(self.window, "core"):
42
- self.channels = int(self.window.core.config.get('audio.input.channels', 1))
43
- self.rate = int(self.window.core.config.get('audio.input.rate', 44100))
44
- else:
45
- self.channels = 1
46
- self.rate = 44100
47
-
48
- self.format = None
49
- self.devices = []
50
- self.selected_device = None
51
-
52
- def init(self):
53
- """
54
- Initialize audio input backend.
55
- """
56
- import pyaudio
57
- if not self.initialized:
58
- self.format = pyaudio.paInt16 # We use paInt16 as default format
59
- self.pyaudio_instance = pyaudio.PyAudio()
60
- self.check_audio_devices()
61
- self.initialized = True
62
-
63
- def set_mode(self, mode: str):
64
- """
65
- Set input mode (input|control)
66
-
67
- :param mode: mode name
68
- """
69
- self.mode = mode
70
-
71
- def set_repeat_callback(self, callback):
72
- """
73
- Set callback to be called on loop recording.
74
-
75
- :param callback: function to call on loop recording
76
- """
77
- if callable(callback):
78
- self.stop_callback = callback
79
- else:
80
- raise ValueError("Callback must be a callable function")
81
-
82
- def set_loop(self, loop: bool):
83
- """
84
- Set loop recording.
85
-
86
- :param loop: True to enable loop recording
87
- """
88
- self.loop = loop
89
-
90
- def set_path(self, path: str):
91
- """
92
- Set audio input file path.
93
-
94
- :param path: file path to save recorded audio
95
- """
96
- self.path = path
97
-
98
- def start(self):
99
- """
100
- Start audio input recording using PyAudio.
101
- :return: True if started
102
- """
103
- self.init()
104
-
105
- # Clear previous frames
106
- self.frames = []
107
-
108
- # Prepare selected device from configuration
109
- self.prepare_device()
110
- if self.selected_device is None:
111
- print("No audio input device selected")
112
- return False
113
-
114
- # Prevent multiple recordings
115
- if self.stream is not None:
116
- return False
117
-
118
- # Set up audio input and start recording
119
- self.setup_audio_input()
120
- self.start_time = time.time()
121
- return True
122
-
123
- def stop(self) -> bool:
124
- """
125
- Stop audio input recording.
126
- :return: True if stopped (and file saved) or False otherwise.
127
- """
128
- result = False
129
- if self.stream is not None:
130
- try:
131
- self.stream.stop_stream()
132
- self.stream.close()
133
- except Exception as e:
134
- print(f"Error stopping stream: {e}")
135
- self.stream = None
136
-
137
- if self.frames:
138
- if self.path:
139
- self.save_audio_file(self.path)
140
- result = True
141
- else:
142
- print("File path is not set.")
143
- else:
144
- print("No audio data recorded")
145
- return result
146
-
147
- def has_source(self) -> bool:
148
- """
149
- Check if audio source is available.
150
- :return: True if available.
151
- """
152
- return self.stream is not None
153
-
154
- def has_frames(self) -> bool:
155
- """
156
- Check if audio frames are available.
157
- :return: True if available.
158
- """
159
- return bool(self.frames)
160
-
161
- def has_min_frames(self) -> bool:
162
- """
163
- Check if minimum required audio frames have been recorded.
164
- :return: True if min frames reached.
165
- """
166
- return len(self.frames) >= self.MIN_FRAMES
167
-
168
- def reset_audio_level(self):
169
- """
170
- Reset the audio level bar.
171
- """
172
- self.window.controller.audio.ui.on_input_volume_change(0, self.mode)
173
-
174
- def check_audio_input(self) -> bool:
175
- """
176
- Check if default audio input device is working using PyAudio.
177
- :return: True if working.
178
- """
179
- self.init()
180
- try:
181
- test_stream = self.pyaudio_instance.open(format=self.format,
182
- channels=self.channels,
183
- rate=self.rate,
184
- input=True,
185
- frames_per_buffer=1024)
186
- test_stream.stop_stream()
187
- test_stream.close()
188
- return True
189
- except Exception as e:
190
- return False
191
-
192
- def check_audio_devices(self):
193
- """
194
- Check audio input devices using PyAudio and populate self.devices.
195
- Each device is stored as a dict with keys 'index' and 'name'.
196
- """
197
- self.devices = []
198
- for i in range(self.pyaudio_instance.get_device_count()):
199
- try:
200
- info = self.pyaudio_instance.get_device_info_by_index(i)
201
- if info.get('maxInputChannels', 0) > 0:
202
- self.devices.append({'index': i, 'name': info.get('name', f'Device {i}')})
203
- except Exception as e:
204
- continue
205
-
206
- if not self.devices:
207
- self.selected_device = None
208
- print("No audio input devices found.")
209
- else:
210
- # Set the first available device as default
211
- self.selected_device = self.devices[0]['index']
212
-
213
- def device_changed(self, index: int):
214
- """
215
- Change audio input device based on device list index.
216
- :param index: device list index.
217
- """
218
- self.init()
219
- if 0 <= index < len(self.devices):
220
- self.selected_device = self.devices[index]['index']
221
- else:
222
- self.selected_device = 0
223
-
224
- def prepare_device(self):
225
- """
226
- Set the current audio input device from configuration.
227
- """
228
- self.init()
229
- if self.window is not None and hasattr(self.window, "core"):
230
- device_id = int(self.window.core.config.get('audio.input.device', 0))
231
- self.device_changed(device_id)
232
- else:
233
- # Default to first available device
234
- if self.devices:
235
- print(self.devices)
236
- self.selected_device = self.devices[0]['index']
237
- else:
238
- self.selected_device = None
239
-
240
- def setup_audio_input(self):
241
- """
242
- Set up audio input device and start recording using PyAudio.
243
- """
244
- self.init()
245
- if self.selected_device is None:
246
- print("No audio input device selected")
247
- return
248
-
249
- print("Opening audio stream with device index:", self.selected_device)
250
- try:
251
- self.stream = self.pyaudio_instance.open(format=self.format,
252
- channels=self.channels,
253
- rate=self.rate,
254
- input=True,
255
- frames_per_buffer=1024,
256
- stream_callback=self._audio_callback)
257
- except Exception as e:
258
- print(f"Failed to open audio stream: {e}")
259
- self.stream = None
260
-
261
- def _audio_callback(self, in_data, frame_count, time_info, status):
262
- """
263
- PyAudio callback to process incoming audio data.
264
- """
265
- import pyaudio
266
- import numpy as np
267
-
268
- # Append raw data to the frames list for saving
269
- self.frames.append(in_data)
270
-
271
- # Convert bytes data to a NumPy array using the correct data type
272
- dtype = self.get_dtype_from_format(self.format)
273
- samples = np.frombuffer(in_data, dtype=dtype)
274
- if samples.size == 0:
275
- return None, pyaudio.paContinue
276
-
277
- # Compute root mean square (RMS) for the audio samples
278
- rms = np.sqrt(np.mean(samples.astype(np.float64) ** 2))
279
- normalization_factor = self.get_normalization_factor(self.format)
280
- level = rms / normalization_factor
281
- # Clamp level to 0.0 - 1.0 range
282
- level = min(max(level, 0.0), 1.0)
283
- level_percent = int(level * 100)
284
-
285
- # Update the audio level bar if available.
286
- try:
287
- QTimer.singleShot(0, lambda: self.window.controller.audio.ui.on_input_volume_change(level_percent, self.mode))
288
- except Exception:
289
- pass
290
-
291
- # Handle loop recording if enabled.
292
- if self.loop and self.stop_callback is not None:
293
- stop_interval = int(self.window.core.config.get('audio.input.stop_interval', 10)) \
294
- if self.window and hasattr(self.window, "core") else 10
295
- current_time = time.time()
296
- if current_time - self.start_time >= stop_interval:
297
- self.start_time = current_time
298
- QTimer.singleShot(0, self.stop_callback)
299
-
300
- return None, pyaudio.paContinue
301
-
302
- def update_audio_level(self, level: int):
303
- """
304
- Update the audio level bar.
305
- :param level: audio level (0-100).
306
- """
307
- self.window.controller.audio.ui.on_input_volume_change(level, self.mode)
308
-
309
- def save_audio_file(self, filename: str):
310
- """
311
- Save the recorded audio frames to a WAV file.
312
- :param filename: output file name.
313
- """
314
- sample_width = self.pyaudio_instance.get_sample_size(self.format)
315
- with wave.open(filename, 'wb') as wf:
316
- wf.setnchannels(self.channels)
317
- wf.setsampwidth(sample_width)
318
- wf.setframerate(self.rate)
319
- wf.writeframes(b''.join(self.frames))
320
-
321
- def get_dtype_from_format(self, fmt):
322
- """
323
- Get the NumPy dtype corresponding to the PyAudio format.
324
- :param fmt: PyAudio format constant.
325
- """
326
- import pyaudio
327
- import numpy as np
328
- if fmt == pyaudio.paInt16:
329
- return np.int16
330
- elif fmt == pyaudio.paInt8:
331
- return np.int8
332
- elif fmt == pyaudio.paUInt8:
333
- return np.uint8
334
- elif fmt == pyaudio.paFloat32:
335
- return np.float32
336
- else:
337
- raise ValueError("Unsupported audio format")
338
-
339
- def get_normalization_factor(self, fmt):
340
- """
341
- Get the normalization factor for the given PyAudio format.
342
- :param fmt: PyAudio format constant.
343
- """
344
- import pyaudio
345
- if fmt == pyaudio.paInt16:
346
- return 32768.0
347
- elif fmt == pyaudio.paInt8:
348
- return 128.0
349
- elif fmt == pyaudio.paUInt8:
350
- return 255.0
351
- elif fmt == pyaudio.paFloat32:
352
- return 1.0
353
- else:
354
- raise ValueError("Unsupported audio format")
355
-
356
- def stop_audio(self) -> bool:
357
- """
358
- Stop audio input recording.
359
- :return: True if stopped.
360
- """
361
- if self.stream is not None:
362
- try:
363
- self.stream.stop_stream()
364
- self.stream.close()
365
- except Exception as e:
366
- print(f"Error stopping stream: {e}")
367
- self.stream = None
368
- return True
369
- return False
370
-
371
- def play(
372
- self,
373
- audio_file: str,
374
- event_name: str,
375
- stopped: callable,
376
- signals=None
377
- ):
378
- """
379
- Play audio file using PyAudio
380
-
381
- :param audio_file: audio file path
382
- :param event_name: event name to emit when playback starts
383
- :param stopped: callable to check if playback should stop
384
- :param signals: signals object to emit playback events
385
- """
386
- import io
387
- import wave
388
- import pyaudio
389
- import numpy as np
390
- from pydub import AudioSegment
391
-
392
- num_device = int(self.window.core.config.get('audio.output.device', 0))
393
- signals.playback.emit(event_name)
394
- audio = AudioSegment.from_file(audio_file)
395
- audio = audio.set_frame_rate(44100) # resample to 44.1 kHz
396
- wav_io = io.BytesIO()
397
- audio.export(wav_io, format='wav')
398
- wav_io.seek(0)
399
- wf = wave.open(wav_io, 'rb')
400
- self.pyaudio_instance_output = pyaudio.PyAudio()
401
- self.stream_output = self.pyaudio_instance_output.open(
402
- format=self.pyaudio_instance_output.get_format_from_width(wf.getsampwidth()),
403
- channels=wf.getnchannels(),
404
- rate=wf.getframerate(),
405
- output=True,
406
- output_device_index=num_device
407
- )
408
-
409
- sample_width = wf.getsampwidth()
410
- format = self.pyaudio_instance_output.get_format_from_width(sample_width)
411
-
412
- if format == pyaudio.paInt8:
413
- dtype = np.int8
414
- max_value = 2 ** 7 - 1 # 127
415
- offset = 0
416
- elif format == pyaudio.paInt16:
417
- dtype = np.int16
418
- max_value = 2 ** 15 - 1 # 32767
419
- offset = 0
420
- elif format == pyaudio.paInt32:
421
- dtype = np.int32
422
- max_value = 2 ** 31 - 1 # 2147483647
423
- offset = 0
424
- elif format == pyaudio.paUInt8:
425
- dtype = np.uint8
426
- max_value = 2 ** 8 - 1 # 255
427
- offset = 128 # center unsigned data
428
- else:
429
- raise ValueError(f"Unsupported format: {format}")
430
-
431
- chunk_size = 512
432
- data = wf.readframes(chunk_size)
433
-
434
- while data != b'' and not stopped():
435
- self.stream_output.write(data)
436
-
437
- audio_data = np.frombuffer(data, dtype=dtype)
438
- if len(audio_data) > 0:
439
- audio_data = audio_data.astype(np.float32)
440
- if dtype == np.uint8:
441
- audio_data -= offset
442
-
443
- # compute RMS
444
- rms = np.sqrt(np.mean(audio_data ** 2))
445
-
446
- if rms > 0:
447
- # RMS to decibels
448
- db = 20 * np.log10(rms / max_value)
449
-
450
- # define minimum and maximum dB levels
451
- min_db = -60 # adjust as needed
452
- max_db = 0
453
-
454
- # clamp the db value to the range [min_db, max_db]
455
- if db < min_db:
456
- db = min_db
457
- elif db > max_db:
458
- db = max_db
459
-
460
- # map decibel value to volume percentage
461
- volume_percentage = ((db - min_db) / (max_db - min_db)) * 100
462
- else:
463
- volume_percentage = 0
464
-
465
- # emit volume signal
466
- signals.volume_changed.emit(volume_percentage)
467
- else:
468
- # if empty audio_data
469
- signals.volume_changed.emit(0)
470
-
471
- data = wf.readframes(chunk_size)
472
-
473
- # close the stream
474
- if self.stream_output is not None:
475
- if self.stream_output.is_active():
476
- self.stream_output.stop_stream()
477
- self.stream_output.close()
478
- if self.pyaudio_instance_output is not None:
479
- self.pyaudio_instance_output.terminate()
480
-
481
- wf.close()
482
- signals.volume_changed.emit(0)
483
-
484
- def stop_playback(self, signals=None):
485
- """
486
- Stop audio playback if it is currently playing.
487
-
488
- :param signals: signals object to emit stop event
489
- """
490
- pass
491
-
492
- def get_input_devices(self) -> List[Tuple[int, str]]:
493
- """
494
- Get input devices
495
-
496
- :return devices list: [(id, name)]
497
- """
498
- from bs4 import UnicodeDammit
499
- self.init()
500
- devices_list = []
501
- for item in self.devices:
502
- index = item['index']
503
- device_name = item['name']
504
- dammit = UnicodeDammit(device_name)
505
- devices_list.append((index, dammit.unicode_markup))
506
- return devices_list
507
-
508
- def get_output_devices(self) -> List[Tuple[int, str]]:
509
- """
510
- Get output devices using pyaudio
511
-
512
- :return: List of tuples with (device_id, device_name)
513
- """
514
- import pyaudio
515
- p = pyaudio.PyAudio()
516
- devices_list = []
517
- for i in range(p.get_device_count()):
518
- device_info = p.get_device_info_by_index(i)
519
- if device_info.get('maxOutputChannels', 0) > 0:
520
- devices_list.append((i, device_info.get('name', 'Unknown')))
521
- p.terminate()
522
- return devices_list
523
-
524
- def get_default_input_device(self) -> tuple:
525
- """
526
- Retrieve the default input device using PyAudio.
527
- """
528
- import pyaudio
529
- p = pyaudio.PyAudio()
530
- try:
531
- default_info = p.get_default_input_device_info()
532
- device_id = default_info.get('index')
533
- device_name = default_info.get('name', 'Unknown')
534
- except IOError as e:
535
- print("Error getting default output device:", e)
536
- device_id, device_name = None, None
537
- p.terminate()
538
- return device_id, device_name
539
-
540
- def get_default_output_device(self) -> tuple:
541
- """
542
- Retrieve the default output device using PyAudio.
543
- """
544
- import pyaudio
545
- p = pyaudio.PyAudio()
546
- try:
547
- default_info = p.get_default_output_device_info()
548
- device_id = default_info.get('index')
549
- device_name = default_info.get('name', 'Unknown')
550
- except IOError as e:
551
- print("Error getting default output device:", e)
552
- device_id, device_name = None, None
553
- p.terminate()
554
- return device_id, device_name