py-tgcalls 2.0.6__py3-none-any.whl → 2.1.0.dev2__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 (44) hide show
  1. {py_tgcalls-2.0.6.dist-info → py_tgcalls-2.1.0.dev2.dist-info}/METADATA +6 -8
  2. {py_tgcalls-2.0.6.dist-info → py_tgcalls-2.1.0.dev2.dist-info}/RECORD +41 -35
  3. {py_tgcalls-2.0.6.dist-info → py_tgcalls-2.1.0.dev2.dist-info}/WHEEL +1 -1
  4. pytgcalls/__init__.py +2 -0
  5. pytgcalls/__version__.py +1 -1
  6. pytgcalls/filters.py +46 -6
  7. pytgcalls/media_devices/__init__.py +6 -2
  8. pytgcalls/media_devices/device_info.py +8 -15
  9. pytgcalls/media_devices/input_device.py +11 -0
  10. pytgcalls/media_devices/media_devices.py +41 -92
  11. pytgcalls/media_devices/screen_device.py +10 -0
  12. pytgcalls/media_devices/speaker_device.py +10 -0
  13. pytgcalls/methods/stream/__init__.py +4 -2
  14. pytgcalls/methods/stream/play.py +73 -11
  15. pytgcalls/methods/stream/record.py +41 -0
  16. pytgcalls/methods/stream/{played_time.py → time.py} +5 -3
  17. pytgcalls/methods/utilities/call_holder.py +5 -2
  18. pytgcalls/methods/utilities/start.py +118 -13
  19. pytgcalls/methods/utilities/stream_params.py +63 -22
  20. pytgcalls/mtproto/bridged_client.py +34 -2
  21. pytgcalls/mtproto/client_cache.py +46 -16
  22. pytgcalls/mtproto/hydrogram_client.py +53 -11
  23. pytgcalls/mtproto/mtproto_client.py +30 -4
  24. pytgcalls/mtproto/pyrogram_client.py +53 -11
  25. pytgcalls/mtproto/telethon_client.py +61 -19
  26. pytgcalls/scaffold.py +6 -0
  27. pytgcalls/types/__init__.py +10 -4
  28. pytgcalls/types/calls/call.py +5 -3
  29. pytgcalls/types/chats/group_call_participant.py +21 -0
  30. pytgcalls/types/raw/audio_stream.py +3 -3
  31. pytgcalls/types/raw/stream.py +8 -4
  32. pytgcalls/types/raw/video_stream.py +5 -4
  33. pytgcalls/types/stream/__init__.py +10 -4
  34. pytgcalls/types/stream/device.py +24 -0
  35. pytgcalls/types/stream/direction.py +18 -0
  36. pytgcalls/types/stream/media_stream.py +126 -102
  37. pytgcalls/types/stream/record_stream.py +93 -0
  38. pytgcalls/types/stream/stream_ended.py +32 -0
  39. pytgcalls/types/stream/stream_frame.py +35 -0
  40. pytgcalls/media_devices/screen_info.py +0 -45
  41. pytgcalls/types/stream/stream_audio_ended.py +0 -9
  42. pytgcalls/types/stream/stream_video_ended.py +0 -9
  43. {py_tgcalls-2.0.6.dist-info → py_tgcalls-2.1.0.dev2.dist-info}/LICENSE +0 -0
  44. {py_tgcalls-2.0.6.dist-info → py_tgcalls-2.1.0.dev2.dist-info}/top_level.txt +0 -0
@@ -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,19 @@
1
1
  from .audio_quality import AudioQuality
2
+ from .device import Device
3
+ from .direction import Direction
2
4
  from .media_stream import MediaStream
3
- from .stream_audio_ended import StreamAudioEnded
4
- from .stream_video_ended import StreamVideoEnded
5
+ from .record_stream import RecordStream
6
+ from .stream_ended import StreamEnded
7
+ from .stream_frame import StreamFrame
5
8
  from .video_quality import VideoQuality
6
9
 
7
10
  __all__ = (
8
11
  'AudioQuality',
12
+ 'Device',
13
+ 'Direction',
9
14
  'MediaStream',
10
- 'StreamAudioEnded',
11
- 'StreamVideoEnded',
15
+ 'RecordStream',
16
+ 'StreamEnded',
17
+ 'StreamFrame',
12
18
  'VideoQuality',
13
19
  )
@@ -0,0 +1,24 @@
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
@@ -0,0 +1,18 @@
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.OUTGOING:
15
+ return Direction.OUTGOING
16
+ if direction == StreamMode.INCOMING:
17
+ return Direction.INCOMING
18
+ return None
@@ -4,7 +4,7 @@ from typing import Dict
4
4
  from typing import Optional
5
5
  from typing import Union
6
6
 
7
- from ntgcalls import InputMode
7
+ from ntgcalls import MediaSource
8
8
 
9
9
  from ...exceptions import ImageSourceFound
10
10
  from ...exceptions import LiveStreamFound
@@ -12,8 +12,8 @@ from ...exceptions import NoAudioSourceFound
12
12
  from ...exceptions import NoVideoSourceFound
13
13
  from ...ffmpeg import build_command
14
14
  from ...ffmpeg import check_stream
15
- from ...media_devices import DeviceInfo
16
- from ...media_devices import ScreenInfo
15
+ from ...media_devices.input_device import InputDevice
16
+ from ...media_devices.screen_device import ScreenDevice
17
17
  from ...statictypes import statictypes
18
18
  from ...ytdlp import YtDlp
19
19
  from ..flag import Flag
@@ -31,12 +31,11 @@ class MediaStream(Stream):
31
31
  AUTO_DETECT = auto()
32
32
  REQUIRED = auto()
33
33
  IGNORE = auto()
34
- NO_LATENCY = auto()
35
34
 
36
35
  @statictypes
37
36
  def __init__(
38
37
  self,
39
- media_path: Union[str, Path, ScreenInfo, DeviceInfo],
38
+ media_path: Union[str, Path, InputDevice],
40
39
  audio_parameters: Union[
41
40
  AudioParameters,
42
41
  AudioQuality,
@@ -45,7 +44,7 @@ class MediaStream(Stream):
45
44
  VideoParameters,
46
45
  VideoQuality,
47
46
  ] = VideoQuality.SD_480p,
48
- audio_path: Optional[Union[str, Path, DeviceInfo]] = None,
47
+ audio_path: Optional[Union[str, Path, InputDevice]] = None,
49
48
  audio_flags: Optional[Flags] = Flags.AUTO_DETECT,
50
49
  video_flags: Optional[Flags] = Flags.AUTO_DETECT,
51
50
  headers: Optional[Dict[str, str]] = None,
@@ -69,35 +68,47 @@ class MediaStream(Stream):
69
68
 
70
69
  self._media_path: Optional[str] = None
71
70
  self._audio_path: Optional[str] = None
71
+ self._is_media_device: bool = False
72
+ self._is_audio_device: bool = False
72
73
  if isinstance(media_path, str):
73
74
  self._media_path = media_path
74
75
  elif isinstance(media_path, Path):
75
76
  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
- )
77
+ elif isinstance(media_path, (InputDevice, ScreenDevice)):
78
+ print('MediaStream', media_path.is_video)
79
+ if media_path.is_video:
80
+ self._media_path = media_path.metadata
81
+ self._is_media_device = True
82
+ else:
83
+ self._audio_path = media_path.metadata
84
+ self._is_audio_device = True
82
85
 
83
86
  if isinstance(audio_path, str):
84
87
  self._audio_path = audio_path
85
88
  elif isinstance(audio_path, Path):
86
89
  self._audio_path = str(audio_path)
87
- elif isinstance(audio_path, DeviceInfo):
88
- self._audio_path = audio_path.build_ffmpeg_command()
90
+ elif isinstance(audio_path, (InputDevice, ScreenDevice)):
91
+ if audio_path.is_video:
92
+ raise ValueError('Audio path must be an audio device')
93
+ self._audio_path = audio_path.metadata
94
+ self._is_audio_device = True
89
95
 
90
96
  self._audio_flags = self._filter_flags(audio_flags)
91
97
  self._video_flags = self._filter_flags(video_flags)
92
98
  self._ffmpeg_parameters = ffmpeg_parameters
93
99
  self._ytdlp_parameters = ytdlp_parameters
94
100
  self._headers = headers
95
-
96
101
  super().__init__(
97
- stream_audio=None
102
+ microphone=None
98
103
  if self._audio_flags & MediaStream.Flags.IGNORE else
99
104
  AudioStream(
100
- self._flags(self._audio_flags),
105
+ MediaSource.DEVICE,
106
+ self._audio_path,
107
+ self._audio_parameters,
108
+ )
109
+ if self._is_audio_device else
110
+ AudioStream(
111
+ MediaSource.SHELL,
101
112
  ' '.join(
102
113
  build_command(
103
114
  'ffmpeg',
@@ -111,10 +122,17 @@ class MediaStream(Stream):
111
122
  ),
112
123
  self._audio_parameters,
113
124
  ),
114
- stream_video=None
125
+ camera=None
115
126
  if self._video_flags & MediaStream.Flags.IGNORE else
116
127
  VideoStream(
117
- self._flags(self._video_flags),
128
+ MediaSource.DESKTOP if isinstance(media_path, ScreenDevice)
129
+ else MediaSource.DEVICE,
130
+ self._media_path,
131
+ self._video_parameters,
132
+ )
133
+ if self._is_media_device else
134
+ VideoStream(
135
+ MediaSource.SHELL,
118
136
  ' '.join(
119
137
  build_command(
120
138
  'ffmpeg',
@@ -132,91 +150,104 @@ class MediaStream(Stream):
132
150
 
133
151
  async def check_stream(self):
134
152
  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,
153
+ if self._is_media_device:
154
+ if not self._media_path:
155
+ self.camera = None
156
+ elif self._media_path:
157
+ if YtDlp.is_valid(self._media_path):
158
+ links = await YtDlp.extract(
150
159
  self._media_path,
151
160
  self._video_parameters,
152
- [],
153
- self._headers,
161
+ self._ytdlp_parameters,
154
162
  )
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
163
+ self._media_path = links[0]
164
+ if not self._audio_path:
165
+ self._audio_path = links[1]
166
+ try:
167
+ image_commands = []
168
+ live_stream = False
169
+ try:
170
+ await check_stream(
171
+ self._ffmpeg_parameters,
172
+ self._media_path,
173
+ self._video_parameters,
174
+ [],
175
+ self._headers,
176
+ )
177
+ except ImageSourceFound:
178
+ image_commands = [
179
+ '-loop',
180
+ '1',
181
+ '-framerate',
182
+ '1',
183
+ ]
184
+ except LiveStreamFound:
185
+ live_stream = True
186
+ self.camera.path = ' '.join(
187
+ build_command(
188
+ 'ffmpeg',
189
+ self._ffmpeg_parameters,
190
+ self._media_path,
191
+ self._video_parameters,
192
+ image_commands,
193
+ self._headers,
194
+ live_stream,
195
+ ),
196
+ )
197
+ except NoVideoSourceFound as e:
198
+ if self._video_flags & MediaStream.Flags.REQUIRED:
199
+ raise e
200
+ self.camera = None
201
+ else:
202
+ self.camera = None
179
203
 
180
- self._audio_path = self._audio_path \
181
- if self._audio_path else self._media_path
204
+ if not self._is_media_device:
205
+ self._audio_path = self._audio_path \
206
+ if self._audio_path else self._media_path
182
207
 
183
208
  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]
209
+ if self._is_audio_device:
210
+ if not self._audio_path:
211
+ self.microphone = None
212
+ elif self._audio_path:
213
+ if YtDlp.is_valid(self._audio_path):
214
+ self._audio_path = (
215
+ await YtDlp.extract(
216
+ self._audio_path,
217
+ self._video_parameters,
218
+ self._ytdlp_parameters,
219
+ )
220
+ )[1]
192
221
 
193
- try:
194
- live_stream = False
195
222
  try:
196
- await check_stream(
197
- self._ffmpeg_parameters,
198
- self._audio_path,
199
- self._audio_parameters,
200
- [],
201
- self._headers,
223
+ live_stream = False
224
+ try:
225
+ await check_stream(
226
+ self._ffmpeg_parameters,
227
+ self._audio_path,
228
+ self._audio_parameters,
229
+ [],
230
+ self._headers,
231
+ )
232
+ except LiveStreamFound:
233
+ live_stream = True
234
+ self.microphone.path = ' '.join(
235
+ build_command(
236
+ 'ffmpeg',
237
+ self._ffmpeg_parameters,
238
+ self._audio_path,
239
+ self._audio_parameters,
240
+ [],
241
+ self._headers,
242
+ live_stream,
243
+ ),
202
244
  )
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
245
+ except NoAudioSourceFound as e:
246
+ if self._audio_flags & MediaStream.Flags.REQUIRED:
247
+ raise e
248
+ self.microphone = None
249
+ else:
250
+ self.microphone = None
220
251
 
221
252
  @staticmethod
222
253
  def _filter_flags(flags: Optional[Flags]) -> Flags:
@@ -236,10 +267,3 @@ class MediaStream(Stream):
236
267
  key=lambda flag: flag.value,
237
268
  )
238
269
  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
@@ -0,0 +1,93 @@
1
+ from typing import Union
2
+
3
+ from ntgcalls import MediaSource
4
+
5
+ from ...media_devices.speaker_device import SpeakerDevice
6
+ from ...statictypes import statictypes
7
+ from ..raw.audio_parameters import AudioParameters
8
+ from ..raw.audio_stream import AudioStream
9
+ from ..raw.stream import Stream
10
+ from ..raw.video_parameters import VideoParameters
11
+ from ..raw.video_stream import VideoStream
12
+ from ..stream.audio_quality import AudioQuality
13
+
14
+
15
+ class RecordStream(Stream):
16
+ @statictypes
17
+ def __init__(
18
+ self,
19
+ audio: Union[bool, str, SpeakerDevice] = False,
20
+ audio_parameters: Union[
21
+ AudioParameters,
22
+ AudioQuality,
23
+ ] = AudioQuality.HIGH,
24
+ camera: bool = False,
25
+ screen: bool = False,
26
+ ):
27
+ raw_audio_parameters = (
28
+ audio_parameters
29
+ if isinstance(audio_parameters, AudioParameters)
30
+ else AudioParameters(*audio_parameters.value)
31
+ if isinstance(audio_parameters, AudioQuality)
32
+ else ValueError('Invalid audio parameters')
33
+ )
34
+ super().__init__(
35
+ microphone=self._get_audio_stream(audio, raw_audio_parameters),
36
+ speaker=None,
37
+ camera=self._get_video_stream(camera),
38
+ screen=self._get_video_stream(screen),
39
+ )
40
+
41
+ @staticmethod
42
+ def _get_audio_stream(audio, raw_audio_parameters):
43
+ if isinstance(audio, bool) and audio:
44
+ return AudioStream(
45
+ media_source=MediaSource.EXTERNAL,
46
+ path='',
47
+ parameters=raw_audio_parameters,
48
+ )
49
+ if isinstance(audio, str):
50
+ is_lossless = raw_audio_parameters.bitrate > 48000
51
+ commands = [
52
+ 'ffmpeg',
53
+ '-loglevel',
54
+ 'quiet',
55
+ '-f',
56
+ 's16le',
57
+ '-ar',
58
+ str(raw_audio_parameters.bitrate),
59
+ '-ac',
60
+ str(raw_audio_parameters.channels),
61
+ '-i',
62
+ 'pipe:0',
63
+ '-codec:a',
64
+ 'flac' if is_lossless else 'libmp3lame',
65
+ audio,
66
+ ]
67
+ return AudioStream(
68
+ media_source=MediaSource.SHELL,
69
+ path=' '.join(commands),
70
+ parameters=raw_audio_parameters,
71
+ )
72
+ if isinstance(audio, SpeakerDevice):
73
+ return AudioStream(
74
+ media_source=MediaSource.DEVICE,
75
+ path=audio.metadata,
76
+ parameters=raw_audio_parameters,
77
+ )
78
+
79
+ @staticmethod
80
+ def _get_video_stream(enable):
81
+ return (
82
+ VideoStream(
83
+ media_source=MediaSource.EXTERNAL,
84
+ path='',
85
+ parameters=VideoParameters(
86
+ width=-1,
87
+ height=-1,
88
+ frame_rate=-1,
89
+ ),
90
+ )
91
+ if enable
92
+ else None
93
+ )
@@ -0,0 +1,32 @@
1
+ from enum import auto
2
+
3
+ from ntgcalls import StreamType
4
+
5
+ from ...types.update import Update
6
+ from ..flag import Flag
7
+ from .device import Device
8
+
9
+
10
+ class StreamEnded(Update):
11
+
12
+ class Type(Flag):
13
+ AUDIO = auto()
14
+ VIDEO = auto()
15
+
16
+ @staticmethod
17
+ def from_raw(kind: StreamType):
18
+ if kind == StreamType.AUDIO:
19
+ return StreamEnded.Type.AUDIO
20
+ if kind == StreamType.VIDEO:
21
+ return StreamEnded.Type.VIDEO
22
+ return None
23
+
24
+ def __init__(
25
+ self,
26
+ chat_id: int,
27
+ stream_type: Type,
28
+ device: Device,
29
+ ):
30
+ super().__init__(chat_id)
31
+ self.stream_type = stream_type
32
+ self.device = device
@@ -0,0 +1,35 @@
1
+ from ...types.update import Update
2
+ from .device import Device
3
+ from .direction import Direction
4
+
5
+
6
+ class StreamFrame(Update):
7
+
8
+ class Info:
9
+ def __init__(
10
+ self,
11
+ capture_time: int,
12
+ width: int,
13
+ height: int,
14
+ rotation: int,
15
+ ):
16
+ self.capture_time = capture_time
17
+ self.width = width
18
+ self.height = height
19
+ self.rotation = rotation
20
+
21
+ def __init__(
22
+ self,
23
+ chat_id: int,
24
+ ssrc: int,
25
+ direction: Direction,
26
+ device: Device,
27
+ frame: bytes,
28
+ info: Info,
29
+ ):
30
+ super().__init__(chat_id)
31
+ self.ssrc = ssrc
32
+ self.direction = direction
33
+ self.device = device
34
+ self.frame = frame
35
+ self.info = info
@@ -1,45 +0,0 @@
1
- from sys import platform
2
-
3
- from ..types.py_object import PyObject
4
-
5
-
6
- class ScreenInfo(PyObject):
7
- def __init__(
8
- self,
9
- x: int,
10
- y: int,
11
- width: int,
12
- height: int,
13
- is_primary: bool,
14
- title: str,
15
- ):
16
- self.offset_x = x
17
- self.offset_y = y
18
- self.width = width
19
- self.height = height
20
- self.is_primary = is_primary
21
- self.title = title
22
- self.ffmpeg_parameters = ['-f']
23
-
24
- def build_ffmpeg_command(self, frame_rate: int):
25
- if platform == 'win32':
26
- path = 'desktop'
27
-
28
- self.ffmpeg_parameters += [
29
- 'gdigrab',
30
- '-offset_x',
31
- str(self.offset_x),
32
- '-offset_y',
33
- str(self.offset_y),
34
- ]
35
- else:
36
- path = f':0.0+{self.offset_x},{self.offset_y}'
37
- self.ffmpeg_parameters += ['x11grab']
38
-
39
- self.ffmpeg_parameters += [
40
- '-video_size',
41
- f'{self.width}x{self.height}',
42
- '-framerate',
43
- str(frame_rate),
44
- ]
45
- return path
@@ -1,9 +0,0 @@
1
- from ...types.update import Update
2
-
3
-
4
- class StreamAudioEnded(Update):
5
- def __init__(
6
- self,
7
- chat_id: int,
8
- ):
9
- super().__init__(chat_id)
@@ -1,9 +0,0 @@
1
- from ...types.update import Update
2
-
3
-
4
- class StreamVideoEnded(Update):
5
- def __init__(
6
- self,
7
- chat_id: int,
8
- ):
9
- super().__init__(chat_id)