py-tgcalls 2.0.6__py3-none-any.whl → 2.1.0__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 (65) hide show
  1. {py_tgcalls-2.0.6.dist-info → py_tgcalls-2.1.0.dist-info}/METADATA +11 -16
  2. py_tgcalls-2.1.0.dist-info/RECORD +106 -0
  3. {py_tgcalls-2.0.6.dist-info → py_tgcalls-2.1.0.dist-info}/WHEEL +1 -1
  4. pytgcalls/__init__.py +2 -0
  5. pytgcalls/__version__.py +1 -1
  6. pytgcalls/exceptions.py +7 -24
  7. pytgcalls/ffmpeg.py +2 -2
  8. pytgcalls/filters.py +49 -6
  9. pytgcalls/handlers/handlers_holder.py +1 -1
  10. pytgcalls/media_devices/__init__.py +6 -2
  11. pytgcalls/media_devices/device_info.py +8 -15
  12. pytgcalls/media_devices/input_device.py +11 -0
  13. pytgcalls/media_devices/media_devices.py +41 -92
  14. pytgcalls/media_devices/screen_device.py +10 -0
  15. pytgcalls/media_devices/speaker_device.py +10 -0
  16. pytgcalls/methods/calls/change_volume_call.py +3 -0
  17. pytgcalls/methods/calls/get_participants.py +5 -1
  18. pytgcalls/methods/calls/leave_call.py +3 -1
  19. pytgcalls/methods/stream/__init__.py +14 -10
  20. pytgcalls/methods/stream/{mute_stream.py → mute.py} +2 -2
  21. pytgcalls/methods/stream/{pause_stream.py → pause.py} +2 -2
  22. pytgcalls/methods/stream/play.py +27 -21
  23. pytgcalls/methods/stream/record.py +49 -0
  24. pytgcalls/methods/stream/{resume_stream.py → resume.py} +2 -2
  25. pytgcalls/methods/stream/send_frame.py +38 -0
  26. pytgcalls/methods/stream/{played_time.py → time.py} +5 -3
  27. pytgcalls/methods/stream/{unmute_stream.py → unmute.py} +2 -2
  28. pytgcalls/methods/utilities/__init__.py +6 -0
  29. pytgcalls/methods/utilities/call_holder.py +5 -2
  30. pytgcalls/methods/utilities/join_presentation.py +50 -0
  31. pytgcalls/methods/utilities/log_retries.py +14 -0
  32. pytgcalls/methods/utilities/start.py +136 -16
  33. pytgcalls/methods/utilities/stream_params.py +69 -22
  34. pytgcalls/methods/utilities/update_sources.py +42 -0
  35. pytgcalls/mtproto/bridged_client.py +36 -2
  36. pytgcalls/mtproto/client_cache.py +46 -16
  37. pytgcalls/mtproto/hydrogram_client.py +72 -23
  38. pytgcalls/mtproto/mtproto_client.py +32 -4
  39. pytgcalls/mtproto/pyrogram_client.py +72 -23
  40. pytgcalls/mtproto/telethon_client.py +97 -33
  41. pytgcalls/scaffold.py +15 -0
  42. pytgcalls/types/__init__.py +14 -4
  43. pytgcalls/types/calls/__init__.py +2 -0
  44. pytgcalls/types/calls/call.py +5 -3
  45. pytgcalls/types/calls/call_sources.py +4 -0
  46. pytgcalls/types/chats/group_call_participant.py +23 -0
  47. pytgcalls/types/py_object.py +9 -10
  48. pytgcalls/types/raw/audio_stream.py +3 -3
  49. pytgcalls/types/raw/stream.py +8 -4
  50. pytgcalls/types/raw/video_stream.py +5 -4
  51. pytgcalls/types/stream/__init__.py +14 -4
  52. pytgcalls/types/stream/device.py +36 -0
  53. pytgcalls/types/stream/direction.py +25 -0
  54. pytgcalls/types/stream/external_media.py +8 -0
  55. pytgcalls/types/stream/frame.py +26 -0
  56. pytgcalls/types/stream/media_stream.py +178 -107
  57. pytgcalls/types/stream/record_stream.py +99 -0
  58. pytgcalls/types/stream/stream_ended.py +32 -0
  59. pytgcalls/types/stream/stream_frames.py +20 -0
  60. py_tgcalls-2.0.6.dist-info/RECORD +0 -93
  61. pytgcalls/media_devices/screen_info.py +0 -45
  62. pytgcalls/types/stream/stream_audio_ended.py +0 -9
  63. pytgcalls/types/stream/stream_video_ended.py +0 -9
  64. {py_tgcalls-2.0.6.dist-info → py_tgcalls-2.1.0.dist-info}/LICENSE +0 -0
  65. {py_tgcalls-2.0.6.dist-info → py_tgcalls-2.1.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,8 @@
1
1
  from enum import auto
2
+ from typing import List
3
+ from typing import Optional
4
+
5
+ from ntgcalls import SsrcGroup
2
6
 
3
7
  from ...types.py_object import PyObject
4
8
  from ..flag import Flag
@@ -10,6 +14,15 @@ class GroupCallParticipant(PyObject):
10
14
  LEFT = auto()
11
15
  UPDATED = auto()
12
16
 
17
+ class SourceInfo(PyObject):
18
+ def __init__(
19
+ self,
20
+ endpoint: str,
21
+ sources: List[SsrcGroup],
22
+ ):
23
+ self.endpoint: str = endpoint
24
+ self.sources: List[SsrcGroup] = sources
25
+
13
26
  def __init__(
14
27
  self,
15
28
  user_id: int,
@@ -22,12 +35,16 @@ class GroupCallParticipant(PyObject):
22
35
  volume: int,
23
36
  joined: bool,
24
37
  left: bool,
38
+ source: int,
39
+ video_info: Optional[SourceInfo],
40
+ presentation_info: Optional[SourceInfo],
25
41
  ):
26
42
  self.user_id: int = user_id
27
43
  self.muted: bool = muted
28
44
  self.muted_by_admin: bool = muted_by_admin
29
45
  self.video: bool = video
30
46
  self.screen_sharing: bool = screen_sharing
47
+ self.source: int = source
31
48
  self.video_camera: bool = video_camera
32
49
  self.raised_hand: bool = raised_hand
33
50
  self.volume: int = volume
@@ -37,3 +54,9 @@ class GroupCallParticipant(PyObject):
37
54
  self.action = self.Action.LEFT
38
55
  else:
39
56
  self.action = self.Action.UPDATED
57
+ self.video_info: Optional[
58
+ GroupCallParticipant.SourceInfo
59
+ ] = video_info
60
+ self.presentation_info: Optional[
61
+ GroupCallParticipant.SourceInfo
62
+ ] = presentation_info
@@ -13,16 +13,15 @@ class PyObject:
13
13
  return repr(obj)
14
14
  if isinstance(obj, Enum):
15
15
  return repr(obj)
16
- if hasattr(obj, '__dict__'):
17
- return {
18
- '_': obj.__class__.__name__,
19
- **{
20
- attr: vars(obj)[attr]
21
- for attr in vars(obj)
22
- if not attr.startswith('_')
23
- },
24
- }
25
- return {}
16
+ return {
17
+ '_': obj.__class__.__name__,
18
+ **{
19
+ attr: getattr(obj, attr)
20
+ for attr in dir(obj)
21
+ if not attr.startswith('_') and
22
+ not callable(getattr(obj, attr)) and not attr == 'default'
23
+ },
24
+ }
26
25
 
27
26
  def __str__(self) -> str:
28
27
  return dumps(
@@ -1,4 +1,4 @@
1
- from ntgcalls import InputMode
1
+ from ntgcalls import MediaSource
2
2
 
3
3
  from ...statictypes import statictypes
4
4
  from ..py_object import PyObject
@@ -9,10 +9,10 @@ class AudioStream(PyObject):
9
9
  @statictypes
10
10
  def __init__(
11
11
  self,
12
- input_mode: InputMode,
12
+ media_source: MediaSource,
13
13
  path: str,
14
14
  parameters: AudioParameters = AudioParameters(),
15
15
  ):
16
- self.input_mode: InputMode = input_mode
16
+ self.media_source: MediaSource = media_source
17
17
  self.path: str = path
18
18
  self.parameters: AudioParameters = parameters
@@ -10,8 +10,12 @@ class Stream(PyObject):
10
10
  @statictypes
11
11
  def __init__(
12
12
  self,
13
- stream_audio: Optional[AudioStream] = None,
14
- stream_video: Optional[VideoStream] = None,
13
+ microphone: Optional[AudioStream] = None,
14
+ speaker: Optional[AudioStream] = None,
15
+ camera: Optional[VideoStream] = None,
16
+ screen: Optional[VideoStream] = None,
15
17
  ):
16
- self.stream_audio: Optional[AudioStream] = stream_audio
17
- self.stream_video: Optional[VideoStream] = stream_video
18
+ self.microphone: Optional[AudioStream] = microphone
19
+ self.speaker: Optional[AudioStream] = speaker
20
+ self.camera: Optional[VideoStream] = camera
21
+ self.screen: Optional[VideoStream] = screen
@@ -1,17 +1,18 @@
1
- from ntgcalls import InputMode
1
+ from ntgcalls import MediaSource
2
2
 
3
3
  from ...statictypes import statictypes
4
+ from ..py_object import PyObject
4
5
  from .video_parameters import VideoParameters
5
6
 
6
7
 
7
- class VideoStream:
8
+ class VideoStream(PyObject):
8
9
  @statictypes
9
10
  def __init__(
10
11
  self,
11
- input_mode: InputMode,
12
+ media_source: MediaSource,
12
13
  path: str,
13
14
  parameters: VideoParameters = VideoParameters(),
14
15
  ):
15
- self.input_mode: InputMode = input_mode
16
+ self.media_source: MediaSource = media_source
16
17
  self.path: str = path
17
18
  self.parameters: VideoParameters = parameters
@@ -1,13 +1,23 @@
1
1
  from .audio_quality import AudioQuality
2
+ from .device import Device
3
+ from .direction import Direction
4
+ from .external_media import ExternalMedia
5
+ from .frame import Frame
2
6
  from .media_stream import MediaStream
3
- from .stream_audio_ended import StreamAudioEnded
4
- from .stream_video_ended import StreamVideoEnded
7
+ from .record_stream import RecordStream
8
+ from .stream_ended import StreamEnded
9
+ from .stream_frames import StreamFrames
5
10
  from .video_quality import VideoQuality
6
11
 
7
12
  __all__ = (
8
13
  'AudioQuality',
14
+ 'Device',
15
+ 'Direction',
16
+ 'ExternalMedia',
17
+ 'Frame',
9
18
  'MediaStream',
10
- 'StreamAudioEnded',
11
- 'StreamVideoEnded',
19
+ 'RecordStream',
20
+ 'StreamEnded',
21
+ 'StreamFrames',
12
22
  'VideoQuality',
13
23
  )
@@ -0,0 +1,36 @@
1
+ from enum import auto
2
+
3
+ from ntgcalls import StreamDevice
4
+
5
+ from ..flag import Flag
6
+
7
+
8
+ class Device(Flag):
9
+ MICROPHONE = auto()
10
+ SPEAKER = auto()
11
+ CAMERA = auto()
12
+ SCREEN = auto()
13
+
14
+ @staticmethod
15
+ def from_raw(device: StreamDevice):
16
+ if device == StreamDevice.MICROPHONE:
17
+ return Device.MICROPHONE
18
+ if device == StreamDevice.SPEAKER:
19
+ return Device.SPEAKER
20
+ if device == StreamDevice.CAMERA:
21
+ return Device.CAMERA
22
+ if device == StreamDevice.SCREEN:
23
+ return Device.SCREEN
24
+ return None
25
+
26
+ @staticmethod
27
+ def to_raw(device: 'Device'):
28
+ if device == Device.MICROPHONE:
29
+ return StreamDevice.MICROPHONE
30
+ if device == Device.SPEAKER:
31
+ return StreamDevice.SPEAKER
32
+ if device == Device.CAMERA:
33
+ return StreamDevice.CAMERA
34
+ if device == Device.SCREEN:
35
+ return StreamDevice.SCREEN
36
+ return None
@@ -0,0 +1,25 @@
1
+ from enum import auto
2
+
3
+ from ntgcalls import StreamMode
4
+
5
+ from ..flag import Flag
6
+
7
+
8
+ class Direction(Flag):
9
+ OUTGOING = auto()
10
+ INCOMING = auto()
11
+
12
+ @staticmethod
13
+ def from_raw(direction: StreamMode):
14
+ if direction == StreamMode.CAPTURE:
15
+ return Direction.OUTGOING
16
+ if direction == StreamMode.PLAYBACK:
17
+ return Direction.INCOMING
18
+ return None
19
+
20
+ def to_raw(self):
21
+ if self is Direction.OUTGOING:
22
+ return StreamMode.CAPTURE
23
+ if self is Direction.INCOMING:
24
+ return StreamMode.PLAYBACK
25
+ return None
@@ -0,0 +1,8 @@
1
+ from enum import auto
2
+
3
+ from ..flag import Flag
4
+
5
+
6
+ class ExternalMedia(Flag):
7
+ AUDIO = auto()
8
+ VIDEO = auto()
@@ -0,0 +1,26 @@
1
+ from ...types.update import PyObject
2
+
3
+
4
+ class Frame(PyObject):
5
+ class Info(PyObject):
6
+ def __init__(
7
+ self,
8
+ capture_time: int = 0,
9
+ width: int = 0,
10
+ height: int = 0,
11
+ rotation: int = 0,
12
+ ):
13
+ self.capture_time = capture_time
14
+ self.width = width
15
+ self.height = height
16
+ self.rotation = rotation
17
+
18
+ def __init__(
19
+ self,
20
+ ssrc: int,
21
+ frame: bytes,
22
+ info: Info,
23
+ ):
24
+ self.ssrc = ssrc
25
+ self.frame = frame
26
+ self.info = info
@@ -1,10 +1,11 @@
1
+ import logging
1
2
  from enum import auto
2
3
  from pathlib import Path
3
4
  from typing import Dict
4
5
  from typing import Optional
5
6
  from typing import Union
6
7
 
7
- from ntgcalls import InputMode
8
+ from ntgcalls import MediaSource
8
9
 
9
10
  from ...exceptions import ImageSourceFound
10
11
  from ...exceptions import LiveStreamFound
@@ -12,8 +13,8 @@ from ...exceptions import NoAudioSourceFound
12
13
  from ...exceptions import NoVideoSourceFound
13
14
  from ...ffmpeg import build_command
14
15
  from ...ffmpeg import check_stream
15
- from ...media_devices import DeviceInfo
16
- from ...media_devices import ScreenInfo
16
+ from ...media_devices.input_device import InputDevice
17
+ from ...media_devices.screen_device import ScreenDevice
17
18
  from ...statictypes import statictypes
18
19
  from ...ytdlp import YtDlp
19
20
  from ..flag import Flag
@@ -24,6 +25,9 @@ from ..raw.video_parameters import VideoParameters
24
25
  from ..raw.video_stream import VideoStream
25
26
  from ..stream.audio_quality import AudioQuality
26
27
  from ..stream.video_quality import VideoQuality
28
+ from .external_media import ExternalMedia
29
+
30
+ py_logger = logging.getLogger('pytgcalls')
27
31
 
28
32
 
29
33
  class MediaStream(Stream):
@@ -31,12 +35,11 @@ class MediaStream(Stream):
31
35
  AUTO_DETECT = auto()
32
36
  REQUIRED = auto()
33
37
  IGNORE = auto()
34
- NO_LATENCY = auto()
35
38
 
36
39
  @statictypes
37
40
  def __init__(
38
41
  self,
39
- media_path: Union[str, Path, ScreenInfo, DeviceInfo],
42
+ media_path: Union[str, Path, InputDevice, ExternalMedia],
40
43
  audio_parameters: Union[
41
44
  AudioParameters,
42
45
  AudioQuality,
@@ -44,8 +47,13 @@ class MediaStream(Stream):
44
47
  video_parameters: Union[
45
48
  VideoParameters,
46
49
  VideoQuality,
47
- ] = VideoQuality.SD_480p,
48
- audio_path: Optional[Union[str, Path, DeviceInfo]] = None,
50
+ ] = VideoQuality.HD_720p,
51
+ audio_path: Optional[
52
+ Union[
53
+ str, Path,
54
+ InputDevice, ExternalMedia,
55
+ ]
56
+ ] = None,
49
57
  audio_flags: Optional[Flags] = Flags.AUTO_DETECT,
50
58
  video_flags: Optional[Flags] = Flags.AUTO_DETECT,
51
59
  headers: Optional[Dict[str, str]] = None,
@@ -69,35 +77,74 @@ class MediaStream(Stream):
69
77
 
70
78
  self._media_path: Optional[str] = None
71
79
  self._audio_path: Optional[str] = None
80
+ self._is_media_device: bool = False
81
+ self._is_audio_device: bool = False
82
+ self._is_audio_external: bool = False
83
+ self._is_video_external: bool = False
72
84
  if isinstance(media_path, str):
73
85
  self._media_path = media_path
74
86
  elif isinstance(media_path, Path):
75
87
  self._media_path = str(media_path)
76
- elif isinstance(media_path, DeviceInfo):
77
- self._media_path = media_path.build_ffmpeg_command()
78
- elif isinstance(media_path, ScreenInfo):
79
- self._media_path = media_path.build_ffmpeg_command(
80
- self._video_parameters.frame_rate,
81
- )
88
+ elif isinstance(media_path, ExternalMedia):
89
+ if media_path & ExternalMedia.AUDIO:
90
+ self._is_audio_external = True
91
+ if media_path & ExternalMedia.VIDEO:
92
+ self._is_video_external = True
93
+ elif isinstance(media_path, (InputDevice, ScreenDevice)):
94
+ if media_path.is_video:
95
+ self._media_path = media_path.metadata
96
+ self._is_media_device = True
97
+ else:
98
+ self._audio_path = media_path.metadata
99
+ self._is_audio_device = True
82
100
 
83
101
  if isinstance(audio_path, str):
84
102
  self._audio_path = audio_path
85
103
  elif isinstance(audio_path, Path):
86
104
  self._audio_path = str(audio_path)
87
- elif isinstance(audio_path, DeviceInfo):
88
- self._audio_path = audio_path.build_ffmpeg_command()
105
+ elif isinstance(audio_path, ExternalMedia):
106
+ self._audio_path = ''
107
+ if audio_path == ExternalMedia.AUDIO:
108
+ if self._is_audio_external:
109
+ py_logger.warning(
110
+ 'Audio path is already an audio source, '
111
+ 'ignoring audio path',
112
+ )
113
+ self._is_audio_external = True
114
+ else:
115
+ raise ValueError('Audio path must be an audio source')
116
+ elif isinstance(audio_path, (InputDevice, ScreenDevice)):
117
+ if audio_path.is_video:
118
+ raise ValueError('Audio path must be an audio device')
119
+ self._audio_path = audio_path.metadata
120
+ self._is_audio_device = True
89
121
 
90
122
  self._audio_flags = self._filter_flags(audio_flags)
91
123
  self._video_flags = self._filter_flags(video_flags)
92
124
  self._ffmpeg_parameters = ffmpeg_parameters
93
125
  self._ytdlp_parameters = ytdlp_parameters
94
126
  self._headers = headers
95
-
96
127
  super().__init__(
97
- stream_audio=None
98
- if self._audio_flags & MediaStream.Flags.IGNORE else
128
+ microphone=None
129
+ if (
130
+ self._audio_flags & MediaStream.Flags.IGNORE or
131
+ self._media_path is None and
132
+ self._audio_path is None
133
+ ) and not self._is_audio_external else
134
+ AudioStream(
135
+ MediaSource.DEVICE,
136
+ self._audio_path,
137
+ self._audio_parameters,
138
+ )
139
+ if self._is_audio_device else
99
140
  AudioStream(
100
- self._flags(self._audio_flags),
141
+ MediaSource.EXTERNAL,
142
+ '',
143
+ self._audio_parameters,
144
+ )
145
+ if self._is_audio_external else
146
+ AudioStream(
147
+ MediaSource.SHELL,
101
148
  ' '.join(
102
149
  build_command(
103
150
  'ffmpeg',
@@ -111,10 +158,26 @@ class MediaStream(Stream):
111
158
  ),
112
159
  self._audio_parameters,
113
160
  ),
114
- stream_video=None
115
- if self._video_flags & MediaStream.Flags.IGNORE else
161
+ camera=None
162
+ if (
163
+ self._video_flags & MediaStream.Flags.IGNORE or
164
+ self._media_path is None
165
+ ) and not self._is_video_external else
166
+ VideoStream(
167
+ MediaSource.DESKTOP if isinstance(media_path, ScreenDevice)
168
+ else MediaSource.DEVICE,
169
+ self._media_path,
170
+ self._video_parameters,
171
+ )
172
+ if self._is_media_device else
116
173
  VideoStream(
117
- self._flags(self._video_flags),
174
+ MediaSource.EXTERNAL,
175
+ '',
176
+ self._video_parameters,
177
+ )
178
+ if self._is_video_external else
179
+ VideoStream(
180
+ MediaSource.SHELL,
118
181
  ' '.join(
119
182
  build_command(
120
183
  'ffmpeg',
@@ -131,92 +194,107 @@ class MediaStream(Stream):
131
194
  )
132
195
 
133
196
  async def check_stream(self):
134
- if not self._video_flags & MediaStream.Flags.IGNORE:
135
- if YtDlp.is_valid(self._media_path):
136
- links = await YtDlp.extract(
137
- self._media_path,
138
- self._video_parameters,
139
- self._ytdlp_parameters,
140
- )
141
- self._media_path = links[0]
142
- if not self._audio_path:
143
- self._audio_path = links[1]
144
- try:
145
- image_commands = []
146
- live_stream = False
147
- try:
148
- await check_stream(
149
- self._ffmpeg_parameters,
197
+ if not self._video_flags & MediaStream.Flags.IGNORE and \
198
+ not self._is_video_external:
199
+ if self._is_media_device:
200
+ if not self._media_path:
201
+ self.camera = None
202
+ elif self._media_path:
203
+ if YtDlp.is_valid(self._media_path):
204
+ links = await YtDlp.extract(
150
205
  self._media_path,
151
206
  self._video_parameters,
152
- [],
153
- self._headers,
207
+ self._ytdlp_parameters,
154
208
  )
155
- except ImageSourceFound:
156
- image_commands = [
157
- '-loop',
158
- '1',
159
- '-framerate',
160
- '1',
161
- ]
162
- except LiveStreamFound:
163
- live_stream = True
164
- self.stream_video.path = ' '.join(
165
- build_command(
166
- 'ffmpeg',
167
- self._ffmpeg_parameters,
168
- self._media_path,
169
- self._video_parameters,
170
- image_commands,
171
- self._headers,
172
- live_stream,
173
- ),
174
- )
175
- except NoVideoSourceFound as e:
176
- if self._video_flags & MediaStream.Flags.REQUIRED:
177
- raise e
178
- self.stream_video = None
209
+ self._media_path = links[0]
210
+ if not self._audio_path:
211
+ self._audio_path = links[1]
212
+ try:
213
+ image_commands = []
214
+ live_stream = False
215
+ try:
216
+ await check_stream(
217
+ self._ffmpeg_parameters,
218
+ self._media_path,
219
+ self._video_parameters,
220
+ [],
221
+ self._headers,
222
+ )
223
+ except ImageSourceFound:
224
+ image_commands = [
225
+ '-loop',
226
+ '1',
227
+ '-framerate',
228
+ '1',
229
+ ]
230
+ except LiveStreamFound:
231
+ live_stream = True
232
+ self.camera.path = ' '.join(
233
+ build_command(
234
+ 'ffmpeg',
235
+ self._ffmpeg_parameters,
236
+ self._media_path,
237
+ self._video_parameters,
238
+ image_commands,
239
+ self._headers,
240
+ live_stream,
241
+ ),
242
+ )
243
+ except NoVideoSourceFound as e:
244
+ if self._video_flags & MediaStream.Flags.REQUIRED:
245
+ raise e
246
+ self.camera = None
247
+ else:
248
+ self.camera = None
179
249
 
180
- self._audio_path = self._audio_path \
181
- if self._audio_path else self._media_path
250
+ if not self._is_media_device:
251
+ self._audio_path = self._audio_path \
252
+ if self._audio_path else self._media_path
182
253
 
183
- if not self._audio_flags & MediaStream.Flags.IGNORE:
184
- if YtDlp.is_valid(self._audio_path):
185
- self._audio_path = (
186
- await YtDlp.extract(
187
- self._audio_path,
188
- self._video_parameters,
189
- self._ytdlp_parameters,
190
- )
191
- )[1]
254
+ if not self._audio_flags & MediaStream.Flags.IGNORE and \
255
+ not self._is_audio_external:
256
+ if self._is_audio_device:
257
+ if not self._audio_path:
258
+ self.microphone = None
259
+ elif self._audio_path:
260
+ if YtDlp.is_valid(self._audio_path):
261
+ self._audio_path = (
262
+ await YtDlp.extract(
263
+ self._audio_path,
264
+ self._video_parameters,
265
+ self._ytdlp_parameters,
266
+ )
267
+ )[1]
192
268
 
193
- try:
194
- live_stream = False
195
269
  try:
196
- await check_stream(
197
- self._ffmpeg_parameters,
198
- self._audio_path,
199
- self._audio_parameters,
200
- [],
201
- self._headers,
270
+ live_stream = False
271
+ try:
272
+ await check_stream(
273
+ self._ffmpeg_parameters,
274
+ self._audio_path,
275
+ self._audio_parameters,
276
+ [],
277
+ self._headers,
278
+ )
279
+ except LiveStreamFound:
280
+ live_stream = True
281
+ self.microphone.path = ' '.join(
282
+ build_command(
283
+ 'ffmpeg',
284
+ self._ffmpeg_parameters,
285
+ self._audio_path,
286
+ self._audio_parameters,
287
+ [],
288
+ self._headers,
289
+ live_stream,
290
+ ),
202
291
  )
203
- except LiveStreamFound:
204
- live_stream = True
205
- self.stream_audio.path = ' '.join(
206
- build_command(
207
- 'ffmpeg',
208
- self._ffmpeg_parameters,
209
- self._audio_path,
210
- self._audio_parameters,
211
- [],
212
- self._headers,
213
- live_stream,
214
- ),
215
- )
216
- except NoAudioSourceFound as e:
217
- if self._audio_flags & MediaStream.Flags.REQUIRED:
218
- raise e
219
- self.stream_audio = None
292
+ except NoAudioSourceFound as e:
293
+ if self._audio_flags & MediaStream.Flags.REQUIRED:
294
+ raise e
295
+ self.microphone = None
296
+ else:
297
+ self.microphone = None
220
298
 
221
299
  @staticmethod
222
300
  def _filter_flags(flags: Optional[Flags]) -> Flags:
@@ -236,10 +314,3 @@ class MediaStream(Stream):
236
314
  key=lambda flag: flag.value,
237
315
  )
238
316
  return flags & ~combined_flags_value | potential_flag
239
-
240
- @staticmethod
241
- def _flags(flags: Flags) -> InputMode:
242
- new_flags = InputMode.SHELL
243
- if flags & MediaStream.Flags.NO_LATENCY:
244
- new_flags |= InputMode.NO_LATENCY
245
- return new_flags