yta-video-opengl 0.0.11__py3-none-any.whl → 0.0.12__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 +143 -51
- yta_video_opengl/complete/track.py +286 -19
- yta_video_opengl/complete/video_on_track.py +50 -1
- yta_video_opengl/reader/__init__.py +180 -42
- yta_video_opengl/reader/cache.py +221 -17
- yta_video_opengl/utils.py +140 -1
- yta_video_opengl/writer.py +13 -3
- {yta_video_opengl-0.0.11.dist-info → yta_video_opengl-0.0.12.dist-info}/METADATA +1 -1
- yta_video_opengl-0.0.12.dist-info/RECORD +20 -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.12.dist-info}/LICENSE +0 -0
- {yta_video_opengl-0.0.11.dist-info → yta_video_opengl-0.0.12.dist-info}/WHEEL +0 -0
@@ -16,6 +16,7 @@ finished at `t=4`
|
|
16
16
|
from yta_video_opengl.video import Video
|
17
17
|
from yta_validation.parameter import ParameterValidator
|
18
18
|
from av.video.frame import VideoFrame
|
19
|
+
from av.audio.frame import AudioFrame
|
19
20
|
from typing import Union
|
20
21
|
|
21
22
|
|
@@ -97,4 +98,52 @@ class VideoOnTrack:
|
|
97
98
|
self.video.reader.get_frame_from_t(self._get_video_t(t))
|
98
99
|
if self.is_playing(t) else
|
99
100
|
None
|
100
|
-
)
|
101
|
+
)
|
102
|
+
|
103
|
+
def get_audio_frame_at(
|
104
|
+
self,
|
105
|
+
t: float
|
106
|
+
) -> Union[AudioFrame, None]:
|
107
|
+
"""
|
108
|
+
Get the audio frame for the 't' time moment
|
109
|
+
provided, that could be None if the video
|
110
|
+
is not playing in that moment.
|
111
|
+
"""
|
112
|
+
return (
|
113
|
+
self.video.reader.get_audio_frame_from_t(self._get_video_t(t))
|
114
|
+
if self.is_playing(t) else
|
115
|
+
None
|
116
|
+
)
|
117
|
+
|
118
|
+
def get_audio_frames_at(
|
119
|
+
self,
|
120
|
+
t: float
|
121
|
+
) -> Union[any, None]:
|
122
|
+
"""
|
123
|
+
Get the audio frames that must be played at
|
124
|
+
the 't' time moment provided, that could be
|
125
|
+
None if the video is not playing at that
|
126
|
+
moment.
|
127
|
+
|
128
|
+
This method will return None if no audio
|
129
|
+
frames found in that 't' time moment, or an
|
130
|
+
iterator if yes.
|
131
|
+
"""
|
132
|
+
frames = (
|
133
|
+
self.video.reader.get_audio_frames_from_t(self._get_video_t(t))
|
134
|
+
if self.is_playing(t) else
|
135
|
+
[]
|
136
|
+
)
|
137
|
+
|
138
|
+
for frame in frames:
|
139
|
+
# TODO: I am generating a tuple in the
|
140
|
+
# src\yta_video_opengl\reader\cache.py
|
141
|
+
# get_frames method... maybe remove it (?)
|
142
|
+
yield frame[0]
|
143
|
+
|
144
|
+
# # TODO: This was a simple return before
|
145
|
+
# return (
|
146
|
+
# self.video.reader.get_audio_frames_from_t(self._get_video_t(t))
|
147
|
+
# if self.is_playing(t) else
|
148
|
+
# None
|
149
|
+
# )
|
@@ -231,15 +231,39 @@ class VideoReader:
|
|
231
231
|
audio.
|
232
232
|
"""
|
233
233
|
return next(self.packet_with_audio_iterator)
|
234
|
+
|
235
|
+
@property
|
236
|
+
def has_video(
|
237
|
+
self
|
238
|
+
) -> bool:
|
239
|
+
"""
|
240
|
+
Flag to indicate if there is a video stream
|
241
|
+
or not.
|
242
|
+
"""
|
243
|
+
return self.video_stream is not None
|
244
|
+
|
245
|
+
@property
|
246
|
+
def has_audio(
|
247
|
+
self
|
248
|
+
) -> bool:
|
249
|
+
"""
|
250
|
+
Flag to indicate if there is an audio stream
|
251
|
+
or not.
|
252
|
+
"""
|
253
|
+
return self.audio_stream is not None
|
234
254
|
|
235
255
|
@property
|
236
256
|
def codec_name(
|
237
257
|
self
|
238
|
-
) -> str:
|
258
|
+
) -> Union[str, None]:
|
239
259
|
"""
|
240
260
|
Get the name of the video codec.
|
241
261
|
"""
|
242
|
-
return
|
262
|
+
return (
|
263
|
+
self.video_stream.codec_context.name
|
264
|
+
if self.has_video else
|
265
|
+
None
|
266
|
+
)
|
243
267
|
|
244
268
|
@property
|
245
269
|
def audio_codec_name(
|
@@ -248,64 +272,91 @@ class VideoReader:
|
|
248
272
|
"""
|
249
273
|
Get the name of the audio codec.
|
250
274
|
"""
|
251
|
-
return
|
275
|
+
return (
|
276
|
+
self.audio_stream.codec_context.name
|
277
|
+
if self.has_audio else
|
278
|
+
None
|
279
|
+
)
|
252
280
|
|
253
281
|
@property
|
254
282
|
def number_of_frames(
|
255
283
|
self
|
256
|
-
) -> int:
|
284
|
+
) -> Union[int, None]:
|
257
285
|
"""
|
258
286
|
The number of frames in the video.
|
259
287
|
"""
|
260
|
-
return
|
288
|
+
return (
|
289
|
+
self.video_stream.frames
|
290
|
+
if self.has_video else
|
291
|
+
None
|
292
|
+
)
|
261
293
|
|
262
294
|
@property
|
263
295
|
def number_of_audio_frames(
|
264
296
|
self
|
265
|
-
) -> int:
|
297
|
+
) -> Union[int, None]:
|
266
298
|
"""
|
267
299
|
The number of frames in the audio.
|
268
300
|
"""
|
269
|
-
return
|
301
|
+
return (
|
302
|
+
self.audio_stream.frames
|
303
|
+
if self.has_audio else
|
304
|
+
None
|
305
|
+
)
|
270
306
|
|
271
307
|
@property
|
272
308
|
def fps(
|
273
309
|
self
|
274
|
-
) -> Fraction:
|
310
|
+
) -> Union[Fraction, None]:
|
275
311
|
"""
|
276
312
|
The fps of the video.
|
277
313
|
"""
|
278
|
-
|
279
|
-
|
314
|
+
return (
|
315
|
+
# They return it as a Fraction but we usually
|
316
|
+
# use it as float or even int
|
317
|
+
self.video_stream.average_rate
|
318
|
+
if self.has_video else
|
319
|
+
None
|
320
|
+
)
|
280
321
|
|
281
322
|
@property
|
282
323
|
def audio_fps(
|
283
324
|
self
|
284
|
-
) ->
|
325
|
+
) -> Union[int, None]:
|
285
326
|
"""
|
286
327
|
The fps of the audio.
|
287
328
|
"""
|
288
|
-
|
289
|
-
|
329
|
+
return (
|
330
|
+
self.audio_stream.rate
|
331
|
+
if self.has_audio else
|
332
|
+
None
|
333
|
+
)
|
290
334
|
|
291
335
|
@property
|
292
336
|
def time_base(
|
293
337
|
self
|
294
|
-
) -> Fraction:
|
338
|
+
) -> Union[Fraction, None]:
|
295
339
|
"""
|
296
340
|
The time base of the video.
|
297
341
|
"""
|
298
|
-
return
|
342
|
+
return (
|
343
|
+
self.video_stream.time_base
|
344
|
+
if self.has_video else
|
345
|
+
None
|
346
|
+
)
|
299
347
|
|
300
348
|
@property
|
301
349
|
def audio_time_base(
|
302
350
|
self
|
303
|
-
) -> Fraction:
|
351
|
+
) -> Union[Fraction, None]:
|
304
352
|
"""
|
305
353
|
The time base of the audio.
|
306
354
|
"""
|
307
|
-
|
308
|
-
|
355
|
+
return (
|
356
|
+
self.audio_stream.time_base
|
357
|
+
if self.has_audio else
|
358
|
+
None
|
359
|
+
)
|
309
360
|
|
310
361
|
@property
|
311
362
|
def duration(
|
@@ -314,10 +365,15 @@ class VideoReader:
|
|
314
365
|
"""
|
315
366
|
The duration of the video.
|
316
367
|
"""
|
368
|
+
# TODO: What to do in the case we have
|
369
|
+
# video stream but not 'duration'
|
370
|
+
# attribute (?)
|
317
371
|
return (
|
318
372
|
float(self.video_stream.duration * self.video_stream.time_base)
|
319
|
-
if
|
320
|
-
|
373
|
+
if (
|
374
|
+
self.has_video and
|
375
|
+
self.video_stream.duration
|
376
|
+
) else
|
321
377
|
None
|
322
378
|
)
|
323
379
|
|
@@ -328,43 +384,59 @@ class VideoReader:
|
|
328
384
|
"""
|
329
385
|
The duration of the audio.
|
330
386
|
"""
|
331
|
-
# TODO: What
|
387
|
+
# TODO: What to do in the case we have
|
388
|
+
# audio stream but not 'duration'
|
389
|
+
# attribute (?)
|
332
390
|
return (
|
333
391
|
float(self.audio_stream.duration * self.audio_stream.time_base)
|
334
|
-
if
|
335
|
-
|
392
|
+
if (
|
393
|
+
self.has_audio and
|
394
|
+
self.audio_stream.duration
|
395
|
+
) else
|
336
396
|
None
|
337
397
|
)
|
338
398
|
|
339
399
|
@property
|
340
400
|
def size(
|
341
401
|
self
|
342
|
-
) -> tuple[int, int]:
|
402
|
+
) -> Union[tuple[int, int], None]:
|
343
403
|
"""
|
344
404
|
The size of the video in a (width, height) format.
|
345
405
|
"""
|
346
406
|
return (
|
347
|
-
|
348
|
-
|
407
|
+
(
|
408
|
+
self.video_stream.width,
|
409
|
+
self.video_stream.height
|
410
|
+
)
|
411
|
+
if self.has_video else
|
412
|
+
None
|
349
413
|
)
|
350
414
|
|
351
415
|
@property
|
352
416
|
def width(
|
353
417
|
self
|
354
|
-
) -> int:
|
418
|
+
) -> Union[int, None]:
|
355
419
|
"""
|
356
420
|
The width of the video, in pixels.
|
357
421
|
"""
|
358
|
-
return
|
422
|
+
return (
|
423
|
+
self.size[0]
|
424
|
+
if self.size is not None else
|
425
|
+
None
|
426
|
+
)
|
359
427
|
|
360
428
|
@property
|
361
429
|
def height(
|
362
430
|
self
|
363
|
-
) -> int:
|
431
|
+
) -> Union[int, None]:
|
364
432
|
"""
|
365
433
|
The height of the video, in pixels.
|
366
434
|
"""
|
367
|
-
return
|
435
|
+
return (
|
436
|
+
self.size[1]
|
437
|
+
if self.size is not None else
|
438
|
+
None
|
439
|
+
)
|
368
440
|
|
369
441
|
# Any property related to audio has to
|
370
442
|
# start with 'audio_property_name'
|
@@ -389,14 +461,15 @@ class VideoReader:
|
|
389
461
|
video (that also includes the audio) we
|
390
462
|
are reading.
|
391
463
|
"""
|
392
|
-
self.video_stream: VideoStream = None
|
464
|
+
self.video_stream: Union[VideoStream, None] = None
|
393
465
|
"""
|
394
|
-
The stream that includes the video.
|
466
|
+
The stream that includes the video. If
|
467
|
+
no video stream this will be None.
|
395
468
|
"""
|
396
|
-
|
397
|
-
self.audio_stream: AudioStream = None
|
469
|
+
self.audio_stream: Union[AudioStream, None] = None
|
398
470
|
"""
|
399
|
-
The stream that includes the audio.
|
471
|
+
The stream that includes the audio. If
|
472
|
+
no audio stream this will be None.
|
400
473
|
"""
|
401
474
|
self.video_cache: VideoFrameCache = None
|
402
475
|
"""
|
@@ -434,11 +507,28 @@ class VideoReader:
|
|
434
507
|
#self.container.close()
|
435
508
|
else:
|
436
509
|
self.container = av_open(self.filename)
|
510
|
+
|
511
|
+
self.video_stream = (
|
512
|
+
self.container.streams.video[0]
|
513
|
+
if self.container.streams.video else
|
514
|
+
None
|
515
|
+
)
|
437
516
|
# TODO: Should this be 'AUTO' (?)
|
438
|
-
self.video_stream = self.container.streams.video[0]
|
439
517
|
self.video_stream.thread_type = 'AUTO'
|
440
|
-
self.audio_stream =
|
518
|
+
self.audio_stream = (
|
519
|
+
self.container.streams.audio[0]
|
520
|
+
if self.container.streams.audio else
|
521
|
+
None
|
522
|
+
)
|
523
|
+
# TODO: Should this be 'AUTO' (?)
|
441
524
|
self.audio_stream.thread_type = 'AUTO'
|
525
|
+
|
526
|
+
if (
|
527
|
+
self.video_stream is None and
|
528
|
+
self.audio_stream is None
|
529
|
+
):
|
530
|
+
raise Exception(f'No video nor audio stream found in the "{self.filename}" file.')
|
531
|
+
|
442
532
|
self.video_cache = VideoFrameCache(self.container, self.video_stream)
|
443
533
|
self.audio_cache = VideoFrameCache(self.container, self.audio_stream)
|
444
534
|
|
@@ -449,7 +539,9 @@ class VideoReader:
|
|
449
539
|
) -> 'VideoReader':
|
450
540
|
"""
|
451
541
|
Call the container '.seek()' method with
|
452
|
-
the given 'pts' packet time stamp.
|
542
|
+
the given 'pts' packet time stamp. By
|
543
|
+
default, the video stream is the one in
|
544
|
+
which we apply the seek.
|
453
545
|
"""
|
454
546
|
stream = (
|
455
547
|
self.video_stream
|
@@ -458,7 +550,10 @@ class VideoReader:
|
|
458
550
|
)
|
459
551
|
|
460
552
|
# TODO: Is 'offset' actually a 'pts' (?)
|
461
|
-
self.container.seek(
|
553
|
+
self.container.seek(
|
554
|
+
offset = pts,
|
555
|
+
stream = stream
|
556
|
+
)
|
462
557
|
|
463
558
|
return self
|
464
559
|
|
@@ -585,16 +680,44 @@ class VideoReader:
|
|
585
680
|
"""
|
586
681
|
return self.video_cache.get_frame_from_t(t)
|
587
682
|
|
588
|
-
# TODO: Will we use this (?)
|
589
683
|
def get_audio_frame(
|
590
684
|
self,
|
591
685
|
index: int
|
592
|
-
) -> '
|
686
|
+
) -> 'AudioFrame':
|
593
687
|
"""
|
594
688
|
Get the audio frame with the given 'index',
|
595
689
|
using the audio cache system.
|
596
690
|
"""
|
597
|
-
return self.
|
691
|
+
return self.audio_cache.get_frame(index)
|
692
|
+
|
693
|
+
def get_audio_frame_from_t(
|
694
|
+
self,
|
695
|
+
t: float
|
696
|
+
) -> 'AudioFrame':
|
697
|
+
"""
|
698
|
+
Get the audio frame with the given 't' time
|
699
|
+
moment, using the audio cache system.
|
700
|
+
"""
|
701
|
+
return self.audio_cache.get_frame_from_t(t)
|
702
|
+
|
703
|
+
def get_audio_frames_from_t(
|
704
|
+
self,
|
705
|
+
t: float
|
706
|
+
):
|
707
|
+
"""
|
708
|
+
Get the sequence of audio frames for the
|
709
|
+
given video 't' time moment, using the
|
710
|
+
audio cache system.
|
711
|
+
|
712
|
+
This is useful when we want to write a
|
713
|
+
video frame with its audio, so we obtain
|
714
|
+
all the audio frames associated to it
|
715
|
+
(remember that a video frame is associated
|
716
|
+
with more than 1 audio frame).
|
717
|
+
"""
|
718
|
+
print(f'Getting audio frames from video, from t:{str(t)} - nt:{str(t + (1 / self.fps))}')
|
719
|
+
for frame in self.audio_cache.get_frames(t, t + (1 / self.fps)):
|
720
|
+
yield frame
|
598
721
|
|
599
722
|
def get_frames(
|
600
723
|
self,
|
@@ -629,6 +752,21 @@ class VideoReader:
|
|
629
752
|
self.container.close()
|
630
753
|
|
631
754
|
|
755
|
+
def audio_ts_for_video_t(
|
756
|
+
t: float,
|
757
|
+
video_fps: float,
|
758
|
+
audio_fps: float
|
759
|
+
):
|
760
|
+
# Remember, from [t_start, t_end), the last one
|
761
|
+
# is not included
|
762
|
+
audio_t_start = int(t * audio_fps)
|
763
|
+
audio_t_end = int((t + 1.0 / video_fps) * audio_fps)
|
764
|
+
|
765
|
+
return [
|
766
|
+
i / audio_fps
|
767
|
+
for i in range(audio_t_start, audio_t_end)
|
768
|
+
]
|
769
|
+
|
632
770
|
|
633
771
|
|
634
772
|
"""
|