yta-video-opengl 0.0.11__py3-none-any.whl → 0.0.13__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/complete/timeline.py +147 -59
- yta_video_opengl/complete/track.py +302 -27
- yta_video_opengl/complete/video_on_track.py +72 -9
- yta_video_opengl/reader/__init__.py +190 -89
- yta_video_opengl/reader/cache.py +258 -32
- yta_video_opengl/t.py +185 -0
- yta_video_opengl/tests.py +4 -2
- yta_video_opengl/utils.py +169 -8
- yta_video_opengl/video.py +85 -12
- yta_video_opengl/writer.py +23 -14
- {yta_video_opengl-0.0.11.dist-info → yta_video_opengl-0.0.13.dist-info}/METADATA +2 -1
- yta_video_opengl-0.0.13.dist-info/RECORD +21 -0
- yta_video_opengl-0.0.11.dist-info/RECORD +0 -20
- {yta_video_opengl-0.0.11.dist-info → yta_video_opengl-0.0.13.dist-info}/LICENSE +0 -0
- {yta_video_opengl-0.0.11.dist-info → yta_video_opengl-0.0.13.dist-info}/WHEEL +0 -0
@@ -4,6 +4,7 @@ that, using ffmpeg, detects the video.
|
|
4
4
|
"""
|
5
5
|
from yta_video_opengl.reader.cache import VideoFrameCache
|
6
6
|
from yta_video_opengl.utils import iterate_stream_frames_demuxing
|
7
|
+
from yta_video_opengl.t import T
|
7
8
|
from yta_validation import PythonValidator
|
8
9
|
from av.video.frame import VideoFrame
|
9
10
|
from av.audio.frame import AudioFrame
|
@@ -11,7 +12,7 @@ from av.packet import Packet
|
|
11
12
|
from av.video.stream import VideoStream
|
12
13
|
from av.audio.stream import AudioStream
|
13
14
|
from av.container.input import InputContainer
|
14
|
-
from
|
15
|
+
from quicktions import Fraction
|
15
16
|
from av import open as av_open
|
16
17
|
from typing import Union
|
17
18
|
from dataclasses import dataclass
|
@@ -58,7 +59,7 @@ class VideoReaderFrame:
|
|
58
59
|
self,
|
59
60
|
# TODO: Add the type, please
|
60
61
|
frame: any,
|
61
|
-
t: float = None,
|
62
|
+
t: Union[int, float, Fraction] = None,
|
62
63
|
pixel_format: str = 'rgb24'
|
63
64
|
):
|
64
65
|
self.value: Union[AudioFrame, VideoFrame] = frame
|
@@ -66,7 +67,7 @@ class VideoReaderFrame:
|
|
66
67
|
The frame content, that can be audio or video
|
67
68
|
frame.
|
68
69
|
"""
|
69
|
-
self.t:
|
70
|
+
self.t: Fraction = Fraction(t)
|
70
71
|
"""
|
71
72
|
The 't' time moment of the frame.
|
72
73
|
"""
|
@@ -139,6 +140,8 @@ class VideoReader:
|
|
139
140
|
"""
|
140
141
|
return self.container.decode(self.video_stream)
|
141
142
|
|
143
|
+
# TODO: Remove this when possible, we are not
|
144
|
+
# using 'next' properties
|
142
145
|
@property
|
143
146
|
def next_frame(
|
144
147
|
self
|
@@ -159,16 +162,6 @@ class VideoReader:
|
|
159
162
|
"""
|
160
163
|
return self.container.decode(self.audio_stream)
|
161
164
|
|
162
|
-
@property
|
163
|
-
def next_audio_frame(
|
164
|
-
self
|
165
|
-
) -> Union[AudioFrame, None]:
|
166
|
-
"""
|
167
|
-
Get the next audio frame (decoded) from the
|
168
|
-
iterator.
|
169
|
-
"""
|
170
|
-
return next(self.audio_frame_iterator)
|
171
|
-
|
172
165
|
@property
|
173
166
|
def packet_iterator(
|
174
167
|
self
|
@@ -179,16 +172,6 @@ class VideoReader:
|
|
179
172
|
"""
|
180
173
|
return self.container.demux(self.video_stream)
|
181
174
|
|
182
|
-
@property
|
183
|
-
def next_packet(
|
184
|
-
self
|
185
|
-
) -> Union[Packet, None]:
|
186
|
-
"""
|
187
|
-
Get the next video packet (not decoded) from
|
188
|
-
the iterator.
|
189
|
-
"""
|
190
|
-
return next(self.packet_iterator)
|
191
|
-
|
192
175
|
@property
|
193
176
|
def audio_packet_iterator(
|
194
177
|
self
|
@@ -199,16 +182,6 @@ class VideoReader:
|
|
199
182
|
"""
|
200
183
|
return self.container.demux(self.audio_stream)
|
201
184
|
|
202
|
-
@property
|
203
|
-
def next_audio_packet(
|
204
|
-
self
|
205
|
-
) -> Union[Packet, None]:
|
206
|
-
"""
|
207
|
-
Get the next audio packet (not decoded) from
|
208
|
-
the iterator.
|
209
|
-
"""
|
210
|
-
return next(self.packet_iterator)
|
211
|
-
|
212
185
|
@property
|
213
186
|
def packet_with_audio_iterator(
|
214
187
|
self
|
@@ -221,25 +194,37 @@ class VideoReader:
|
|
221
194
|
return self.container.demux((self.video_stream, self.audio_stream))
|
222
195
|
|
223
196
|
@property
|
224
|
-
def
|
197
|
+
def has_video(
|
225
198
|
self
|
226
|
-
) ->
|
199
|
+
) -> bool:
|
227
200
|
"""
|
228
|
-
|
229
|
-
|
230
|
-
on the position, the packet can be video or
|
231
|
-
audio.
|
201
|
+
Flag to indicate if there is a video stream
|
202
|
+
or not.
|
232
203
|
"""
|
233
|
-
return
|
204
|
+
return self.video_stream is not None
|
205
|
+
|
206
|
+
@property
|
207
|
+
def has_audio(
|
208
|
+
self
|
209
|
+
) -> bool:
|
210
|
+
"""
|
211
|
+
Flag to indicate if there is an audio stream
|
212
|
+
or not.
|
213
|
+
"""
|
214
|
+
return self.audio_stream is not None
|
234
215
|
|
235
216
|
@property
|
236
217
|
def codec_name(
|
237
218
|
self
|
238
|
-
) -> str:
|
219
|
+
) -> Union[str, None]:
|
239
220
|
"""
|
240
221
|
Get the name of the video codec.
|
241
222
|
"""
|
242
|
-
return
|
223
|
+
return (
|
224
|
+
self.video_stream.codec_context.name
|
225
|
+
if self.has_video else
|
226
|
+
None
|
227
|
+
)
|
243
228
|
|
244
229
|
@property
|
245
230
|
def audio_codec_name(
|
@@ -248,64 +233,89 @@ class VideoReader:
|
|
248
233
|
"""
|
249
234
|
Get the name of the audio codec.
|
250
235
|
"""
|
251
|
-
return
|
236
|
+
return (
|
237
|
+
self.audio_stream.codec_context.name
|
238
|
+
if self.has_audio else
|
239
|
+
None
|
240
|
+
)
|
252
241
|
|
253
242
|
@property
|
254
243
|
def number_of_frames(
|
255
244
|
self
|
256
|
-
) -> int:
|
245
|
+
) -> Union[int, None]:
|
257
246
|
"""
|
258
247
|
The number of frames in the video.
|
259
248
|
"""
|
260
|
-
return
|
249
|
+
return (
|
250
|
+
self.video_stream.frames
|
251
|
+
if self.has_video else
|
252
|
+
None
|
253
|
+
)
|
261
254
|
|
262
255
|
@property
|
263
256
|
def number_of_audio_frames(
|
264
257
|
self
|
265
|
-
) -> int:
|
258
|
+
) -> Union[int, None]:
|
266
259
|
"""
|
267
260
|
The number of frames in the audio.
|
268
261
|
"""
|
269
|
-
return
|
262
|
+
return (
|
263
|
+
self.audio_stream.frames
|
264
|
+
if self.has_audio else
|
265
|
+
None
|
266
|
+
)
|
270
267
|
|
271
268
|
@property
|
272
269
|
def fps(
|
273
270
|
self
|
274
|
-
) -> Fraction:
|
271
|
+
) -> Union[Fraction, None]:
|
275
272
|
"""
|
276
273
|
The fps of the video.
|
277
274
|
"""
|
278
|
-
|
279
|
-
|
275
|
+
return (
|
276
|
+
self.video_stream.average_rate
|
277
|
+
if self.has_video else
|
278
|
+
None
|
279
|
+
)
|
280
280
|
|
281
281
|
@property
|
282
282
|
def audio_fps(
|
283
283
|
self
|
284
|
-
) ->
|
284
|
+
) -> Union[int, None]:
|
285
285
|
"""
|
286
286
|
The fps of the audio.
|
287
287
|
"""
|
288
|
-
|
289
|
-
|
288
|
+
return (
|
289
|
+
self.audio_stream.rate
|
290
|
+
if self.has_audio else
|
291
|
+
None
|
292
|
+
)
|
290
293
|
|
291
294
|
@property
|
292
295
|
def time_base(
|
293
296
|
self
|
294
|
-
) -> Fraction:
|
297
|
+
) -> Union[Fraction, None]:
|
295
298
|
"""
|
296
299
|
The time base of the video.
|
297
300
|
"""
|
298
|
-
return
|
301
|
+
return (
|
302
|
+
self.video_stream.time_base
|
303
|
+
if self.has_video else
|
304
|
+
None
|
305
|
+
)
|
299
306
|
|
300
307
|
@property
|
301
308
|
def audio_time_base(
|
302
309
|
self
|
303
|
-
) -> Fraction:
|
310
|
+
) -> Union[Fraction, None]:
|
304
311
|
"""
|
305
312
|
The time base of the audio.
|
306
313
|
"""
|
307
|
-
|
308
|
-
|
314
|
+
return (
|
315
|
+
self.audio_stream.time_base
|
316
|
+
if self.has_audio else
|
317
|
+
None
|
318
|
+
)
|
309
319
|
|
310
320
|
@property
|
311
321
|
def duration(
|
@@ -314,10 +324,15 @@ class VideoReader:
|
|
314
324
|
"""
|
315
325
|
The duration of the video.
|
316
326
|
"""
|
327
|
+
# TODO: What to do in the case we have
|
328
|
+
# video stream but not 'duration'
|
329
|
+
# attribute (?)
|
317
330
|
return (
|
318
331
|
float(self.video_stream.duration * self.video_stream.time_base)
|
319
|
-
if
|
320
|
-
|
332
|
+
if (
|
333
|
+
self.has_video and
|
334
|
+
self.video_stream.duration
|
335
|
+
) else
|
321
336
|
None
|
322
337
|
)
|
323
338
|
|
@@ -328,43 +343,59 @@ class VideoReader:
|
|
328
343
|
"""
|
329
344
|
The duration of the audio.
|
330
345
|
"""
|
331
|
-
# TODO: What
|
346
|
+
# TODO: What to do in the case we have
|
347
|
+
# audio stream but not 'duration'
|
348
|
+
# attribute (?)
|
332
349
|
return (
|
333
350
|
float(self.audio_stream.duration * self.audio_stream.time_base)
|
334
|
-
if
|
335
|
-
|
351
|
+
if (
|
352
|
+
self.has_audio and
|
353
|
+
self.audio_stream.duration
|
354
|
+
) else
|
336
355
|
None
|
337
356
|
)
|
338
357
|
|
339
358
|
@property
|
340
359
|
def size(
|
341
360
|
self
|
342
|
-
) -> tuple[int, int]:
|
361
|
+
) -> Union[tuple[int, int], None]:
|
343
362
|
"""
|
344
363
|
The size of the video in a (width, height) format.
|
345
364
|
"""
|
346
365
|
return (
|
347
|
-
|
348
|
-
|
366
|
+
(
|
367
|
+
self.video_stream.width,
|
368
|
+
self.video_stream.height
|
369
|
+
)
|
370
|
+
if self.has_video else
|
371
|
+
None
|
349
372
|
)
|
350
373
|
|
351
374
|
@property
|
352
375
|
def width(
|
353
376
|
self
|
354
|
-
) -> int:
|
377
|
+
) -> Union[int, None]:
|
355
378
|
"""
|
356
379
|
The width of the video, in pixels.
|
357
380
|
"""
|
358
|
-
return
|
381
|
+
return (
|
382
|
+
self.size[0]
|
383
|
+
if self.size is not None else
|
384
|
+
None
|
385
|
+
)
|
359
386
|
|
360
387
|
@property
|
361
388
|
def height(
|
362
389
|
self
|
363
|
-
) -> int:
|
390
|
+
) -> Union[int, None]:
|
364
391
|
"""
|
365
392
|
The height of the video, in pixels.
|
366
393
|
"""
|
367
|
-
return
|
394
|
+
return (
|
395
|
+
self.size[1]
|
396
|
+
if self.size is not None else
|
397
|
+
None
|
398
|
+
)
|
368
399
|
|
369
400
|
# Any property related to audio has to
|
370
401
|
# start with 'audio_property_name'
|
@@ -389,14 +420,15 @@ class VideoReader:
|
|
389
420
|
video (that also includes the audio) we
|
390
421
|
are reading.
|
391
422
|
"""
|
392
|
-
self.video_stream: VideoStream = None
|
423
|
+
self.video_stream: Union[VideoStream, None] = None
|
393
424
|
"""
|
394
|
-
The stream that includes the video.
|
425
|
+
The stream that includes the video. If
|
426
|
+
no video stream this will be None.
|
395
427
|
"""
|
396
|
-
|
397
|
-
self.audio_stream: AudioStream = None
|
428
|
+
self.audio_stream: Union[AudioStream, None] = None
|
398
429
|
"""
|
399
|
-
The stream that includes the audio.
|
430
|
+
The stream that includes the audio. If
|
431
|
+
no audio stream this will be None.
|
400
432
|
"""
|
401
433
|
self.video_cache: VideoFrameCache = None
|
402
434
|
"""
|
@@ -434,22 +466,42 @@ class VideoReader:
|
|
434
466
|
#self.container.close()
|
435
467
|
else:
|
436
468
|
self.container = av_open(self.filename)
|
469
|
+
|
470
|
+
self.video_stream = (
|
471
|
+
self.container.streams.video[0]
|
472
|
+
if self.container.streams.video else
|
473
|
+
None
|
474
|
+
)
|
437
475
|
# TODO: Should this be 'AUTO' (?)
|
438
|
-
self.video_stream = self.container.streams.video[0]
|
439
476
|
self.video_stream.thread_type = 'AUTO'
|
440
|
-
|
477
|
+
|
478
|
+
self.audio_stream = (
|
479
|
+
self.container.streams.audio[0]
|
480
|
+
if self.container.streams.audio else
|
481
|
+
None
|
482
|
+
)
|
483
|
+
# TODO: Should this be 'AUTO' (?)
|
441
484
|
self.audio_stream.thread_type = 'AUTO'
|
485
|
+
|
486
|
+
if (
|
487
|
+
self.video_stream is None and
|
488
|
+
self.audio_stream is None
|
489
|
+
):
|
490
|
+
raise Exception(f'No video nor audio stream found in the "{self.filename}" file.')
|
491
|
+
|
442
492
|
self.video_cache = VideoFrameCache(self.container, self.video_stream)
|
443
493
|
self.audio_cache = VideoFrameCache(self.container, self.audio_stream)
|
444
494
|
|
445
495
|
def seek(
|
446
496
|
self,
|
447
|
-
pts,
|
497
|
+
pts: int,
|
448
498
|
stream = None
|
449
499
|
) -> 'VideoReader':
|
450
500
|
"""
|
451
501
|
Call the container '.seek()' method with
|
452
|
-
the given 'pts' packet time stamp.
|
502
|
+
the given 'pts' packet time stamp. By
|
503
|
+
default, the video stream is the one in
|
504
|
+
which we apply the seek.
|
453
505
|
"""
|
454
506
|
stream = (
|
455
507
|
self.video_stream
|
@@ -458,7 +510,10 @@ class VideoReader:
|
|
458
510
|
)
|
459
511
|
|
460
512
|
# TODO: Is 'offset' actually a 'pts' (?)
|
461
|
-
self.container.seek(
|
513
|
+
self.container.seek(
|
514
|
+
offset = pts,
|
515
|
+
stream = stream
|
516
|
+
)
|
462
517
|
|
463
518
|
return self
|
464
519
|
|
@@ -472,7 +527,8 @@ class VideoReader:
|
|
472
527
|
for frame in self.frame_iterator:
|
473
528
|
yield VideoReaderFrame(
|
474
529
|
frame = frame,
|
475
|
-
|
530
|
+
# TODO: Maybe use util to transform it (?)
|
531
|
+
t = frame.pts * self.time_base,
|
476
532
|
pixel_format = self.pixel_format
|
477
533
|
)
|
478
534
|
|
@@ -577,7 +633,7 @@ class VideoReader:
|
|
577
633
|
|
578
634
|
def get_frame_from_t(
|
579
635
|
self,
|
580
|
-
t: float
|
636
|
+
t: Union[int, float, Fraction]
|
581
637
|
) -> 'VideoFrame':
|
582
638
|
"""
|
583
639
|
Get the video frame with the given 't' time
|
@@ -585,21 +641,49 @@ class VideoReader:
|
|
585
641
|
"""
|
586
642
|
return self.video_cache.get_frame_from_t(t)
|
587
643
|
|
588
|
-
# TODO: Will we use this (?)
|
589
644
|
def get_audio_frame(
|
590
645
|
self,
|
591
646
|
index: int
|
592
|
-
) -> '
|
647
|
+
) -> 'AudioFrame':
|
593
648
|
"""
|
594
649
|
Get the audio frame with the given 'index',
|
595
650
|
using the audio cache system.
|
596
651
|
"""
|
597
|
-
return self.
|
652
|
+
return self.audio_cache.get_frame(index)
|
653
|
+
|
654
|
+
def get_audio_frame_from_t(
|
655
|
+
self,
|
656
|
+
t: Union[int, float, Fraction]
|
657
|
+
) -> 'AudioFrame':
|
658
|
+
"""
|
659
|
+
Get the audio frame with the given 't' time
|
660
|
+
moment, using the audio cache system.
|
661
|
+
"""
|
662
|
+
return self.audio_cache.get_frame_from_t(t)
|
663
|
+
|
664
|
+
def get_audio_frames_from_t(
|
665
|
+
self,
|
666
|
+
t: Union[int, float, Fraction]
|
667
|
+
):
|
668
|
+
"""
|
669
|
+
Get the sequence of audio frames for the
|
670
|
+
given video 't' time moment, using the
|
671
|
+
audio cache system.
|
672
|
+
|
673
|
+
This is useful when we want to write a
|
674
|
+
video frame with its audio, so we obtain
|
675
|
+
all the audio frames associated to it
|
676
|
+
(remember that a video frame is associated
|
677
|
+
with more than 1 audio frame).
|
678
|
+
"""
|
679
|
+
t: T = T.from_fps(t, self.fps)
|
680
|
+
for frame in self.audio_cache.get_frames(t.truncated, t.next(1).truncated):
|
681
|
+
yield frame
|
598
682
|
|
599
683
|
def get_frames(
|
600
684
|
self,
|
601
|
-
start: float = 0.0,
|
602
|
-
end: Union[float, None] = None
|
685
|
+
start: Union[int, float, Fraction] = 0.0,
|
686
|
+
end: Union[int, float, Fraction, None] = None
|
603
687
|
):
|
604
688
|
"""
|
605
689
|
Iterator to get the video frames in between
|
@@ -610,8 +694,8 @@ class VideoReader:
|
|
610
694
|
|
611
695
|
def get_audio_frames(
|
612
696
|
self,
|
613
|
-
start: float = 0.0,
|
614
|
-
end: Union[float, None] = None
|
697
|
+
start: Union[int, float, Fraction] = 0.0,
|
698
|
+
end: Union[int, float, Fraction, None] = None
|
615
699
|
):
|
616
700
|
"""
|
617
701
|
Iterator to get the audio frames in between
|
@@ -629,6 +713,23 @@ class VideoReader:
|
|
629
713
|
self.container.close()
|
630
714
|
|
631
715
|
|
716
|
+
# TODO: I think I'm not using this...
|
717
|
+
# Remove it please
|
718
|
+
def audio_ts_for_video_t(
|
719
|
+
t: float,
|
720
|
+
video_fps: float,
|
721
|
+
audio_fps: float
|
722
|
+
):
|
723
|
+
# Remember, from [t_start, t_end), the last one
|
724
|
+
# is not included
|
725
|
+
audio_t_start = int(t * audio_fps)
|
726
|
+
audio_t_end = int((t + 1.0 / video_fps) * audio_fps)
|
727
|
+
|
728
|
+
return [
|
729
|
+
i / audio_fps
|
730
|
+
for i in range(audio_t_start, audio_t_end)
|
731
|
+
]
|
732
|
+
|
632
733
|
|
633
734
|
|
634
735
|
"""
|