yta-video-opengl 0.0.22__py3-none-any.whl → 0.0.24__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 (33) hide show
  1. yta_video_opengl/editor.py +333 -0
  2. yta_video_opengl/nodes/__init__.py +32 -28
  3. yta_video_opengl/nodes/audio/__init__.py +164 -55
  4. yta_video_opengl/nodes/video/__init__.py +27 -1
  5. yta_video_opengl/nodes/video/{opengl.py → opengl/__init__.py} +8 -4
  6. yta_video_opengl/nodes/video/opengl/experimental.py +760 -0
  7. yta_video_opengl/tests.py +236 -358
  8. yta_video_opengl/utils.py +9 -421
  9. {yta_video_opengl-0.0.22.dist-info → yta_video_opengl-0.0.24.dist-info}/METADATA +2 -6
  10. yta_video_opengl-0.0.24.dist-info/RECORD +13 -0
  11. yta_video_opengl/audio.py +0 -219
  12. yta_video_opengl/classes.py +0 -1276
  13. yta_video_opengl/complete/__init__.py +0 -0
  14. yta_video_opengl/complete/frame_combinator.py +0 -204
  15. yta_video_opengl/complete/frame_generator.py +0 -319
  16. yta_video_opengl/complete/frame_wrapper.py +0 -135
  17. yta_video_opengl/complete/timeline.py +0 -571
  18. yta_video_opengl/complete/track/__init__.py +0 -500
  19. yta_video_opengl/complete/track/media/__init__.py +0 -222
  20. yta_video_opengl/complete/track/parts.py +0 -267
  21. yta_video_opengl/complete/track/utils.py +0 -78
  22. yta_video_opengl/media.py +0 -347
  23. yta_video_opengl/reader/__init__.py +0 -710
  24. yta_video_opengl/reader/cache/__init__.py +0 -253
  25. yta_video_opengl/reader/cache/audio.py +0 -195
  26. yta_video_opengl/reader/cache/utils.py +0 -48
  27. yta_video_opengl/reader/cache/video.py +0 -113
  28. yta_video_opengl/t.py +0 -233
  29. yta_video_opengl/video.py +0 -277
  30. yta_video_opengl/writer.py +0 -278
  31. yta_video_opengl-0.0.22.dist-info/RECORD +0 -31
  32. {yta_video_opengl-0.0.22.dist-info → yta_video_opengl-0.0.24.dist-info}/LICENSE +0 -0
  33. {yta_video_opengl-0.0.22.dist-info → yta_video_opengl-0.0.24.dist-info}/WHEEL +0 -0
File without changes
@@ -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,319 +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.full(
64
- shape = (size[1], size[0], 3),
65
- fill_value = (255, 255, 255),
66
- dtype = dtype
67
- )
68
-
69
- def full_red(
70
- self,
71
- size: tuple[int, int] = (1920, 1080),
72
- dtype: np.dtype = np.uint8
73
- ):
74
- """
75
- Get a numpy array that represents a full
76
- red frame of the given 'size' and with
77
- the given 'dtype'.
78
- """
79
- # TODO: I think 'ones' only work if dtype
80
- # is int
81
- return np.full(
82
- shape = (size[1], size[0], 3),
83
- fill_value = (255, 0, 0),
84
- dtype = dtype
85
- )
86
-
87
- class _BackgroundFrameGenerator:
88
- """
89
- Internal class to simplify the way we
90
- access to the generation of background
91
- frames form the general generator class.
92
- """
93
-
94
- def __init__(
95
- self
96
- ):
97
- self._frame_generator: _FrameGenerator = _FrameGenerator()
98
- """
99
- Shortcut to the FrameGenerator.
100
- """
101
-
102
- def full_black(
103
- self,
104
- size: tuple[int, int] = (1920, 1080),
105
- dtype: np.dtype = np.uint8,
106
- format: str = 'rgb24',
107
- pts: Union[int, None] = None,
108
- time_base: Union['Fraction', None] = None
109
- ) -> VideoFrame:
110
- """
111
- Get a video frame that is completely black
112
- and of the given 'size'.
113
- """
114
- return numpy_to_video_frame(
115
- frame = self._frame_generator.full_black(size, dtype),
116
- format = format,
117
- pts = pts,
118
- time_base = time_base
119
- )
120
-
121
- def full_white(
122
- self,
123
- size: tuple[int, int] = (1920, 1080),
124
- dtype: np.dtype = np.uint8,
125
- format: str = 'rgb24',
126
- pts: Union[int, None] = None,
127
- time_base: Union['Fraction', None] = None
128
- ) -> VideoFrame:
129
- """
130
- Get a video frame that is completely white
131
- and of the given 'size'.
132
- """
133
- return numpy_to_video_frame(
134
- frame = self._frame_generator.full_white(size, dtype),
135
- format = format,
136
- pts = pts,
137
- time_base = time_base
138
- )
139
-
140
- def full_red(
141
- self,
142
- size: tuple[int, int] = (1920, 1080),
143
- dtype: np.dtype = np.uint8,
144
- format: str = 'rgb24',
145
- pts: Union[int, None] = None,
146
- time_base: Union['Fraction', None] = None
147
- ) -> VideoFrame:
148
- """
149
- Get a video frame that is completely red
150
- and of the given 'size'.
151
- """
152
- return numpy_to_video_frame(
153
- frame = self._frame_generator.full_red(size, dtype),
154
- format = format,
155
- pts = pts,
156
- time_base = time_base
157
- )
158
-
159
- class VideoFrameGenerator:
160
- """
161
- Class to wrap the functionality related to
162
- generating a pyav video frame.
163
-
164
- This class is useful when we need to
165
- generate the black background for empty
166
- parts within the tracks and in other
167
- situations.
168
- """
169
-
170
- def __init__(
171
- self
172
- ):
173
- self.background = _BackgroundFrameGenerator()
174
- """
175
- Shortcut to the background creation.
176
- """
177
-
178
- def numpy_to_video_frame(
179
- frame: np.ndarray,
180
- format: str = 'rgb24',
181
- pts: Union[int, None] = None,
182
- time_base: Union['Fraction', None] = None
183
- ) -> VideoFrame:
184
- """
185
- Transform the given numpy 'frame' into a
186
- pyav video frame with the given 'format'
187
- and also the 'pts' and/or 'time_base' if
188
- provided.
189
- """
190
- frame = VideoFrame.from_ndarray(
191
- # TODO: What if we want alpha (?)
192
- array = frame,
193
- format = format
194
- )
195
-
196
- if pts is not None:
197
- frame.pts = pts
198
-
199
- if time_base is not None:
200
- frame.time_base = time_base
201
-
202
- return frame
203
-
204
- class AudioFrameGenerator:
205
- """
206
- Class to wrap the functionality related to
207
- generating a pyav audio frame.
208
-
209
- This class is useful when we need to
210
- generate the silent audio for empty parts
211
- within the tracks and in other situations.
212
- """
213
-
214
- def silent(
215
- self,
216
- sample_rate: int,
217
- layout = 'stereo',
218
- number_of_samples: int = 1024,
219
- format = 's16',
220
- pts: Union[int, None] = None,
221
- time_base: Union['Fraction', None] = None
222
- ) -> AudioFrame:
223
- """
224
- Get an audio frame that is completely silent.
225
- This is useful when we want to fill the empty
226
- parts of our tracks.
227
- """
228
- dtype = audio_format_to_dtype(format)
229
-
230
- if dtype is None:
231
- raise Exception(f'The format "{format}" is not accepted.')
232
-
233
- # TODO: Is this raising exception if the
234
- # 'layout' is not valid? I think yes (?)
235
- number_of_channels = len(AudioLayout(layout).channels)
236
-
237
- # TODO: I leave these comments below because
238
- # I'm not sure what is true and what is not
239
- # so, until it is more clear... here it is:
240
- # For packed (or planar) formats we apply:
241
- # (1, samples * channels). This is the same
242
- # amount of data but planar, in 1D only
243
- # TODO: This wasn't in the previous version
244
- # and it was working, we were sending the
245
- # same 'number_of_samples' even when 'fltp'
246
- # that includes the 'p'
247
- # TODO: This is making the audio last 2x
248
- # if 'p' in format:
249
- # number_of_samples *= number_of_channels
250
-
251
- silent_numpy_array = np.zeros(
252
- shape = (number_of_channels, number_of_samples),
253
- dtype = dtype
254
- )
255
-
256
- return numpy_to_audio_frame(
257
- frame = silent_numpy_array,
258
- sample_rate = sample_rate,
259
- layout = layout,
260
- format = format,
261
- pts = pts,
262
- time_base = time_base
263
- )
264
-
265
- def numpy_to_audio_frame(
266
- frame: np.ndarray,
267
- sample_rate: int,
268
- layout: str = 'stereo',
269
- format: str = ' s16',
270
- pts: Union[int, None] = None,
271
- time_base: Union['Fraction', None] = None
272
- ) -> AudioFrame:
273
- """
274
- Transform the given numpy 'frame' into a
275
- pyav audio frame with the given 'sample_rate',
276
- 'layout' and 'format, and also the 'pts
277
- and/or 'time_base' if provided.
278
- """
279
- frame = AudioFrame.from_ndarray(
280
- array = frame,
281
- format = format,
282
- layout = layout
283
- )
284
-
285
- frame.sample_rate = sample_rate
286
-
287
- if pts is not None:
288
- frame.pts = pts
289
-
290
- if time_base is not None:
291
- frame.time_base = time_base
292
-
293
- return frame
294
-
295
- # TODO: Maybe transform into a Enum (?)
296
- def audio_format_to_dtype(
297
- audio_format: str
298
- ) -> Union[np.dtype, None]:
299
- """
300
- Transform the given 'audio_format' into
301
- the corresponding numpy dtype value. If
302
- the 'audio_format' is not accepted this
303
- method will return None.
304
-
305
- This method must be used when we are
306
- building the numpy array that will be
307
- used to build a pyav audio frame because
308
- the pyav 'audio_format' need a specific
309
- np.dtype to be built.
310
-
311
- For example, 's16' will return 'np.int16'
312
- and 'fltp' will return 'np.float32'.
313
- """
314
- return {
315
- 's16': np.int16,
316
- 'flt': np.float32,
317
- 'fltp': np.float32
318
- }.get(audio_format, None)
319
-
@@ -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()