yta-video-opengl 0.0.14__py3-none-any.whl → 0.0.16__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,512 +0,0 @@
1
- """
2
- The pyav container stores the information based
3
- on the packets timestamps (called 'pts'). Some
4
- of the packets are considered key_frames because
5
- they include those key frames.
6
-
7
- Also, this library uses those key frames to start
8
- decodifying from there to the next one, obtaining
9
- all the frames in between able to be read and
10
- modified.
11
-
12
- This cache system will look for the range of
13
- frames that belong to the key frame related to the
14
- frame we are requesting in the moment, keeping in
15
- memory all those frames to be handled fast. It
16
- will remove the old frames if needed to use only
17
- the 'size' we set when creating it.
18
-
19
- A stream can have 'fps = 60' but use another
20
- different time base that make the pts values go 0,
21
- 256, 512... for example. The 'time_base' is the
22
- only accurate way to obtain the pts.
23
-
24
- Feel free to move this explanation to other
25
- place, its about the duration.
26
-
27
- The stream 'duration' parameter is measured
28
- on ticks, the amount of ticks that the
29
- stream lasts. Here below is an example:
30
-
31
- - Duration raw: 529200
32
- - Time base: 1/44100
33
- - Duration (seconds): 12.0
34
- """
35
- from yta_video_opengl.t import T
36
- from av.container import InputContainer
37
- from av.video.stream import VideoStream
38
- from av.audio.stream import AudioStream
39
- from av.video.frame import VideoFrame
40
- from av.audio.frame import AudioFrame
41
- from av.packet import Packet
42
- from yta_validation.parameter import ParameterValidator
43
- from yta_validation import PythonValidator
44
- from quicktions import Fraction
45
- from collections import OrderedDict
46
- from typing import Union
47
-
48
- import numpy as np
49
- import math
50
-
51
-
52
- # TODO: This is not actually a Video
53
- # cache, is a FrameCache because we
54
- # create one for video but another
55
- # one for audio. Rename it please.
56
- class VideoFrameCache:
57
- """
58
- Class to manage the frames cache of a video
59
- within a video reader instance.
60
- """
61
-
62
- @property
63
- def fps(
64
- self
65
- ) -> Union[int, Fraction, None]:
66
- """
67
- The frames per second.
68
- """
69
- return (
70
- self.stream.average_rate
71
- if self.stream.type == 'video' else
72
- self.stream.rate
73
- )
74
-
75
- @property
76
- def time_base(
77
- self
78
- ) -> Union[Fraction, None]:
79
- """
80
- The time base of the stream.
81
- """
82
- return self.stream.time_base
83
-
84
- def __init__(
85
- self,
86
- container: InputContainer,
87
- stream: Union[VideoStream, AudioStream],
88
- size: Union[int, None] = None
89
- ):
90
- ParameterValidator.validate_mandatory_instance_of('container', container, InputContainer)
91
- ParameterValidator.validate_mandatory_instance_of('stream', stream, [VideoStream, AudioStream])
92
- ParameterValidator.validate_positive_int('size', size)
93
-
94
- self.container: InputContainer = container
95
- """
96
- The pyav container.
97
- """
98
- self.stream: Union[VideoStream, AudioStream] = stream
99
- """
100
- The pyav stream.
101
- """
102
- self.cache: OrderedDict = OrderedDict()
103
- """
104
- The cache ordered dictionary.
105
- """
106
- self.size: Union[int, None] = size
107
- """
108
- The size (in number of frames) of the cache.
109
- """
110
- self.key_frames_pts: list[int] = []
111
- """
112
- The list that contains the timestamps of the
113
- key frame packets, ordered from begining to
114
- end.
115
- """
116
-
117
- # TODO: This is new, remove this comment if
118
- # it is ok
119
- # TODO: This way of obtaining the duration
120
- # in ticks must be a utils
121
- self.frame_duration: int = (
122
- self.stream.duration / self.stream.frames
123
- if PythonValidator.is_instance_of(stream, VideoStream) else
124
- # TODO: Is this below ok (?)
125
- self.stream.frames
126
- )
127
- """
128
- The duration (in ticks) of the frame, that
129
- is the step between the different pts.
130
- """
131
- self._last_packet_accessed: Union[Packet, None] = None
132
- """
133
- The last packet that has been accessed
134
- """
135
- self._last_frame_read: Union[VideoFrame, AudioFrame, None] = None
136
- """
137
- The last frame we have read when decoding.
138
- Useful to avoid seeking all the time when we
139
- don't need it.
140
- """
141
-
142
- self._prepare()
143
-
144
- def _prepare(
145
- self
146
- ):
147
- # Index key frames
148
- for packet in self.container.demux(self.stream):
149
- if packet.is_keyframe:
150
- self.key_frames_pts.append(packet.pts)
151
-
152
- # The cache size will be auto-calculated to
153
- # use the amount of frames of the biggest
154
- # interval of frames that belongs to a key
155
- # frame, or a value by default
156
- # TODO: Careful if this is too big
157
- fps = (
158
- float(self.stream.average_rate)
159
- if PythonValidator.is_instance_of(self.stream, VideoStream) else
160
- float(self.stream.rate)
161
- )
162
- # Intervals, but in number of frames
163
- intervals = np.diff(
164
- # Intervals of time between keyframes
165
- np.array(self.key_frames_pts) * self.time_base
166
- ) * fps
167
-
168
- self.size = (
169
- math.ceil(np.max(intervals))
170
- if intervals.size > 0 else
171
- (
172
- self.size or
173
- # TODO: Make this 'default_size' a setting or something
174
- 60
175
- )
176
- )
177
-
178
- self.container.seek(0)
179
-
180
- def _get_nearest_keyframe_pts(
181
- self,
182
- pts: int
183
- ):
184
- """
185
- Get the fps of the keyframe that is the
186
- nearest to the provided 'pts'. Useful to
187
- seek and start decoding frames from that
188
- keyframe.
189
- """
190
- return max([
191
- key_frame_pts
192
- for key_frame_pts in self.key_frames_pts
193
- if key_frame_pts <= pts
194
- ])
195
-
196
- def _store_frame_in_cache(
197
- self,
198
- frame: Union[VideoFrame, AudioFrame]
199
- ) -> Union[VideoFrame, AudioFrame]:
200
- """
201
- Store the provided 'frame' in cache if it
202
- is not on it, removing the first item of
203
- the cache if full.
204
- """
205
- if frame.pts not in self.cache:
206
- self.cache[frame.pts] = frame
207
-
208
- # Clean cache if full
209
- if len(self.cache) > self.size:
210
- self.cache.popitem(last = False)
211
-
212
- return frame
213
-
214
- def _seek(
215
- self,
216
- pts: int
217
- ):
218
- """
219
- Seek to the given 'pts' only if it is not
220
- the next 'pts' to the last read, and it
221
- will also apply a pad to avoid problems
222
- when reading audio frames.
223
-
224
- TODO: Apply the padding only to audio
225
- frame reading (?)
226
- """
227
- # I found that it is recommended to
228
- # read ~100ms before the pts we want to
229
- # actually read so we obtain the frames
230
- # clean (this is important in audio)
231
- # TODO: This is maybe too much for a
232
- # video and not needed
233
- pts_pad = int(0.1 / self.time_base)
234
- self.container.seek(
235
- offset = max(0, pts - pts_pad),
236
- stream = self.stream
237
- )
238
-
239
- def get_video_frame(
240
- self,
241
- t: Union[int, float, Fraction]
242
- ) -> VideoFrame:
243
- """
244
- Get the video frame that is in the 't'
245
- time moment provided.
246
- """
247
- for frame in self.get_video_frames(t):
248
- return frame
249
-
250
- def get_video_frames(
251
- self,
252
- start: Union[int, float, Fraction] = 0,
253
- end: Union[int, float, Fraction, None] = None
254
- ):
255
- """
256
- Get all the frames in the range between
257
- the provided 'start' and 'end' time in
258
- seconds.
259
-
260
- This method is an iterator that yields
261
- the frame, its t and its index.
262
- """
263
- start = T(start, self.time_base).truncated
264
- end = (
265
- T(end, self.time_base).truncated
266
- if end is not None else
267
- # The next frame
268
- start + (1 / self.fps)
269
- )
270
-
271
- key_frame_pts = self._get_nearest_keyframe_pts(start / self.time_base)
272
-
273
- if (
274
- self._last_packet_accessed is None or
275
- self._last_packet_accessed.pts != key_frame_pts
276
- ):
277
- self._seek(key_frame_pts)
278
-
279
- for packet in self.container.demux(self.stream):
280
- if packet.pts is None:
281
- continue
282
-
283
- self._last_packet_accessed = packet
284
-
285
- for frame in packet.decode():
286
- if frame.pts is None:
287
- continue
288
-
289
- # We store all the frames in cache
290
- self._store_frame_in_cache(frame)
291
-
292
- current_frame_time = frame.pts * self.time_base
293
-
294
- # We want the range [start, end)
295
- if start <= current_frame_time < end:
296
- yield frame
297
-
298
- if current_frame_time >= end:
299
- break
300
-
301
- def get_audio_frame_from_t(
302
- self,
303
- t: Union[int, float, Fraction]
304
- ):
305
- """
306
- Get the single audio frame that must be
307
- played at the 't' time moment provided.
308
- This method is useful to get the single
309
- audio frame that we need to combine
310
- when using it in a composition.
311
-
312
- TODO: Are we actually using this method (?)
313
- """
314
- t: T = T(t, self.time_base)
315
- # We need the just one audio frame
316
- for frame in self.get_audio_frames(t.truncated, t.next(1).truncated):
317
- return frame
318
-
319
- def get_audio_frames_from_t(
320
- self,
321
- t: Union[int, float, Fraction]
322
- ):
323
- """
324
- Get all the audio frames that must be
325
- played at the 't' time moment provided.
326
- """
327
- for frame in self.get_audio_frames(t):
328
- yield frame
329
-
330
- def get_audio_frames(
331
- self,
332
- start: Union[int, float, Fraction] = 0,
333
- end: Union[int, float, Fraction, None] = None
334
- ):
335
- """
336
- Get all the audio frames in the range
337
- between the provided 'start' and 'end'
338
- time (in seconds).
339
-
340
- This method is an iterator that yields
341
- the frame, its t and its index.
342
- """
343
- # TODO: Is this ok? We are trying to obtain
344
- # the audio frames for a video frame, so
345
- # should we use the 'self.time_base' to
346
- # truncate (?)
347
- start = T(start, self.time_base).truncated
348
- end = (
349
- T(end, self.time_base).truncated
350
- if end is not None else
351
- start + (1 / self.fps)
352
- )
353
-
354
- key_frame_pts = self._get_nearest_keyframe_pts(start / self.time_base)
355
-
356
- if (
357
- self._last_packet_accessed is None or
358
- self._last_packet_accessed.pts != key_frame_pts
359
- ):
360
- self._seek(key_frame_pts)
361
-
362
- for packet in self.container.demux(self.stream):
363
- if packet.pts is None:
364
- continue
365
-
366
- self._last_packet_accessed = packet
367
-
368
- for frame in packet.decode():
369
- if frame.pts is None:
370
- continue
371
-
372
- # We store all the frames in cache
373
- self._store_frame_in_cache(frame)
374
-
375
- current_frame_time = frame.pts * self.time_base
376
- # End is not included, its the start of the
377
- # next frame actually
378
- frame_end = current_frame_time + (frame.samples / self.stream.sample_rate)
379
-
380
- # For the next comments imagine we are looking
381
- # for the [1.0, 2.0) audio time range
382
- # Previous frame and nothing is inside
383
- if frame_end <= start:
384
- # From 0.25 to 1.0
385
- continue
386
-
387
- # We finished, nothing is inside and its after
388
- if current_frame_time >= end:
389
- # From 2.0 to 2.75
390
- return
391
-
392
- # If we need audio from 1 to 2, audio is:
393
- # - from 0 to 0.75 (Not included, omit)
394
- # - from 0.5 to 1.5 (Included, take 1.0 to 1.5)
395
- # - from 0.5 to 2.5 (Included, take 1.0 to 2.0)
396
- # - from 1.25 to 1.5 (Included, take 1.25 to 1.5)
397
- # - from 1.25 to 2.5 (Included, take 1.25 to 2.0)
398
- # - from 2.5 to 3.5 (Not included, omit)
399
-
400
- # Here below, at least a part is inside
401
- if (
402
- current_frame_time < start and
403
- frame_end > start
404
- ):
405
- # A part at the end is included
406
- end_time = (
407
- # From 0.5 to 1.5 0> take 1.0 to 1.5
408
- frame_end
409
- if frame_end <= end else
410
- # From 0.5 to 2.5 => take 1.0 to 2.0
411
- end
412
- )
413
- #print('A part at the end is included.')
414
- frame = trim_audio_frame(
415
- frame = frame,
416
- start = start,
417
- end = end_time,
418
- time_base = self.time_base
419
- )
420
- elif (
421
- current_frame_time >= start and
422
- current_frame_time < end
423
- ):
424
- end_time = (
425
- # From 1.25 to 1.5 => take 1.25 to 1.5
426
- frame_end
427
- if frame_end <= end else
428
- # From 1.25 to 2.5 => take 1.25 to 2.0
429
- end
430
- )
431
- # A part at the begining is included
432
- #print('A part at the begining is included.')
433
- frame = trim_audio_frame(
434
- frame = frame,
435
- start = current_frame_time,
436
- end = end_time,
437
- time_base = self.time_base
438
- )
439
-
440
- # If the whole frame is in, past as it is
441
- yield frame
442
-
443
- def clear(
444
- self
445
- ) -> 'VideoFrameCache':
446
- """
447
- Clear the cache by removing all the items.
448
- """
449
- self.cache.clear()
450
-
451
- return self
452
-
453
- def trim_audio_frame(
454
- frame: AudioFrame,
455
- start: Union[int, float, Fraction],
456
- end: Union[int, float, Fraction],
457
- time_base: Fraction
458
- ) -> AudioFrame:
459
- """
460
- Trim an audio frame to obtain the part between
461
- [start, end), that is provided in seconds.
462
- """
463
- # (channels, n_samples)
464
- samples = frame.to_ndarray()
465
- n_samples = samples.shape[1]
466
-
467
- # In seconds
468
- frame_start = frame.pts * float(time_base)
469
- frame_end = frame_start + (n_samples / frame.sample_rate)
470
-
471
- # Overlapping
472
- cut_start = max(frame_start, float(start))
473
- cut_end = min(frame_end, float(end))
474
-
475
- if cut_start >= cut_end:
476
- # No overlapping
477
- return None
478
-
479
- # To sample indexes
480
- start_index = int(round((cut_start - frame_start) * frame.sample_rate))
481
- end_index = int(round((cut_end - frame_start) * frame.sample_rate))
482
-
483
- new_frame = AudioFrame.from_ndarray(
484
- # end_index is not included: so [start, end)
485
- array = samples[:, start_index:end_index],
486
- format = frame.format,
487
- layout = frame.layout
488
- )
489
-
490
- # Set attributes
491
- new_frame.sample_rate = frame.sample_rate
492
- new_frame.time_base = time_base
493
- new_frame.pts = int(round(cut_start / float(time_base)))
494
-
495
- return new_frame
496
-
497
-
498
-
499
- """
500
- There is a way of editing videos being
501
- able to arbitrary access to frames, that
502
- is transforming the source videos to
503
- intra-frame videos. This is a ffmpeg
504
- command that can do it:
505
-
506
- - `ffmpeg -i input.mp4 -c:v libx264 -x264opts keyint=1 -preset fast -crf 18 -c:a copy output_intra.mp4`
507
-
508
- Once you have the 'output_intra.mp4',
509
- each packet can decodify its frame
510
- depending not on the previous one, being
511
- able to seek and jump easy.
512
- """
@@ -1,21 +0,0 @@
1
- yta_video_opengl/__init__.py,sha256=ycAx_XYMVDfkuObSvtW6irQ0Wo-fgxEz3fjIRMe8PpY,205
2
- yta_video_opengl/classes.py,sha256=t5-Tfc7ecvHl8JlVBp_FVzZT6ole6Ly5-FeBBH7wcxo,37742
3
- yta_video_opengl/complete/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- yta_video_opengl/complete/timeline.py,sha256=tMYeAFrOY3M7DAREOYnPT_RzHoFtQnCxk68DusKmJDU,9433
5
- yta_video_opengl/complete/track.py,sha256=qIJd3RLizutmCtqk8pkyW40xr6Vz0Aub5_CDJZ0KORY,13735
6
- yta_video_opengl/complete/video_on_track.py,sha256=oBWlSFumP1khpWE-z3MEBihTxdnjDvdWHbtFrQCjJgE,4964
7
- yta_video_opengl/nodes/__init__.py,sha256=TZ-ZO05PZ0_ABq675E22_PngLWOe-_w5s1cLlV3NbWM,3469
8
- yta_video_opengl/nodes/audio/__init__.py,sha256=4nKkC70k1UgLcCSPqFWm3cKdaJM0KUmQTwGWv1xFarQ,2926
9
- yta_video_opengl/nodes/video/__init__.py,sha256=gSoaoEmjdQmyRwH18mf5z3NAhap3S0RgbeBbfBXi4jc,132
10
- yta_video_opengl/nodes/video/opengl.py,sha256=K2pyCJEd9z4gnZqJetKyGPbtHuBzFsx74ZYyzhSqYPo,8510
11
- yta_video_opengl/reader/__init__.py,sha256=Go2rp9flUIBXuo5d_3eqB5CyIE9SqB8_pKsESyZXO-A,19648
12
- yta_video_opengl/reader/cache.py,sha256=vGb1JgrTAoChw5n-F24Z2Dmgadt0Wa4PVRRDYMy63Q0,16414
13
- yta_video_opengl/t.py,sha256=xOhT1xBEwChlXf-Tuy-WxA_08iRJWVlnL_Hyzr-9-sk,6633
14
- yta_video_opengl/tests.py,sha256=EdTyYtTUd_mj6geWnrvnF-wZSHCKKvhYgiLclkV73O0,26576
15
- yta_video_opengl/utils.py,sha256=yUi17EjNR4SVpvdDUwUaKl4mBCb1uyFCSGoIX3Zr2F0,15586
16
- yta_video_opengl/video.py,sha256=3jBuBW0IRpHrl8wgSoSit2x5pdoi_Q98ZVAg8hK_59I,8638
17
- yta_video_opengl/writer.py,sha256=QwvjQcEkzn1WAVqVTFiI6tYIXJO67LKKUTJGO_eflFM,8893
18
- yta_video_opengl-0.0.14.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
19
- yta_video_opengl-0.0.14.dist-info/METADATA,sha256=gto3fNuFhgs_NWWM8dmqNiNRNk826zlK6YFMkd0EkfM,714
20
- yta_video_opengl-0.0.14.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
21
- yta_video_opengl-0.0.14.dist-info/RECORD,,