py-tgcalls 2.1.2b2__py3-none-any.whl → 2.2.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 (32) hide show
  1. {py_tgcalls-2.1.2b2.dist-info → py_tgcalls-2.2.0.dist-info}/METADATA +2 -2
  2. {py_tgcalls-2.1.2b2.dist-info → py_tgcalls-2.2.0.dist-info}/RECORD +32 -31
  3. {py_tgcalls-2.1.2b2.dist-info → py_tgcalls-2.2.0.dist-info}/WHEEL +1 -1
  4. pytgcalls/__version__.py +1 -1
  5. pytgcalls/ffmpeg.py +2 -2
  6. pytgcalls/filters.py +6 -2
  7. pytgcalls/list_to_cmd.py +11 -0
  8. pytgcalls/methods/calls/leave_call.py +4 -0
  9. pytgcalls/methods/internal/connect_call.py +10 -1
  10. pytgcalls/methods/internal/handle_mtproto_updates.py +8 -15
  11. pytgcalls/methods/stream/play.py +2 -1
  12. pytgcalls/mtproto/bridged_client.py +64 -2
  13. pytgcalls/mtproto/client_cache.py +65 -72
  14. pytgcalls/mtproto/hydrogram_client.py +132 -35
  15. pytgcalls/mtproto/mtproto_client.py +11 -0
  16. pytgcalls/mtproto/pyrogram_client.py +135 -43
  17. pytgcalls/mtproto/telethon_client.py +95 -35
  18. pytgcalls/mutex.py +3 -1
  19. pytgcalls/types/cache.py +10 -5
  20. pytgcalls/types/chats/group_call_participant.py +1 -8
  21. pytgcalls/types/chats/updated_group_call_participant.py +2 -0
  22. pytgcalls/types/dict.py +1 -1
  23. pytgcalls/types/flag.py +4 -3
  24. pytgcalls/types/list.py +1 -1
  25. pytgcalls/types/participant_list.py +3 -3
  26. pytgcalls/types/py_object.py +4 -2
  27. pytgcalls/types/stream/media_stream.py +5 -4
  28. pytgcalls/types/stream/record_stream.py +3 -1
  29. pytgcalls/types/stream/video_quality.py +3 -1
  30. pytgcalls/ytdlp.py +3 -2
  31. {py_tgcalls-2.1.2b2.dist-info → py_tgcalls-2.2.0.dist-info}/licenses/LICENSE +0 -0
  32. {py_tgcalls-2.1.2b2.dist-info → py_tgcalls-2.2.0.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,9 @@ from typing import Union
6
6
  from ntgcalls import MediaSegmentQuality
7
7
  from ntgcalls import Protocol
8
8
  from telethon import TelegramClient
9
+ from telethon.errors import BadRequestError
9
10
  from telethon.errors import ChannelPrivateError
11
+ from telethon.errors import FileMigrateError
10
12
  from telethon.errors import FloodWaitError
11
13
  from telethon.events import Raw
12
14
  from telethon.tl.functions.channels import GetFullChannelRequest
@@ -16,6 +18,7 @@ from telethon.tl.functions.phone import AcceptCallRequest
16
18
  from telethon.tl.functions.phone import ConfirmCallRequest
17
19
  from telethon.tl.functions.phone import CreateGroupCallRequest
18
20
  from telethon.tl.functions.phone import DiscardCallRequest
21
+ from telethon.tl.functions.phone import DiscardGroupCallRequest
19
22
  from telethon.tl.functions.phone import EditGroupCallParticipantRequest
20
23
  from telethon.tl.functions.phone import GetGroupCallRequest
21
24
  from telethon.tl.functions.phone import GetGroupCallStreamChannelsRequest
@@ -68,7 +71,6 @@ from ..types import CallProtocol
68
71
  from ..types import ChatUpdate
69
72
  from ..types import GroupCallParticipant
70
73
  from ..types import RawCallUpdate
71
- from ..types import UpdatedGroupCallParticipant
72
74
  from .bridged_client import BridgedClient
73
75
  from .client_cache import ClientCache
74
76
 
@@ -178,19 +180,22 @@ class TelethonClient(BridgedClient):
178
180
  update,
179
181
  UpdateGroupCallParticipants,
180
182
  ):
181
- participants = update.participants
182
- for participant in participants:
183
- result = self._cache.set_participants_cache_call(
184
- update.call.id,
185
- self.parse_participant(participant),
183
+ for participant in update.participants:
184
+ chat_id = self._cache.get_chat_id(update.call.id)
185
+ p_updates = await self.diff_participants_update(
186
+ self._cache,
187
+ chat_id,
188
+ participant,
186
189
  )
187
- if result is not None:
188
- await self._propagate(
189
- UpdatedGroupCallParticipant(
190
- self._cache.get_chat_id(update.call.id),
191
- result,
192
- ),
190
+ for p_update in p_updates:
191
+ result = self._cache.set_participants_cache(
192
+ chat_id,
193
+ update.call.id,
194
+ p_update.action,
195
+ p_update.participant,
193
196
  )
197
+ if result is not None:
198
+ await self._propagate(p_update)
194
199
  if isinstance(
195
200
  update,
196
201
  UpdateGroupCall,
@@ -309,7 +314,7 @@ class TelethonClient(BridgedClient):
309
314
  chat = await self._app.get_input_entity(chat_id)
310
315
  if isinstance(chat, InputPeerChannel):
311
316
  input_call = (
312
- await self._app(
317
+ await self._invoke(
313
318
  GetFullChannelRequest(
314
319
  InputChannel(
315
320
  chat.channel_id,
@@ -320,14 +325,14 @@ class TelethonClient(BridgedClient):
320
325
  ).full_chat.call
321
326
  else:
322
327
  input_call = (
323
- await self._app(
328
+ await self._invoke(
324
329
  GetFullChatRequest(chat_id),
325
330
  )
326
331
  ).full_chat.call
327
332
 
328
333
  if input_call is not None:
329
334
  raw_call = (
330
- await self._app(
335
+ await self._invoke(
331
336
  GetGroupCallRequest(
332
337
  call=input_call,
333
338
  limit=-1,
@@ -337,9 +342,10 @@ class TelethonClient(BridgedClient):
337
342
  call: GroupCall = raw_call.call
338
343
  participants: List[GroupCallParticipant] = raw_call.participants
339
344
  for participant in participants:
340
- self._cache.set_participants_cache_chat(
345
+ self._cache.set_participants_cache(
341
346
  chat_id,
342
347
  call.id,
348
+ self.parse_participant_action(participant),
343
349
  self.parse_participant(participant),
344
350
  )
345
351
  if call.schedule_date is not None:
@@ -348,7 +354,7 @@ class TelethonClient(BridgedClient):
348
354
  return input_call
349
355
 
350
356
  async def get_dhc(self) -> DhConfig:
351
- return await self._app(
357
+ return await self._invoke(
352
358
  GetDhConfigRequest(
353
359
  version=0,
354
360
  random_length=256,
@@ -370,7 +376,7 @@ class TelethonClient(BridgedClient):
370
376
  participants = []
371
377
  next_offset = ''
372
378
  while True:
373
- result = await self._app(
379
+ result = await self._invoke(
374
380
  GetGroupParticipantsRequest(
375
381
  call=input_call,
376
382
  ids=[],
@@ -397,7 +403,7 @@ class TelethonClient(BridgedClient):
397
403
  ) -> str:
398
404
  chat_call = await self._cache.get_full_chat(chat_id)
399
405
  if chat_call is not None:
400
- result: Updates = await self._app(
406
+ result: Updates = await self._invoke(
401
407
  JoinGroupCallRequest(
402
408
  call=chat_call,
403
409
  params=DataJSON(data=json_join),
@@ -414,8 +420,10 @@ class TelethonClient(BridgedClient):
414
420
  ):
415
421
  participants = update.participants
416
422
  for participant in participants:
417
- self._cache.set_participants_cache_call(
423
+ self._cache.set_participants_cache(
424
+ chat_id,
418
425
  update.call.id,
426
+ self.parse_participant_action(participant),
419
427
  self.parse_participant(participant),
420
428
  )
421
429
  if isinstance(update, UpdateGroupCallConnection):
@@ -430,7 +438,7 @@ class TelethonClient(BridgedClient):
430
438
  ):
431
439
  chat_call = await self._cache.get_full_chat(chat_id)
432
440
  if chat_call is not None:
433
- result: Updates = await self._app(
441
+ result: Updates = await self._invoke(
434
442
  JoinGroupCallPresentationRequest(
435
443
  call=chat_call,
436
444
  params=DataJSON(data=json_join),
@@ -448,7 +456,7 @@ class TelethonClient(BridgedClient):
448
456
  ):
449
457
  chat_call = await self._cache.get_full_chat(chat_id)
450
458
  if chat_call is not None:
451
- await self._app(
459
+ await self._invoke(
452
460
  LeaveGroupCallPresentationRequest(
453
461
  call=chat_call,
454
462
  ),
@@ -461,7 +469,7 @@ class TelethonClient(BridgedClient):
461
469
  protocol: Protocol,
462
470
  has_video: bool,
463
471
  ):
464
- update = await self._app(
472
+ update = await self._invoke(
465
473
  RequestCallRequest(
466
474
  user_id=await self.resolve_peer(user_id),
467
475
  random_id=self.rnd_id(),
@@ -484,7 +492,7 @@ class TelethonClient(BridgedClient):
484
492
  g_b: bytes,
485
493
  protocol: Protocol,
486
494
  ):
487
- return await self._app(
495
+ return await self._invoke(
488
496
  AcceptCallRequest(
489
497
  peer=self._cache.get_phone_call(user_id),
490
498
  g_b=g_b,
@@ -500,7 +508,7 @@ class TelethonClient(BridgedClient):
500
508
  protocol: Protocol,
501
509
  ) -> CallProtocol:
502
510
  res = (
503
- await self._app(
511
+ await self._invoke(
504
512
  ConfirmCallRequest(
505
513
  peer=self._cache.get_phone_call(user_id),
506
514
  g_a=g_a,
@@ -520,7 +528,7 @@ class TelethonClient(BridgedClient):
520
528
  user_id: int,
521
529
  data: bytes,
522
530
  ):
523
- await self._app(
531
+ await self._invoke(
524
532
  SendSignalingDataRequest(
525
533
  peer=self._cache.get_phone_call(user_id),
526
534
  data=data,
@@ -531,7 +539,7 @@ class TelethonClient(BridgedClient):
531
539
  self,
532
540
  chat_id: int,
533
541
  ):
534
- result: Updates = await self._app(
542
+ result: Updates = await self._invoke(
535
543
  CreateGroupCallRequest(
536
544
  peer=await self.resolve_peer(chat_id),
537
545
  random_id=self.rnd_id(),
@@ -561,13 +569,26 @@ class TelethonClient(BridgedClient):
561
569
  ):
562
570
  chat_call = await self._cache.get_full_chat(chat_id)
563
571
  if chat_call is not None:
564
- await self._app(
572
+ await self._invoke(
565
573
  LeaveGroupCallRequest(
566
574
  call=chat_call,
567
575
  source=0,
568
576
  ),
569
577
  )
570
578
 
579
+ async def close_voice_chat(
580
+ self,
581
+ chat_id: int,
582
+ ):
583
+ chat_call = await self._cache.get_full_chat(chat_id)
584
+ if chat_call is not None:
585
+ await self._invoke(
586
+ DiscardGroupCallRequest(
587
+ call=chat_call,
588
+ ),
589
+ )
590
+ self._cache.drop_cache(chat_id)
591
+
571
592
  async def discard_call(
572
593
  self,
573
594
  chat_id: int,
@@ -581,7 +602,7 @@ class TelethonClient(BridgedClient):
581
602
  if is_missed
582
603
  else PhoneCallDiscardReasonHangup()
583
604
  )
584
- await self._app(
605
+ await self._invoke(
585
606
  DiscardCallRequest(
586
607
  peer=peer,
587
608
  duration=0,
@@ -600,7 +621,7 @@ class TelethonClient(BridgedClient):
600
621
  ):
601
622
  chat_call = await self._cache.get_full_chat(chat_id)
602
623
  if chat_call is not None:
603
- await self._app(
624
+ await self._invoke(
604
625
  EditGroupCallParticipantRequest(
605
626
  call=chat_call,
606
627
  participant=participant,
@@ -621,7 +642,7 @@ class TelethonClient(BridgedClient):
621
642
  if chat_call is not None:
622
643
  try:
623
644
  return (
624
- await self._app(
645
+ await self._invoke(
625
646
  GetFileRequest(
626
647
  location=InputGroupCallStream(
627
648
  call=chat_call,
@@ -635,7 +656,8 @@ class TelethonClient(BridgedClient):
635
656
  offset=0,
636
657
  limit=limit,
637
658
  ),
638
- flood_sleep_threshold=0,
659
+ chat_id=chat_id,
660
+ sleep_threshold=0,
639
661
  )
640
662
  ).bytes
641
663
  except FloodWaitError:
@@ -648,12 +670,12 @@ class TelethonClient(BridgedClient):
648
670
  ):
649
671
  chat_call = await self._cache.get_full_chat(chat_id)
650
672
  if chat_call is not None:
651
- # noinspection PyBroadException
652
673
  channels = (
653
- await self._app(
674
+ await self._invoke(
654
675
  GetGroupCallStreamChannelsRequest(
655
676
  call=chat_call,
656
677
  ),
678
+ chat_id=chat_id,
657
679
  )
658
680
  ).channels
659
681
  if len(channels) > 0:
@@ -672,7 +694,7 @@ class TelethonClient(BridgedClient):
672
694
  ):
673
695
  chat_call = await self._cache.get_full_chat(chat_id)
674
696
  if chat_call is not None:
675
- await self._app(
697
+ await self._invoke(
676
698
  EditGroupCallParticipantRequest(
677
699
  call=chat_call,
678
700
  participant=participant,
@@ -711,6 +733,44 @@ class TelethonClient(BridgedClient):
711
733
  def no_updates(self):
712
734
  return False
713
735
 
736
+ # noinspection PyProtectedMember,PyUnresolvedReferences
737
+ async def _invoke(
738
+ self,
739
+ request,
740
+ dc_id: Optional[int] = None,
741
+ chat_id: Optional[int] = None,
742
+ sleep_threshold: Optional[int] = None,
743
+ ):
744
+ try:
745
+ if chat_id is not None:
746
+ dc_id = self._cache.get_dc_call(chat_id)
747
+ if dc_id is None or self._app.session.dc_id == dc_id:
748
+ sender_dc = self._app._sender
749
+ else:
750
+ sender_dc = await self._app._borrow_exported_sender(dc_id)
751
+ return await self._app._call(
752
+ sender_dc,
753
+ request,
754
+ flood_sleep_threshold=sleep_threshold,
755
+ )
756
+ except (BadRequestError, FileMigrateError) as e:
757
+ dc_new = BridgedClient.extract_dc(
758
+ str(e),
759
+ )
760
+ if dc_new is not None:
761
+ if chat_id is not None:
762
+ self._cache.set_dc_call(
763
+ chat_id,
764
+ dc_new,
765
+ )
766
+ return await self._invoke(
767
+ request,
768
+ dc_new,
769
+ chat_id,
770
+ sleep_threshold,
771
+ )
772
+ raise
773
+
714
774
  # noinspection PyUnresolvedReferences
715
775
  async def start(self):
716
776
  await self._app.start()
pytgcalls/mutex.py CHANGED
@@ -7,7 +7,9 @@ def mutex(func):
7
7
  @wraps(func)
8
8
  async def async_wrapper(*args, **kwargs):
9
9
  self = args[0]
10
- chat_id = await self.resolve_chat_id(args[1])
10
+ chat_id = await self.resolve_chat_id(
11
+ args[1] if len(args) > 1 else kwargs['chat_id'],
12
+ )
11
13
  async with self._lock:
12
14
  self._calls_lock[chat_id] = self._calls_lock.get(
13
15
  chat_id,
pytgcalls/types/cache.py CHANGED
@@ -8,13 +8,13 @@ from typing import Optional
8
8
  @dataclass
9
9
  class CacheEntry:
10
10
  time: int
11
- expiry_time: int
12
11
  data: Any
13
12
 
14
13
 
15
14
  class Cache:
16
- def __init__(self):
15
+ def __init__(self, expiry_time: int = 0):
17
16
  self._store: Dict[int, CacheEntry] = {} # type: ignore
17
+ self._expiry_time = expiry_time
18
18
 
19
19
  def get(self, chat_id: int):
20
20
  if chat_id in self._store:
@@ -25,13 +25,18 @@ class Cache:
25
25
  self._store.pop(chat_id, None)
26
26
  return None
27
27
 
28
- def put(self, chat_id: int, data: Any, expiry_time: int = 0) -> None:
28
+ def put(self, chat_id: int, data: Any) -> None:
29
29
  self._store[chat_id] = CacheEntry(
30
- time=0 if expiry_time == 0 else (int(time()) + expiry_time),
31
- expiry_time=expiry_time,
30
+ time=0
31
+ if self._expiry_time == 0 else
32
+ (int(time()) + self._expiry_time),
32
33
  data=data,
33
34
  )
34
35
 
36
+ def update_cache(self, chat_id: int) -> None:
37
+ if chat_id in self._store:
38
+ self._store[chat_id].time = int(time()) + self._expiry_time
39
+
35
40
  @property
36
41
  def keys(self):
37
42
  return list(self._store)
@@ -12,6 +12,7 @@ class GroupCallParticipant(PyObject):
12
12
  class Action(Flag):
13
13
  JOINED = auto()
14
14
  LEFT = auto()
15
+ KICKED = auto()
15
16
  UPDATED = auto()
16
17
 
17
18
  class SourceInfo(PyObject):
@@ -33,8 +34,6 @@ class GroupCallParticipant(PyObject):
33
34
  video_camera: bool,
34
35
  raised_hand: bool,
35
36
  volume: int,
36
- joined: bool,
37
- left: bool,
38
37
  source: int,
39
38
  video_info: Optional[SourceInfo],
40
39
  presentation_info: Optional[SourceInfo],
@@ -48,12 +47,6 @@ class GroupCallParticipant(PyObject):
48
47
  self.video_camera: bool = video_camera
49
48
  self.raised_hand: bool = raised_hand
50
49
  self.volume: int = volume
51
- if joined:
52
- self.action = self.Action.JOINED
53
- elif left:
54
- self.action = self.Action.LEFT
55
- else:
56
- self.action = self.Action.UPDATED
57
50
  self.video_info: Optional[
58
51
  GroupCallParticipant.SourceInfo
59
52
  ] = video_info
@@ -6,7 +6,9 @@ class UpdatedGroupCallParticipant(Update):
6
6
  def __init__(
7
7
  self,
8
8
  chat_id: int,
9
+ action: GroupCallParticipant.Action,
9
10
  participant: GroupCallParticipant,
10
11
  ):
11
12
  super().__init__(chat_id)
13
+ self.action = action
12
14
  self.participant = participant
pytgcalls/types/dict.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from ..types.py_object import PyObject
2
2
 
3
3
 
4
- class Dict(dict, PyObject):
4
+ class Dict(PyObject, dict):
5
5
  pass
pytgcalls/types/flag.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from enum import Flag as _Flag
2
2
 
3
+ from .py_object import PyObject
3
4
 
4
- class Flag(_Flag):
5
- def __repr__(self):
6
- return f'{self.__class__.__name__}.{self.name}'
5
+
6
+ class Flag(PyObject, _Flag):
7
+ pass
pytgcalls/types/list.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from ..types.py_object import PyObject
2
2
 
3
3
 
4
- class List(list, PyObject):
4
+ class List(PyObject, list):
5
5
  pass
@@ -10,14 +10,14 @@ class ParticipantList:
10
10
  input_id: int,
11
11
  ):
12
12
  self._list_participants: Dict[int, GroupCallParticipant] = {}
13
- self.last_mtproto_update: int = 0
14
13
  self.input_id: int = input_id
15
14
 
16
15
  def update_participant(
17
16
  self,
17
+ action: GroupCallParticipant.Action,
18
18
  participant: GroupCallParticipant,
19
- ):
20
- if participant.action == GroupCallParticipant.Action.LEFT:
19
+ ) -> GroupCallParticipant:
20
+ if action == GroupCallParticipant.Action.LEFT:
21
21
  if participant.user_id in self._list_participants:
22
22
  del self._list_participants[participant.user_id]
23
23
  else:
@@ -11,8 +11,10 @@ class PyObject:
11
11
  def default(obj) -> Union[str, Dict[str, str], List[Any]]:
12
12
  if isinstance(obj, bytes):
13
13
  return repr(obj)
14
- if isinstance(obj, Enum):
15
- return repr(obj)
14
+ elif isinstance(obj, Enum):
15
+ return ' | '.join(
16
+ [f"{obj.__class__.__name__}.{x}" for x in obj.name.split('|')],
17
+ )
16
18
  return {
17
19
  '_': obj.__class__.__name__,
18
20
  **{
@@ -13,6 +13,7 @@ from ...exceptions import NoAudioSourceFound
13
13
  from ...exceptions import NoVideoSourceFound
14
14
  from ...ffmpeg import build_command
15
15
  from ...ffmpeg import check_stream
16
+ from ...list_to_cmd import list_to_cmd
16
17
  from ...media_devices.input_device import InputDevice
17
18
  from ...media_devices.screen_device import ScreenDevice
18
19
  from ...statictypes import statictypes
@@ -145,7 +146,7 @@ class MediaStream(Stream):
145
146
  if self._is_audio_external else
146
147
  AudioStream(
147
148
  MediaSource.SHELL,
148
- ' '.join(
149
+ list_to_cmd(
149
150
  build_command(
150
151
  'ffmpeg',
151
152
  self._ffmpeg_parameters,
@@ -178,7 +179,7 @@ class MediaStream(Stream):
178
179
  if self._is_video_external else
179
180
  VideoStream(
180
181
  MediaSource.SHELL,
181
- ' '.join(
182
+ list_to_cmd(
182
183
  build_command(
183
184
  'ffmpeg',
184
185
  self._ffmpeg_parameters,
@@ -229,7 +230,7 @@ class MediaStream(Stream):
229
230
  ]
230
231
  except LiveStreamFound:
231
232
  live_stream = True
232
- self.camera.path = ' '.join(
233
+ self.camera.path = list_to_cmd(
233
234
  build_command(
234
235
  'ffmpeg',
235
236
  self._ffmpeg_parameters,
@@ -278,7 +279,7 @@ class MediaStream(Stream):
278
279
  )
279
280
  except LiveStreamFound:
280
281
  live_stream = True
281
- self.microphone.path = ' '.join(
282
+ self.microphone.path = list_to_cmd(
282
283
  build_command(
283
284
  'ffmpeg',
284
285
  self._ffmpeg_parameters,
@@ -3,6 +3,7 @@ from typing import Union
3
3
 
4
4
  from ntgcalls import MediaSource
5
5
 
6
+ from ...list_to_cmd import list_to_cmd
6
7
  from ...media_devices.speaker_device import SpeakerDevice
7
8
  from ...statictypes import statictypes
8
9
  from ..raw.audio_parameters import AudioParameters
@@ -72,7 +73,7 @@ class RecordStream(Stream):
72
73
  ]
73
74
  return AudioStream(
74
75
  media_source=MediaSource.SHELL,
75
- path=' '.join(commands),
76
+ path=list_to_cmd(commands),
76
77
  parameters=raw_audio_parameters,
77
78
  )
78
79
  if isinstance(audio, SpeakerDevice):
@@ -81,6 +82,7 @@ class RecordStream(Stream):
81
82
  path=audio.metadata,
82
83
  parameters=raw_audio_parameters,
83
84
  )
85
+ return None
84
86
 
85
87
  @staticmethod
86
88
  def _get_video_stream(enable):
@@ -1,7 +1,9 @@
1
1
  from enum import Enum
2
2
 
3
+ from ..py_object import PyObject
3
4
 
4
- class VideoQuality(Enum):
5
+
6
+ class VideoQuality(PyObject, Enum):
5
7
  UHD_4K = (3840, 2160, 60)
6
8
  QHD_2K = (2560, 1440, 60)
7
9
  FHD_1080p = (1920, 1080, 60)
pytgcalls/ytdlp.py CHANGED
@@ -7,6 +7,7 @@ from typing import Tuple
7
7
 
8
8
  from .exceptions import YtDlpError
9
9
  from .ffmpeg import cleanup_commands
10
+ from .list_to_cmd import list_to_cmd
10
11
  from .types.raw import VideoParameters
11
12
 
12
13
  py_logger = logging.getLogger('pytgcalls')
@@ -37,7 +38,7 @@ class YtDlp:
37
38
  'yt-dlp',
38
39
  '-g',
39
40
  '-f',
40
- 'bestvideo[vcodec~=\'(vp09|avc1)\']+m4a/best',
41
+ 'bestvideo[vcodec~="(vp09|avc1)"]+m4a/best',
41
42
  '-S',
42
43
  'res:'
43
44
  f'{min(video_parameters.width, video_parameters.height)}',
@@ -59,7 +60,7 @@ class YtDlp:
59
60
 
60
61
  py_logger.log(
61
62
  logging.DEBUG,
62
- f'Running with "{" ".join(commands)}" command',
63
+ f'Running with "{list_to_cmd(commands)}" command',
63
64
  )
64
65
  try:
65
66
  proc = await asyncio.create_subprocess_exec(