yta-video-frame-time 0.0.5__py3-none-any.whl → 0.0.16__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.
@@ -10,12 +10,14 @@ that is 256 * 15 (because first index
10
10
  is 0).
11
11
  """
12
12
  from yta_validation.parameter import ParameterValidator
13
- from yta_validation.number import NumberValidator
14
- from yta_validation import PythonValidator
15
13
  from quicktions import Fraction
16
14
  from typing import Union
17
15
 
16
+ import math
18
17
 
18
+
19
+ # TODO: This T class has to be removed
20
+ # and replaced by the THandler
19
21
  class T:
20
22
  """
21
23
  Class to simplify the way we work with a
@@ -37,7 +39,11 @@ class T:
37
39
  The 't' but as a Fraction that is multiple
38
40
  of the given 'time_base' and truncated.
39
41
  """
40
- return round_t(self._t, self.time_base)
42
+ return round_t(
43
+ t = self._t,
44
+ time_base = self.time_base,
45
+ do_truncate = True
46
+ )
41
47
 
42
48
  @property
43
49
  def rounded(
@@ -49,7 +55,35 @@ class T:
49
55
  value could be the same as truncated if it
50
56
  is closer to the previous value).
51
57
  """
52
- return round_t(self._t, self.time_base, do_truncate = False)
58
+ return round_t(
59
+ t = self._t,
60
+ time_base = self.time_base,
61
+ do_truncate = False
62
+ )
63
+
64
+ @property
65
+ def rounded_up(
66
+ self
67
+ ) -> Fraction:
68
+ """
69
+ The `t` but as a Fraction that is multiple of
70
+ its `time_base` and rounded up (the value is
71
+ the `start` and `end` of the time interval it
72
+ belongs to).
73
+
74
+ This method can be also called `ceil`.
75
+ """
76
+ truncated = self.truncated
77
+
78
+ return (
79
+ truncated
80
+ if check_values_are_same(
81
+ value_a = self._t,
82
+ value_b = truncated,
83
+ tolerance = 0.000001
84
+ ) else
85
+ self.next(1)._t
86
+ )
53
87
 
54
88
  @property
55
89
  def truncated_pts(
@@ -74,6 +108,18 @@ class T:
74
108
  displayed in that moment.
75
109
  """
76
110
  return int(self.rounded / self.time_base)
111
+
112
+ @property
113
+ def rounded_up_pts(
114
+ self
115
+ ) -> int:
116
+ """
117
+ The `rounded_up` value but as a pts, which is
118
+ the int value to be set in the audio and video
119
+ frames while using the `pyav` library, to be
120
+ displayed in that specific moment.
121
+ """
122
+ return int(self.rounded_up / self.time_base)
77
123
 
78
124
  def __init__(
79
125
  self,
@@ -105,7 +151,10 @@ class T:
105
151
  Useful when you need the next value for a
106
152
  range in an iteration or similar.
107
153
  """
108
- return T(self.truncated + n * self.time_base, self.time_base)
154
+ return T(
155
+ t = self.truncated + n * self.time_base,
156
+ time_base = self.time_base
157
+ )
109
158
 
110
159
  def previous(
111
160
  self,
@@ -123,7 +172,10 @@ class T:
123
172
  this will give you an unexpected negative
124
173
  value.
125
174
  """
126
- return T(self.truncated - n * self.time_base, self.time_base)
175
+ return T(
176
+ t = self.truncated - n * self.time_base,
177
+ time_base = self.time_base
178
+ )
127
179
 
128
180
  @staticmethod
129
181
  def from_fps(
@@ -135,7 +187,10 @@ class T:
135
187
  (or sample rate) value directly, that will
136
188
  be turned into a time base.
137
189
  """
138
- return T(t, fps_to_time_base(fps))
190
+ return T(
191
+ t = t,
192
+ time_base = fps_to_time_base(fps)
193
+ )
139
194
 
140
195
  @staticmethod
141
196
  def from_pts(
@@ -146,19 +201,15 @@ class T:
146
201
  Get the instance but providing the 'pts'
147
202
  and the 'time_base'.
148
203
  """
149
- return T(pts * time_base, time_base)
204
+ return T(
205
+ t = pts * time_base,
206
+ time_base = time_base
207
+ )
150
208
 
151
209
 
152
- # TODO: Careful with this below
153
- """
154
- To obtain the pts step, or frame duration in
155
- ticks, you need to apply 2 formulas that are
156
- different according to if the frame is video
157
- or audio:
158
- - Audio: .samples
159
- - Video: int(round((1 / .fps) / .time_base))
160
- """
161
-
210
+ # TODO: This below is interesting, above
211
+ # is old...
212
+
162
213
  def get_ts(
163
214
  start: Union[int, float, Fraction],
164
215
  end: Union[int, float, Fraction],
@@ -173,21 +224,50 @@ def get_ts(
173
224
  [start, end) because the last frame is the
174
225
  start of another time range.
175
226
  """
176
- start = T.from_fps(start, fps).truncated
177
- end = T.from_fps(end, fps).truncated
227
+ thandler = THandler(fps)
228
+
229
+ start = thandler.t.truncated(start)
230
+ end = thandler.t.truncated(end)
178
231
 
179
- time_base = fps_to_time_base(fps)
180
-
181
232
  return [
182
- start + i * time_base
183
- for i in range((end - start) // time_base)
233
+ start + i * thandler.time_base
234
+ for i in range((end - start) // thandler.time_base)
184
235
  ]
185
236
 
237
+ def round_t_with_fps(
238
+ t: Union[int, float, Fraction],
239
+ fps = float,
240
+ do_truncate: bool = True
241
+ ) -> Fraction:
242
+ """
243
+ Round the given `t` time moment to the most
244
+ near multiple of `1/fps` (or the previous
245
+ one if 'do_truncate' is True) using fractions
246
+ module to be precise.
247
+
248
+ This method is very useful to truncate `t`
249
+ time moments in order to get the frames or
250
+ samples for the specific and exact time
251
+ moments according to their fps or sample
252
+ rate (that should be passed as the parameter).
253
+
254
+ Examples below, with `fps = 5`:
255
+ - `t=0.25` => `0.2` (truncated or rounded)
256
+ - `t=0.35` => `0.2` (truncated)
257
+ - `t=0.45` => `0.4` (truncated or rounded)
258
+ - `t=0.55` => `0.6` (rounded)
259
+ """
260
+ return round_t(
261
+ t = t,
262
+ time_base = Fraction(1, fps),
263
+ do_truncate = do_truncate
264
+ )
265
+
186
266
  def round_t(
187
267
  t: Union[int, float, Fraction],
188
268
  time_base = Fraction(1, 60),
189
269
  do_truncate: bool = True
190
- ):
270
+ ) -> Fraction:
191
271
  """
192
272
  Round the given 't' time moment to the most
193
273
  near multiple of the given 'time_base' (or
@@ -202,10 +282,10 @@ def round_t(
202
282
  'time_base' parameter).
203
283
 
204
284
  Examples below, with `time_base = 1/5`:
205
- - `t = 0.25` => `0.2` (truncated or rounded)
206
- - `t = 0.35` => `0.2` (truncated)
207
- - `t = 0.45` => `0.4` (truncated or rounded)
208
- - `t = 0.55` => `0.6` (rounded)
285
+ - `t=0.25` => `0.2` (truncated or rounded)
286
+ - `t=0.35` => `0.2` (truncated)
287
+ - `t=0.45` => `0.4` (truncated or rounded)
288
+ - `t=0.55` => `0.6` (rounded)
209
289
  """
210
290
  t = Fraction(t).limit_denominator()
211
291
  steps = t / time_base
@@ -216,19 +296,772 @@ def round_t(
216
296
  round(steps) # round(float(steps))
217
297
  )
218
298
 
219
- return snapped_steps * time_base
299
+ return parse_fraction(snapped_steps * time_base)
300
+
301
+ def round_up_t(
302
+ t: Union[int, float, Fraction],
303
+ time_base = Fraction(1, 60)
304
+ ):
305
+ """
306
+ Round the `t` time moment provided up always,
307
+ unless the value is exactly the `start` time
308
+ moment of a time interval defined by the given
309
+ `time_base`. This means that we will always
310
+ obtain the `end` part of a time interval.
311
+
312
+ Examples below, with `time_base = 1/5`:
313
+ - `t=0.20` => `0.20`
314
+ - `t=0.21` => `0.40`
315
+ - `t=0.35` => `0.40`
316
+ - `t=0.45` => `0.60`
317
+ - `t=0.55` => `0.60`
318
+ - `t=0.60` => `0.60`
319
+ """
320
+ return check_values_are_same(
321
+ value_a = t,
322
+ value_b = self.truncated(t),
323
+ tolerance = 0.000001
324
+ )
325
+
326
+ def round_pts(
327
+ pts: int,
328
+ fps: Union[int, float, Fraction] = 60,
329
+ time_base = Fraction(1, 60),
330
+ do_truncate: bool = True
331
+ ) -> int:
332
+ """
333
+ Round the given 'pts' presentation
334
+ timestamp to the most near index
335
+ corresponding pts value (or the previous
336
+ one always if 'do_truncate' is True).
337
+
338
+ This method is very useful to truncate
339
+ 'pts' values in order to get the frames or
340
+ samples for the specific and exact time
341
+ moments according to their fps or sample
342
+ rate (that should be passed as the
343
+ 'time_base' parameter).
344
+
345
+ Pts value is calculated based on the 'fps'
346
+ and 'time_base', but here is an easier
347
+ example using the time moments.
348
+
349
+ (!) This is valid only for video.
350
+
351
+ Examples below, with `time_base = 1/5`:
352
+ - `t = 0.25` => `0.2` (truncated or rounded)
353
+ - `t = 0.35` => `0.2` (truncated)
354
+ - `t = 0.45` => `0.4` (truncated or rounded)
355
+ - `t = 0.55` => `0.6` (rounded)
356
+ """
357
+ ticks_per_frame = get_ticks_per_frame(fps, time_base)
358
+
359
+ frame_index = pts / ticks_per_frame
360
+
361
+ frame_index = (
362
+ math.floor(frame_index)
363
+ if do_truncate else
364
+ round(frame_index)
365
+ )
366
+
367
+ return int(frame_index * ticks_per_frame)
368
+
369
+ """
370
+ When we are working with the 't' time
371
+ moment we need to use the fps, and when
372
+ we are working with the 'pts' we need
373
+ to use the 'time_base'.
374
+ """
375
+
376
+ class _T:
377
+ """
378
+ Internal class to be used by the THandler
379
+ as a shortcut to the functionality
380
+ related with 't' values.
381
+ """
382
+
383
+ def __init__(
384
+ self,
385
+ t_handler: 'THandler'
386
+ ):
387
+ self._t_handler: THandler = t_handler
388
+ """
389
+ Instance of the parent THandler to
390
+ access to its properties.
391
+ """
392
+
393
+ def from_pts(
394
+ self,
395
+ pts: int,
396
+ do_truncate: Union[bool, None] = True
397
+ ) -> Fraction:
398
+ """
399
+ Get the 't' time moment for the frame
400
+ defined by the 'pts' presentation
401
+ timestamp.
402
+
403
+ If 't' is in a [start, end) range, we
404
+ will obtain the 'start' value if 't'
405
+ value is closer to it than to the 'end
406
+ value.
407
+
408
+ If 'do_truncate' is True, we will
409
+ always receive the 'start' value. If
410
+ None, we will not make any conversion
411
+ and the value received could be useless
412
+ because it is in the middle of a range.
413
+
414
+ The formula:
415
+ - `pts * time_base`
416
+ """
417
+ t = Fraction(pts * self._t_handler.time_base)
418
+
419
+ return (
420
+ self._t_handler.t.truncated(t)
421
+ if do_truncate is True else
422
+ self._t_handler.t.rounded(t)
423
+ if do_truncate is False else
424
+ t # if None
425
+ )
426
+
427
+ # TODO: Remove this below in the next
428
+ # commit
429
+ pts = (
430
+ self._t_handler.pts.truncated(pts)
431
+ if do_truncate is True else
432
+ self._t_handler.pts.rounded(pts)
433
+ if do_truncate is False else
434
+ pts # if None
435
+ )
436
+
437
+ return Fraction(pts * self._t_handler.time_base)
438
+
439
+ def to_pts(
440
+ self,
441
+ t: Union[int, float, Fraction],
442
+ do_truncate: Union[bool, None] = True
443
+ ) -> int:
444
+ """
445
+ Transform the given 't' to a 'pts' value
446
+ truncating, rounding or applying no
447
+ variation.
448
+
449
+ The formula:
450
+ - `int(t / time_base)`
451
+ """
452
+ return self._t_handler.pts.from_t(t, do_truncate)
453
+
454
+ def to_index(
455
+ self,
456
+ t: Union[int, float, Fraction],
457
+ do_truncate: Union[bool, None] = True
458
+ ) -> int:
459
+ """
460
+ Transform the given `t` to a index value
461
+ truncating, rounding or applying no
462
+ variation.
463
+
464
+ The formula:
465
+ - `int(round(t * fps))`
466
+ """
467
+ t = (
468
+ self.truncated(t)
469
+ if do_truncate is True else
470
+ self.rounded(t)
471
+ if do_truncate is False else
472
+ t
473
+ )
474
+
475
+ return frame_t_to_index(t, self.fps)
476
+
477
+ def from_index(
478
+ self,
479
+ index: int
480
+ ) -> Fraction:
481
+ """
482
+ Transform the given index to a `t` time
483
+ moment value.
484
+
485
+ The formula:
486
+ - `frame_index * (1 / fps)`
487
+ """
488
+ return frame_index_to_t(index, self.fps)
489
+
490
+ def truncated(
491
+ self,
492
+ t: Union[int, float, Fraction]
493
+ ) -> Fraction:
494
+ """
495
+ Get the `t` value provided but truncated.
496
+
497
+ This means that if `t` is in a
498
+ `[start, end)` range, we will obtain the
499
+ `start` value always.
500
+ """
501
+ return round_t(t, Fraction(1, self._t_handler.fps), do_truncate = True)
502
+
503
+ def is_truncated(
504
+ self,
505
+ t: Union[int, float, Fraction]
506
+ ) -> bool:
507
+ """
508
+ Check if the `t` value provided is the truncated
509
+ value, which means that the `t` provided is the
510
+ `start` from the `[start, end)` range defined by
511
+ the fps.
512
+ """
513
+ return check_values_are_same(
514
+ value_a = t,
515
+ value_b = self.truncated(t),
516
+ tolerance = 0.000001
517
+ )
518
+
519
+ def rounded(
520
+ self,
521
+ t: Union[int, float, Fraction]
522
+ ) -> Fraction:
523
+ """
524
+ Get the `t` value provided but rounded.
525
+
526
+ This means that if `t` is in a `[start, end)`
527
+ time interval, we will obtain the `start` or
528
+ the `end` according to which one is closer to
529
+ that `t` time moment provided.
530
+
531
+ Examples below (with `fps=5`):
532
+ - `rounded_up(0.2) = 0.2`
533
+ - `rounded_up(0.21) = 0.2`
534
+ - `rounded_up(0.29) = 0.2`
535
+ - `rounded_up(0.31) = 0.4`
536
+ - `rounded_up(0.39) = 0.4`
537
+ """
538
+ return round_t(t, Fraction(1, self._t_handler.fps), do_truncate = False)
539
+
540
+ def rounded_up(
541
+ self,
542
+ t: Union[int, float, Fraction]
543
+ ) -> Fraction:
544
+ """
545
+ Get the `t` value provided but rounded up.
546
+
547
+ This means that if the `t` value is the `start`
548
+ of the `[start, end)` time range, we will obtain
549
+ the `start`, but we will get the `end` in any
550
+ other situation.
551
+
552
+ This method could be named also as `ceil`.
553
+
554
+ Examples below (with `fps=5`):
555
+ - `rounded_up(0.2) = 0.2`
556
+ - `rounded_up(0.21) = 0.4`
557
+ - `rounded_up(0.33) = 0.4`
558
+ - `rounded_up(0.4) = 0.4`
559
+ """
560
+ truncated = self.truncated(t)
561
+
562
+ return (
563
+ # `start` if it is already `start`
564
+ truncated
565
+ if check_values_are_same(
566
+ value_a = t,
567
+ value_b = truncated,
568
+ tolerance = 0.000001
569
+ ) else
570
+ # `end` in any other case
571
+ self.next(
572
+ t = t,
573
+ n = 1,
574
+ do_truncate = True
575
+ )
576
+ )
577
+
578
+ def next(
579
+ self,
580
+ t: Union[int, float, Fraction],
581
+ n: int = 1,
582
+ do_truncate: bool = True
583
+ ) -> Fraction:
584
+ """
585
+ Get the value that is `n` times ahead of
586
+ the `t` property of this instance
587
+ (truncated or rounded according to the
588
+ `do_truncate` parameter provided).
589
+
590
+ Useful when you need the next value for a
591
+ range in an iteration or similar.
592
+
593
+ The formula:
594
+ - `t + n * (1 / fps)`
595
+ """
596
+ t = (
597
+ self.truncated(t)
598
+ if do_truncate else
599
+ self.rounded(t)
600
+ )
601
+
602
+ return t + n * (1 / self._t_handler.fps)
603
+
604
+ def previous(
605
+ self,
606
+ t: Union[int, float, Fraction],
607
+ n: int = 1,
608
+ do_truncate: bool = True
609
+ ) -> Fraction:
610
+ """
611
+ Get the value that is `n` times before
612
+ the `t` property of this instance
613
+ (truncated or rounded according to the
614
+ `do_truncate` parameter provided).
615
+
616
+ Useful when you need the previous value to
617
+ check if the current is the next one or
618
+ similar.
619
+
620
+ Be careful, if the `truncated` value is 0
621
+ this will give you an unexpected negative
622
+ value.
623
+
624
+ The formula:
625
+ - `t - n * (1 / fps)`
626
+ """
627
+ t = (
628
+ self.truncated(t)
629
+ if do_truncate else
630
+ self.rounded(t)
631
+ )
632
+
633
+ return t - n * (1 / self._t_handler.fps)
634
+
635
+ class _Pts:
636
+ """
637
+ Internal class to be used by the THandler
638
+ as a shortcut to the functionality
639
+ related with `pts` values.
640
+ """
641
+
642
+ def __init__(
643
+ self,
644
+ t_handler: 'THandler'
645
+ ):
646
+ self._t_handler: THandler = t_handler
647
+ """
648
+ Instance of the parent THandler to
649
+ access to its properties.
650
+ """
651
+
652
+ def from_t(
653
+ self,
654
+ t: Union[int, float, Fraction],
655
+ do_truncate: Union[bool, None] = True
656
+ ) -> int:
657
+ """
658
+ Get the pts (the amount of accumulated
659
+ ticks, also called presentation timestamp),
660
+ for the frame defined by the 't' time
661
+ moment provided.
662
+
663
+ If 't' is in a [start, end) range, we
664
+ will obtain the 'start' value if 't'
665
+ value is closer to it than to the 'end
666
+ value.
667
+
668
+ If 'do_truncate' is True, we will
669
+ always receive the 'start' value. If
670
+ None, we will not make any conversion
671
+ and the value received could be useless
672
+ because it is in the middle of a range.
673
+
674
+ The formula:
675
+ - `int(t / time_base)`
676
+ """
677
+ t = (
678
+ self._t_handler.t.truncated(t)
679
+ if do_truncate is True else
680
+ self._t_handler.t.rounded(t)
681
+ if do_truncate is False else
682
+ t # if None
683
+ )
684
+
685
+ return int(t / self._t_handler.time_base)
686
+
687
+ def to_t(
688
+ self,
689
+ pts: int,
690
+ do_truncate: Union[bool, None] = True
691
+ ) -> Fraction:
692
+ """
693
+ Transform the given 'pts' to a 't' value
694
+ truncating, rounding or applying no
695
+ variation.
696
+
697
+ The formula:
698
+ - `pts * time_base`
699
+ """
700
+ return self._t_handler.t.from_pts(pts, do_truncate)
701
+
702
+ def to_index(
703
+ self,
704
+ pts: int,
705
+ do_truncate: Union[bool, None] = True
706
+ ) -> int:
707
+ """
708
+ Transform the given 'pts' to a index value
709
+ truncating, rounding or applying no
710
+ variation.
711
+
712
+ The formula:
713
+ - `int(round((pts * time_base) * fps))`
714
+ """
715
+ return self._t_handler.t.to_index(
716
+ self.to_t(pts, do_truncate = None),
717
+ do_truncate = do_truncate
718
+ )
719
+
720
+ def from_index(
721
+ self,
722
+ index: int
723
+ ) -> Fraction:
724
+ """
725
+ Transform the given index to a 't' time
726
+ moment value.
727
+
728
+ The formula:
729
+ - `int((frame_index * (1 / fps)) * time_base)`
730
+ """
731
+ return self.from_t(
732
+ t = self._t_handler.t.from_index(index),
733
+ do_truncate = True
734
+ )
735
+
736
+ """
737
+ These 2 methods below are here because they
738
+ seem to work for videos, but I think they
739
+ could work not if the video has dynamic frame
740
+ rate or in some other situations, thats why
741
+ this is here as a reminder.
742
+
743
+ I found one video that had audio_fps=44100
744
+ and time_base=256/11025, so it was impossible
745
+ to make a conversion using this formula with
746
+ the audio. With video seems to be ok, but...
747
+
748
+ Use these methods below at your own risk.
749
+ """
750
+
751
+ def truncated(
752
+ self,
753
+ pts: int
754
+ ):
755
+ """
756
+ (!) This is valid only for video and/or
757
+ could work not properly. Use it at your
758
+ own risk.
759
+
760
+ Get the 'pts' value provided but truncated.
761
+
762
+ This means that if 't' is in a
763
+ [start, end) range, we will obtain the
764
+ 'start' value always.
765
+ """
766
+ return round_pts(
767
+ pts = pts,
768
+ fps = self._t_handler.fps,
769
+ time_base = self._t_handler.time_base,
770
+ do_truncate = True
771
+ )
772
+
773
+ def rounded(
774
+ self,
775
+ pts: int
776
+ ) -> int:
777
+ """
778
+ (!) This is valid only for video and/or
779
+ could work not properly. Use it at your
780
+ own risk.
781
+
782
+ Get the 'pts' value provided but rounded.
783
+
784
+ This means that if 't' is in a
785
+ [start, end) range, we will obtain
786
+ the 'start' or the 'end' value according
787
+ to which one is closer to the that 't'
788
+ value provided.
789
+ """
790
+ return round_pts(
791
+ pts = pts,
792
+ fps = self._t_handler.fps,
793
+ time_base = self._t_handler.time_base,
794
+ do_truncate = False
795
+ )
796
+
797
+ def next(
798
+ self,
799
+ pts: int,
800
+ n: int = 1,
801
+ do_truncate: bool = True
802
+ ) -> int:
803
+ """
804
+ (!) This is valid only for video and/or
805
+ could work not properly. Use it at your
806
+ own risk.
807
+
808
+ Get the value that is 'n' times ahead of
809
+ the 'pts' value provided (truncated or
810
+ rounded according to the 'do_truncate'
811
+ parameter provided).
812
+
813
+ Useful when you need the next value for a
814
+ range in an iteration or similar.
815
+
816
+ The formula:
817
+ - `pts + n * ticks_per_frame`
818
+ """
819
+ pts = (
820
+ self.truncated(pts)
821
+ if do_truncate else
822
+ self.rounded(pts)
823
+ )
824
+
825
+ return pts + n * get_ticks_per_frame(self._t_handler.fps, self._t_handler.time_base)
826
+
827
+ def previous(
828
+ self,
829
+ pts: int,
830
+ n: int = 1,
831
+ do_truncate: bool = True
832
+ ) -> int:
833
+ """
834
+ (!) This is valid only for video and/or
835
+ could work not properly. Use it at your
836
+ own risk.
837
+
838
+ Get the value that is 'n' times before
839
+ the 't' property of this instance
840
+ (truncated or rounded according to the
841
+ 'do_truncate' parameter provided).
842
+
843
+ Useful when you need the previous value to
844
+ check if the current is the next one or
845
+ similar.
846
+
847
+ Be careful, if the 'truncated' value is 0
848
+ this will give you an unexpected negative
849
+ value.
850
+
851
+ The formula:
852
+ - `pts - n * ticks_per_frame`
853
+ """
854
+ pts = (
855
+ self.truncated(pts)
856
+ if do_truncate else
857
+ self.rounded(pts)
858
+ )
859
+
860
+ return pts - n * get_ticks_per_frame(self._t_handler.fps, self._t_handler.time_base)
861
+
862
+ class THandler:
863
+ """
864
+ Class to simplify the way we work with
865
+ pyav frames time moments, indexes and
866
+ pts values.
867
+
868
+ This is an example of what a video has:
869
+ - `fps = 60`
870
+ - `time_base = 1 / 15360`
871
+ - `tick = fps * time_base = 256`
872
+
873
+ So, considering this above:
874
+ - Frame #1: `pts[0] = 256 * 0 = 0`
875
+ - Frame #2: `pts[1] = 256 * 1 = 256`
876
+ - Frame #16: `pts[15] = 256 * 15 = 3840`
877
+ """
878
+
879
+ def __init__(
880
+ self,
881
+ fps: Union[int, float, Fraction],
882
+ time_base: Union[Fraction, None] = None
883
+ ):
884
+ """
885
+ If the 'time_base' provided is None it will
886
+ be automatically `1/fps`.
887
+ """
888
+ ParameterValidator.validate_mandatory_positive_number('fps', fps, do_include_zero = False)
889
+ ParameterValidator.validate_instance_of('time_base', time_base, 'Fraction')
890
+
891
+ self.fps: Fraction = parse_fraction(fps)
892
+ """
893
+ The frames per second.
894
+ """
895
+ self.time_base: Fraction = (
896
+ time_base
897
+ if time_base is not None else
898
+ fps_to_time_base(self.fps)
899
+ )
900
+ """
901
+ The time base, that is basically the `time_unit` we
902
+ will use for the calculations.
903
+ """
904
+ self.t: _T = _T(self)
905
+ """
906
+ Shortcut to the instance that handles
907
+ the 't' related functionality.
908
+ """
909
+ self.pts: _Pts = _Pts(self)
910
+ """
911
+ Shortcut to the instance that handles
912
+ the 'pts' related functionality.
913
+ """
914
+
915
+ # TODO: I think I should create a THandler
916
+ # that receives 'fps' and 'time_base' and
917
+ # then, by passing a 't' value, we can
918
+ # calculate everything we need, so we
919
+ # simplify all these processes
920
+ def frame_t_to_index(
921
+ t: Union[float, int, Fraction],
922
+ fps: Union[float, int, Fraction]
923
+ ) -> int:
924
+ """
925
+ Get the index of the frame with the
926
+ given 't' time moment, based on the
927
+ also provided 'fps'.
928
+
929
+ The formula:
930
+ - `int(round(t * fps))`
931
+ """
932
+ return int(round(t * fps))
933
+
934
+ def frame_index_to_t(
935
+ index: int,
936
+ fps: Union[float, int, Fraction]
937
+ ):
938
+ """
939
+ Get the 't' time moment for the frame
940
+ with the given 'index', based on the
941
+ also provided 'fps'.
942
+
943
+ The formula:
944
+ - `frame_index * (1 / fps)`
945
+ """
946
+ return index * parse_fraction(1, parse_fraction(fps))
947
+
948
+ def frame_t_to_pts(
949
+ t: Union[float, int, Fraction],
950
+ fps: Union[float, int, Fraction],
951
+ time_base: Fraction
952
+ ):
953
+ """
954
+ Get the pts (the amount of accumulated
955
+ ticks, also called presentation timestamp),
956
+ for the frame defined by the 't' time
957
+ moment provided, based on the also provided
958
+ 'fps' and 'time_base'.
959
+
960
+ (!) This is valid only for videos.
961
+
962
+ The formula:
963
+ - `frame_index * ticks_per_frame`
964
+ """
965
+ return frame_t_to_index(t, fps) * get_ticks_per_frame(fps, time_base)
966
+
967
+ def frame_pts_to_t(
968
+ pts: int,
969
+ time_base: Fraction
970
+ ) -> Fraction:
971
+ """
972
+ Get the 't' time moment of the frame with
973
+ the given 'pts' (the amount of accumulated
974
+ ticks, also called presentation timestamp),
975
+ based on the also provided 'time_base'.
976
+
977
+ The formula:
978
+ - `pts * time_base`
979
+ """
980
+ return parse_fraction(pts * time_base)
981
+
982
+ def get_audio_frame_duration(
983
+ samples: int,
984
+ audio_fps: Fraction
985
+ ) -> Fraction:
986
+ """
987
+ Get the audio frame duration by giving the
988
+ number of '.samples' and also the rate (that
989
+ we call 'audio_fps').
990
+
991
+ This is useful when trying to guess the next
992
+ pts or t.
993
+
994
+ The formula:
995
+ - `samples / audio_fps`
996
+ """
997
+ return Fraction(samples / audio_fps)
998
+
999
+ def get_ticks_per_frame(
1000
+ fps: Union[float, int, Fraction],
1001
+ time_base: Fraction
1002
+ ) -> int:
1003
+ """
1004
+ Get the amount of ticks per frame. A
1005
+ tick is the minimum amount of time we
1006
+ spend from one frame to the next.
1007
+
1008
+ (!) This is only valid for video
1009
+ apparently.
1010
+
1011
+ The formula:
1012
+ - `1 / (fps * time_base)`
1013
+ """
1014
+ return int(Fraction(1, 1) / (fps * time_base))
1015
+
1016
+ def fps_to_frame_duration(
1017
+ fps: Union[float, int, Fraction]
1018
+ ) -> Fraction:
1019
+ """
1020
+ Get the frame duration based on the 'fps'
1021
+ provided.
1022
+
1023
+ The formula:
1024
+ - `1 / fps`
1025
+ """
1026
+ return Fraction(1, parse_fraction(fps))
220
1027
 
221
1028
  def fps_to_time_base(
222
- fps: Union[int, float, Fraction]
1029
+ fps: Union[float, int, Fraction]
1030
+ ) -> Fraction:
1031
+ """
1032
+ Get the time base based on the given 'fps',
1033
+ that will be basically `1/fps`. This is a
1034
+ bit useless, just when we don't want to
1035
+ think too much to use a time base and we
1036
+ want to use the fps.
1037
+
1038
+ The formula:
1039
+ - `1 / fps`
1040
+ """
1041
+ return Fraction(1, parse_fraction(fps))
1042
+
1043
+ def parse_fraction(
1044
+ value: Union[float, int, Fraction]
223
1045
  ) -> Fraction:
224
1046
  """
225
- Get the pyav time base from the given
226
- 'fps'.
1047
+ Parse the provided 'value' as a Fraction
1048
+ and limits its denominator.
227
1049
  """
228
- return (
229
- Fraction(1, fps)
230
- if NumberValidator.is_int(fps) else
231
- Fraction(1, 1) / fps
232
- if PythonValidator.is_instance_of(fps, 'Fraction') else
233
- Fraction(1, 1) / Fraction.from_float(fps).limit_denominator(1000000) # if float
234
- )
1050
+ fraction = Fraction(value)#.limit_denominator(100_000)
1051
+
1052
+ return fraction
1053
+
1054
+ def check_values_are_same(
1055
+ value_a: Union[Fraction, float],
1056
+ value_b: Union[Fraction, float],
1057
+ tolerance: float = 1e-6
1058
+ ) -> bool:
1059
+ """
1060
+ Check that the `value_a` and the `value_b` are the same
1061
+ by applying the `tolerance` value, that is 0.000001 by
1062
+ default.
1063
+
1064
+ For example, `0.016666666666666666` is the same value as
1065
+ `1/60` with `tolerance=0.000001`.
1066
+ """
1067
+ return abs(float(value_a) - float(value_b)) < tolerance