yta-video-opengl 0.0.10__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.
@@ -0,0 +1,149 @@
1
+ """
2
+ If we have a video placed in a timeline,
3
+ starting at the t=2s and the video lasts
4
+ 2 seconds, the `t` time range in which the
5
+ video is playing is `[2s, 4s]`, so here
6
+ you have some examples with global `t`
7
+ values:
8
+ - `t=1`, the video is not playing because
9
+ it starts at `t=2`
10
+ - `t=3`, the video is playing, it started
11
+ at `t=2` and it has been playing during 1s
12
+ - `t=5`, the video is not playing because
13
+ it started at `t=2`, lasting 2s, so it
14
+ finished at `t=4`
15
+ """
16
+ from yta_video_opengl.video import Video
17
+ from yta_validation.parameter import ParameterValidator
18
+ from av.video.frame import VideoFrame
19
+ from av.audio.frame import AudioFrame
20
+ from typing import Union
21
+
22
+
23
+ class VideoOnTrack:
24
+ """
25
+ A video in the timeline.
26
+ """
27
+
28
+ @property
29
+ def end(
30
+ self
31
+ ) -> float:
32
+ """
33
+ The end time moment 't' of the video once
34
+ once its been placed on the track, which
35
+ is affected by the video duration and its
36
+ start time moment on the track.
37
+
38
+ This end is different from the video end.
39
+ """
40
+ return self.start + self.video.duration
41
+
42
+ def __init__(
43
+ self,
44
+ video: Video,
45
+ start: float = 0.0
46
+ ):
47
+ ParameterValidator.validate_mandatory_instance_of('video', video, Video)
48
+ ParameterValidator.validate_mandatory_positive_number('start', start, do_include_zero = True)
49
+
50
+ self.video: Video = video
51
+ """
52
+ The video source, with all its properties,
53
+ that is placed in the timeline.
54
+ """
55
+ self.start: float = float(start)
56
+ """
57
+ The time moment in which the video should
58
+ start playing, within the timeline.
59
+
60
+ This is the time respect to the timeline
61
+ and its different from the video `start`
62
+ time, which is related to the file.
63
+ """
64
+
65
+ def _get_video_t(
66
+ self,
67
+ t: float
68
+ ) -> float:
69
+ """
70
+ The video 't' time moment for the given
71
+ global 't' time moment. This 't' is the one
72
+ to use inside the video content to display
73
+ its frame.
74
+ """
75
+ return t - self.start
76
+
77
+ def is_playing(
78
+ self,
79
+ t: float
80
+ ) -> bool:
81
+ """
82
+ Check if this video is playing at the general
83
+ 't' time moment, which is a global time moment
84
+ for the whole project.
85
+ """
86
+ return self.start <= t < self.end
87
+
88
+ def get_frame_at(
89
+ self,
90
+ t: float
91
+ ) -> Union[VideoFrame, None]:
92
+ """
93
+ Get the frame for the 't' time moment provided,
94
+ that could be None if the video is not playing
95
+ in that moment.
96
+ """
97
+ return (
98
+ self.video.reader.get_frame_from_t(self._get_video_t(t))
99
+ if self.is_playing(t) else
100
+ None
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
 
@@ -575,16 +670,54 @@ class VideoReader:
575
670
  """
576
671
  return self.video_cache.get_frame(index)
577
672
 
578
- # TODO: Will we use this (?)
673
+ def get_frame_from_t(
674
+ self,
675
+ t: float
676
+ ) -> 'VideoFrame':
677
+ """
678
+ Get the video frame with the given 't' time
679
+ moment, using the video cache system.
680
+ """
681
+ return self.video_cache.get_frame_from_t(t)
682
+
579
683
  def get_audio_frame(
580
684
  self,
581
685
  index: int
582
- ) -> 'VideoFrame':
686
+ ) -> 'AudioFrame':
583
687
  """
584
688
  Get the audio frame with the given 'index',
585
689
  using the audio cache system.
586
690
  """
587
- 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
588
721
 
589
722
  def get_frames(
590
723
  self,
@@ -619,6 +752,21 @@ class VideoReader:
619
752
  self.container.close()
620
753
 
621
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
+
622
770
 
623
771
 
624
772
  """