yta-video-opengl 0.0.21__py3-none-any.whl → 0.0.23__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,204 +0,0 @@
1
- """
2
- TODO: I don't like the name nor the
3
- location of this file, but it is here
4
- to encapsulate some functionality
5
- related to combining video frames.
6
-
7
- Module to contain methods that combine
8
- video frames. Call them with the 2
9
- frames you want to combine and you
10
- will get the combined frame as return.
11
- """
12
- from av.audio.resampler import AudioResampler
13
- from av.audio.frame import AudioFrame
14
-
15
- import numpy as np
16
-
17
-
18
- class VideoFrameCombinator:
19
- """
20
- Class to wrap the functionality related
21
- to combine different video frames.
22
- """
23
-
24
- @staticmethod
25
- def blend_alpha(
26
- bottom: np.ndarray,
27
- top: np.ndarray,
28
- alpha = 0.5
29
- ):
30
- return (alpha * top + (1 - alpha) * bottom).astype(np.uint8)
31
-
32
- @staticmethod
33
- def blend_add(
34
- bottom: np.ndarray,
35
- top: np.ndarray
36
- ):
37
- """
38
- Aclara la imagen combinada, como si superpusieras dos proyectores de luz.
39
- """
40
- return np.clip(bottom.astype(np.int16) + top.astype(np.int16), 0, 255).astype(np.uint8)
41
-
42
- @staticmethod
43
- def blend_multiply(
44
- bottom: np.ndarray,
45
- top: np.ndarray
46
- ):
47
- """
48
- Oscurece, como proyectar dos transparencias juntas.
49
- """
50
- return ((bottom.astype(np.float32) * top.astype(np.float32)) / 255).astype(np.uint8)
51
-
52
- @staticmethod
53
- def blend_screen(
54
- bottom: np.ndarray,
55
- top: np.ndarray
56
- ):
57
- """
58
- Hace lo contrario a Multiply, aclara la imagen.
59
- """
60
- return (255 - ((255 - bottom.astype(np.float32)) * (255 - top.astype(np.float32)) / 255)).astype(np.uint8)
61
-
62
- @staticmethod
63
- def blend_overlay(
64
- bottom: np.ndarray,
65
- top: np.ndarray
66
- ):
67
- """
68
- Mezcla entre Multiply y Screen según el brillo de cada píxel.
69
- """
70
- b = bottom.astype(np.float32) / 255
71
- t = top.astype(np.float32) / 255
72
- mask = b < 0.5
73
- result = np.zeros_like(b)
74
- result[mask] = 2 * b[mask] * t[mask]
75
- result[~mask] = 1 - 2 * (1 - b[~mask]) * (1 - t[~mask])
76
-
77
- return (result * 255).astype(np.uint8)
78
-
79
- @staticmethod
80
- def blend_difference(
81
- bottom: np.ndarray,
82
- top: np.ndarray
83
- ):
84
- """
85
- Resalta las diferencias entre los dos frames.
86
- """
87
- return np.abs(bottom.astype(np.int16) - top.astype(np.int16)).astype(np.uint8)
88
-
89
- # TODO: This one needs a mask, thats why
90
- # it is commented
91
- # @staticmethod
92
- # def blend_mask(
93
- # bottom,
94
- # top,
95
- # mask
96
- # ):
97
- # """
98
- # En lugar de un alpha fijo, puedes pasar una máscara (por ejemplo, un degradado o un canal alfa real)
99
-
100
- # mask: array float32 entre 0 y 1, mismo tamaño que frame.
101
- # """
102
- # return (mask * top + (1 - mask) * bottom).astype(np.uint8)
103
-
104
- class AudioFrameCombinator:
105
- """
106
- Class to wrap the functionality related
107
- to combine different audio frames.
108
- """
109
-
110
- @staticmethod
111
- def sum_tracks_frames(
112
- tracks_frames: list[AudioFrame],
113
- sample_rate: int = 44100,
114
- layout: str = 'stereo',
115
- format: str = 'fltp',
116
- do_normalize: bool = True
117
- ) -> AudioFrame:
118
- """
119
- Sum all the audio frames from the different
120
- tracks that are given in the 'tracks_frames'
121
- list (each column is a single audio frame of
122
- a track). This must be a list that should
123
- come from a converted matrix that was
124
- representing each track in a row and the
125
- different audio frames for that track on each
126
- column.
127
-
128
- This method is to sum audio frames of one
129
- specific 't' time moment of a video.
130
-
131
- The output will be the sum of all the audio
132
- frames and it will be normalized to avoid
133
- distortion if 'do_normalize' is True (it is
134
- recommended).
135
- """
136
- if len(tracks_frames) == 0:
137
- raise Exception('The "tracks_frames" list of audio frames is empty.')
138
-
139
- arrays = []
140
- resampler: AudioResampler = AudioResampler(
141
- format = format,
142
- layout = layout,
143
- rate = sample_rate
144
- )
145
-
146
- for track_frame in tracks_frames:
147
- # Resample to output format
148
- # TODO: What if the resampler creates more
149
- # than one single frame? I don't know what
150
- # to do... I'll see when it happens
151
- track_frame = resampler.resample(track_frame)
152
-
153
- if len(track_frame) > 1:
154
- print('[ ! ] The resampler has given more than 1 frame...')
155
-
156
- track_frame_array = track_frame[0].to_ndarray()
157
-
158
- # Transform to 'float32' [-1, 1]
159
- # TODO: I think this is because the output
160
- # is 'fltp' but we have more combinations
161
- # so this must be refactored
162
- if track_frame_array.dtype == np.int16:
163
- track_frame_array = track_frame_array.astype(np.float32) / 32768.0
164
- elif track_frame_array.dtype != np.float32:
165
- track_frame_array = track_frame_array.astype(np.float32)
166
-
167
- # Mono to stereo if needed
168
- # TODO: What if source is 'stereo' and we
169
- # want mono (?)
170
- if (
171
- track_frame_array.shape[0] == 1 and
172
- layout == 'stereo'
173
- ):
174
- track_frame_array = np.repeat(track_frame_array, 2, axis = 0)
175
-
176
- arrays.append(track_frame_array)
177
-
178
- # Same length and fill with zeros if needed
179
- max_len = max(a.shape[1] for a in arrays)
180
- stacked = []
181
- for a in arrays:
182
- # TODO: Again, this 'float32' is because output
183
- # is 'fltp' I think...
184
- buf = np.zeros((a.shape[0], max_len), dtype = np.float32)
185
- buf[:, :a.shape[1]] = a
186
- stacked.append(buf)
187
-
188
- # Sum all the sounds
189
- mix = np.sum(stacked, axis = 0)
190
- if do_normalize:
191
- # Avoid distortion and saturation
192
- mix /= len(stacked)
193
-
194
- # Avoid clipping
195
- mix = np.clip(mix, -1.0, 1.0)
196
-
197
- out = AudioFrame.from_ndarray(
198
- array = mix,
199
- format = format,
200
- layout = layout
201
- )
202
- out.sample_rate = sample_rate
203
-
204
- return out
@@ -1,318 +0,0 @@
1
- """
2
- The video frames must be built using the
3
- (height, width) size when giving the numpy
4
- array that will be used for it. We will
5
- receive the values as (width, height) but
6
- we will invert them when needed.
7
-
8
- The frames that come from an empty part
9
- are flagged with the .metadata attribute
10
- 'is_from_empty_part' so we can recognize
11
- them and ignore when combining on the
12
- timeline. We have that metadata in the
13
- wrapper class we created.
14
-
15
- TODO: Check because we have a similar
16
- module in other project or projects.
17
- """
18
- from av.video.frame import VideoFrame
19
- from av.audio.frame import AudioFrame
20
- from av.audio.layout import AudioLayout
21
- from typing import Union
22
-
23
- import numpy as np
24
-
25
-
26
- class _FrameGenerator:
27
- """
28
- Class to generate frames as numpy arrays.
29
- """
30
-
31
- # TODO: I have some library doing this with
32
- # colors and numpy frames, so please refactor
33
-
34
- def full_black(
35
- self,
36
- size: tuple[int, int] = (1920, 1080),
37
- dtype: np.dtype = np.uint8
38
- ):
39
- """
40
- Get a numpy array that represents a full
41
- black frame of the given 'size' and with
42
- the given 'dtype'.
43
- """
44
- # TODO: I think 'zeros' only work if dtype
45
- # is int
46
- return np.zeros(
47
- shape = (size[1], size[0], 3),
48
- dtype = dtype
49
- )
50
-
51
- def full_white(
52
- self,
53
- size: tuple[int, int] = (1920, 1080),
54
- dtype: np.dtype = np.uint8
55
- ):
56
- """
57
- Get a numpy array that represents a full
58
- black frame of the given 'size' and with
59
- the given 'dtype'.
60
- """
61
- # TODO: I think 'ones' only work if dtype
62
- # is int
63
- return np.ones(
64
- shape = (size[1], size[0], 3),
65
- dtype = dtype
66
- )
67
-
68
- def full_red(
69
- self,
70
- size: tuple[int, int] = (1920, 1080),
71
- dtype: np.dtype = np.uint8
72
- ):
73
- """
74
- Get a numpy array that represents a full
75
- red frame of the given 'size' and with
76
- the given 'dtype'.
77
- """
78
- # TODO: I think 'ones' only work if dtype
79
- # is int
80
- return np.full(
81
- shape = (size[1], size[0], 3),
82
- fill_value = (255, 0, 0),
83
- dtype = dtype
84
- )
85
-
86
- class _BackgroundFrameGenerator:
87
- """
88
- Internal class to simplify the way we
89
- access to the generation of background
90
- frames form the general generator class.
91
- """
92
-
93
- def __init__(
94
- self
95
- ):
96
- self._frame_generator: _FrameGenerator = _FrameGenerator()
97
- """
98
- Shortcut to the FrameGenerator.
99
- """
100
-
101
- def full_black(
102
- self,
103
- size: tuple[int, int] = (1920, 1080),
104
- dtype: np.dtype = np.uint8,
105
- format: str = 'rgb24',
106
- pts: Union[int, None] = None,
107
- time_base: Union['Fraction', None] = None
108
- ) -> VideoFrame:
109
- """
110
- Get a video frame that is completely black
111
- and of the given 'size'.
112
- """
113
- return numpy_to_video_frame(
114
- frame = self._frame_generator.full_black(size, dtype),
115
- format = format,
116
- pts = pts,
117
- time_base = time_base
118
- )
119
-
120
- def full_white(
121
- self,
122
- size: tuple[int, int] = (1920, 1080),
123
- dtype: np.dtype = np.uint8,
124
- format: str = 'rgb24',
125
- pts: Union[int, None] = None,
126
- time_base: Union['Fraction', None] = None
127
- ) -> VideoFrame:
128
- """
129
- Get a video frame that is completely white
130
- and of the given 'size'.
131
- """
132
- return numpy_to_video_frame(
133
- frame = self._frame_generator.full_white(size, dtype),
134
- format = format,
135
- pts = pts,
136
- time_base = time_base
137
- )
138
-
139
- def full_red(
140
- self,
141
- size: tuple[int, int] = (1920, 1080),
142
- dtype: np.dtype = np.uint8,
143
- format: str = 'rgb24',
144
- pts: Union[int, None] = None,
145
- time_base: Union['Fraction', None] = None
146
- ) -> VideoFrame:
147
- """
148
- Get a video frame that is completely red
149
- and of the given 'size'.
150
- """
151
- return numpy_to_video_frame(
152
- frame = self._frame_generator.full_red(size, dtype),
153
- format = format,
154
- pts = pts,
155
- time_base = time_base
156
- )
157
-
158
- class VideoFrameGenerator:
159
- """
160
- Class to wrap the functionality related to
161
- generating a pyav video frame.
162
-
163
- This class is useful when we need to
164
- generate the black background for empty
165
- parts within the tracks and in other
166
- situations.
167
- """
168
-
169
- def __init__(
170
- self
171
- ):
172
- self.background = _BackgroundFrameGenerator()
173
- """
174
- Shortcut to the background creation.
175
- """
176
-
177
- def numpy_to_video_frame(
178
- frame: np.ndarray,
179
- format: str = 'rgb24',
180
- pts: Union[int, None] = None,
181
- time_base: Union['Fraction', None] = None
182
- ) -> VideoFrame:
183
- """
184
- Transform the given numpy 'frame' into a
185
- pyav video frame with the given 'format'
186
- and also the 'pts' and/or 'time_base' if
187
- provided.
188
- """
189
- frame = VideoFrame.from_ndarray(
190
- # TODO: What if we want alpha (?)
191
- array = frame,
192
- format = format
193
- )
194
-
195
- if pts is not None:
196
- frame.pts = pts
197
-
198
- if time_base is not None:
199
- frame.time_base = time_base
200
-
201
- return frame
202
-
203
- class AudioFrameGenerator:
204
- """
205
- Class to wrap the functionality related to
206
- generating a pyav audio frame.
207
-
208
- This class is useful when we need to
209
- generate the silent audio for empty parts
210
- within the tracks and in other situations.
211
- """
212
-
213
- def silent(
214
- self,
215
- sample_rate: int,
216
- layout = 'stereo',
217
- number_of_samples: int = 1024,
218
- format = 's16',
219
- pts: Union[int, None] = None,
220
- time_base: Union['Fraction', None] = None
221
- ) -> AudioFrame:
222
- """
223
- Get an audio frame that is completely silent.
224
- This is useful when we want to fill the empty
225
- parts of our tracks.
226
- """
227
- dtype = audio_format_to_dtype(format)
228
-
229
- if dtype is None:
230
- raise Exception(f'The format "{format}" is not accepted.')
231
-
232
- # TODO: Is this raising exception if the
233
- # 'layout' is not valid? I think yes (?)
234
- number_of_channels = len(AudioLayout(layout).channels)
235
-
236
- # TODO: I leave these comments below because
237
- # I'm not sure what is true and what is not
238
- # so, until it is more clear... here it is:
239
- # For packed (or planar) formats we apply:
240
- # (1, samples * channels). This is the same
241
- # amount of data but planar, in 1D only
242
- # TODO: This wasn't in the previous version
243
- # and it was working, we were sending the
244
- # same 'number_of_samples' even when 'fltp'
245
- # that includes the 'p'
246
- # TODO: This is making the audio last 2x
247
- # if 'p' in format:
248
- # number_of_samples *= number_of_channels
249
-
250
- silent_numpy_array = np.zeros(
251
- shape = (number_of_channels, number_of_samples),
252
- dtype = dtype
253
- )
254
-
255
- return numpy_to_audio_frame(
256
- frame = silent_numpy_array,
257
- sample_rate = sample_rate,
258
- layout = layout,
259
- format = format,
260
- pts = pts,
261
- time_base = time_base
262
- )
263
-
264
- def numpy_to_audio_frame(
265
- frame: np.ndarray,
266
- sample_rate: int,
267
- layout: str = 'stereo',
268
- format: str = ' s16',
269
- pts: Union[int, None] = None,
270
- time_base: Union['Fraction', None] = None
271
- ) -> AudioFrame:
272
- """
273
- Transform the given numpy 'frame' into a
274
- pyav audio frame with the given 'sample_rate',
275
- 'layout' and 'format, and also the 'pts
276
- and/or 'time_base' if provided.
277
- """
278
- frame = AudioFrame.from_ndarray(
279
- array = frame,
280
- format = format,
281
- layout = layout
282
- )
283
-
284
- frame.sample_rate = sample_rate
285
-
286
- if pts is not None:
287
- frame.pts = pts
288
-
289
- if time_base is not None:
290
- frame.time_base = time_base
291
-
292
- return frame
293
-
294
- # TODO: Maybe transform into a Enum (?)
295
- def audio_format_to_dtype(
296
- audio_format: str
297
- ) -> Union[np.dtype, None]:
298
- """
299
- Transform the given 'audio_format' into
300
- the corresponding numpy dtype value. If
301
- the 'audio_format' is not accepted this
302
- method will return None.
303
-
304
- This method must be used when we are
305
- building the numpy array that will be
306
- used to build a pyav audio frame because
307
- the pyav 'audio_format' need a specific
308
- np.dtype to be built.
309
-
310
- For example, 's16' will return 'np.int16'
311
- and 'fltp' will return 'np.float32'.
312
- """
313
- return {
314
- 's16': np.int16,
315
- 'flt': np.float32,
316
- 'fltp': np.float32
317
- }.get(audio_format, None)
318
-
@@ -1,135 +0,0 @@
1
- from yta_validation.parameter import ParameterValidator
2
- from av.video.frame import VideoFrame
3
- from av.audio.frame import AudioFrame
4
- from typing import Union
5
-
6
-
7
- IS_FROM_EMPTY_PART_METADATA = 'is_from_empty_part'
8
- """
9
- Metadata key to indicate if the frame
10
- has been generated by an empty part
11
- and should be ignored when trying to
12
- combine with others.
13
- """
14
-
15
- class _FrameWrappedBase:
16
- """
17
- Class to wrap video and audio frames from
18
- the pyav library but to support a metadata
19
- field to inject some information we need
20
- when processing and combining them.
21
- """
22
-
23
- @property
24
- def is_from_empty_part(
25
- self
26
- ) -> bool:
27
- """
28
- Flag to indicate if the frame comes from
29
- an empty part or not, that will be done
30
- by checking the 'is_from_empty_part'
31
- attribute in the metadata.
32
- """
33
- return IS_FROM_EMPTY_PART_METADATA in self.metadata
34
-
35
- def __init__(
36
- self,
37
- frame,
38
- metadata: dict = {}
39
- ):
40
- ParameterValidator.validate_mandatory_instance_of('frame', frame, [VideoFrame, AudioFrame])
41
- ParameterValidator.validate_mandatory_dict('metadata', metadata)
42
-
43
- self._frame: Union[VideoFrame, AudioFrame] = frame
44
- """
45
- The VideoFrame or AudioFrame pyav instance.
46
- """
47
- self.metadata: dict = metadata or {}
48
- """
49
- The metadata we want to include with the
50
- frame.
51
- """
52
-
53
- def __getattr__(
54
- self,
55
- name
56
- ):
57
- return getattr(self._frame, name)
58
-
59
- def __setattr__(
60
- self,
61
- name,
62
- value
63
- ):
64
- super().__setattr__(name, value)
65
- #setattr(self._frame, name, value)
66
- # if name in ('_frame', 'metadata'):
67
- # super().__setattr__(name, value)
68
- # else:
69
- # setattr(self._frame, name, value)
70
-
71
- def __repr__(
72
- self
73
- ):
74
- cname = self.__class__.__name__
75
- return f'<{cname} metadata={self.metadata} frame={self._frame!r}>'
76
-
77
- def set_as_from_empty_part(
78
- self
79
- ) -> None:
80
- """
81
- Add the metadata information to indicate
82
- that this is a frame that comes from an
83
- empty part.
84
- """
85
- self.metadata[IS_FROM_EMPTY_PART_METADATA] = 'True'
86
-
87
- def unwrap(
88
- self
89
- ):
90
- """
91
- Get the original frame instance.
92
- """
93
- return self._frame
94
-
95
- class VideoFrameWrapped(_FrameWrappedBase):
96
- """
97
- Class to wrap video frames from the pyav
98
- library but to support a metadata field
99
- to inject some information we need when
100
- processing and combining them.
101
- """
102
-
103
- def __init__(
104
- self,
105
- frame: VideoFrame,
106
- metadata: dict = {},
107
- is_from_empty_part: bool = False
108
- ):
109
- ParameterValidator.validate_mandatory_instance_of('frame', frame, VideoFrame)
110
-
111
- super().__init__(frame, metadata)
112
-
113
- if is_from_empty_part:
114
- self.set_as_from_empty_part()
115
-
116
- class AudioFrameWrapped(_FrameWrappedBase):
117
- """
118
- Class to wrap audio frames from the pyav
119
- library but to support a metadata field
120
- to inject some information we need when
121
- processing and combining them.
122
- """
123
-
124
- def __init__(
125
- self,
126
- frame: AudioFrame,
127
- metadata: dict = {},
128
- is_from_empty_part: bool = False
129
- ):
130
- ParameterValidator.validate_mandatory_instance_of('frame', frame, AudioFrame)
131
-
132
- super().__init__(frame, metadata)
133
-
134
- if is_from_empty_part:
135
- self.set_as_from_empty_part()