py-tgcalls 2.1.0.dev4__py3-none-any.whl → 2.1.0.dev5__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: py-tgcalls
3
- Version: 2.1.0.dev4
3
+ Version: 2.1.0.dev5
4
4
  Summary: Async client API for the Telegram Calls.
5
5
  Author-email: Laky-64 <iraci.matteo@gmail.com>
6
6
  License: GNU LESSER GENERAL PUBLIC LICENSE
@@ -1,8 +1,8 @@
1
1
  pytgcalls/__init__.py,sha256=qbfwN7rYwIdCegMOzdcbvwazeNjDzgmowgcqLFNqKIM,308
2
- pytgcalls/__version__.py,sha256=RShHDU6VmHiby-18I-reafFfkORbcfCW2afHTL4C6MY,27
2
+ pytgcalls/__version__.py,sha256=KQAc7KU-mJvztNT933s927lrNsCxny7RGtGEb4R-foc,27
3
3
  pytgcalls/environment.py,sha256=ctCHACvG6l8SdpPewSBhOvc70kbwpv18maC0TwLvZ08,1924
4
4
  pytgcalls/exceptions.py,sha256=0MmAktc53ajYAc7ThjD2tJ9PDyibUi0iHZMfUy2IoKs,4109
5
- pytgcalls/ffmpeg.py,sha256=uAqFDPwWoABW_WfubxeulpIh5vWpylxFARgYokn3ff8,8640
5
+ pytgcalls/ffmpeg.py,sha256=tm6DBxyNfPh3h3an-b2s9x1UyX-cvkCdov9prlXxVZY,8649
6
6
  pytgcalls/filters.py,sha256=dHRXgxhQoLaLsSAsYYaq4jCwVran9sBWBG1txKWIOjY,6039
7
7
  pytgcalls/mtproto_required.py,sha256=6B-31p5qH_6oekUgypV4nK3hqPS6Nr-pA8S81wjnbaY,630
8
8
  pytgcalls/mutex.py,sha256=Frjji5Ctzlk4AXEBuBLnDK-7HbtreoV6zuyKpFpMNI4,236
@@ -27,15 +27,16 @@ pytgcalls/methods/__init__.py,sha256=hk1blAT5u_Isemdrg0nqInLsdRzTTZnak5NdAfkBPAk
27
27
  pytgcalls/methods/calls/__init__.py,sha256=xg4DZZClEnxwaj-DAq3e8gSR-g-MiYBdUEBth64lSXA,214
28
28
  pytgcalls/methods/calls/change_volume_call.py,sha256=viA3yHVxPJ421yE1dfFTh-kNEFTxMlaVFxVjC-PeX-0,719
29
29
  pytgcalls/methods/calls/get_participants.py,sha256=gKUAzvha1RpvbtSI_n-GlBQkOONxzibCaCr4aA17Mag,567
30
- pytgcalls/methods/calls/leave_call.py,sha256=e3lYJKFD29Qg1qgolmLKUk9_X1uApOT1pGYF8ZgwMqQ,1369
30
+ pytgcalls/methods/calls/leave_call.py,sha256=dJg5JWmbTIKQJsCccvea4Ma-SaSKVZcRVRqkmsWOLx8,1376
31
31
  pytgcalls/methods/decorators/__init__.py,sha256=TCGaEVZnHjtOwv-3PNfaCVm0kyFhJApUPUNntt6MwyM,78
32
32
  pytgcalls/methods/decorators/on_update.py,sha256=ZTL4YcQk0N4Ru56a5WItUvkSN5SAqr6_RDZvXmZMIHs,316
33
- pytgcalls/methods/stream/__init__.py,sha256=EeB0SHvnrOMQELZMAX0ex8_lbdRcW_S4E7epsb5epkw,363
33
+ pytgcalls/methods/stream/__init__.py,sha256=dBx5cqvVyvhqykIuT_2rYCAvIzuPM1J4Hh4sZ083UXU,412
34
34
  pytgcalls/methods/stream/mute_stream.py,sha256=auo2aAazfEC90Ab6MzaiPdddiJ1w4fN_9HaORkAeOBY,570
35
35
  pytgcalls/methods/stream/pause_stream.py,sha256=z_AIWABrQMHmTwvlah_PrH9EjXbro8gKxZni4Km5ICg,573
36
- pytgcalls/methods/stream/play.py,sha256=w88pp2LeYd8qjejGJYwtzHRsAKAaEd-Pk48nFlzPX4E,10097
36
+ pytgcalls/methods/stream/play.py,sha256=1NiAIKBdSsiighc74EKHQv_Fl_ehWLYgGPq5x62s0sE,10189
37
37
  pytgcalls/methods/stream/record.py,sha256=toW1LtgUMiaw-KGe9DnWTRsGYLhoKlG0r8eJphMhGIA,1212
38
38
  pytgcalls/methods/stream/resume_stream.py,sha256=z_DgP4cDExjEqEeX_ZL--50MXQ9lrATK876SIwE71PQ,576
39
+ pytgcalls/methods/stream/send_frame.py,sha256=TAHXfKgBrX-nT9x-08GZNOeObqyhtgGYi7i4kuGHO6c,1080
39
40
  pytgcalls/methods/stream/time.py,sha256=5y9TMBf_d6YPLbMcGx3yMZQUZdo8zb5fQb9STsh7R3Y,656
40
41
  pytgcalls/methods/stream/unmute_stream.py,sha256=KUMhfMbhsPmZsmpF4cGWC1FVW7YwXha2MmQnqrBhM8s,576
41
42
  pytgcalls/methods/utilities/__init__.py,sha256=JcKwqNo6fFXXfuab94fNEraKF1P9fnSSgr0WQDRjF2w,339
@@ -47,16 +48,16 @@ pytgcalls/methods/utilities/idle.py,sha256=MDdzHTv1ws2yBhsvhBUnssGdghkZ2KwR0HUCP
47
48
  pytgcalls/methods/utilities/ping.py,sha256=hhIMSHk2BzMB-IKpwLdZFVrsEvGm2ftJwKLs1k4anh8,244
48
49
  pytgcalls/methods/utilities/resolve_chat_id.py,sha256=92x2LHbUlnJMm-kS3fXOYmzYpY2TZbqtQD2rw3eBXDY,382
49
50
  pytgcalls/methods/utilities/run.py,sha256=cnYQd2xB5Cr_WS0Q2cXJZPGiN6JOCULzj1r4xXVyrlg,152
50
- pytgcalls/methods/utilities/start.py,sha256=ObY79b_2qxDSAytQEYeHKK4k4JRD2_iyLCPyGyKpH-E,13340
51
- pytgcalls/methods/utilities/stream_params.py,sha256=g-DmwTY_aYTWhhZ4RDthORQXz_VrhK1JQKPFYmB64II,2992
51
+ pytgcalls/methods/utilities/start.py,sha256=pG2I3bSWDBn7qNTxwbSCvkwpkhWAYKlAqJ5TzapMgCc,13406
52
+ pytgcalls/methods/utilities/stream_params.py,sha256=V9bKF3w4XLsbzYNAuCXrcjrojioGHLlFeZ3Jz9H3z-0,3235
52
53
  pytgcalls/mtproto/__init__.py,sha256=X4zvzFG7km7qHyE0fdvA550WcOVO_xl_p__gvIfDGmw,130
53
- pytgcalls/mtproto/bridged_client.py,sha256=OlFCV7Xe2a9I0_AAWfrWJciAe1b7WtVocVjl1EsBMyY,5843
54
+ pytgcalls/mtproto/bridged_client.py,sha256=69d59-Vm_CSexd7maHIeGc9BUCiBLoa5YKZy_y-a540,5868
54
55
  pytgcalls/mtproto/client_cache.py,sha256=Mt0827e_T8DXJHOTkXhkIQUT9EUBWjoLcFcXP1gBnZY,5973
55
- pytgcalls/mtproto/hydrogram_client.py,sha256=3oMeEQN85yYkjeNA9oMCZvuniLTjdRdP_GJMJiOXfkI,22813
56
- pytgcalls/mtproto/mtproto_client.py,sha256=3TN2MfFYr8Lfd5AKp_u8G64XHSfCtpy59h4_SACa8G4,7564
57
- pytgcalls/mtproto/pyrogram_client.py,sha256=TumcUPm4HkQBnOpuQ2BgE6EgrZ9heTGjjhwh1_EbBbU,22996
58
- pytgcalls/mtproto/telethon_client.py,sha256=vzhIQG3u9sbkoyWED3BezI-p_VQ1Bu41sGMVM9lDzPE,21800
59
- pytgcalls/types/__init__.py,sha256=E-R2HKAH2AtMxVFDiPa1C7zY13cEqrPW6nTdCBIEOZU,1031
56
+ pytgcalls/mtproto/hydrogram_client.py,sha256=HWYhScpVd7dwfuwngYcohHcNNnPZxcQA0mHb_HAp7SE,23020
57
+ pytgcalls/mtproto/mtproto_client.py,sha256=1C1Cc1GOrKom-70NqUOICKxjfjgpPZBSdKdwFUjZzBc,7616
58
+ pytgcalls/mtproto/pyrogram_client.py,sha256=Z24edwj8wApTqECW5o5fF257fZhEIFvMw69uSULXlPU,23202
59
+ pytgcalls/mtproto/telethon_client.py,sha256=U9FeII1-nmKCJlAekpAQ7Ljb9doHD0aFlKSbMjlszls,22440
60
+ pytgcalls/types/__init__.py,sha256=6DkID8ItCV7G6PjkKBMCgUEWfSHbMJjn6lvJ8ysIq7U,1086
60
61
  pytgcalls/types/browsers.py,sha256=47Kr5q96n4Q4WvVhA6IUlS2egEcA9GRLlDeFcQYyc9M,9545
61
62
  pytgcalls/types/cache.py,sha256=FfsOcmYnsBGPlJoTPIXXYcUSpGE3rhx6cjIH77hyUL0,1059
62
63
  pytgcalls/types/dict.py,sha256=lAo9hu4VlVJa9S7P8Y81BYmKtvz0rH7hwpGcH3ynHUw,78
@@ -83,17 +84,18 @@ pytgcalls/types/raw/audio_stream.py,sha256=oN7Sx9oLbNFuNXiGYpoNabMwqWKGquLiHEywe
83
84
  pytgcalls/types/raw/stream.py,sha256=xJ3w77ofKFnLIA4cAWIuw2yREpMPvTzIvtei-3xEbJQ,666
84
85
  pytgcalls/types/raw/video_parameters.py,sha256=nUl9gkfYTVU0iLNGTtlZ5cZg8K6F7odIi9n8POJXCK4,639
85
86
  pytgcalls/types/raw/video_stream.py,sha256=uE3jU9kJsrAoefXtDtEKNqTjcYGaQbZ1gbJ1SfiVWIc,488
86
- pytgcalls/types/stream/__init__.py,sha256=a-3kNWnp4Vpja_x_Un6misT3VVVllChpeinc3w6Yd1k,457
87
+ pytgcalls/types/stream/__init__.py,sha256=YCJ2sg0ebhFMUzugj4V7SvvqDeeTvc0n_evdnhYFHls,520
87
88
  pytgcalls/types/stream/audio_quality.py,sha256=4X94ErmTeLP4TVcE3eLtPPdtluSPxgxbgTosuNJOVhc,141
88
- pytgcalls/types/stream/device.py,sha256=lnWrChNArFl3Sk-DqVEs61K-YXl2rqRAQq6PSPgjBCU,578
89
+ pytgcalls/types/stream/device.py,sha256=EdoDg6lPE7fgoZI04Nr0E9zbIk-iRIBgYYAzVqoCBPM,961
89
90
  pytgcalls/types/stream/direction.py,sha256=gd10wUmpfsqx87kCAPZt6u8pFiPb09WZfHKcMWAZokU,394
90
- pytgcalls/types/stream/media_stream.py,sha256=_gevQrmDJyLxGQCGk9qAfxe7949S8w51X2aF2rNCYM0,10222
91
+ pytgcalls/types/stream/external_media.py,sha256=RiuSX5tZGdNsQZ8LIRk5Lp4Ksv9oTvaccmInJRZYo4M,114
92
+ pytgcalls/types/stream/media_stream.py,sha256=sFlP8BMmCjhYp_A8U7XkGV4jbxR7PYsmJiII5Fxs0_A,11971
91
93
  pytgcalls/types/stream/record_stream.py,sha256=pp08OahflXJ5Cs281KxU-K2ULRP88ega5WBkJ7dwBjk,3038
92
94
  pytgcalls/types/stream/stream_ended.py,sha256=xR_kZwFf03hA6rw_nvI7Be7GwoCKzQf_1MKaGpPDXqY,716
93
- pytgcalls/types/stream/stream_frame.py,sha256=TMoJUbC2tsMDL1tt4oWCkaMsHoeYKj8dnV6JIs7EoEk,797
95
+ pytgcalls/types/stream/stream_frame.py,sha256=NuRaGoRvy4icJruG8ejQQoYVV282JxlIcSB-MQUIWYI,813
94
96
  pytgcalls/types/stream/video_quality.py,sha256=HBfWq005kh-D19MaVE9VzVdnODzrXf4IJUimCfslfiU,231
95
- py_tgcalls-2.1.0.dev4.dist-info/LICENSE,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
96
- py_tgcalls-2.1.0.dev4.dist-info/METADATA,sha256=7rEWejYp7D_VbEgx5MpiriREJ9jK41pBRMBTIP9Gstg,14399
97
- py_tgcalls-2.1.0.dev4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
98
- py_tgcalls-2.1.0.dev4.dist-info/top_level.txt,sha256=IUDUwn0KkcbUYZbCe9R5AUb2Ob-lmllNUGQqyeXXd8A,10
99
- py_tgcalls-2.1.0.dev4.dist-info/RECORD,,
97
+ py_tgcalls-2.1.0.dev5.dist-info/LICENSE,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
98
+ py_tgcalls-2.1.0.dev5.dist-info/METADATA,sha256=gg0QIm1PMKjSbyvLHlt8HYlleLRCcNYuP9kO_ckaQjA,14399
99
+ py_tgcalls-2.1.0.dev5.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
100
+ py_tgcalls-2.1.0.dev5.dist-info/top_level.txt,sha256=IUDUwn0KkcbUYZbCe9R5AUb2Ob-lmllNUGQqyeXXd8A,10
101
+ py_tgcalls-2.1.0.dev5.dist-info/RECORD,,
pytgcalls/__version__.py CHANGED
@@ -1 +1 @@
1
- __version__ = '2.1.0.dev4'
1
+ __version__ = '2.1.0.dev5'
pytgcalls/ffmpeg.py CHANGED
@@ -74,7 +74,7 @@ async def check_stream(
74
74
  codec_name = stream.get('codec_name', '')
75
75
  image_codecs = ['png', 'jpeg', 'jpg', 'mjpeg']
76
76
  if codec_type == 'video':
77
- is_image = codec_name in image_codecs
77
+ is_image &= codec_name in image_codecs
78
78
  have_video = True
79
79
  original_width = int(stream.get('width', 0))
80
80
  original_height = int(stream.get('height', 0))
@@ -270,7 +270,7 @@ def _extract_stream_params(command: List[str]):
270
270
  def _build_ffmpeg_options(
271
271
  stream_parameters: Union[AudioParameters, VideoParameters],
272
272
  ) -> List[str]:
273
- log_level = logging.getLogger().level
273
+ log_level = logging.getLogger('ffmpeg').level
274
274
  ffmpeg_level = 'info' if log_level == logging.DEBUG else 'quiet'
275
275
 
276
276
  options = ['-v', ffmpeg_level, '-f']
@@ -40,7 +40,7 @@ class LeaveCall(Scaffold):
40
40
  chat_id,
41
41
  )
42
42
  else:
43
- await self._app.discard_call(chat_id)
43
+ await self._app.discard_call(chat_id, False)
44
44
  if is_p2p_waiting:
45
45
  self._p2p_configs.pop(chat_id)
46
46
  return
@@ -3,6 +3,7 @@ from .pause_stream import PauseStream
3
3
  from .play import Play
4
4
  from .record import Record
5
5
  from .resume_stream import ResumeStream
6
+ from .send_frame import SendFrame
6
7
  from .time import Time
7
8
  from .unmute_stream import UnMuteStream
8
9
 
@@ -12,6 +13,7 @@ class StreamMethods(
12
13
  PauseStream,
13
14
  Play,
14
15
  Record,
16
+ SendFrame,
15
17
  Time,
16
18
  ResumeStream,
17
19
  UnMuteStream,
@@ -5,9 +5,9 @@ from typing import Union
5
5
 
6
6
  from ntgcalls import ConnectionNotFound
7
7
  from ntgcalls import FileError
8
- from ntgcalls import InvalidParams
9
8
  from ntgcalls import StreamMode
10
9
  from ntgcalls import TelegramServerError
10
+ from ntgcalls import TransportParseException
11
11
 
12
12
  from ...exceptions import NoActiveGroupCall
13
13
  from ...exceptions import TimedOutAnswer
@@ -159,6 +159,7 @@ class Play(Scaffold):
159
159
  await self._binding.stop(chat_id)
160
160
  except ConnectionNotFound:
161
161
  pass
162
+ await self._app.discard_call(chat_id, True)
162
163
  raise TimedOutAnswer()
163
164
  finally:
164
165
  self._p2p_configs.pop(chat_id, None)
@@ -238,7 +239,7 @@ class Play(Scaffold):
238
239
  self._need_unmute.add(chat_id)
239
240
  except FileError as e:
240
241
  raise FileNotFoundError(e)
241
- except InvalidParams:
242
+ except TransportParseException:
242
243
  raise UnMuteNeeded()
243
244
  except Exception:
244
245
  if isinstance(config, GroupCallConfig):
@@ -0,0 +1,38 @@
1
+ from typing import Union
2
+
3
+ from ntgcalls import ConnectionNotFound
4
+ from ntgcalls import FrameData
5
+
6
+ from ...exceptions import NotInCallError
7
+ from ...mtproto_required import mtproto_required
8
+ from ...scaffold import Scaffold
9
+ from ...statictypes import statictypes
10
+ from ...types import Device
11
+ from ...types import StreamFrame
12
+
13
+
14
+ class SendFrame(Scaffold):
15
+ @statictypes
16
+ @mtproto_required
17
+ async def send_frame(
18
+ self,
19
+ chat_id: Union[int, str],
20
+ device: Device,
21
+ data: bytes,
22
+ frame_data: StreamFrame.Info = StreamFrame.Info(),
23
+ ):
24
+ chat_id = await self.resolve_chat_id(chat_id)
25
+ try:
26
+ return await self._binding.send_external_frame(
27
+ chat_id,
28
+ Device.to_raw(device),
29
+ data,
30
+ FrameData(
31
+ frame_data.capture_time,
32
+ frame_data.rotation,
33
+ frame_data.width,
34
+ frame_data.height,
35
+ ),
36
+ )
37
+ except ConnectionNotFound:
38
+ raise NotInCallError()
@@ -49,6 +49,7 @@ class Start(Scaffold):
49
49
  if isinstance(update, ChatUpdate) and \
50
50
  p2p_config.outgoing:
51
51
  if update.status & ChatUpdate.Status.DISCARDED_CALL:
52
+ self._wait_connect.pop(chat_id, None)
52
53
  p2p_config.wait_data.set_exception(
53
54
  CallDeclined(
54
55
  chat_id,
@@ -5,6 +5,7 @@ from ntgcalls import AudioDescription
5
5
  from ntgcalls import MediaDescription
6
6
  from ntgcalls import VideoDescription
7
7
 
8
+ from ...types import RecordStream
8
9
  from ...types.raw import AudioStream
9
10
  from ...types.raw import Stream
10
11
  from ...types.raw import VideoStream
@@ -19,6 +20,11 @@ class StreamParams:
19
20
  if stream is not None:
20
21
  if isinstance(stream, MediaStream):
21
22
  await stream.check_stream()
23
+ elif isinstance(stream, RecordStream):
24
+ raise ValueError(
25
+ 'Stream should be an instance of '
26
+ 'MediaStream or a raw Stream',
27
+ )
22
28
 
23
29
  return MediaDescription(
24
30
  microphone=StreamParams._parse_media_description(
@@ -78,6 +78,7 @@ class BridgedClient(HandlersHolder):
78
78
  async def discard_call(
79
79
  self,
80
80
  chat_id: int,
81
+ is_missed: bool,
81
82
  ):
82
83
  pass
83
84
 
@@ -43,6 +43,7 @@ from hydrogram.raw.types import PhoneCall
43
43
  from hydrogram.raw.types import PhoneCallAccepted
44
44
  from hydrogram.raw.types import PhoneCallDiscarded
45
45
  from hydrogram.raw.types import PhoneCallDiscardReasonHangup
46
+ from hydrogram.raw.types import PhoneCallDiscardReasonMissed
46
47
  from hydrogram.raw.types import PhoneCallProtocol
47
48
  from hydrogram.raw.types import PhoneCallRequested
48
49
  from hydrogram.raw.types import PhoneCallWaiting
@@ -558,15 +559,21 @@ class HydrogramClient(BridgedClient):
558
559
  async def discard_call(
559
560
  self,
560
561
  chat_id: int,
562
+ is_missed: bool,
561
563
  ):
562
564
  peer = self._cache.get_phone_call(chat_id)
563
565
  if peer is None:
564
566
  return
567
+ reason = (
568
+ PhoneCallDiscardReasonMissed()
569
+ if is_missed
570
+ else PhoneCallDiscardReasonHangup()
571
+ )
565
572
  await self._app.invoke(
566
573
  DiscardCall(
567
574
  peer=peer,
568
575
  duration=0,
569
- reason=PhoneCallDiscardReasonHangup(),
576
+ reason=reason,
570
577
  connection_id=0,
571
578
  video=False,
572
579
  ),
@@ -126,10 +126,12 @@ class MtProtoClient:
126
126
  async def discard_call(
127
127
  self,
128
128
  user_id: int,
129
+ is_missed: bool,
129
130
  ):
130
131
  if self._bind_client is not None:
131
132
  return await self._bind_client.discard_call(
132
133
  user_id,
134
+ is_missed,
133
135
  )
134
136
  else:
135
137
  raise InvalidMTProtoClient()
@@ -45,6 +45,7 @@ from pyrogram.raw.types import PhoneCall
45
45
  from pyrogram.raw.types import PhoneCallAccepted
46
46
  from pyrogram.raw.types import PhoneCallDiscarded
47
47
  from pyrogram.raw.types import PhoneCallDiscardReasonHangup
48
+ from pyrogram.raw.types import PhoneCallDiscardReasonMissed
48
49
  from pyrogram.raw.types import PhoneCallProtocol
49
50
  from pyrogram.raw.types import PhoneCallRequested
50
51
  from pyrogram.raw.types import PhoneCallWaiting
@@ -566,15 +567,21 @@ class PyrogramClient(BridgedClient):
566
567
  async def discard_call(
567
568
  self,
568
569
  chat_id: int,
570
+ is_missed: bool,
569
571
  ):
570
572
  peer = self._cache.get_phone_call(chat_id)
571
573
  if peer is None:
572
574
  return
575
+ reason = (
576
+ PhoneCallDiscardReasonMissed()
577
+ if is_missed
578
+ else PhoneCallDiscardReasonHangup()
579
+ )
573
580
  await self._app.invoke(
574
581
  DiscardCall(
575
582
  peer=peer,
576
583
  duration=0,
577
- reason=PhoneCallDiscardReasonHangup(),
584
+ reason=reason,
578
585
  connection_id=0,
579
586
  video=False,
580
587
  ),
@@ -34,11 +34,13 @@ from telethon.tl.types import InputPhoneCall
34
34
  from telethon.tl.types import MessageActionChatDeleteUser
35
35
  from telethon.tl.types import MessageActionInviteToGroupCall
36
36
  from telethon.tl.types import MessageService
37
+ from telethon.tl.types import PeerChannel
37
38
  from telethon.tl.types import PeerChat
38
39
  from telethon.tl.types import PhoneCall
39
40
  from telethon.tl.types import PhoneCallAccepted
40
41
  from telethon.tl.types import PhoneCallDiscarded
41
42
  from telethon.tl.types import PhoneCallDiscardReasonHangup
43
+ from telethon.tl.types import PhoneCallDiscardReasonMissed
42
44
  from telethon.tl.types import PhoneCallProtocol
43
45
  from telethon.tl.types import PhoneCallRequested
44
46
  from telethon.tl.types import PhoneCallWaiting
@@ -182,7 +184,9 @@ class TelethonClient(BridgedClient):
182
184
  UpdateGroupCall,
183
185
  ):
184
186
  chat_id = self.chat_id(
185
- await self._app.get_entity(update.chat_id),
187
+ await self._get_entity_group(
188
+ update.chat_id,
189
+ ),
186
190
  )
187
191
  if isinstance(
188
192
  update.call,
@@ -215,7 +219,9 @@ class TelethonClient(BridgedClient):
215
219
  ):
216
220
  chat_id = self.chat_id(update)
217
221
  try:
218
- await self._app.get_entity(chat_id)
222
+ await self._app.get_entity(
223
+ PeerChannel(chat_id),
224
+ )
219
225
  except ChannelPrivateError:
220
226
  self._cache.drop_cache(chat_id)
221
227
  await self.propagate(
@@ -274,6 +280,16 @@ class TelethonClient(BridgedClient):
274
280
  ),
275
281
  )
276
282
 
283
+ async def _get_entity_group(self, chat_id):
284
+ try:
285
+ return await self._app.get_entity(
286
+ PeerChannel(chat_id),
287
+ )
288
+ except ValueError:
289
+ return await self._app.get_entity(
290
+ PeerChat(chat_id),
291
+ )
292
+
277
293
  async def get_call(
278
294
  self,
279
295
  chat_id: int,
@@ -535,15 +551,21 @@ class TelethonClient(BridgedClient):
535
551
  async def discard_call(
536
552
  self,
537
553
  chat_id: int,
554
+ is_missed: bool,
538
555
  ):
539
556
  peer = self._cache.get_phone_call(chat_id)
540
557
  if peer is None:
541
558
  return
559
+ reason = (
560
+ PhoneCallDiscardReasonMissed()
561
+ if is_missed
562
+ else PhoneCallDiscardReasonHangup()
563
+ )
542
564
  await self._app(
543
565
  DiscardCallRequest(
544
566
  peer=peer,
545
567
  duration=0,
546
- reason=PhoneCallDiscardReasonHangup(),
568
+ reason=reason,
547
569
  connection_id=0,
548
570
  video=False,
549
571
  ),
@@ -12,6 +12,7 @@ from .chats import UpdatedGroupCallParticipant
12
12
  from .stream import AudioQuality
13
13
  from .stream import Device
14
14
  from .stream import Direction
15
+ from .stream import ExternalMedia
15
16
  from .stream import MediaStream
16
17
  from .stream import RecordStream
17
18
  from .stream import StreamEnded
@@ -23,6 +24,7 @@ __all__ = (
23
24
  'AudioQuality',
24
25
  'Device',
25
26
  'Direction',
27
+ 'ExternalMedia',
26
28
  'Browsers',
27
29
  'Cache',
28
30
  'ChatUpdate',
@@ -1,6 +1,7 @@
1
1
  from .audio_quality import AudioQuality
2
2
  from .device import Device
3
3
  from .direction import Direction
4
+ from .external_media import ExternalMedia
4
5
  from .media_stream import MediaStream
5
6
  from .record_stream import RecordStream
6
7
  from .stream_ended import StreamEnded
@@ -11,6 +12,7 @@ __all__ = (
11
12
  'AudioQuality',
12
13
  'Device',
13
14
  'Direction',
15
+ 'ExternalMedia',
14
16
  'MediaStream',
15
17
  'RecordStream',
16
18
  'StreamEnded',
@@ -22,3 +22,15 @@ class Device(Flag):
22
22
  if device == StreamDevice.SCREEN:
23
23
  return Device.SCREEN
24
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,8 @@
1
+ from enum import auto
2
+
3
+ from ..flag import Flag
4
+
5
+
6
+ class ExternalMedia(Flag):
7
+ AUDIO = auto()
8
+ VIDEO = auto()
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  from enum import auto
2
3
  from pathlib import Path
3
4
  from typing import Dict
@@ -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):
@@ -35,7 +39,7 @@ class MediaStream(Stream):
35
39
  @statictypes
36
40
  def __init__(
37
41
  self,
38
- media_path: Union[str, Path, InputDevice],
42
+ media_path: Union[str, Path, InputDevice, ExternalMedia],
39
43
  audio_parameters: Union[
40
44
  AudioParameters,
41
45
  AudioQuality,
@@ -44,7 +48,12 @@ class MediaStream(Stream):
44
48
  VideoParameters,
45
49
  VideoQuality,
46
50
  ] = VideoQuality.SD_480p,
47
- audio_path: Optional[Union[str, Path, InputDevice]] = None,
51
+ audio_path: Optional[
52
+ Union[
53
+ str, Path,
54
+ InputDevice, ExternalMedia,
55
+ ]
56
+ ] = None,
48
57
  audio_flags: Optional[Flags] = Flags.AUTO_DETECT,
49
58
  video_flags: Optional[Flags] = Flags.AUTO_DETECT,
50
59
  headers: Optional[Dict[str, str]] = None,
@@ -70,10 +79,17 @@ class MediaStream(Stream):
70
79
  self._audio_path: Optional[str] = None
71
80
  self._is_media_device: bool = False
72
81
  self._is_audio_device: bool = False
82
+ self._is_audio_external: bool = False
83
+ self._is_video_external: bool = False
73
84
  if isinstance(media_path, str):
74
85
  self._media_path = media_path
75
86
  elif isinstance(media_path, Path):
76
87
  self._media_path = str(media_path)
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
77
93
  elif isinstance(media_path, (InputDevice, ScreenDevice)):
78
94
  print('MediaStream', media_path.is_video)
79
95
  if media_path.is_video:
@@ -87,6 +103,17 @@ class MediaStream(Stream):
87
103
  self._audio_path = audio_path
88
104
  elif isinstance(audio_path, Path):
89
105
  self._audio_path = str(audio_path)
106
+ elif isinstance(audio_path, ExternalMedia):
107
+ self._audio_path = ''
108
+ if audio_path == ExternalMedia.AUDIO:
109
+ if self._is_audio_external:
110
+ py_logger.warning(
111
+ 'Audio path is already an audio source, '
112
+ 'ignoring audio path',
113
+ )
114
+ self._is_audio_external = True
115
+ else:
116
+ raise ValueError('Audio path must be an audio source')
90
117
  elif isinstance(audio_path, (InputDevice, ScreenDevice)):
91
118
  if audio_path.is_video:
92
119
  raise ValueError('Audio path must be an audio device')
@@ -100,13 +127,23 @@ class MediaStream(Stream):
100
127
  self._headers = headers
101
128
  super().__init__(
102
129
  microphone=None
103
- if self._audio_flags & MediaStream.Flags.IGNORE else
130
+ if (
131
+ self._audio_flags & MediaStream.Flags.IGNORE or
132
+ self._media_path is None and
133
+ self._audio_path is None
134
+ ) and not self._is_audio_external else
104
135
  AudioStream(
105
136
  MediaSource.DEVICE,
106
137
  self._audio_path,
107
138
  self._audio_parameters,
108
139
  )
109
140
  if self._is_audio_device else
141
+ AudioStream(
142
+ MediaSource.EXTERNAL,
143
+ '',
144
+ self._audio_parameters,
145
+ )
146
+ if self._is_audio_external else
110
147
  AudioStream(
111
148
  MediaSource.SHELL,
112
149
  ' '.join(
@@ -123,7 +160,10 @@ class MediaStream(Stream):
123
160
  self._audio_parameters,
124
161
  ),
125
162
  camera=None
126
- if self._video_flags & MediaStream.Flags.IGNORE else
163
+ if (
164
+ self._video_flags & MediaStream.Flags.IGNORE or
165
+ self._media_path is None
166
+ ) and not self._is_video_external else
127
167
  VideoStream(
128
168
  MediaSource.DESKTOP if isinstance(media_path, ScreenDevice)
129
169
  else MediaSource.DEVICE,
@@ -131,6 +171,12 @@ class MediaStream(Stream):
131
171
  self._video_parameters,
132
172
  )
133
173
  if self._is_media_device else
174
+ VideoStream(
175
+ MediaSource.EXTERNAL,
176
+ '',
177
+ self._video_parameters,
178
+ )
179
+ if self._is_video_external else
134
180
  VideoStream(
135
181
  MediaSource.SHELL,
136
182
  ' '.join(
@@ -149,7 +195,8 @@ class MediaStream(Stream):
149
195
  )
150
196
 
151
197
  async def check_stream(self):
152
- if not self._video_flags & MediaStream.Flags.IGNORE:
198
+ if not self._video_flags & MediaStream.Flags.IGNORE and \
199
+ not self._is_video_external:
153
200
  if self._is_media_device:
154
201
  if not self._media_path:
155
202
  self.camera = None
@@ -205,7 +252,8 @@ class MediaStream(Stream):
205
252
  self._audio_path = self._audio_path \
206
253
  if self._audio_path else self._media_path
207
254
 
208
- if not self._audio_flags & MediaStream.Flags.IGNORE:
255
+ if not self._audio_flags & MediaStream.Flags.IGNORE and \
256
+ not self._is_audio_external:
209
257
  if self._is_audio_device:
210
258
  if not self._audio_path:
211
259
  self.microphone = None
@@ -8,10 +8,10 @@ class StreamFrame(Update):
8
8
  class Info:
9
9
  def __init__(
10
10
  self,
11
- capture_time: int,
12
- width: int,
13
- height: int,
14
- rotation: int,
11
+ capture_time: int = 0,
12
+ width: int = 0,
13
+ height: int = 0,
14
+ rotation: int = 0,
15
15
  ):
16
16
  self.capture_time = capture_time
17
17
  self.width = width