py-tgcalls 2.0.5__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.
- {py_tgcalls-2.0.5.dist-info → py_tgcalls-2.1.0.dist-info}/METADATA +11 -16
- py_tgcalls-2.1.0.dist-info/RECORD +106 -0
- {py_tgcalls-2.0.5.dist-info → py_tgcalls-2.1.0.dist-info}/WHEEL +1 -1
- pytgcalls/__init__.py +2 -0
- pytgcalls/__version__.py +1 -1
- pytgcalls/exceptions.py +7 -24
- pytgcalls/ffmpeg.py +2 -2
- pytgcalls/filters.py +52 -7
- pytgcalls/handlers/handlers_holder.py +1 -1
- pytgcalls/media_devices/__init__.py +6 -2
- pytgcalls/media_devices/device_info.py +8 -15
- pytgcalls/media_devices/input_device.py +11 -0
- pytgcalls/media_devices/media_devices.py +41 -92
- pytgcalls/media_devices/screen_device.py +10 -0
- pytgcalls/media_devices/speaker_device.py +10 -0
- pytgcalls/methods/calls/change_volume_call.py +3 -0
- pytgcalls/methods/calls/get_participants.py +5 -1
- pytgcalls/methods/calls/leave_call.py +3 -1
- pytgcalls/methods/stream/__init__.py +14 -10
- pytgcalls/methods/stream/{mute_stream.py → mute.py} +2 -2
- pytgcalls/methods/stream/{pause_stream.py → pause.py} +2 -2
- pytgcalls/methods/stream/play.py +27 -21
- pytgcalls/methods/stream/record.py +49 -0
- pytgcalls/methods/stream/{resume_stream.py → resume.py} +2 -2
- pytgcalls/methods/stream/send_frame.py +38 -0
- pytgcalls/methods/stream/{played_time.py → time.py} +5 -3
- pytgcalls/methods/stream/{unmute_stream.py → unmute.py} +2 -2
- pytgcalls/methods/utilities/__init__.py +6 -0
- pytgcalls/methods/utilities/call_holder.py +5 -2
- pytgcalls/methods/utilities/join_presentation.py +50 -0
- pytgcalls/methods/utilities/log_retries.py +14 -0
- pytgcalls/methods/utilities/start.py +136 -16
- pytgcalls/methods/utilities/stream_params.py +69 -22
- pytgcalls/methods/utilities/update_sources.py +42 -0
- pytgcalls/mtproto/bridged_client.py +36 -2
- pytgcalls/mtproto/client_cache.py +46 -16
- pytgcalls/mtproto/hydrogram_client.py +72 -23
- pytgcalls/mtproto/mtproto_client.py +32 -4
- pytgcalls/mtproto/pyrogram_client.py +72 -23
- pytgcalls/mtproto/telethon_client.py +97 -33
- pytgcalls/scaffold.py +15 -0
- pytgcalls/types/__init__.py +14 -4
- pytgcalls/types/calls/__init__.py +2 -0
- pytgcalls/types/calls/call.py +5 -3
- pytgcalls/types/calls/call_sources.py +4 -0
- pytgcalls/types/chats/group_call_participant.py +23 -0
- pytgcalls/types/py_object.py +9 -10
- pytgcalls/types/raw/audio_stream.py +3 -3
- pytgcalls/types/raw/stream.py +8 -4
- pytgcalls/types/raw/video_stream.py +5 -4
- pytgcalls/types/stream/__init__.py +14 -4
- pytgcalls/types/stream/device.py +36 -0
- pytgcalls/types/stream/direction.py +25 -0
- pytgcalls/types/stream/external_media.py +8 -0
- pytgcalls/types/stream/frame.py +26 -0
- pytgcalls/types/stream/media_stream.py +178 -107
- pytgcalls/types/stream/record_stream.py +99 -0
- pytgcalls/types/stream/stream_ended.py +32 -0
- pytgcalls/types/stream/stream_frames.py +20 -0
- py_tgcalls-2.0.5.dist-info/RECORD +0 -93
- pytgcalls/media_devices/screen_info.py +0 -45
- pytgcalls/types/stream/stream_audio_ended.py +0 -9
- pytgcalls/types/stream/stream_video_ended.py +0 -9
- {py_tgcalls-2.0.5.dist-info → py_tgcalls-2.1.0.dist-info}/LICENSE +0 -0
- {py_tgcalls-2.0.5.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
|
pytgcalls/types/py_object.py
CHANGED
|
@@ -13,16 +13,15 @@ class PyObject:
|
|
|
13
13
|
return repr(obj)
|
|
14
14
|
if isinstance(obj, Enum):
|
|
15
15
|
return repr(obj)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
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
|
-
|
|
12
|
+
media_source: MediaSource,
|
|
13
13
|
path: str,
|
|
14
14
|
parameters: AudioParameters = AudioParameters(),
|
|
15
15
|
):
|
|
16
|
-
self.
|
|
16
|
+
self.media_source: MediaSource = media_source
|
|
17
17
|
self.path: str = path
|
|
18
18
|
self.parameters: AudioParameters = parameters
|
pytgcalls/types/raw/stream.py
CHANGED
|
@@ -10,8 +10,12 @@ class Stream(PyObject):
|
|
|
10
10
|
@statictypes
|
|
11
11
|
def __init__(
|
|
12
12
|
self,
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
microphone: Optional[AudioStream] = None,
|
|
14
|
+
speaker: Optional[AudioStream] = None,
|
|
15
|
+
camera: Optional[VideoStream] = None,
|
|
16
|
+
screen: Optional[VideoStream] = None,
|
|
15
17
|
):
|
|
16
|
-
self.
|
|
17
|
-
self.
|
|
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
|
|
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
|
-
|
|
12
|
+
media_source: MediaSource,
|
|
12
13
|
path: str,
|
|
13
14
|
parameters: VideoParameters = VideoParameters(),
|
|
14
15
|
):
|
|
15
|
-
self.
|
|
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 .
|
|
4
|
-
from .
|
|
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
|
-
'
|
|
11
|
-
'
|
|
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,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
|
|
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
|
|
16
|
-
from ...media_devices import
|
|
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,
|
|
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.
|
|
48
|
-
audio_path: Optional[
|
|
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,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
self.
|
|
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,
|
|
88
|
-
self._audio_path =
|
|
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
|
-
|
|
98
|
-
if
|
|
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
|
-
|
|
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
|
-
|
|
115
|
-
if
|
|
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
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
self.
|
|
139
|
-
|
|
140
|
-
)
|
|
141
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
image_commands
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
181
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|