py-tgcalls 2.0.6__py3-none-any.whl → 2.1.0.dev1__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.dev1.dist-info}/METADATA +6 -8
  2. {py_tgcalls-2.0.6.dist-info → py_tgcalls-2.1.0.dev1.dist-info}/RECORD +41 -35
  3. {py_tgcalls-2.0.6.dist-info → py_tgcalls-2.1.0.dev1.dist-info}/WHEEL +1 -1
  4. pytgcalls/__init__.py +2 -0
  5. pytgcalls/__version__.py +1 -1
  6. pytgcalls/filters.py +46 -7
  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 +3 -3
  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 +35 -29
  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.dev1.dist-info}/LICENSE +0 -0
  44. {py_tgcalls-2.0.6.dist-info → py_tgcalls-2.1.0.dev1.dist-info}/top_level.txt +0 -0
@@ -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 .video_parameters import VideoParameters
@@ -8,10 +8,10 @@ class VideoStream:
8
8
  @statictypes
9
9
  def __init__(
10
10
  self,
11
- input_mode: InputMode,
11
+ media_source: MediaSource,
12
12
  path: str,
13
13
  parameters: VideoParameters = VideoParameters(),
14
14
  ):
15
- self.input_mode: InputMode = input_mode
15
+ self.media_source: MediaSource = media_source
16
16
  self.path: str = path
17
17
  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,
@@ -73,19 +72,20 @@ class MediaStream(Stream):
73
72
  self._media_path = media_path
74
73
  elif isinstance(media_path, Path):
75
74
  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
- )
75
+ elif isinstance(media_path, InputDevice):
76
+ if media_path.is_video:
77
+ self._media_path = media_path.metadata
78
+ else:
79
+ self._audio_path = media_path.metadata
82
80
 
83
81
  if isinstance(audio_path, str):
84
82
  self._audio_path = audio_path
85
83
  elif isinstance(audio_path, Path):
86
84
  self._audio_path = str(audio_path)
87
- elif isinstance(audio_path, DeviceInfo):
88
- self._audio_path = audio_path.build_ffmpeg_command()
85
+ elif isinstance(audio_path, InputDevice):
86
+ if audio_path.is_video:
87
+ raise ValueError('Audio path must be an audio device')
88
+ self._audio_path = audio_path.metadata
89
89
 
90
90
  self._audio_flags = self._filter_flags(audio_flags)
91
91
  self._video_flags = self._filter_flags(video_flags)
@@ -94,10 +94,16 @@ class MediaStream(Stream):
94
94
  self._headers = headers
95
95
 
96
96
  super().__init__(
97
- stream_audio=None
97
+ microphone=None
98
98
  if self._audio_flags & MediaStream.Flags.IGNORE else
99
99
  AudioStream(
100
- self._flags(self._audio_flags),
100
+ MediaSource.DEVICE,
101
+ self._audio_path,
102
+ self._audio_parameters,
103
+ )
104
+ if isinstance(audio_path, InputDevice) else
105
+ AudioStream(
106
+ MediaSource.SHELL,
101
107
  ' '.join(
102
108
  build_command(
103
109
  'ffmpeg',
@@ -111,10 +117,17 @@ class MediaStream(Stream):
111
117
  ),
112
118
  self._audio_parameters,
113
119
  ),
114
- stream_video=None
120
+ camera=None
115
121
  if self._video_flags & MediaStream.Flags.IGNORE else
116
122
  VideoStream(
117
- self._flags(self._video_flags),
123
+ MediaSource.DESKTOP if isinstance(audio_path, ScreenDevice)
124
+ else MediaSource.DEVICE,
125
+ self._media_path,
126
+ self._video_parameters,
127
+ )
128
+ if isinstance(audio_path, InputDevice) else
129
+ VideoStream(
130
+ MediaSource.SHELL,
118
131
  ' '.join(
119
132
  build_command(
120
133
  'ffmpeg',
@@ -161,7 +174,7 @@ class MediaStream(Stream):
161
174
  ]
162
175
  except LiveStreamFound:
163
176
  live_stream = True
164
- self.stream_video.path = ' '.join(
177
+ self.camera.path = ' '.join(
165
178
  build_command(
166
179
  'ffmpeg',
167
180
  self._ffmpeg_parameters,
@@ -175,7 +188,7 @@ class MediaStream(Stream):
175
188
  except NoVideoSourceFound as e:
176
189
  if self._video_flags & MediaStream.Flags.REQUIRED:
177
190
  raise e
178
- self.stream_video = None
191
+ self.camera = None
179
192
 
180
193
  self._audio_path = self._audio_path \
181
194
  if self._audio_path else self._media_path
@@ -202,7 +215,7 @@ class MediaStream(Stream):
202
215
  )
203
216
  except LiveStreamFound:
204
217
  live_stream = True
205
- self.stream_audio.path = ' '.join(
218
+ self.microphone.path = ' '.join(
206
219
  build_command(
207
220
  'ffmpeg',
208
221
  self._ffmpeg_parameters,
@@ -216,7 +229,7 @@ class MediaStream(Stream):
216
229
  except NoAudioSourceFound as e:
217
230
  if self._audio_flags & MediaStream.Flags.REQUIRED:
218
231
  raise e
219
- self.stream_audio = None
232
+ self.microphone = None
220
233
 
221
234
  @staticmethod
222
235
  def _filter_flags(flags: Optional[Flags]) -> Flags:
@@ -236,10 +249,3 @@ class MediaStream(Stream):
236
249
  key=lambda flag: flag.value,
237
250
  )
238
251
  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)