py-tgcalls 2.0.5__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.
- {py_tgcalls-2.0.5.dist-info → py_tgcalls-2.1.0.dev1.dist-info}/METADATA +6 -8
- {py_tgcalls-2.0.5.dist-info → py_tgcalls-2.1.0.dev1.dist-info}/RECORD +41 -35
- {py_tgcalls-2.0.5.dist-info → py_tgcalls-2.1.0.dev1.dist-info}/WHEEL +1 -1
- pytgcalls/__init__.py +2 -0
- pytgcalls/__version__.py +1 -1
- pytgcalls/filters.py +49 -8
- 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/stream/__init__.py +4 -2
- pytgcalls/methods/stream/play.py +73 -11
- pytgcalls/methods/stream/record.py +41 -0
- pytgcalls/methods/stream/{played_time.py → time.py} +5 -3
- pytgcalls/methods/utilities/call_holder.py +5 -2
- pytgcalls/methods/utilities/start.py +118 -13
- pytgcalls/methods/utilities/stream_params.py +63 -22
- pytgcalls/mtproto/bridged_client.py +34 -2
- pytgcalls/mtproto/client_cache.py +46 -16
- pytgcalls/mtproto/hydrogram_client.py +53 -11
- pytgcalls/mtproto/mtproto_client.py +30 -4
- pytgcalls/mtproto/pyrogram_client.py +53 -11
- pytgcalls/mtproto/telethon_client.py +61 -19
- pytgcalls/scaffold.py +6 -0
- pytgcalls/types/__init__.py +10 -4
- pytgcalls/types/calls/call.py +5 -3
- pytgcalls/types/chats/group_call_participant.py +21 -0
- pytgcalls/types/raw/audio_stream.py +3 -3
- pytgcalls/types/raw/stream.py +8 -4
- pytgcalls/types/raw/video_stream.py +3 -3
- pytgcalls/types/stream/__init__.py +10 -4
- pytgcalls/types/stream/device.py +24 -0
- pytgcalls/types/stream/direction.py +18 -0
- pytgcalls/types/stream/media_stream.py +35 -29
- pytgcalls/types/stream/record_stream.py +93 -0
- pytgcalls/types/stream/stream_ended.py +32 -0
- pytgcalls/types/stream/stream_frame.py +35 -0
- 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.dev1.dist-info}/LICENSE +0 -0
- {py_tgcalls-2.0.5.dist-info → py_tgcalls-2.1.0.dev1.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from ntgcalls import
|
|
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
|
-
|
|
11
|
+
media_source: MediaSource,
|
|
12
12
|
path: str,
|
|
13
13
|
parameters: VideoParameters = VideoParameters(),
|
|
14
14
|
):
|
|
15
|
-
self.
|
|
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 .
|
|
4
|
-
from .
|
|
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
|
-
'
|
|
11
|
-
'
|
|
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
|
|
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
|
|
16
|
-
from ...media_devices import
|
|
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,
|
|
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,
|
|
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,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
self.
|
|
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,
|
|
88
|
-
|
|
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
|
-
|
|
97
|
+
microphone=None
|
|
98
98
|
if self._audio_flags & MediaStream.Flags.IGNORE else
|
|
99
99
|
AudioStream(
|
|
100
|
-
|
|
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
|
-
|
|
120
|
+
camera=None
|
|
115
121
|
if self._video_flags & MediaStream.Flags.IGNORE else
|
|
116
122
|
VideoStream(
|
|
117
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|