yta-video-opengl 0.0.13__py3-none-any.whl → 0.0.15__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,529 +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
- from yta_video_opengl.utils import t_to_pts, pts_to_t, pts_to_index, index_to_pts
25
- from yta_video_opengl.t import T
26
- from av.container import InputContainer
27
- from av.video.stream import VideoStream
28
- from av.audio.stream import AudioStream
29
- from av.video.frame import VideoFrame
30
- from av.audio.frame import AudioFrame
31
- from yta_validation.parameter import ParameterValidator
32
- from yta_validation import PythonValidator
33
- from quicktions import Fraction
34
- from collections import OrderedDict
35
- from typing import Union
36
-
37
- import numpy as np
38
- import av
39
- import math
40
-
41
-
42
- # TODO: This is not actually a Video
43
- # cache, is a FrameCache because we
44
- # create one for video but another
45
- # one for audio. Rename it please.
46
- class VideoFrameCache:
47
- """
48
- Class to manage the frames cache of a video
49
- within a video reader instance.
50
- """
51
-
52
- @property
53
- def fps(
54
- self
55
- ) -> float:
56
- """
57
- The frames per second as a float.
58
- """
59
- return (
60
- float(self.stream.average_rate)
61
- if self.stream.type == 'video' else
62
- float(self.stream.rate)
63
- )
64
-
65
- @property
66
- def time_base(
67
- self
68
- ) -> Union[Fraction, None]:
69
- """
70
- The time base of the stream.
71
- """
72
- return self.stream.time_base
73
-
74
- def __init__(
75
- self,
76
- container: InputContainer,
77
- stream: Union[VideoStream, AudioStream],
78
- size: Union[int, None] = None
79
- ):
80
- ParameterValidator.validate_mandatory_instance_of('container', container, InputContainer)
81
- ParameterValidator.validate_mandatory_instance_of('stream', stream, [VideoStream, AudioStream])
82
- ParameterValidator.validate_positive_int('size', size)
83
-
84
- self.container: InputContainer = container
85
- """
86
- The pyav container.
87
- """
88
- self.stream: Union[VideoStream, AudioStream] = stream
89
- """
90
- The pyav stream.
91
- """
92
- self.cache: OrderedDict = OrderedDict()
93
- """
94
- The cache ordered dictionary.
95
- """
96
- self.size: Union[int, None] = size
97
- """
98
- The size (in number of frames) of the cache.
99
- """
100
- self.key_frames_pts: list[int] = []
101
- """
102
- The list that contains the timestamps of the
103
- key frame packets, ordered from begining to
104
- end.
105
- """
106
-
107
- self._prepare()
108
-
109
- def _prepare(
110
- self
111
- ):
112
- # Index key frames
113
- for packet in self.container.demux(self.stream):
114
- if packet.is_keyframe:
115
- self.key_frames_pts.append(packet.pts)
116
-
117
- # The cache size will be auto-calculated to
118
- # use the amount of frames of the biggest
119
- # interval of frames that belongs to a key
120
- # frame, or a value by default
121
- # TODO: Careful if this is too big
122
- fps = (
123
- float(self.stream.average_rate)
124
- if PythonValidator.is_instance_of(self.stream, VideoStream) else
125
- float(self.stream.rate)
126
- )
127
- # Intervals, but in number of frames
128
- intervals = np.diff(
129
- # Intervals of time between keyframes
130
- np.array(self.key_frames_pts) * self.time_base
131
- ) * fps
132
-
133
- self.size = (
134
- math.ceil(np.max(intervals))
135
- if intervals.size > 0 else
136
- (
137
- self.size or
138
- # TODO: Make this 'default_size' a setting or something
139
- 60
140
- )
141
- )
142
-
143
- self.container.seek(0)
144
-
145
- def _get_nearest_keyframe_pts(
146
- self,
147
- pts: int
148
- ):
149
- """
150
- Get the fps of the keyframe that is the
151
- nearest to the provided 'pts'. Useful to
152
- seek and start decoding frames from that
153
- keyframe.
154
- """
155
- return max([
156
- key_frame_pts
157
- for key_frame_pts in self.key_frames_pts
158
- if key_frame_pts <= pts
159
- ])
160
-
161
- def _store_frame_in_cache(
162
- self,
163
- frame: Union[VideoFrame, AudioFrame]
164
- ) -> Union[VideoFrame, AudioFrame]:
165
- """
166
- Store the provided 'frame' in cache if it
167
- is not on it, removing the first item of
168
- the cache if full.
169
- """
170
- if frame.pts not in self.cache:
171
- self.cache[frame.pts] = frame
172
-
173
- # Clean cache if full
174
- if len(self.cache) > self.size:
175
- self.cache.popitem(last = False)
176
-
177
- return frame
178
-
179
- def get_frame_from_pts(
180
- self,
181
- pts: int
182
- ) -> Union[VideoFrame, AudioFrame, None]:
183
- """
184
- Get the frame that has the provided 'pts'.
185
-
186
- This method will start decoding frames from the
187
- most near key frame (the one with the nearer
188
- pts) until the one requested is found. All those
189
- frames will be stored in cache.
190
-
191
- This method must be called when the frame
192
- requested is not stored in the caché.
193
- """
194
- if pts in self.cache:
195
- return self.cache[pts]
196
-
197
- # Look for the most near key frame
198
- key_frame_pts = self._get_nearest_keyframe_pts(pts)
199
-
200
- # Go to the key frame that includes it
201
- # but I read that it is recommended to
202
- # read ~100ms before the pts we want to
203
- # actually read so we obtain the frames
204
- # clean (this is important in audio)
205
- # TODO: This code is repeated, refactor
206
- pts_pad = int(0.1 / self.time_base)
207
- self.container.seek(
208
- offset = max(0, key_frame_pts - pts_pad),
209
- stream = self.stream
210
- )
211
-
212
- decoded = None
213
- for frame in self.container.decode(self.stream):
214
- # TODO: Could 'frame' be None (?)
215
- if frame.pts is None:
216
- continue
217
-
218
- # Store in cache if needed
219
- self._store_frame_in_cache(frame)
220
-
221
- """
222
- The 'frame.pts * frame.time_base' will give
223
- us the index of the frame, and actually the
224
- 'pts' que are looking for seems to be the
225
- index and not a pts.
226
-
227
- TODO: Review all this in all the logic
228
- please.
229
- """
230
- if frame.pts >= pts:
231
- decoded = self.cache[frame.pts]
232
- break
233
-
234
- # TODO: Is this working? We need previous
235
- # frames to be able to decode...
236
- return decoded
237
-
238
- # TODO: I'm not using this method...
239
- def get_frame(
240
- self,
241
- index: int
242
- ) -> Union[VideoFrame, AudioFrame]:
243
- """
244
- Get the frame with the given 'index' from
245
- the cache.
246
- """
247
- # TODO: Maybe we can accept 'pts' also
248
- pts = index_to_pts(index, self.time_base, self.fps)
249
-
250
- return (
251
- self.cache[pts]
252
- if pts in self.cache else
253
- self.get_frame_from_pts(pts)
254
- )
255
-
256
- def get_frame_from_t(
257
- self,
258
- t: Union[int, float, Fraction]
259
- ) -> Union[VideoFrame, AudioFrame]:
260
- """
261
- Get the frame with the given 't' time moment
262
- from the cache.
263
- """
264
- return self.get_frame_from_pts(T(t, self.time_base).truncated_pts)
265
-
266
- def get_frames(
267
- self,
268
- start: Union[int, float, Fraction] = 0,
269
- end: Union[int, float, Fraction, None] = None
270
- ):
271
- """
272
- Get all the frames in the range between
273
- the provided 'start' and 'end' time in
274
- seconds.
275
-
276
- This method is an iterator that yields
277
- the frame, its t and its index.
278
- """
279
- # We use the cache as iterator if all the frames
280
- # requested are stored there
281
- # TODO: I think this is not ok... I will never
282
- # have all the pts form here stored, as they come
283
- # from 't' that is different...
284
-
285
- """
286
- Feel free to move this explanation to other
287
- place, its about the duration.
288
-
289
- The stream 'duration' parameter is measured
290
- on ticks, the amount of ticks that the
291
- stream lasts. Here below is an example:
292
-
293
- - Duration raw: 529200
294
- - Time base: 1/44100
295
- - Duration (seconds): 12.0
296
- """
297
-
298
- # The 'duration' is on pts ticks
299
- duration = float(self.stream.duration * self.time_base)
300
- # TODO: I think it would be better to
301
- # receive and work with pts instead of
302
- # 't' time moments...
303
- # pts_list = [
304
- # t_to_pts(t, self.time_base)
305
- # for t in T.get_frame_indexes(duration, self.fps, start, end)
306
- # ]
307
-
308
- # if all(
309
- # pts in self.cache
310
- # for pts in pts_list
311
- # ):
312
- # for pts in pts_list:
313
- # yield self.cache[pts]
314
-
315
- # If not all, we ignore the cache because we
316
- # need to decode and they are all consecutive
317
- start = T(start, self.time_base).truncated_pts
318
- end = (
319
- T(end, self.time_base).truncated_pts
320
- if end is not None else
321
- None
322
- )
323
- key_frame_pts = self._get_nearest_keyframe_pts(start)
324
-
325
- # Go to the key frame that includes it
326
- # but I read that it is recommended to
327
- # read ~100ms before the pts we want to
328
- # actually read so we obtain the frames
329
- # clean (this is important in audio)
330
- # TODO: This code is repeated, refactor
331
- pts_pad = int(0.1 / self.time_base)
332
- self.container.seek(
333
- offset = max(0, key_frame_pts - pts_pad),
334
- stream = self.stream
335
- )
336
-
337
- for packet in self.container.demux(self.stream):
338
- for frame in packet.decode():
339
- if frame.pts is None:
340
- continue
341
-
342
- # We store all the frames in cache
343
- self._store_frame_in_cache(frame)
344
-
345
- frame_end_pts = frame.pts + int(frame.samples * (1 / self.stream.sample_rate) / self.time_base)
346
- #frame_end_pts = frame.pts + int(frame.samples)
347
- #frame_end_pts = frame.pts + int(frame.samples / (self.stream.sample_rate * self.time_base))
348
-
349
- # For the next comments imagine we are looking
350
- # for the [1.0, 2.0) audio time range
351
- # Previous frame and nothing is inside
352
- if frame_end_pts <= start:
353
- # From 0.25 to 1.0
354
- continue
355
-
356
- # We finished, nothing is inside and its after
357
- if (
358
- end is not None and
359
- frame.pts >= end
360
- ):
361
- # From 2.0 to 2.75
362
- return
363
-
364
- # We need: from 1 to 2
365
- # Audio is:
366
- # - from 0 to 0.75 (Not included, omit)
367
- # - from 0.5 to 1.5 (Included, take 1.0 to 1.5)
368
- # - from 0.5 to 2.5 (Included, take 1.0 to 2.0)
369
- # - from 1.25 to 1.5 (Included, take 1.25 to 1.5)
370
- # - from 1.25 to 2.5 (Included, take 1.25 to 2.0)
371
- # - from 2.5 to 3.5 (Not included, omit)
372
-
373
- # Here below, at least a part is inside
374
- if (
375
- frame.pts < start and
376
- frame_end_pts > start
377
- ):
378
- # A part at the end is included
379
- end_time = (
380
- # From 0.5 to 1.5 0> take 1.0 to 1.5
381
- frame_end_pts
382
- if frame_end_pts <= end else
383
- # From 0.5 to 2.5 => take 1.0 to 2.0
384
- end
385
- )
386
- #print('A part at the end is included.')
387
- # TODO: I'm using too much 'pts_to_t'
388
- frame = trim_audio_frame_pts(
389
- frame = frame,
390
- start_pts = start,
391
- end_pts = end_time,
392
- time_base = self.time_base
393
- )
394
- elif (
395
- frame.pts >= start and
396
- frame.pts < end
397
- ):
398
- end_time = (
399
- # From 1.25 to 1.5 => take 1.25 to 1.5
400
- frame_end_pts
401
- if frame_end_pts <= end else
402
- # From 1.25 to 2.5 => take 1.25 to 2.0
403
- end
404
- )
405
- # A part at the begining is included
406
- #print('A part at the begining is included.')
407
- # TODO: I'm using too much 'pts_to_t'
408
- frame = trim_audio_frame_pts(
409
- frame = frame,
410
- start_pts = frame.pts,
411
- end_pts = end_time,
412
- time_base = self.time_base
413
- )
414
-
415
- # If the whole frame is in, past as it is
416
-
417
- # TODO: Maybe send a @dataclass instead (?)
418
- # TODO: Do I really need these 't' and 'index' (?)
419
- yield (
420
- frame,
421
- pts_to_t(frame.pts, self.time_base),
422
- pts_to_index(frame.pts, self.time_base, self.fps)
423
- )
424
-
425
- def clear(
426
- self
427
- ) -> 'VideoFrameCache':
428
- """
429
- Clear the cache by removing all the items.
430
- """
431
- self.cache.clear()
432
-
433
- return self
434
-
435
-
436
- # TODO: Move this to a utils when refactored
437
- def trim_audio_frame_pts(
438
- frame: av.AudioFrame,
439
- start_pts: int,
440
- end_pts: int,
441
- time_base
442
- ) -> av.AudioFrame:
443
- """
444
- Recorta un AudioFrame para quedarse solo con la parte entre [start_pts, end_pts] en ticks (PTS).
445
- """
446
- samples = frame.to_ndarray() # (channels, n_samples)
447
- n_channels, n_samples = samples.shape
448
- sr = frame.sample_rate
449
-
450
- #frame_end_pts = frame.pts + int((n_samples / sr) / time_base)
451
- # TODO: This could be wrong
452
- frame_end_pts = frame.pts + int(frame.samples)
453
-
454
- # solapamiento en PTS
455
- cut_start_pts = max(frame.pts, start_pts)
456
- cut_end_pts = min(frame_end_pts, end_pts)
457
-
458
- if cut_start_pts >= cut_end_pts:
459
- raise Exception('Oops...')
460
- return None # no hay solapamiento
461
-
462
- # convertir a índices de samples (en ticks → segundos → samples)
463
- cut_start_time = (cut_start_pts - frame.pts) * time_base
464
- cut_end_time = (cut_end_pts - frame.pts) * time_base
465
-
466
- start_idx = int(cut_start_time * sr)
467
- end_idx = int(cut_end_time * sr)
468
-
469
- # print(
470
- # f"cutting [{frame.pts}, {frame_end_pts}] "
471
- # f"to [{cut_start_pts}, {cut_end_pts}] "
472
- # f"({start_idx}:{end_idx} / {frame.samples})"
473
- # #f"({start_idx}:{end_idx} / {n_samples})"
474
- # )
475
-
476
- cut_samples = samples[:, start_idx:end_idx]
477
-
478
- # crear nuevo AudioFrame
479
- new_frame = av.AudioFrame.from_ndarray(cut_samples, format=frame.format, layout=frame.layout)
480
- new_frame.sample_rate = sr
481
-
482
- # ajustar PTS → corresponde al inicio real del recorte
483
- new_frame.pts = cut_start_pts
484
- new_frame.time_base = time_base
485
-
486
- return new_frame
487
-
488
-
489
-
490
- def trim_audio_frame_t(
491
- frame: av.AudioFrame,
492
- start_time: float,
493
- end_time: float,
494
- time_base
495
- ) -> av.AudioFrame:
496
- """
497
- Recorta un AudioFrame para quedarse solo con la parte entre [start_time, end_time] en segundos.
498
- """
499
- samples = frame.to_ndarray() # (channels, n_samples)
500
- n_channels, n_samples = samples.shape
501
- sr = frame.sample_rate
502
-
503
- frame_start = float(frame.pts * time_base)
504
- frame_end = frame_start + (n_samples / sr)
505
-
506
- # calcular solapamiento en segundos
507
- cut_start = max(frame_start, start_time)
508
- cut_end = min(frame_end, end_time)
509
-
510
- if cut_start >= cut_end:
511
- return None # no hay solapamiento
512
-
513
- # convertir a índices de samples
514
- start_idx = int((cut_start - frame_start) * sr)
515
- end_idx = int((cut_end - frame_start) * sr)
516
-
517
- # print(f'cutting [{str(frame_start)}, {str(frame_end)}] to [{str(float(start_time))}, {str(float(end_time))}] from {str(start_idx)} to {str(end_idx)} of {str(int((frame_end - frame_start) * sr))}')
518
- cut_samples = samples[:, start_idx:end_idx]
519
-
520
- # crear nuevo AudioFrame
521
- new_frame = av.AudioFrame.from_ndarray(cut_samples, format = frame.format, layout = frame.layout)
522
- new_frame.sample_rate = sr
523
-
524
- # ajustar PTS → corresponde al inicio real del recorte
525
- new_pts = int(cut_start / time_base)
526
- new_frame.pts = new_pts
527
- new_frame.time_base = time_base
528
-
529
- return new_frame
@@ -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=drIeM1xYv-8OM-uwjUifwA5teT5X9LZOEK5Nfzux4FI,9384
5
- yta_video_opengl/complete/track.py,sha256=MQJPFyZpl1XFRwYqfq32QzzEESCVtPcnz9yhwm7crng,13729
6
- yta_video_opengl/complete/video_on_track.py,sha256=iDD3wyEHblbRxm1e-clMecx104wUfUwKdCq7ZXSq6CU,5127
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=Cxk838QMjIUOTiO2ZyPGkiPojHlX0c7x6iJkm81_UB4,20039
12
- yta_video_opengl/reader/cache.py,sha256=YYx5AX0BkGj1KtiDePHKj-AZlET4ATTqPB0xbr98WOw,17742
13
- yta_video_opengl/t.py,sha256=vK23hBQQt_KtBO3ceLQ-SwQbB2jfFsPFX2pYNjYaVwk,5488
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=ap3H-aA29Yj-SAsTKsNq2gotcBUatQVQ2jmplgKMm9Q,8465
17
- yta_video_opengl/writer.py,sha256=QwvjQcEkzn1WAVqVTFiI6tYIXJO67LKKUTJGO_eflFM,8893
18
- yta_video_opengl-0.0.13.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
19
- yta_video_opengl-0.0.13.dist-info/METADATA,sha256=DvAvfeMY-o4WMVKdk5d8TiphrseGwMFadDrinfE0wh8,714
20
- yta_video_opengl-0.0.13.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
21
- yta_video_opengl-0.0.13.dist-info/RECORD,,