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.
- yta_video_opengl/__init__.py +3 -2
- yta_video_opengl/classes.py +1 -1
- yta_video_opengl/nodes/__init__.py +4 -4
- yta_video_opengl/tests.py +424 -545
- yta_video_opengl/utils.py +9 -421
- {yta_video_opengl-0.0.21.dist-info → yta_video_opengl-0.0.23.dist-info}/METADATA +1 -5
- yta_video_opengl-0.0.23.dist-info/RECORD +12 -0
- yta_video_opengl/audio.py +0 -219
- yta_video_opengl/complete/__init__.py +0 -0
- yta_video_opengl/complete/frame_combinator.py +0 -204
- yta_video_opengl/complete/frame_generator.py +0 -318
- yta_video_opengl/complete/frame_wrapper.py +0 -135
- yta_video_opengl/complete/timeline.py +0 -571
- yta_video_opengl/complete/track/__init__.py +0 -500
- yta_video_opengl/complete/track/media/__init__.py +0 -222
- yta_video_opengl/complete/track/parts.py +0 -267
- yta_video_opengl/complete/track/utils.py +0 -78
- yta_video_opengl/reader/__init__.py +0 -710
- yta_video_opengl/reader/cache/__init__.py +0 -253
- yta_video_opengl/reader/cache/audio.py +0 -195
- yta_video_opengl/reader/cache/utils.py +0 -48
- yta_video_opengl/reader/cache/video.py +0 -113
- yta_video_opengl/t.py +0 -233
- yta_video_opengl/video.py +0 -277
- yta_video_opengl/writer.py +0 -278
- yta_video_opengl-0.0.21.dist-info/RECORD +0 -30
- {yta_video_opengl-0.0.21.dist-info → yta_video_opengl-0.0.23.dist-info}/LICENSE +0 -0
- {yta_video_opengl-0.0.21.dist-info → yta_video_opengl-0.0.23.dist-info}/WHEEL +0 -0
@@ -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()
|