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.
- yta_video_frame_time/decorators.py +99 -0
- yta_video_frame_time/interval.py +1818 -0
- yta_video_frame_time/t_fraction.py +873 -40
- {yta_video_frame_time-0.0.5.dist-info → yta_video_frame_time-0.0.16.dist-info}/METADATA +3 -2
- yta_video_frame_time-0.0.16.dist-info/RECORD +8 -0
- {yta_video_frame_time-0.0.5.dist-info → yta_video_frame_time-0.0.16.dist-info}/WHEEL +1 -1
- yta_video_frame_time-0.0.5.dist-info/RECORD +0 -6
- {yta_video_frame_time-0.0.5.dist-info → yta_video_frame_time-0.0.16.dist-info/licenses}/LICENSE +0 -0
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
204
|
+
return T(
|
|
205
|
+
t = pts * time_base,
|
|
206
|
+
time_base = time_base
|
|
207
|
+
)
|
|
150
208
|
|
|
151
209
|
|
|
152
|
-
# TODO:
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
177
|
-
|
|
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
|
|
206
|
-
- `t
|
|
207
|
-
- `t
|
|
208
|
-
- `t
|
|
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[
|
|
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
|
-
|
|
226
|
-
|
|
1047
|
+
Parse the provided 'value' as a Fraction
|
|
1048
|
+
and limits its denominator.
|
|
227
1049
|
"""
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|