yta-video-opengl 0.0.6__tar.gz → 0.0.7__tar.gz
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-0.0.6 → yta_video_opengl-0.0.7}/PKG-INFO +1 -1
- {yta_video_opengl-0.0.6 → yta_video_opengl-0.0.7}/pyproject.toml +1 -1
- {yta_video_opengl-0.0.6 → yta_video_opengl-0.0.7}/src/yta_video_opengl/classes.py +256 -72
- {yta_video_opengl-0.0.6 → yta_video_opengl-0.0.7}/src/yta_video_opengl/reader/__init__.py +66 -11
- {yta_video_opengl-0.0.6 → yta_video_opengl-0.0.7}/src/yta_video_opengl/tests.py +25 -6
- yta_video_opengl-0.0.7/src/yta_video_opengl/utils.py +100 -0
- {yta_video_opengl-0.0.6 → yta_video_opengl-0.0.7}/src/yta_video_opengl/writer.py +0 -1
- yta_video_opengl-0.0.6/src/yta_video_opengl/utils.py +0 -24
- {yta_video_opengl-0.0.6 → yta_video_opengl-0.0.7}/LICENSE +0 -0
- {yta_video_opengl-0.0.6 → yta_video_opengl-0.0.7}/README.md +0 -0
- {yta_video_opengl-0.0.6 → yta_video_opengl-0.0.7}/src/yta_video_opengl/__init__.py +0 -0
- {yta_video_opengl-0.0.6 → yta_video_opengl-0.0.7}/src/yta_video_opengl/reader/cache.py +0 -0
@@ -8,7 +8,8 @@ so we use different triangles to build
|
|
8
8
|
our shapes (quad normally).
|
9
9
|
"""
|
10
10
|
from yta_validation.parameter import ParameterValidator
|
11
|
-
from
|
11
|
+
from yta_validation import PythonValidator
|
12
|
+
from yta_video_opengl.utils import frame_to_texture, get_fullscreen_quad_vao
|
12
13
|
from abc import ABC, abstractmethod
|
13
14
|
from typing import Union
|
14
15
|
|
@@ -24,29 +25,48 @@ class _Uniforms:
|
|
24
25
|
"""
|
25
26
|
|
26
27
|
@property
|
27
|
-
def
|
28
|
+
def uniforms(
|
28
29
|
self
|
29
|
-
):
|
30
|
+
) -> dict:
|
30
31
|
"""
|
31
|
-
|
32
|
+
The uniforms in the program, as a dict, in
|
33
|
+
the format `{key, value}`.
|
32
34
|
"""
|
33
|
-
return
|
35
|
+
return {
|
36
|
+
key: self.program[key].value
|
37
|
+
for key in self.program
|
38
|
+
if PythonValidator.is_instance_of(self.program[key], moderngl.Uniform)
|
39
|
+
}
|
34
40
|
|
35
41
|
def __init__(
|
36
42
|
self,
|
37
|
-
|
43
|
+
program: moderngl.Program
|
38
44
|
):
|
39
|
-
self.
|
45
|
+
self.program: moderngl.Program = program
|
46
|
+
"""
|
47
|
+
The program instance this handler class
|
48
|
+
belongs to.
|
49
|
+
"""
|
50
|
+
|
51
|
+
def get(
|
52
|
+
self,
|
53
|
+
name: str
|
54
|
+
) -> Union[any, None]:
|
40
55
|
"""
|
41
|
-
|
42
|
-
|
56
|
+
Get the value of the uniform with the
|
57
|
+
given 'name'.
|
43
58
|
"""
|
59
|
+
return self.uniforms.get(name, None)
|
44
60
|
|
61
|
+
# TODO: I need to refactor these method to
|
62
|
+
# accept a **kwargs maybe, or to auto-detect
|
63
|
+
# the type and add the uniform as it must be
|
64
|
+
# done
|
45
65
|
def set(
|
46
66
|
self,
|
47
67
|
name: str,
|
48
68
|
value
|
49
|
-
) -> '
|
69
|
+
) -> '_Uniforms':
|
50
70
|
"""
|
51
71
|
Set the provided 'value' to the normal type
|
52
72
|
uniform with the given 'name'. Here you have
|
@@ -59,13 +79,13 @@ class _Uniforms:
|
|
59
79
|
if name in self.program:
|
60
80
|
self.program[name].value = value
|
61
81
|
|
62
|
-
return self
|
82
|
+
return self
|
63
83
|
|
64
84
|
def set_vec(
|
65
85
|
self,
|
66
86
|
name: str,
|
67
87
|
values
|
68
|
-
) -> '
|
88
|
+
) -> '_Uniforms':
|
69
89
|
"""
|
70
90
|
Set the provided 'value' to the normal type
|
71
91
|
uniform with the given 'name'. Here you have
|
@@ -78,11 +98,13 @@ class _Uniforms:
|
|
78
98
|
if name in self.program:
|
79
99
|
self.program[name].write(np.array(values, dtype = 'f4').tobytes())
|
80
100
|
|
101
|
+
return self
|
102
|
+
|
81
103
|
def set_mat(
|
82
104
|
self,
|
83
105
|
name: str,
|
84
106
|
value
|
85
|
-
) -> '
|
107
|
+
) -> '_Uniforms':
|
86
108
|
"""
|
87
109
|
Set the provided 'value' to a `matN` type
|
88
110
|
uniform with the given 'name'. The 'value'
|
@@ -99,16 +121,26 @@ class _Uniforms:
|
|
99
121
|
if name in self.program:
|
100
122
|
self.program[name].write(value)
|
101
123
|
|
102
|
-
return self
|
124
|
+
return self
|
125
|
+
|
126
|
+
def print(
|
127
|
+
self
|
128
|
+
) -> '_Uniforms':
|
129
|
+
"""
|
130
|
+
Print the defined uniforms in console.
|
131
|
+
"""
|
132
|
+
for key, value in self.uniforms.items():
|
133
|
+
print(f'"{key}": {str(value)}')
|
103
134
|
|
104
|
-
class
|
135
|
+
class BaseNode:
|
105
136
|
"""
|
106
|
-
|
107
|
-
|
137
|
+
The basic class of a node to manipulate frames
|
138
|
+
as opengl textures. This node will process the
|
139
|
+
frame as an input texture and will generate
|
140
|
+
also a texture as the output.
|
108
141
|
|
109
|
-
|
110
|
-
|
111
|
-
one by one.
|
142
|
+
Nodes can be chained and the result from one
|
143
|
+
node can be applied on another node.
|
112
144
|
"""
|
113
145
|
|
114
146
|
@property
|
@@ -117,7 +149,7 @@ class FrameShaderBase(ABC):
|
|
117
149
|
self
|
118
150
|
) -> str:
|
119
151
|
"""
|
120
|
-
|
152
|
+
The code of the vertex shader.
|
121
153
|
"""
|
122
154
|
pass
|
123
155
|
|
@@ -127,47 +159,211 @@ class FrameShaderBase(ABC):
|
|
127
159
|
self
|
128
160
|
) -> str:
|
129
161
|
"""
|
130
|
-
|
162
|
+
The code of the fragment shader.
|
131
163
|
"""
|
132
164
|
pass
|
133
165
|
|
166
|
+
def __init__(
|
167
|
+
self,
|
168
|
+
context: moderngl.Context,
|
169
|
+
size: tuple[int, int],
|
170
|
+
**kwargs
|
171
|
+
):
|
172
|
+
ParameterValidator.validate_mandatory_instance_of('context', context, moderngl.Context)
|
173
|
+
# TODO: Validate size
|
174
|
+
|
175
|
+
self.context: moderngl.Context = context
|
176
|
+
"""
|
177
|
+
The context of the program.
|
178
|
+
"""
|
179
|
+
self.size: tuple[int, int] = size
|
180
|
+
"""
|
181
|
+
The size we want to use for the frame buffer
|
182
|
+
in a (width, height) format.
|
183
|
+
"""
|
184
|
+
# Compile shaders within the program
|
185
|
+
self.program: moderngl.Program = self.context.program(
|
186
|
+
vertex_shader = self.vertex_shader,
|
187
|
+
fragment_shader = self.fragment_shader
|
188
|
+
)
|
189
|
+
|
190
|
+
# Create the fullscreen quad
|
191
|
+
self.quad = get_fullscreen_quad_vao(
|
192
|
+
context = self.context,
|
193
|
+
program = self.program
|
194
|
+
)
|
195
|
+
|
196
|
+
# Create the output fbo
|
197
|
+
self.output_tex = self.context.texture(self.size, 4)
|
198
|
+
self.output_tex.filter = (moderngl.LINEAR, moderngl.LINEAR)
|
199
|
+
self.fbo = self.context.framebuffer(color_attachments = [self.output_tex])
|
200
|
+
|
201
|
+
self.uniforms: _Uniforms = _Uniforms(self.program)
|
202
|
+
"""
|
203
|
+
Shortcut to the uniforms functionality.
|
204
|
+
"""
|
205
|
+
# Auto set uniforms dynamically if existing
|
206
|
+
for key, value in kwargs.items():
|
207
|
+
self.uniforms.set(key, value)
|
208
|
+
|
209
|
+
def process(
|
210
|
+
self,
|
211
|
+
input: Union[moderngl.Texture, 'VideoFrame', 'np.ndarray']
|
212
|
+
) -> moderngl.Texture:
|
213
|
+
"""
|
214
|
+
Apply the shader to the 'input', that
|
215
|
+
must be a frame or a texture, and return
|
216
|
+
the new resulting texture.
|
217
|
+
|
218
|
+
We use and return textures to maintain
|
219
|
+
the process in GPU and optimize it.
|
220
|
+
"""
|
221
|
+
# TODO: Maybe we can accept a VideoFrame
|
222
|
+
# or a numpy array and transform it here
|
223
|
+
# into a texture, ready to be used:
|
224
|
+
# frame_to_texture(
|
225
|
+
# # TODO: Do not use Pillow
|
226
|
+
# frame = np.array(Image.open("input.jpg").convert("RGBA")),
|
227
|
+
# context = self.context,
|
228
|
+
# numpy_format = 'rgba'
|
229
|
+
# )
|
230
|
+
if PythonValidator.is_instance_of(input, ['VideoFrame', 'ndarray']):
|
231
|
+
# TODO: What about the numpy format (?)
|
232
|
+
input = frame_to_texture(input, self.context)
|
233
|
+
|
234
|
+
self.fbo.use()
|
235
|
+
self.context.clear(0.0, 0.0, 0.0, 0.0)
|
236
|
+
|
237
|
+
input.use(location = 0)
|
238
|
+
|
239
|
+
if 'texture' in self.program:
|
240
|
+
self.program['texture'] = 0
|
241
|
+
|
242
|
+
self.quad.render()
|
243
|
+
|
244
|
+
return self.output_tex
|
245
|
+
|
246
|
+
class WavingNode(BaseNode):
|
247
|
+
"""
|
248
|
+
Just an example, without the shaders code
|
249
|
+
actually, to indicate that we can use
|
250
|
+
custom parameters to make it work.
|
251
|
+
"""
|
252
|
+
|
134
253
|
@property
|
135
|
-
def
|
254
|
+
def vertex_shader(
|
136
255
|
self
|
137
|
-
) ->
|
256
|
+
) -> str:
|
257
|
+
return (
|
258
|
+
'''
|
259
|
+
#version 330
|
260
|
+
in vec2 in_vert;
|
261
|
+
in vec2 in_texcoord;
|
262
|
+
out vec2 v_uv;
|
263
|
+
void main() {
|
264
|
+
v_uv = in_texcoord;
|
265
|
+
gl_Position = vec4(in_vert, 0.0, 1.0);
|
266
|
+
}
|
267
|
+
'''
|
268
|
+
)
|
269
|
+
|
270
|
+
@property
|
271
|
+
def fragment_shader(
|
272
|
+
self
|
273
|
+
) -> str:
|
274
|
+
return (
|
275
|
+
'''
|
276
|
+
#version 330
|
277
|
+
uniform sampler2D tex;
|
278
|
+
uniform float time;
|
279
|
+
uniform float amplitude;
|
280
|
+
uniform float frequency;
|
281
|
+
uniform float speed;
|
282
|
+
in vec2 v_uv;
|
283
|
+
out vec4 f_color;
|
284
|
+
void main() {
|
285
|
+
float wave = sin(v_uv.x * frequency + time * speed) * amplitude;
|
286
|
+
vec2 uv = vec2(v_uv.x, v_uv.y + wave);
|
287
|
+
f_color = texture(tex, uv);
|
288
|
+
}
|
289
|
+
'''
|
290
|
+
)
|
291
|
+
|
292
|
+
def __init__(
|
293
|
+
self,
|
294
|
+
context: moderngl.Context,
|
295
|
+
size: tuple[int, int],
|
296
|
+
amplitude: float = 0.05,
|
297
|
+
frequency: float = 10.0,
|
298
|
+
speed: float = 2.0
|
299
|
+
):
|
300
|
+
super().__init__(
|
301
|
+
context = context,
|
302
|
+
size = size,
|
303
|
+
amplitude = amplitude,
|
304
|
+
frequency = frequency,
|
305
|
+
speed = speed
|
306
|
+
)
|
307
|
+
|
308
|
+
# This is just an example and we are not
|
309
|
+
# using the parameters actually, but we
|
310
|
+
# could set those specific uniforms to be
|
311
|
+
# processed by the code
|
312
|
+
def process(
|
313
|
+
self,
|
314
|
+
input: Union[moderngl.Texture, 'VideoFrame', 'np.ndarray'],
|
315
|
+
t: float = 0.0,
|
316
|
+
) -> moderngl.Texture:
|
317
|
+
"""
|
318
|
+
Apply the shader to the 'input', that
|
319
|
+
must be a frame or a texture, and return
|
320
|
+
the new resulting texture.
|
321
|
+
|
322
|
+
We use and return textures to maintain
|
323
|
+
the process in GPU and optimize it.
|
324
|
+
"""
|
325
|
+
self.uniforms.set('time', t)
|
326
|
+
|
327
|
+
return super().process(input)
|
328
|
+
|
329
|
+
|
330
|
+
|
331
|
+
"""
|
332
|
+
TODO: I should try to use the Node classes
|
333
|
+
to manipulate the frames because this is how
|
334
|
+
Davinci Resolve and other editors work.
|
335
|
+
"""
|
336
|
+
|
337
|
+
|
338
|
+
class FrameShaderBase(ABC):
|
339
|
+
"""
|
340
|
+
Class to be inherited by any of our own
|
341
|
+
custom opengl program classes.
|
342
|
+
|
343
|
+
This shader base class must be used by all
|
344
|
+
the classes that are modifying the frames
|
345
|
+
one by one.
|
346
|
+
"""
|
347
|
+
|
348
|
+
@property
|
349
|
+
@abstractmethod
|
350
|
+
def vertex_shader(
|
351
|
+
self
|
352
|
+
) -> str:
|
138
353
|
"""
|
139
|
-
|
140
|
-
will use to represent the frame by
|
141
|
-
applying it as a texture.
|
354
|
+
Source code of the vertex shader.
|
142
355
|
"""
|
143
|
-
|
144
|
-
# vertex 0 - bottom left
|
145
|
-
-1.0, -1.0, 0.0, 0.0,
|
146
|
-
# vertex 1 - bottom right
|
147
|
-
1.0, -1.0, 1.0, 0.0,
|
148
|
-
# vertex 2 - top left
|
149
|
-
-1.0, 1.0, 0.0, 1.0,
|
150
|
-
# vertex 3 - top right
|
151
|
-
1.0, 1.0, 1.0, 1.0
|
152
|
-
], dtype = 'f4')
|
356
|
+
pass
|
153
357
|
|
154
358
|
@property
|
155
|
-
|
359
|
+
@abstractmethod
|
360
|
+
def fragment_shader(
|
156
361
|
self
|
157
|
-
) ->
|
362
|
+
) -> str:
|
158
363
|
"""
|
159
|
-
|
160
|
-
property) to build the 2 opengl triangles
|
161
|
-
that will represent the quad we need for
|
162
|
-
the frame.
|
364
|
+
Source code of the fragment shader.
|
163
365
|
"""
|
164
|
-
|
165
|
-
[
|
166
|
-
0, 1, 2,
|
167
|
-
2, 1, 3
|
168
|
-
],
|
169
|
-
dtype = 'i4'
|
170
|
-
)
|
366
|
+
pass
|
171
367
|
|
172
368
|
def __init__(
|
173
369
|
self,
|
@@ -203,14 +399,6 @@ class FrameShaderBase(ABC):
|
|
203
399
|
"""
|
204
400
|
The frame buffer object.
|
205
401
|
"""
|
206
|
-
self.vbo: moderngl.Buffer = None
|
207
|
-
"""
|
208
|
-
The vertices buffer object.
|
209
|
-
"""
|
210
|
-
self.ibo: moderngl.Buffer = None
|
211
|
-
"""
|
212
|
-
The indexes buffer object.
|
213
|
-
"""
|
214
402
|
self.uniforms: _Uniforms = None
|
215
403
|
"""
|
216
404
|
Shortcut to the uniforms functionality.
|
@@ -221,6 +409,12 @@ class FrameShaderBase(ABC):
|
|
221
409
|
def _initialize_program(
|
222
410
|
self
|
223
411
|
):
|
412
|
+
"""
|
413
|
+
This method is to allow the effects to
|
414
|
+
change their '__init__' method to be able
|
415
|
+
to provide parameters that will be set as
|
416
|
+
uniforms.
|
417
|
+
"""
|
224
418
|
# Compile shaders within the program
|
225
419
|
self.program: moderngl.Program = self.context.program(
|
226
420
|
vertex_shader = self.vertex_shader,
|
@@ -229,18 +423,9 @@ class FrameShaderBase(ABC):
|
|
229
423
|
|
230
424
|
# Create frame buffer
|
231
425
|
self.fbo = self.context.simple_framebuffer(self.size)
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
# so more than 1 vbo and more than 1 vao...
|
236
|
-
self.vbo: moderngl.Buffer = self.context.buffer(self.vertices.tobytes())
|
237
|
-
self.ibo: moderngl.Buffer = self.context.buffer(self.indexes.tobytes())
|
238
|
-
vao_content = [
|
239
|
-
(self.vbo, "2f 2f", "in_vert", "in_texcoord")
|
240
|
-
]
|
241
|
-
self.vao: moderngl.VertexArray = self.context.vertex_array(self.program, vao_content, self.ibo)
|
242
|
-
|
243
|
-
self.uniforms: _Uniforms = _Uniforms(self)
|
426
|
+
# Create quad vertex array
|
427
|
+
self.vao: moderngl.VertexArray = get_fullscreen_quad_vao(self.context, self.program)
|
428
|
+
self.uniforms: _Uniforms = _Uniforms(self.program)
|
244
429
|
|
245
430
|
# TODO: How do I manage these textures (?)
|
246
431
|
self.textures = {}
|
@@ -270,8 +455,7 @@ class FrameShaderBase(ABC):
|
|
270
455
|
tex = self.context.texture((image.shape[1], image.shape[0]), 4, image.tobytes())
|
271
456
|
tex.use(texture_unit)
|
272
457
|
self.textures[uniform_name] = tex
|
273
|
-
|
274
|
-
self.program[uniform_name].value = texture_unit
|
458
|
+
self.uniforms.set(uniform_name, texture_unit)
|
275
459
|
|
276
460
|
@abstractmethod
|
277
461
|
def _prepare_frame(
|
@@ -32,7 +32,7 @@ class VideoReaderFrame:
|
|
32
32
|
Flag to indicate if the instance is a video
|
33
33
|
frame.
|
34
34
|
"""
|
35
|
-
return PythonValidator.is_instance_of(self.
|
35
|
+
return PythonValidator.is_instance_of(self.value, VideoFrame)
|
36
36
|
|
37
37
|
@property
|
38
38
|
def is_audio(
|
@@ -42,18 +42,37 @@ class VideoReaderFrame:
|
|
42
42
|
Flag to indicate if the instance is an audio
|
43
43
|
frame.
|
44
44
|
"""
|
45
|
-
return PythonValidator.is_instance_of(self.
|
45
|
+
return PythonValidator.is_instance_of(self.value, AudioFrame)
|
46
|
+
|
47
|
+
@property
|
48
|
+
def as_numpy(
|
49
|
+
self
|
50
|
+
):
|
51
|
+
"""
|
52
|
+
The frame as a numpy array.
|
53
|
+
"""
|
54
|
+
return self.value.to_ndarray(format = self.pixel_format)
|
46
55
|
|
47
56
|
def __init__(
|
48
57
|
self,
|
49
58
|
# TODO: Add the type, please
|
50
|
-
|
59
|
+
frame: any,
|
60
|
+
t: float = None,
|
61
|
+
pixel_format: str = 'rgb24'
|
51
62
|
):
|
52
|
-
self.
|
63
|
+
self.value: Union[AudioFrame, VideoFrame] = frame
|
53
64
|
"""
|
54
65
|
The frame content, that can be audio or video
|
55
66
|
frame.
|
56
67
|
"""
|
68
|
+
self.t: float = t
|
69
|
+
"""
|
70
|
+
The 't' time moment of the frame.
|
71
|
+
"""
|
72
|
+
self.pixel_format: str = pixel_format
|
73
|
+
"""
|
74
|
+
The pixel format of the frame.
|
75
|
+
"""
|
57
76
|
|
58
77
|
@dataclass
|
59
78
|
class VideoReaderPacket:
|
@@ -71,7 +90,7 @@ class VideoReaderPacket:
|
|
71
90
|
Flag to indicate if the packet includes video
|
72
91
|
frames or not.
|
73
92
|
"""
|
74
|
-
return self.
|
93
|
+
return self.value.stream.type == 'video'
|
75
94
|
|
76
95
|
@property
|
77
96
|
def is_audio(
|
@@ -81,13 +100,13 @@ class VideoReaderPacket:
|
|
81
100
|
Flag to indicate if the packet includes audio
|
82
101
|
frames or not.
|
83
102
|
"""
|
84
|
-
return self.
|
103
|
+
return self.value.stream.type == 'audio'
|
85
104
|
|
86
105
|
def __init__(
|
87
106
|
self,
|
88
|
-
|
107
|
+
packet: Packet
|
89
108
|
):
|
90
|
-
self.
|
109
|
+
self.value: Packet = packet
|
91
110
|
"""
|
92
111
|
The packet, that can include video or audio
|
93
112
|
frames and can be decoded.
|
@@ -100,7 +119,7 @@ class VideoReaderPacket:
|
|
100
119
|
Get the frames but decoded, perfect to make
|
101
120
|
modifications and encode to save them again.
|
102
121
|
"""
|
103
|
-
return self.
|
122
|
+
return self.value.decode()
|
104
123
|
|
105
124
|
|
106
125
|
class VideoReader:
|
@@ -268,6 +287,24 @@ class VideoReader:
|
|
268
287
|
# TODO: What if no audio (?)
|
269
288
|
return self.audio_stream.average_rate
|
270
289
|
|
290
|
+
@property
|
291
|
+
def time_base(
|
292
|
+
self
|
293
|
+
) -> Fraction:
|
294
|
+
"""
|
295
|
+
The time base of the video.
|
296
|
+
"""
|
297
|
+
return self.video_stream.time_base
|
298
|
+
|
299
|
+
@property
|
300
|
+
def audio_time_base(
|
301
|
+
self
|
302
|
+
) -> Fraction:
|
303
|
+
"""
|
304
|
+
The time base of the audio.
|
305
|
+
"""
|
306
|
+
return self.audio_stream.time_base
|
307
|
+
|
271
308
|
@property
|
272
309
|
def size(
|
273
310
|
self
|
@@ -303,12 +340,18 @@ class VideoReader:
|
|
303
340
|
|
304
341
|
def __init__(
|
305
342
|
self,
|
306
|
-
filename: str
|
343
|
+
filename: str,
|
344
|
+
# Use 'rgba' if alpha channel
|
345
|
+
pixel_format: str = 'rgb24'
|
307
346
|
):
|
308
347
|
self.filename: str = filename
|
309
348
|
"""
|
310
349
|
The filename of the video source.
|
311
350
|
"""
|
351
|
+
self.pixel_format: str = pixel_format
|
352
|
+
"""
|
353
|
+
The pixel format.
|
354
|
+
"""
|
312
355
|
self.container: InputContainer = None
|
313
356
|
"""
|
314
357
|
The av input general container of the
|
@@ -370,7 +413,11 @@ class VideoReader:
|
|
370
413
|
(already decoded).
|
371
414
|
"""
|
372
415
|
for frame in self.frame_iterator:
|
373
|
-
yield VideoReaderFrame(
|
416
|
+
yield VideoReaderFrame(
|
417
|
+
frame = frame,
|
418
|
+
t = float(frame.pts * self.time_base),
|
419
|
+
pixel_format = self.pixel_format
|
420
|
+
)
|
374
421
|
|
375
422
|
def iterate_with_audio(
|
376
423
|
self,
|
@@ -419,6 +466,14 @@ class VideoReader:
|
|
419
466
|
the cache system.
|
420
467
|
"""
|
421
468
|
return self.cache.get_frame(index)
|
469
|
+
|
470
|
+
def close(
|
471
|
+
self
|
472
|
+
) -> None:
|
473
|
+
"""
|
474
|
+
Close the container to free it.
|
475
|
+
"""
|
476
|
+
self.container.close()
|
422
477
|
|
423
478
|
|
424
479
|
|
@@ -579,7 +579,8 @@ def video_modified_stored():
|
|
579
579
|
# TODO: Where do we obtain this from (?)
|
580
580
|
PIXEL_FORMAT = 'yuv420p'
|
581
581
|
|
582
|
-
from yta_video_opengl.classes import WavingFrame, BreathingFrame, HandheldFrame, OrbitingFrame, RotatingInCenterFrame, StrangeTvFrame, GlitchRgbFrame
|
582
|
+
from yta_video_opengl.classes import WavingFrame, BreathingFrame, HandheldFrame, OrbitingFrame, RotatingInCenterFrame, StrangeTvFrame, GlitchRgbFrame, WavingNode
|
583
|
+
from yta_video_opengl.utils import texture_to_frame, frame_to_texture
|
583
584
|
|
584
585
|
video = VideoReader(VIDEO_PATH)
|
585
586
|
video_writer = (
|
@@ -604,6 +605,10 @@ def video_modified_stored():
|
|
604
605
|
size = video.size,
|
605
606
|
first_frame = video.next_frame
|
606
607
|
)
|
608
|
+
context = moderngl.create_context(standalone = True)
|
609
|
+
|
610
|
+
# New way, with nodes
|
611
|
+
node = WavingNode(context, video.size, amplitude = 0.2, frequency = 9, speed = 3)
|
607
612
|
# We need to reset it to being again pointing
|
608
613
|
# to the first frame...
|
609
614
|
# TODO: Improve this by, maybe, storing the first
|
@@ -624,7 +629,7 @@ def video_modified_stored():
|
|
624
629
|
|
625
630
|
# To simplify the process
|
626
631
|
if frame_or_packet is not None:
|
627
|
-
frame_or_packet = frame_or_packet.
|
632
|
+
frame_or_packet = frame_or_packet.value
|
628
633
|
|
629
634
|
if is_audio_packet:
|
630
635
|
video_writer.mux(frame_or_packet)
|
@@ -632,13 +637,27 @@ def video_modified_stored():
|
|
632
637
|
with Timer(is_silent_as_context = True) as timer:
|
633
638
|
t = T.video_frame_index_to_video_frame_time(frame_index, float(video.fps))
|
634
639
|
|
640
|
+
# This is another way of getting 't'
|
641
|
+
#t = float(frame_or_packet.pts * video.time_base)
|
642
|
+
|
643
|
+
# TODO: Pass the frame as a texture
|
644
|
+
|
635
645
|
video_writer.mux_video_frame(
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
646
|
+
frame = texture_to_frame(
|
647
|
+
texture = node.process(
|
648
|
+
input = frame_or_packet,
|
649
|
+
t = t
|
650
|
+
)
|
640
651
|
)
|
641
652
|
)
|
653
|
+
|
654
|
+
# video_writer.mux_video_frame(
|
655
|
+
# effect.process_frame(
|
656
|
+
# frame = frame_or_packet,
|
657
|
+
# t = t,
|
658
|
+
# numpy_format = NUMPY_FORMAT
|
659
|
+
# )
|
660
|
+
# )
|
642
661
|
|
643
662
|
frame_index += 1
|
644
663
|
|
@@ -0,0 +1,100 @@
|
|
1
|
+
from yta_validation import PythonValidator
|
2
|
+
from typing import Union
|
3
|
+
|
4
|
+
import av
|
5
|
+
import numpy as np
|
6
|
+
import moderngl
|
7
|
+
|
8
|
+
|
9
|
+
def frame_to_texture(
|
10
|
+
frame: Union['VideoFrame', 'np.ndarray'],
|
11
|
+
context: moderngl.Context,
|
12
|
+
numpy_format: str = 'rgb24'
|
13
|
+
):
|
14
|
+
"""
|
15
|
+
Transform the given 'frame' to an opengl
|
16
|
+
texture. The frame can be a VideoFrame
|
17
|
+
instance (from pyav library) or a numpy
|
18
|
+
array.
|
19
|
+
"""
|
20
|
+
# To numpy RGB inverted for opengl
|
21
|
+
frame: np.ndarray = (
|
22
|
+
np.flipud(frame.to_ndarray(format = numpy_format))
|
23
|
+
if PythonValidator.is_instance_of(frame, 'VideoFrame') else
|
24
|
+
np.flipud(frame)
|
25
|
+
)
|
26
|
+
|
27
|
+
return context.texture(
|
28
|
+
size = (frame.shape[1], frame.shape[0]),
|
29
|
+
components = frame.shape[2],
|
30
|
+
data = frame.tobytes()
|
31
|
+
)
|
32
|
+
|
33
|
+
# TODO: I should make different methods to
|
34
|
+
# obtain a VideoFrame or a numpy array frame
|
35
|
+
def texture_to_frame(
|
36
|
+
texture: moderngl.Texture
|
37
|
+
) -> 'VideoFrame':
|
38
|
+
"""
|
39
|
+
Transform an opengl texture into a pyav
|
40
|
+
VideoFrame instance.
|
41
|
+
"""
|
42
|
+
# RGBA8
|
43
|
+
data = texture.read(alignment = 1)
|
44
|
+
frame = np.frombuffer(data, dtype = np.uint8).reshape((texture.size[1], texture.size[0], 4))
|
45
|
+
# Opengl gives it with the y inverted
|
46
|
+
frame = np.flipud(frame)
|
47
|
+
# TODO: This can be returned as a numpy frame
|
48
|
+
|
49
|
+
# This is if we need an 'av' VideoFrame (to
|
50
|
+
# export through the demuxer, for example)
|
51
|
+
frame = av.VideoFrame.from_ndarray(frame, format = 'rgba')
|
52
|
+
# TODO: Make this customizable
|
53
|
+
frame = frame.reformat(format = 'yuv420p')
|
54
|
+
|
55
|
+
return frame
|
56
|
+
|
57
|
+
def get_fullscreen_quad_vao(
|
58
|
+
context: moderngl.Context,
|
59
|
+
program: moderngl.Program
|
60
|
+
) -> moderngl.VertexArray:
|
61
|
+
"""
|
62
|
+
Get the vertex array object of a quad, by
|
63
|
+
using the vertices, the indexes, the vbo,
|
64
|
+
the ibo and the vao content.
|
65
|
+
"""
|
66
|
+
# Quad vertices in NDC (-1..1) with texture
|
67
|
+
# coords (0..1)
|
68
|
+
"""
|
69
|
+
The UV coordinates to build the quad we
|
70
|
+
will use to represent the frame by
|
71
|
+
applying it as a texture.
|
72
|
+
"""
|
73
|
+
vertices = np.array([
|
74
|
+
# pos.x, pos.y, tex.u, tex.v
|
75
|
+
-1.0, -1.0, 0.0, 0.0, # vertex 0 - bottom left
|
76
|
+
1.0, -1.0, 1.0, 0.0, # vertex 1 - bottom right
|
77
|
+
-1.0, 1.0, 0.0, 1.0, # vertex 2 - top left
|
78
|
+
1.0, 1.0, 1.0, 1.0, # vertex 3 - top right
|
79
|
+
], dtype = 'f4')
|
80
|
+
|
81
|
+
"""
|
82
|
+
The indexes of the vertices (see 'vertices'
|
83
|
+
property) to build the 2 opengl triangles
|
84
|
+
that will represent the quad we need for
|
85
|
+
the frame.
|
86
|
+
"""
|
87
|
+
indices = np.array([
|
88
|
+
0, 1, 2,
|
89
|
+
2, 1, 3
|
90
|
+
], dtype = 'i4')
|
91
|
+
|
92
|
+
vbo = context.buffer(vertices.tobytes())
|
93
|
+
ibo = context.buffer(indices.tobytes())
|
94
|
+
|
95
|
+
vao_content = [
|
96
|
+
# 2 floats position, 2 floats texcoords
|
97
|
+
(vbo, '2f 2f', 'in_vert', 'in_texcoord'),
|
98
|
+
]
|
99
|
+
|
100
|
+
return context.vertex_array(program, vao_content, ibo)
|
@@ -1,24 +0,0 @@
|
|
1
|
-
from yta_validation import PythonValidator
|
2
|
-
from typing import Union
|
3
|
-
|
4
|
-
import numpy as np
|
5
|
-
import moderngl
|
6
|
-
|
7
|
-
|
8
|
-
def frame_to_texture(
|
9
|
-
frame: Union['VideoFrame', 'np.ndarray'],
|
10
|
-
context: moderngl.Context,
|
11
|
-
numpy_format: str = 'rgb24'
|
12
|
-
):
|
13
|
-
"""
|
14
|
-
Transform the given 'frame' to an opengl
|
15
|
-
texture.
|
16
|
-
"""
|
17
|
-
# To numpy RGB inverted for opengl
|
18
|
-
frame: np.ndarray = (
|
19
|
-
np.flipud(frame.to_ndarray(format = numpy_format))
|
20
|
-
if PythonValidator.is_instance_of(frame, 'VideoFrame') else
|
21
|
-
np.flipud(frame)
|
22
|
-
)
|
23
|
-
|
24
|
-
return context.texture((frame.shape[1], frame.shape[0]), 3, frame.tobytes())
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|