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.
@@ -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 self.video_stream.codec_context.name
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 self.audio_stream.codec_context.name
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 self.video_stream.frames
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 self.audio_stream.frames
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
- # They return it as a Fraction but...
279
- return self.video_stream.average_rate
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
- ) -> Fraction:
325
+ ) -> Union[int, None]:
285
326
  """
286
327
  The fps of the audio.
287
328
  """
288
- # TODO: What if no audio (?)
289
- return self.audio_stream.rate
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 self.video_stream.time_base
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
- # TODO: What if no audio (?)
308
- return self.audio_stream.time_base
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 self.video_stream.duration else
320
- # TODO: What to do in this case (?)
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 if no audio (?)
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 self.audio_stream.duration else
335
- # TODO: What to do in this case (?)
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
- self.video_stream.width,
348
- self.video_stream.height
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 self.size[0]
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 self.size[1]
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
- # TODO: What if no audio (?)
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 = self.container.streams.audio[0]
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(pts, stream = stream)
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
- ) -> 'VideoFrame':
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.video_cache.get_frame(index)
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
  """