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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: yta-video-opengl
3
- Version: 0.0.6
3
+ Version: 0.0.7
4
4
  Summary: Youtube Autonomous Video OpenGL Module
5
5
  Author: danialcala94
6
6
  Author-email: danielalcalavalera@gmail.com
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "yta-video-opengl"
3
- version = "0.0.6"
3
+ version = "0.0.7"
4
4
  description = "Youtube Autonomous Video OpenGL Module"
5
5
  authors = [
6
6
  {name = "danialcala94",email = "danielalcalavalera@gmail.com"}
@@ -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 yta_video_opengl.utils import frame_to_texture
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 program(
28
+ def uniforms(
28
29
  self
29
- ):
30
+ ) -> dict:
30
31
  """
31
- Shortcut to the FrameShader program.
32
+ The uniforms in the program, as a dict, in
33
+ the format `{key, value}`.
32
34
  """
33
- return self._shader_instance.program
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
- shader_instance: 'FrameShaderBase'
43
+ program: moderngl.Program
38
44
  ):
39
- self._shader_instance: 'FrameShaderBase' = shader_instance
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
- The instance of the FrameShader class these
42
- uniforms belong to.
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
- ) -> 'FrameShaderBase':
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._shader_instance
82
+ return self
63
83
 
64
84
  def set_vec(
65
85
  self,
66
86
  name: str,
67
87
  values
68
- ) -> 'FrameShaderBase':
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
- ) -> 'FrameShaderBase':
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._shader_instance
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 FrameShaderBase(ABC):
135
+ class BaseNode:
105
136
  """
106
- Class to be inherited by any of our own
107
- custom opengl program classes.
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
- This shader base class must be used by all
110
- the classes that are modifying the frames
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
- Source code of the vertex shader.
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
- Source code of the fragment shader.
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 vertices(
254
+ def vertex_shader(
136
255
  self
137
- ) -> 'np.ndarray':
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
- The UV coordinates to build the quad we
140
- will use to represent the frame by
141
- applying it as a texture.
354
+ Source code of the vertex shader.
142
355
  """
143
- return np.array([
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
- def indexes(
359
+ @abstractmethod
360
+ def fragment_shader(
156
361
  self
157
- ) -> 'np.ndarray':
362
+ ) -> str:
158
363
  """
159
- The indexes of the vertices (see 'vertices'
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
- return np.array(
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
- # Create buffers
234
- # TODO: I could have more than 1 vertices,
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
- if uniform_name in self.program:
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.data, VideoFrame)
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.data, AudioFrame)
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
- data: any
59
+ frame: any,
60
+ t: float = None,
61
+ pixel_format: str = 'rgb24'
51
62
  ):
52
- self.data: Union[AudioFrame, VideoFrame] = data
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.data.stream.type == 'video'
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.data.stream.type == 'audio'
103
+ return self.value.stream.type == 'audio'
85
104
 
86
105
  def __init__(
87
106
  self,
88
- data: Packet
107
+ packet: Packet
89
108
  ):
90
- self.data: Packet = data
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.data.decode()
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(frame)
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.data
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
- effect.process_frame(
637
- frame = frame_or_packet,
638
- t = t,
639
- numpy_format = NUMPY_FORMAT
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,4 +1,3 @@
1
- from yta_validation import PythonValidator
2
1
  from yta_validation.parameter import ParameterValidator
3
2
  from av.stream import Stream
4
3
  from av.packet import Packet
@@ -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())