yta-video-frame-time 0.0.6__tar.gz → 0.0.14__tar.gz
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-0.0.6 → yta_video_frame_time-0.0.14}/PKG-INFO +3 -2
- {yta_video_frame_time-0.0.6 → yta_video_frame_time-0.0.14}/pyproject.toml +2 -1
- yta_video_frame_time-0.0.14/src/yta_video_frame_time/decorators.py +52 -0
- yta_video_frame_time-0.0.14/src/yta_video_frame_time/interval.py +951 -0
- {yta_video_frame_time-0.0.6 → yta_video_frame_time-0.0.14}/src/yta_video_frame_time/t_fraction.py +198 -9
- {yta_video_frame_time-0.0.6 → yta_video_frame_time-0.0.14}/LICENSE +0 -0
- {yta_video_frame_time-0.0.6 → yta_video_frame_time-0.0.14}/README.md +0 -0
- {yta_video_frame_time-0.0.6 → yta_video_frame_time-0.0.14}/src/yta_video_frame_time/__init__.py +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: yta-video-frame-time
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.14
|
|
4
4
|
Summary: Youtube Autonomous Video Frame Time Module
|
|
5
|
+
License-File: LICENSE
|
|
5
6
|
Author: danialcala94
|
|
6
7
|
Author-email: danielalcalavalera@gmail.com
|
|
7
8
|
Requires-Python: ==3.9
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "yta-video-frame-time"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.14"
|
|
4
4
|
description = "Youtube Autonomous Video Frame Time Module"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "danialcala94",email = "danielalcalavalera@gmail.com"}
|
|
@@ -17,6 +17,7 @@ packages = [{include = "yta_video_frame_time", from = "src"}]
|
|
|
17
17
|
|
|
18
18
|
[tool.poetry.group.dev.dependencies]
|
|
19
19
|
pytest = "^8.3.5"
|
|
20
|
+
yta_testing = ">=0.0.1"
|
|
20
21
|
|
|
21
22
|
[build-system]
|
|
22
23
|
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO: I think this module should not be here, or maybe
|
|
3
|
+
yes, but I need this decorator.
|
|
4
|
+
"""
|
|
5
|
+
from functools import wraps
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def parameter_to_time_interval(
|
|
9
|
+
param_name: str
|
|
10
|
+
):
|
|
11
|
+
"""
|
|
12
|
+
Force the parameter with the given `param_name` to
|
|
13
|
+
be a `TimeInterval` instance.
|
|
14
|
+
|
|
15
|
+
Values accepted:
|
|
16
|
+
- `TimeInterval` instance
|
|
17
|
+
- `tuple[float, float]` that will be `(start, end)`
|
|
18
|
+
"""
|
|
19
|
+
def decorator(
|
|
20
|
+
func
|
|
21
|
+
):
|
|
22
|
+
@wraps(func)
|
|
23
|
+
def wrapper(
|
|
24
|
+
*args,
|
|
25
|
+
**kwargs
|
|
26
|
+
):
|
|
27
|
+
from inspect import signature
|
|
28
|
+
from yta_validation import PythonValidator
|
|
29
|
+
from yta_video_frame_time.interval import TimeInterval
|
|
30
|
+
|
|
31
|
+
sig = signature(func)
|
|
32
|
+
bound = sig.bind(*args, **kwargs)
|
|
33
|
+
bound.apply_defaults()
|
|
34
|
+
|
|
35
|
+
value = bound.arguments[param_name]
|
|
36
|
+
|
|
37
|
+
if PythonValidator.is_instance_of(value, TimeInterval):
|
|
38
|
+
pass
|
|
39
|
+
elif (
|
|
40
|
+
PythonValidator.is_tuple(value) and
|
|
41
|
+
len(value) == 2
|
|
42
|
+
):
|
|
43
|
+
value = TimeInterval(*value)
|
|
44
|
+
bound.arguments[param_name] = value
|
|
45
|
+
else:
|
|
46
|
+
raise Exception(f'The "{param_name}" parameter must be a TimeInterval or a tuple[float, float].')
|
|
47
|
+
|
|
48
|
+
return func(*bound.args, **bound.kwargs)
|
|
49
|
+
|
|
50
|
+
return wrapper
|
|
51
|
+
|
|
52
|
+
return decorator
|
|
@@ -0,0 +1,951 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO: This module is general so we should send it
|
|
3
|
+
to another library related to time intervals and
|
|
4
|
+
not specifically video frame times... Move it.
|
|
5
|
+
"""
|
|
6
|
+
from yta_video_frame_time.decorators import parameter_to_time_interval
|
|
7
|
+
from yta_validation.parameter import ParameterValidator
|
|
8
|
+
from quicktions import Fraction
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
Number = Union[int, float, Fraction]
|
|
13
|
+
"""
|
|
14
|
+
Custom type to represent numbers.
|
|
15
|
+
"""
|
|
16
|
+
TimeIntervalType = Union['TimeInterval', tuple[float, float]]
|
|
17
|
+
"""
|
|
18
|
+
The type we accept as time interval, that could be a
|
|
19
|
+
tuple we transform into a time interval.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
TODO: Maybe we can add the possibility of having an
|
|
24
|
+
`fps` value when initializing it to be able to force
|
|
25
|
+
the time interval values to be multiple of `1/fps`.
|
|
26
|
+
But this, if implemented, should be `TimeIntervalFPS`
|
|
27
|
+
or similar, and inheritance from this one but forcing
|
|
28
|
+
the values to be transformed according to that `1/fps`.
|
|
29
|
+
"""
|
|
30
|
+
class TimeInterval:
|
|
31
|
+
"""
|
|
32
|
+
Class to represent a time interval, which is a tuple
|
|
33
|
+
of time moments representing the time range
|
|
34
|
+
`[start, end)`.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def duration(
|
|
39
|
+
self
|
|
40
|
+
) -> float:
|
|
41
|
+
"""
|
|
42
|
+
The `duration` of the time interval.
|
|
43
|
+
"""
|
|
44
|
+
return self.end - self.start
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def copy(
|
|
48
|
+
self
|
|
49
|
+
) -> 'TimeInterval':
|
|
50
|
+
"""
|
|
51
|
+
A copy of this instance.
|
|
52
|
+
"""
|
|
53
|
+
return TimeInterval(
|
|
54
|
+
start = self.start,
|
|
55
|
+
end = self.end
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def as_tuple(
|
|
60
|
+
self
|
|
61
|
+
) -> tuple[float, float]:
|
|
62
|
+
"""
|
|
63
|
+
The time interval but as a `(start, end)` tuple.
|
|
64
|
+
"""
|
|
65
|
+
return (self.start, self.end)
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def cutter(
|
|
69
|
+
self
|
|
70
|
+
) -> 'TimeIntervalCutter':
|
|
71
|
+
"""
|
|
72
|
+
Shortcut to the static class `TimeIntervalCutter` that
|
|
73
|
+
is capable of cutting time intervals.
|
|
74
|
+
"""
|
|
75
|
+
return TimeIntervalCutter
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
start: Number,
|
|
80
|
+
end: Number,
|
|
81
|
+
):
|
|
82
|
+
"""
|
|
83
|
+
Provide the interval as it actually is, with the `start`
|
|
84
|
+
and `end`. These values will be adjusted to an internal
|
|
85
|
+
interval starting on 0.
|
|
86
|
+
|
|
87
|
+
The `end` value must be greater than the `start` value.
|
|
88
|
+
"""
|
|
89
|
+
if start > end:
|
|
90
|
+
raise Exception('The `start` value provided is greater than the `end` value provided.')
|
|
91
|
+
|
|
92
|
+
if start == end:
|
|
93
|
+
raise Exception('The `start` value provided is exactly the `end` value provided.')
|
|
94
|
+
|
|
95
|
+
self.start: float = start
|
|
96
|
+
"""
|
|
97
|
+
The original `start` of the time segment.
|
|
98
|
+
"""
|
|
99
|
+
self.end: float = end
|
|
100
|
+
"""
|
|
101
|
+
The original `end` of the time segment.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def _validate_t(
|
|
105
|
+
self,
|
|
106
|
+
t: Number,
|
|
107
|
+
do_include_start: bool = False,
|
|
108
|
+
do_include_end: bool = False
|
|
109
|
+
) -> None:
|
|
110
|
+
"""
|
|
111
|
+
Validate that the provided `t` value is between the `start`
|
|
112
|
+
and the `end` parameters provided, including them or not
|
|
113
|
+
according to the boolean parameters provided.
|
|
114
|
+
"""
|
|
115
|
+
ParameterValidator.validate_mandatory_number_between(
|
|
116
|
+
name = 't',
|
|
117
|
+
value = t,
|
|
118
|
+
lower_limit = self.start,
|
|
119
|
+
upper_limit = self.end,
|
|
120
|
+
do_include_lower_limit = do_include_start,
|
|
121
|
+
do_include_upper_limit = do_include_end
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def cut(
|
|
125
|
+
self,
|
|
126
|
+
start: Number,
|
|
127
|
+
end: Number
|
|
128
|
+
) -> tuple[Union['TimeInterval', None], Union['TimeInterval', None], Union['TimeInterval', None]]:
|
|
129
|
+
"""
|
|
130
|
+
Cut a segment from the given `start` to the also provided
|
|
131
|
+
`end` time moments of this time interval instance.
|
|
132
|
+
|
|
133
|
+
This method will return a tuple of 3 elements including the
|
|
134
|
+
segments created by cutting this time interval in the order
|
|
135
|
+
they were generated, but also having the 4th element always
|
|
136
|
+
as the index of the one specifically requested by the user.
|
|
137
|
+
The tuple will include all the segments at the begining and
|
|
138
|
+
the rest will be None (unless the 4th one, which is the
|
|
139
|
+
index).
|
|
140
|
+
|
|
141
|
+
Examples below:
|
|
142
|
+
- A time interval of `[2, 5)` cut with `start=3` and `end=4`
|
|
143
|
+
will generate `((2, 3), (3, 4), (4, 5), 1)`.
|
|
144
|
+
- A time interval of `[2, 5)` cut with `start=2` and `end=4`
|
|
145
|
+
will generate `((2, 4), (4, 5), None, 0)`.
|
|
146
|
+
- A time interval of `[2, 5)` cut with `start=4` and `end=5`
|
|
147
|
+
will generate `((2, 4), (4, 5), None, 1)`.
|
|
148
|
+
- A time interval of `[2, 5)` cut with `start=2` and `end=5`
|
|
149
|
+
will generate `((2, 5), None, None, 0)`.
|
|
150
|
+
|
|
151
|
+
As you can see, the result could be the same in different
|
|
152
|
+
situations, but it's up to you (and the specific method in
|
|
153
|
+
which you are calling to this one) to choose the tuple you
|
|
154
|
+
want to return.
|
|
155
|
+
"""
|
|
156
|
+
return self.cutter.from_to(
|
|
157
|
+
time_interval = self,
|
|
158
|
+
start = start,
|
|
159
|
+
end = end
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def cutted(
|
|
163
|
+
self,
|
|
164
|
+
start: Number,
|
|
165
|
+
end: Number
|
|
166
|
+
) -> 'TimeInterval':
|
|
167
|
+
"""
|
|
168
|
+
Get this time interval instance but cutted from the `start`
|
|
169
|
+
to the `end` time moments provided.
|
|
170
|
+
|
|
171
|
+
(!) This method doesn't modify the original instance but
|
|
172
|
+
returns a new one.
|
|
173
|
+
"""
|
|
174
|
+
tuples = self.cut(
|
|
175
|
+
start = start,
|
|
176
|
+
end = end
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
return tuples[tuples[3]]
|
|
180
|
+
|
|
181
|
+
def trim_start(
|
|
182
|
+
self,
|
|
183
|
+
t_variation: Number,
|
|
184
|
+
limit: Union[Number, None] = None
|
|
185
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
186
|
+
"""
|
|
187
|
+
Get a tuple containing the 2 new `TimeInterval` instances
|
|
188
|
+
generated by trimming this one's start the amount of seconds
|
|
189
|
+
provided as the `t_variation` parameter. The first tuple is
|
|
190
|
+
the remaining, and the second one is the new time interval
|
|
191
|
+
requested by the user.
|
|
192
|
+
|
|
193
|
+
This method will raise an exception if the new `start` value
|
|
194
|
+
becomes a value over the time interval `end` value or the
|
|
195
|
+
`limit`, that must be greater than the `start` and lower
|
|
196
|
+
than the time interval `end` value.
|
|
197
|
+
|
|
198
|
+
The `t_variation` must be a positive value, the amount of
|
|
199
|
+
seconds to be trimmed.
|
|
200
|
+
"""
|
|
201
|
+
return self.cutter.trim_start(
|
|
202
|
+
time_interval = self,
|
|
203
|
+
t_variation = t_variation,
|
|
204
|
+
limit = limit
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def trimmed_start(
|
|
208
|
+
self,
|
|
209
|
+
t_variation: Number,
|
|
210
|
+
limit: Union[Number, None] = None
|
|
211
|
+
) -> 'TimeInterval':
|
|
212
|
+
"""
|
|
213
|
+
Get this time interval instance but trimmed from the `start`
|
|
214
|
+
the `t_variation` amount of seconds provided.
|
|
215
|
+
|
|
216
|
+
(!) This method doesn't modify the original instance but
|
|
217
|
+
returns a new one.
|
|
218
|
+
"""
|
|
219
|
+
return self.trim_start(
|
|
220
|
+
t_variation = t_variation,
|
|
221
|
+
limit = limit
|
|
222
|
+
)[1]
|
|
223
|
+
|
|
224
|
+
def trim_end(
|
|
225
|
+
self,
|
|
226
|
+
t_variation: Number,
|
|
227
|
+
limit: Union[Number, None] = None
|
|
228
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
229
|
+
"""
|
|
230
|
+
Get a tuple containing the 2 new `TimeInterval` instances
|
|
231
|
+
generated by trimming this one's end the amount of seconds
|
|
232
|
+
provided as the `t_variation` parameter. The first tuple is
|
|
233
|
+
the one requested by the user, and the second one is the
|
|
234
|
+
remaining.
|
|
235
|
+
|
|
236
|
+
This method will raise an exception if the new `end` value
|
|
237
|
+
becomes a value under the time interval `start` value or
|
|
238
|
+
the `limit`, that must be greater than the `start` and lower
|
|
239
|
+
than the time interval `end` value.
|
|
240
|
+
|
|
241
|
+
The `t_variation` must be a positive value, the amount of
|
|
242
|
+
seconds to be trimmed.
|
|
243
|
+
"""
|
|
244
|
+
return self.cutter.trim_end(
|
|
245
|
+
time_interval = self,
|
|
246
|
+
t_variation = t_variation,
|
|
247
|
+
limit = limit
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def trimmed_end(
|
|
251
|
+
self,
|
|
252
|
+
t_variation: Number,
|
|
253
|
+
limit: Union[Number, None] = None
|
|
254
|
+
) -> 'TimeInterval':
|
|
255
|
+
"""
|
|
256
|
+
Get this time interval instance but trimmed from the `end`
|
|
257
|
+
the `t_variation` amount of seconds provided.
|
|
258
|
+
|
|
259
|
+
(!) This method doesn't modify the original instance but
|
|
260
|
+
returns a new one.
|
|
261
|
+
"""
|
|
262
|
+
return self.trim_end(
|
|
263
|
+
t_variation = t_variation,
|
|
264
|
+
limit = limit
|
|
265
|
+
)[0]
|
|
266
|
+
|
|
267
|
+
def split(
|
|
268
|
+
self,
|
|
269
|
+
t: Number
|
|
270
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
271
|
+
"""
|
|
272
|
+
Split the time interval at the provided `t` time moment
|
|
273
|
+
and get the 2 new time intervals as a result (as a tuple).
|
|
274
|
+
|
|
275
|
+
This method will raise an exception if the `t` value
|
|
276
|
+
provided is a limit value (or above).
|
|
277
|
+
|
|
278
|
+
Examples below:
|
|
279
|
+
- A time interval of `[2, 5)` cut with `t=3` will generate
|
|
280
|
+
`((2, 3), (3, 5))`.
|
|
281
|
+
- A time interval of `[2, 5)` cut with `t=4` will generate
|
|
282
|
+
`((2, 4), (4, 5))`.
|
|
283
|
+
- A time interval of `[2, 5)` cut with `t>=5` will raise
|
|
284
|
+
exception.
|
|
285
|
+
- A time interval of `[2, 5)` cut with `t<=2` will raise
|
|
286
|
+
exception.
|
|
287
|
+
"""
|
|
288
|
+
return self.cutter.split(
|
|
289
|
+
time_interval = self,
|
|
290
|
+
t = t
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
def splitted_left(
|
|
294
|
+
self,
|
|
295
|
+
t: Number
|
|
296
|
+
) -> 'TimeInterval':
|
|
297
|
+
"""
|
|
298
|
+
Split this time interval instance by the `t` time moment
|
|
299
|
+
provided and obtain the time interval from the left side,
|
|
300
|
+
which goes from this time interval `start` time moment to
|
|
301
|
+
the `t` provided.
|
|
302
|
+
"""
|
|
303
|
+
return self.split(t)[0]
|
|
304
|
+
|
|
305
|
+
def splitted_right(
|
|
306
|
+
self,
|
|
307
|
+
t: Number
|
|
308
|
+
) -> 'TimeInterval':
|
|
309
|
+
"""
|
|
310
|
+
Split this time interval instance by the `t` time moment
|
|
311
|
+
provided and obtain the time interval from the right side,
|
|
312
|
+
which goes from the `t` provided to the time interval `end`
|
|
313
|
+
time moment.
|
|
314
|
+
"""
|
|
315
|
+
return self.split(t)[1]
|
|
316
|
+
|
|
317
|
+
# Other methods below
|
|
318
|
+
def is_t_included(
|
|
319
|
+
self,
|
|
320
|
+
t: float,
|
|
321
|
+
do_include_end: bool = False
|
|
322
|
+
) -> bool:
|
|
323
|
+
"""
|
|
324
|
+
Check if the `t` time moment provided is included in
|
|
325
|
+
this time interval, including the `end` only if the
|
|
326
|
+
`do_include_end` parameter is set as `True`.
|
|
327
|
+
"""
|
|
328
|
+
return TimeIntervalUtils.a_includes_t(
|
|
329
|
+
t = t,
|
|
330
|
+
time_interval_a = self,
|
|
331
|
+
do_include_end = do_include_end
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
def is_adjacent_to(
|
|
335
|
+
self,
|
|
336
|
+
time_interval: 'TimeInterval'
|
|
337
|
+
) -> bool:
|
|
338
|
+
"""
|
|
339
|
+
Check if the `time_interval` provided is adjacent
|
|
340
|
+
to this time interval, which means that the `end`
|
|
341
|
+
of one interval is also the `start` of the other
|
|
342
|
+
one.
|
|
343
|
+
|
|
344
|
+
(!) Giving the time intervals inverted will
|
|
345
|
+
provide the same result.
|
|
346
|
+
|
|
347
|
+
Example below:
|
|
348
|
+
- `a=[2, 5)` and `b=[5, 7)` => `True`
|
|
349
|
+
- `a=[5, 7)` and `b=[2, 5)` => `True`
|
|
350
|
+
- `a=[2, 5)` and `b=[3, 4)` => `False`
|
|
351
|
+
- `a=[2, 5)` and `b=[6, 8)` => `False`
|
|
352
|
+
"""
|
|
353
|
+
return TimeIntervalUtils.a_is_adjacent_to_b(
|
|
354
|
+
time_interval_a = self,
|
|
355
|
+
time_interval_b = time_interval
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
def do_contains_a(
|
|
359
|
+
self,
|
|
360
|
+
time_interval: 'TimeInterval'
|
|
361
|
+
) -> bool:
|
|
362
|
+
"""
|
|
363
|
+
Check if this time interval includes the `time_interval`
|
|
364
|
+
provided or not, which means that the `time_interval`
|
|
365
|
+
provided is fully contained (included) in this one.
|
|
366
|
+
"""
|
|
367
|
+
return TimeIntervalUtils.a_contains_b(
|
|
368
|
+
time_interval_a = self,
|
|
369
|
+
time_interval_b = time_interval
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
def is_contained_in(
|
|
373
|
+
self,
|
|
374
|
+
time_interval: 'TimeInterval'
|
|
375
|
+
) -> bool:
|
|
376
|
+
"""
|
|
377
|
+
Check if this time interval is fully contained in
|
|
378
|
+
the `time_interval` provided, which is a synonim
|
|
379
|
+
of being fully overlapped by that `time_interval`.
|
|
380
|
+
"""
|
|
381
|
+
return TimeIntervalUtils.a_is_contained_in_b(
|
|
382
|
+
time_interval_a = self,
|
|
383
|
+
time_interval_b = time_interval
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
def do_intersects_with(
|
|
387
|
+
self,
|
|
388
|
+
time_interval: 'TimeInterval'
|
|
389
|
+
) -> bool:
|
|
390
|
+
"""
|
|
391
|
+
Check if this time interval intersects with the one
|
|
392
|
+
provided as `time_interval`, which means that they
|
|
393
|
+
have at least a part in common.
|
|
394
|
+
"""
|
|
395
|
+
return TimeIntervalUtils.a_intersects_with_b(
|
|
396
|
+
time_interval_a = self,
|
|
397
|
+
time_interval_b = time_interval
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
def get_intersection_with_a(
|
|
401
|
+
self,
|
|
402
|
+
time_interval: 'TimeInterval'
|
|
403
|
+
) -> Union['TimeInterval', None]:
|
|
404
|
+
"""
|
|
405
|
+
Get the time interval that intersects this one and the
|
|
406
|
+
one provided as `time_interval`. The result can be `None`
|
|
407
|
+
if there is no intersection in between both.
|
|
408
|
+
"""
|
|
409
|
+
return TimeIntervalUtils.get_intersection_of_a_and_b(
|
|
410
|
+
time_interval_a = self,
|
|
411
|
+
time_interval_b = time_interval
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
class TimeIntervalUtils:
|
|
416
|
+
"""
|
|
417
|
+
Static class to wrap the utils related to time intervals.
|
|
418
|
+
"""
|
|
419
|
+
|
|
420
|
+
@staticmethod
|
|
421
|
+
def a_includes_t(
|
|
422
|
+
t: float,
|
|
423
|
+
time_interval_a: 'TimeInterval',
|
|
424
|
+
do_include_end: bool = False
|
|
425
|
+
) -> bool:
|
|
426
|
+
"""
|
|
427
|
+
Check if the `t` time moment provided is included in
|
|
428
|
+
the `time_interval_a` given. The `time_interval_a.end`
|
|
429
|
+
is excluded unless the `do_include_end` parameter is
|
|
430
|
+
set as `True`.
|
|
431
|
+
|
|
432
|
+
A time interval is `[start, end)`, thats why the end is
|
|
433
|
+
excluded by default.
|
|
434
|
+
"""
|
|
435
|
+
return (
|
|
436
|
+
time_interval_a.start <= t <= time_interval_a.end
|
|
437
|
+
if do_include_end else
|
|
438
|
+
time_interval_a.start <= t < time_interval_a.end
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
@staticmethod
|
|
442
|
+
def a_is_adjacent_to_b(
|
|
443
|
+
time_interval_a: 'TimeInterval',
|
|
444
|
+
time_interval_b: 'TimeInterval',
|
|
445
|
+
) -> bool:
|
|
446
|
+
"""
|
|
447
|
+
Check if the `time_interval_a` provided and the
|
|
448
|
+
also given `time_interval_b` are adjacent, which
|
|
449
|
+
means that the `end` of one interval is also the
|
|
450
|
+
`start` of the other one.
|
|
451
|
+
|
|
452
|
+
(!) Giving the time intervals inverted will
|
|
453
|
+
provide the same result.
|
|
454
|
+
|
|
455
|
+
Examples below:
|
|
456
|
+
- `a=[2, 5)` and `b=[5, 7)` => `True`
|
|
457
|
+
- `a=[5, 7)` and `b=[2, 5)` => `True`
|
|
458
|
+
- `a=[2, 5)` and `b=[3, 4)` => `False`
|
|
459
|
+
- `a=[2, 5)` and `b=[6, 8)` => `False`
|
|
460
|
+
"""
|
|
461
|
+
return (
|
|
462
|
+
TimeIntervalUtils.a_is_inmediately_before_b(time_interval_a, time_interval_b) or
|
|
463
|
+
TimeIntervalUtils.a_is_inmediately_after_b(time_interval_a, time_interval_b)
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
@staticmethod
|
|
467
|
+
def a_is_inmediately_before_b(
|
|
468
|
+
time_interval_a: 'TimeInterval',
|
|
469
|
+
time_interval_b: 'TimeInterval',
|
|
470
|
+
) -> bool:
|
|
471
|
+
"""
|
|
472
|
+
Check if the `time_interval_a` provided is inmediately
|
|
473
|
+
before the also given `time_interval_b`, which means
|
|
474
|
+
that the `end` of the first one is also the `start` of
|
|
475
|
+
the second one.
|
|
476
|
+
|
|
477
|
+
Examples below:
|
|
478
|
+
- `a=[2, 5)` and `b=[5, 7)` => `True`
|
|
479
|
+
- `a=[5, 7)` and `b=[2, 5)` => `False`
|
|
480
|
+
- `a=[2, 5)` and `b=[3, 4)` => `False`
|
|
481
|
+
- `a=[2, 5)` and `b=[6, 8)` => `False`
|
|
482
|
+
"""
|
|
483
|
+
return time_interval_a.end == time_interval_b.start
|
|
484
|
+
|
|
485
|
+
@staticmethod
|
|
486
|
+
def a_is_inmediately_after_b(
|
|
487
|
+
time_interval_a: 'TimeInterval',
|
|
488
|
+
time_interval_b: 'TimeInterval',
|
|
489
|
+
) -> bool:
|
|
490
|
+
"""
|
|
491
|
+
Check if the `time_interval_a` provided is inmediately
|
|
492
|
+
after the also given `time_interval_b`, which means
|
|
493
|
+
that the `start` of the first one is also the `end` of
|
|
494
|
+
the second one.
|
|
495
|
+
|
|
496
|
+
Examples below:
|
|
497
|
+
- `a=[2, 5)` and `b=[5, 7)` => `False`
|
|
498
|
+
- `a=[5, 7)` and `b=[2, 5)` => `True`
|
|
499
|
+
- `a=[2, 5)` and `b=[3, 4)` => `False`
|
|
500
|
+
- `a=[2, 5)` and `b=[6, 8)` => `False`
|
|
501
|
+
"""
|
|
502
|
+
return time_interval_a.start == time_interval_b.end
|
|
503
|
+
|
|
504
|
+
@staticmethod
|
|
505
|
+
def a_contains_b(
|
|
506
|
+
time_interval_a: 'TimeInterval',
|
|
507
|
+
time_interval_b: 'TimeInterval'
|
|
508
|
+
) -> bool:
|
|
509
|
+
"""
|
|
510
|
+
Check if the `time_interval_a` time interval provided
|
|
511
|
+
includes the `time_interval_b` or not, which means that
|
|
512
|
+
the `time_interval_b` is fully contained in the first
|
|
513
|
+
one.
|
|
514
|
+
|
|
515
|
+
Examples below:
|
|
516
|
+
- `a=[2, 5)` and `b=[3, 4)` => `True`
|
|
517
|
+
- `a=[2, 5)` and `b=[2, 4)` => `True`
|
|
518
|
+
- `a=[2, 5)` and `b=[3, 6)` => `False`
|
|
519
|
+
- `a=[2, 5)` and `b=[6, 8)` => `False`
|
|
520
|
+
"""
|
|
521
|
+
return (
|
|
522
|
+
time_interval_a.start <= time_interval_b.start and
|
|
523
|
+
time_interval_a.end >= time_interval_b.end
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
@staticmethod
|
|
527
|
+
def a_is_contained_in_b(
|
|
528
|
+
time_interval_a: 'TimeInterval',
|
|
529
|
+
time_interval_b: 'TimeInterval',
|
|
530
|
+
) -> bool:
|
|
531
|
+
"""
|
|
532
|
+
Check if the `time_interval_a` provided is fully
|
|
533
|
+
contained into the also provided `time_interval_b`.
|
|
534
|
+
|
|
535
|
+
Examples below:
|
|
536
|
+
- `a=[2, 5)` and `b=[1, 6)` => `True`
|
|
537
|
+
- `a=[2, 5)` and `b=[0, 9)` => `True`
|
|
538
|
+
- `a=[2, 5)` and `b=[2, 4)` => `False`
|
|
539
|
+
- `a=[2, 5)` and `b=[4, 8)` => `False`
|
|
540
|
+
- `a=[2, 5)` and `b=[7, 8)` => `False`
|
|
541
|
+
"""
|
|
542
|
+
return TimeIntervalUtils.a_contains_b(
|
|
543
|
+
time_interval_a = time_interval_b,
|
|
544
|
+
time_interval_b = time_interval_a
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
@staticmethod
|
|
548
|
+
def a_intersects_with_b(
|
|
549
|
+
time_interval_a: 'TimeInterval',
|
|
550
|
+
time_interval_b: 'TimeInterval',
|
|
551
|
+
) -> bool:
|
|
552
|
+
"""
|
|
553
|
+
Check if the `time_interval_a` and the `time_interval_b`
|
|
554
|
+
provided has at least a part in common.
|
|
555
|
+
|
|
556
|
+
Examples below:
|
|
557
|
+
- `a=[2, 5)` and `b=[4, 6)` => `True`
|
|
558
|
+
- `a=[2, 5)` and `b=[1, 3)` => `True`
|
|
559
|
+
- `a=[2, 5)` and `b=[5, 6)` => `False`
|
|
560
|
+
- `a=[2, 5)` and `b=[7, 8)` => `False`
|
|
561
|
+
- `a=[2, 5)` and `b=[1, 2)` => `False`
|
|
562
|
+
"""
|
|
563
|
+
return (
|
|
564
|
+
time_interval_b.start < time_interval_a.end and
|
|
565
|
+
time_interval_a.start < time_interval_b.end
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
@staticmethod
|
|
569
|
+
def get_intersection_of_a_and_b(
|
|
570
|
+
time_interval_a: 'TimeInterval',
|
|
571
|
+
time_interval_b: 'TimeInterval'
|
|
572
|
+
) -> Union['TimeInterval', None]:
|
|
573
|
+
"""
|
|
574
|
+
Get the time interval that intersects the two time
|
|
575
|
+
intervals provided, that can be `None` if there is no
|
|
576
|
+
intersection in between both.
|
|
577
|
+
"""
|
|
578
|
+
return (
|
|
579
|
+
None
|
|
580
|
+
if not TimeIntervalUtils.a_intersects_with_b(
|
|
581
|
+
time_interval_a = time_interval_a,
|
|
582
|
+
time_interval_b = time_interval_b
|
|
583
|
+
) else
|
|
584
|
+
TimeInterval(
|
|
585
|
+
start = max(time_interval_a.start, time_interval_b.start),
|
|
586
|
+
end = min(time_interval_a.end, time_interval_b.end)
|
|
587
|
+
)
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
class TimeIntervalCutter:
|
|
591
|
+
"""
|
|
592
|
+
Class to wrap the functionality related to cutting
|
|
593
|
+
time intervals.
|
|
594
|
+
"""
|
|
595
|
+
|
|
596
|
+
"""
|
|
597
|
+
TODO: Methods I have to implement:
|
|
598
|
+
trim_end → recorta la parte final
|
|
599
|
+
trim_start → recorta la parte inicial
|
|
600
|
+
cut_segment → elimina un tramo [a, b)
|
|
601
|
+
split_at → divide en dos en un punto dado
|
|
602
|
+
"""
|
|
603
|
+
|
|
604
|
+
@staticmethod
|
|
605
|
+
@parameter_to_time_interval('time_interval')
|
|
606
|
+
def trim_end_to(
|
|
607
|
+
time_interval: TimeIntervalType,
|
|
608
|
+
t: Number
|
|
609
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
610
|
+
"""
|
|
611
|
+
Get a tuple containing the 2 new `TimeInterval` instances
|
|
612
|
+
generated by trimming the `time_interval` end to the `t`
|
|
613
|
+
time moment provided. The first tuple is the requested by
|
|
614
|
+
the user, and the second one is the remaining.
|
|
615
|
+
|
|
616
|
+
The `t` time moment provided must be a value between the
|
|
617
|
+
`start` and `end` of the `time_interval` provided.
|
|
618
|
+
"""
|
|
619
|
+
time_interval._validate_t(t)
|
|
620
|
+
|
|
621
|
+
return (
|
|
622
|
+
TimeInterval(
|
|
623
|
+
start = time_interval.start,
|
|
624
|
+
end = t
|
|
625
|
+
),
|
|
626
|
+
TimeInterval(
|
|
627
|
+
start = t,
|
|
628
|
+
end = time_interval.end
|
|
629
|
+
)
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
@staticmethod
|
|
633
|
+
@parameter_to_time_interval('time_interval')
|
|
634
|
+
def trim_end(
|
|
635
|
+
time_interval: TimeIntervalType,
|
|
636
|
+
t_variation: Number,
|
|
637
|
+
limit: Union[Number, None] = None,
|
|
638
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
639
|
+
"""
|
|
640
|
+
Get a tuple containing the 2 new `TimeInterval` instances
|
|
641
|
+
generated by trimming the `time_interval` end the amount
|
|
642
|
+
of seconds provided as the `t_variation` parameter. The
|
|
643
|
+
first tuple is the requested by the user, and the second one
|
|
644
|
+
is the remaining.
|
|
645
|
+
|
|
646
|
+
This method will raise an exception if the new `end` value
|
|
647
|
+
becomes a value under the time interval `start` value or
|
|
648
|
+
the `limit`, that must be greater than the `start` and lower
|
|
649
|
+
than the time interval `end` value.
|
|
650
|
+
|
|
651
|
+
The `t_variation` must be a positive value, the amount of
|
|
652
|
+
seconds to be trimmed.
|
|
653
|
+
"""
|
|
654
|
+
ParameterValidator.validate_mandatory_positive_number('t_variation', t_variation, do_include_zero = False)
|
|
655
|
+
ParameterValidator.validate_number_between('limit', limit, time_interval.start, time_interval.end, False, False)
|
|
656
|
+
|
|
657
|
+
new_end = time_interval.end - t_variation
|
|
658
|
+
limit = (
|
|
659
|
+
time_interval.start
|
|
660
|
+
if limit is None else
|
|
661
|
+
limit
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
if new_end <= time_interval.start:
|
|
665
|
+
raise Exception('The "t_variation" value provided makes the new end value be lower than the "start" value.')
|
|
666
|
+
|
|
667
|
+
if new_end <= limit:
|
|
668
|
+
raise Exception('The "t_variation" value provided makes the new end value be lower than the "limit" value provided.')
|
|
669
|
+
|
|
670
|
+
return (
|
|
671
|
+
TimeInterval(
|
|
672
|
+
start = time_interval.start,
|
|
673
|
+
end = new_end
|
|
674
|
+
),
|
|
675
|
+
TimeInterval(
|
|
676
|
+
start = new_end,
|
|
677
|
+
end = time_interval.end
|
|
678
|
+
)
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
@staticmethod
|
|
682
|
+
@parameter_to_time_interval('time_interval')
|
|
683
|
+
def trim_start_to(
|
|
684
|
+
time_interval: TimeIntervalType,
|
|
685
|
+
t: Number
|
|
686
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
687
|
+
"""
|
|
688
|
+
Get a tuple containing the 2 new `TimeInterval` instances
|
|
689
|
+
generated by trimming the `time_interval` start to the `t`
|
|
690
|
+
time moment provided. The first tuple is the remaining, and
|
|
691
|
+
the second one is the requested by the user.
|
|
692
|
+
|
|
693
|
+
The `t` time moment provided must be a value between the
|
|
694
|
+
`start` and `end` of the `time_interval` provided.
|
|
695
|
+
"""
|
|
696
|
+
time_interval._validate_t(t)
|
|
697
|
+
|
|
698
|
+
return (
|
|
699
|
+
TimeInterval(
|
|
700
|
+
start = time_interval.start,
|
|
701
|
+
end = t
|
|
702
|
+
),
|
|
703
|
+
TimeInterval(
|
|
704
|
+
start = t,
|
|
705
|
+
end = time_interval.end
|
|
706
|
+
)
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
@staticmethod
|
|
710
|
+
@parameter_to_time_interval('time_interval')
|
|
711
|
+
def trim_start(
|
|
712
|
+
time_interval: TimeIntervalType,
|
|
713
|
+
t_variation: Number,
|
|
714
|
+
limit: Union[Number, None] = None,
|
|
715
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
716
|
+
"""
|
|
717
|
+
Get a tuple containing the 2 new `TimeInterval` instances
|
|
718
|
+
generated by trimming the `time_interval` start the amount
|
|
719
|
+
of seconds provided as the `t_variation` parameter. The
|
|
720
|
+
first tuple is the remaining, and the second one is the
|
|
721
|
+
new time interval requested by the user.
|
|
722
|
+
|
|
723
|
+
This method will raise an exception if the new `end` value
|
|
724
|
+
becomes a value under the time interval `start` value or
|
|
725
|
+
the `limit`, that must be greater than the `start` and lower
|
|
726
|
+
than the time interval `end` value.
|
|
727
|
+
|
|
728
|
+
The `t_variation` must be a positive value, the amount of
|
|
729
|
+
seconds to be trimmed.
|
|
730
|
+
"""
|
|
731
|
+
ParameterValidator.validate_mandatory_positive_number('t_variation', t_variation, do_include_zero = False)
|
|
732
|
+
ParameterValidator.validate_number_between('limit', limit, time_interval.start, time_interval.end, False, False)
|
|
733
|
+
|
|
734
|
+
new_start = time_interval.start + t_variation
|
|
735
|
+
limit = (
|
|
736
|
+
time_interval.end
|
|
737
|
+
if limit is None else
|
|
738
|
+
limit
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
if new_start >= time_interval.end:
|
|
742
|
+
raise Exception('The "t_variation" value provided makes the new end value be lower than the "end" value.')
|
|
743
|
+
|
|
744
|
+
if new_start >= limit:
|
|
745
|
+
raise Exception('The "t_variation" value provided makes the new end value be greater than the "limit" value provided.')
|
|
746
|
+
|
|
747
|
+
return (
|
|
748
|
+
TimeInterval(
|
|
749
|
+
start = time_interval.start,
|
|
750
|
+
end = new_start
|
|
751
|
+
),
|
|
752
|
+
TimeInterval(
|
|
753
|
+
start = new_start,
|
|
754
|
+
end = time_interval.end
|
|
755
|
+
)
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
@staticmethod
|
|
759
|
+
@parameter_to_time_interval('time_interval')
|
|
760
|
+
def from_to(
|
|
761
|
+
time_interval: 'TimeInterval',
|
|
762
|
+
start: Number,
|
|
763
|
+
end: Number
|
|
764
|
+
) -> tuple[Union['TimeInterval', None], Union['TimeInterval', None], Union['TimeInterval', None], int]:
|
|
765
|
+
"""
|
|
766
|
+
Cut a segment from the given `start` to the also provided
|
|
767
|
+
`end` time moments of the `time_interval` passed as
|
|
768
|
+
parameter.
|
|
769
|
+
|
|
770
|
+
This method will return a tuple of 3 elements including the
|
|
771
|
+
segments created by cutting this time interval in the order
|
|
772
|
+
they were generated, but also having the 4th element always
|
|
773
|
+
as the index of the one specifically requested by the user.
|
|
774
|
+
The tuple will include all the segments at the begining and
|
|
775
|
+
the rest will be None (unless the 4th one, which is the
|
|
776
|
+
index).
|
|
777
|
+
|
|
778
|
+
Examples below:
|
|
779
|
+
- A time interval of `[2, 5)` cut with `start=3` and `end=4`
|
|
780
|
+
will generate `((2, 3), (3, 4), (4, 5), 1)`.
|
|
781
|
+
- A time interval of `[2, 5)` cut with `start=2` and `end=4`
|
|
782
|
+
will generate `((2, 4), (4, 5), None, 0)`.
|
|
783
|
+
- A time interval of `[2, 5)` cut with `start=4` and `end=5`
|
|
784
|
+
will generate `((2, 4), (4, 5), None, 1)`.
|
|
785
|
+
- A time interval of `[2, 5)` cut with `start=2` and `end=5`
|
|
786
|
+
will generate `((2, 5), None, None, 0)`.
|
|
787
|
+
|
|
788
|
+
As you can see, the result could be the same in different
|
|
789
|
+
situations, but it's up to you (and the specific method in
|
|
790
|
+
which you are calling to this one) to choose the tuple you
|
|
791
|
+
want to return.
|
|
792
|
+
"""
|
|
793
|
+
time_interval._validate_t(start, do_include_start = True)
|
|
794
|
+
time_interval._validate_t(end, do_include_end = True)
|
|
795
|
+
|
|
796
|
+
return (
|
|
797
|
+
# TODO: What about this case, should we raise except (?)
|
|
798
|
+
(
|
|
799
|
+
time_interval.copy,
|
|
800
|
+
None,
|
|
801
|
+
None,
|
|
802
|
+
0
|
|
803
|
+
)
|
|
804
|
+
if (
|
|
805
|
+
start == time_interval.start and
|
|
806
|
+
end == time_interval.end
|
|
807
|
+
) else
|
|
808
|
+
(
|
|
809
|
+
TimeInterval(
|
|
810
|
+
start = time_interval.start,
|
|
811
|
+
end = end
|
|
812
|
+
),
|
|
813
|
+
TimeInterval(
|
|
814
|
+
start = end,
|
|
815
|
+
end = time_interval.end
|
|
816
|
+
),
|
|
817
|
+
None,
|
|
818
|
+
0
|
|
819
|
+
)
|
|
820
|
+
if start == time_interval.start else
|
|
821
|
+
(
|
|
822
|
+
TimeInterval(
|
|
823
|
+
start = time_interval.start,
|
|
824
|
+
end = start
|
|
825
|
+
),
|
|
826
|
+
TimeInterval(
|
|
827
|
+
start = start,
|
|
828
|
+
end = time_interval.end
|
|
829
|
+
),
|
|
830
|
+
None,
|
|
831
|
+
1
|
|
832
|
+
)
|
|
833
|
+
if end == time_interval.end else
|
|
834
|
+
(
|
|
835
|
+
TimeInterval(
|
|
836
|
+
start = time_interval.start,
|
|
837
|
+
end = start
|
|
838
|
+
),
|
|
839
|
+
TimeInterval(
|
|
840
|
+
start = start,
|
|
841
|
+
end = end
|
|
842
|
+
),
|
|
843
|
+
TimeInterval(
|
|
844
|
+
start = end,
|
|
845
|
+
end = time_interval.end
|
|
846
|
+
),
|
|
847
|
+
1
|
|
848
|
+
)
|
|
849
|
+
)
|
|
850
|
+
|
|
851
|
+
@staticmethod
|
|
852
|
+
@parameter_to_time_interval('time_interval')
|
|
853
|
+
def split(
|
|
854
|
+
time_interval: 'TimeInterval',
|
|
855
|
+
t: Number,
|
|
856
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
857
|
+
"""
|
|
858
|
+
Split the interval at the provided `t` time moment and
|
|
859
|
+
get the 2 new time intervals as a result (as a tuple).
|
|
860
|
+
|
|
861
|
+
This method will raise an exception if the `t` value
|
|
862
|
+
provided is a limit value (or above).
|
|
863
|
+
|
|
864
|
+
Examples below:
|
|
865
|
+
- A time interval of `[2, 5)` cut with `t=3` will generate
|
|
866
|
+
`((2, 3), (3, 5))`.
|
|
867
|
+
- A time interval of `[2, 5)` cut with `t=4` will generate
|
|
868
|
+
`((2, 4), (4, 5))`.
|
|
869
|
+
- A time interval of `[2, 5)` cut with `t>=5` will raise
|
|
870
|
+
exception.
|
|
871
|
+
- A time interval of `[2, 5)` cut with `t<=2` will raise
|
|
872
|
+
exception.
|
|
873
|
+
"""
|
|
874
|
+
if (
|
|
875
|
+
t <= time_interval.start or
|
|
876
|
+
t >= time_interval.end
|
|
877
|
+
):
|
|
878
|
+
raise Exception('The "t" value provided is not a valid value as it is a limit (or more than a limit).')
|
|
879
|
+
|
|
880
|
+
return (
|
|
881
|
+
TimeInterval(
|
|
882
|
+
start = time_interval.start,
|
|
883
|
+
end = t
|
|
884
|
+
),
|
|
885
|
+
TimeInterval(
|
|
886
|
+
start = t,
|
|
887
|
+
end = time_interval.end
|
|
888
|
+
)
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
# # TODO: This method is interesting if we don't want only to
|
|
892
|
+
# # trim but also to extend, so we can use the limits.
|
|
893
|
+
# @staticmethod
|
|
894
|
+
# def trim_end(
|
|
895
|
+
# time_interval: TimeIntervalType,
|
|
896
|
+
# t_variation: Number,
|
|
897
|
+
# lower_limit: Number = 0.0,
|
|
898
|
+
# upper_limit: Union[Number, None] = None,
|
|
899
|
+
# do_include_lower_limit: bool = True,
|
|
900
|
+
# do_include_upper_limit: bool = True
|
|
901
|
+
# ) -> Union['TimeInterval', 'TimeInterval']:
|
|
902
|
+
# """
|
|
903
|
+
# Get a tuple containing the 2 new `TimeInterval` instances
|
|
904
|
+
# generated by trimming the `time_interval` provided from
|
|
905
|
+
# the `end` and at the `t` time moment provided. The first
|
|
906
|
+
# value is the remaining, and the second one is the requested
|
|
907
|
+
# by the user.
|
|
908
|
+
|
|
909
|
+
# This will raise an exception if the new `end` becomes a
|
|
910
|
+
# value under the `lower_limit` provided, above the
|
|
911
|
+
# `upper_limit` given (if given) or even below the time
|
|
912
|
+
# interval `start` value. The limits will be included or not
|
|
913
|
+
# according to the boolean parameters.
|
|
914
|
+
# """
|
|
915
|
+
# new_end = time_interval.end + t_variation
|
|
916
|
+
|
|
917
|
+
# if new_end <= time_interval.start:
|
|
918
|
+
# raise Exception('The "t_variation" value provided makes the new end value be lower than the "start" value.')
|
|
919
|
+
|
|
920
|
+
# is_under_lower_limit = (
|
|
921
|
+
# new_end < lower_limit
|
|
922
|
+
# if do_include_lower_limit else
|
|
923
|
+
# new_end <= lower_limit
|
|
924
|
+
# )
|
|
925
|
+
|
|
926
|
+
# is_over_upper_limit = (
|
|
927
|
+
# upper_limit is None and
|
|
928
|
+
# (
|
|
929
|
+
# new_end > upper_limit
|
|
930
|
+
# if do_include_upper_limit else
|
|
931
|
+
# new_end >= upper_limit
|
|
932
|
+
# )
|
|
933
|
+
# )
|
|
934
|
+
|
|
935
|
+
# if (
|
|
936
|
+
# is_under_lower_limit or
|
|
937
|
+
# is_over_upper_limit
|
|
938
|
+
# ):
|
|
939
|
+
# raise Exception('The "t_variation" value provided makes the new end value be out of the limits.')
|
|
940
|
+
|
|
941
|
+
# return (
|
|
942
|
+
# TimeInterval(
|
|
943
|
+
# start = time_interval.start,
|
|
944
|
+
# end = new_end
|
|
945
|
+
# ),
|
|
946
|
+
# TimeInterval(
|
|
947
|
+
# start = new_end,
|
|
948
|
+
# end = time_interval.end
|
|
949
|
+
# )
|
|
950
|
+
# )
|
|
951
|
+
|
{yta_video_frame_time-0.0.6 → yta_video_frame_time-0.0.14}/src/yta_video_frame_time/t_fraction.py
RENAMED
|
@@ -238,6 +238,8 @@ def round_pts(
|
|
|
238
238
|
and 'time_base', but here is an easier
|
|
239
239
|
example using the time moments.
|
|
240
240
|
|
|
241
|
+
(!) This is valid only for video.
|
|
242
|
+
|
|
241
243
|
Examples below, with `time_base = 1/5`:
|
|
242
244
|
- `t = 0.25` => `0.2` (truncated or rounded)
|
|
243
245
|
- `t = 0.35` => `0.2` (truncated)
|
|
@@ -256,8 +258,6 @@ def round_pts(
|
|
|
256
258
|
|
|
257
259
|
return int(frame_index * ticks_per_frame)
|
|
258
260
|
|
|
259
|
-
# TODO: Create a 'round_pts'
|
|
260
|
-
|
|
261
261
|
"""
|
|
262
262
|
When we are working with the 't' time
|
|
263
263
|
moment we need to use the fps, and when
|
|
@@ -302,7 +302,22 @@ class _T:
|
|
|
302
302
|
None, we will not make any conversion
|
|
303
303
|
and the value received could be useless
|
|
304
304
|
because it is in the middle of a range.
|
|
305
|
+
|
|
306
|
+
The formula:
|
|
307
|
+
- `pts * time_base`
|
|
305
308
|
"""
|
|
309
|
+
t = Fraction(pts * self._t_handler.time_base)
|
|
310
|
+
|
|
311
|
+
return (
|
|
312
|
+
self._t_handler.t.truncated(t)
|
|
313
|
+
if do_truncate is True else
|
|
314
|
+
self._t_handler.t.rounded(t)
|
|
315
|
+
if do_truncate is False else
|
|
316
|
+
t # if None
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
# TODO: Remove this below in the next
|
|
320
|
+
# commit
|
|
306
321
|
pts = (
|
|
307
322
|
self._t_handler.pts.truncated(pts)
|
|
308
323
|
if do_truncate is True else
|
|
@@ -322,13 +337,52 @@ class _T:
|
|
|
322
337
|
Transform the given 't' to a 'pts' value
|
|
323
338
|
truncating, rounding or applying no
|
|
324
339
|
variation.
|
|
340
|
+
|
|
341
|
+
The formula:
|
|
342
|
+
- `int(t / time_base)`
|
|
325
343
|
"""
|
|
326
344
|
return self._t_handler.pts.from_t(t, do_truncate)
|
|
345
|
+
|
|
346
|
+
def to_index(
|
|
347
|
+
self,
|
|
348
|
+
t: Union[int, float, Fraction],
|
|
349
|
+
do_truncate: Union[bool, None] = True
|
|
350
|
+
) -> int:
|
|
351
|
+
"""
|
|
352
|
+
Transform the given 't' to a index value
|
|
353
|
+
truncating, rounding or applying no
|
|
354
|
+
variation.
|
|
355
|
+
|
|
356
|
+
The formula:
|
|
357
|
+
- `int(round(t * fps))`
|
|
358
|
+
"""
|
|
359
|
+
t = (
|
|
360
|
+
self.truncated(t)
|
|
361
|
+
if do_truncate is True else
|
|
362
|
+
self.rounded(t)
|
|
363
|
+
if do_truncate is False else
|
|
364
|
+
t
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
return frame_t_to_index(t, self.fps)
|
|
368
|
+
|
|
369
|
+
def from_index(
|
|
370
|
+
self,
|
|
371
|
+
index: int
|
|
372
|
+
) -> Fraction:
|
|
373
|
+
"""
|
|
374
|
+
Transform the given index to a 't' time
|
|
375
|
+
moment value.
|
|
376
|
+
|
|
377
|
+
The formula:
|
|
378
|
+
- `frame_index * (1 / fps)`
|
|
379
|
+
"""
|
|
380
|
+
return frame_index_to_t(index, self.fps)
|
|
327
381
|
|
|
328
382
|
def truncated(
|
|
329
383
|
self,
|
|
330
384
|
t: Union[int, float, Fraction]
|
|
331
|
-
):
|
|
385
|
+
) -> Fraction:
|
|
332
386
|
"""
|
|
333
387
|
Get the 't' value provided but truncated.
|
|
334
388
|
|
|
@@ -338,10 +392,26 @@ class _T:
|
|
|
338
392
|
"""
|
|
339
393
|
return round_t(t, Fraction(1, self._t_handler.fps), do_truncate = True)
|
|
340
394
|
|
|
395
|
+
def is_truncated(
|
|
396
|
+
self,
|
|
397
|
+
t: Union[int, float, Fraction]
|
|
398
|
+
) -> bool:
|
|
399
|
+
"""
|
|
400
|
+
Check if the `t` value provided is the truncated
|
|
401
|
+
value, which means that the `t` provided is the
|
|
402
|
+
`start` from the `[start, end)` range defined by
|
|
403
|
+
the fps.
|
|
404
|
+
"""
|
|
405
|
+
return check_values_are_same(
|
|
406
|
+
value_a = t,
|
|
407
|
+
value_b = self.truncated(t),
|
|
408
|
+
tolerance = 0.000001
|
|
409
|
+
)
|
|
410
|
+
|
|
341
411
|
def rounded(
|
|
342
412
|
self,
|
|
343
413
|
t: Union[int, float, Fraction]
|
|
344
|
-
):
|
|
414
|
+
) -> Fraction:
|
|
345
415
|
"""
|
|
346
416
|
Get the 't' value provided but rounded.
|
|
347
417
|
|
|
@@ -367,6 +437,9 @@ class _T:
|
|
|
367
437
|
|
|
368
438
|
Useful when you need the next value for a
|
|
369
439
|
range in an iteration or similar.
|
|
440
|
+
|
|
441
|
+
The formula:
|
|
442
|
+
- `t + n * (1 / fps)`
|
|
370
443
|
"""
|
|
371
444
|
t = (
|
|
372
445
|
self.truncated(t)
|
|
@@ -374,7 +447,7 @@ class _T:
|
|
|
374
447
|
self.rounded(t)
|
|
375
448
|
)
|
|
376
449
|
|
|
377
|
-
return t + n * self._t_handler.
|
|
450
|
+
return t + n * (1 / self._t_handler.fps)
|
|
378
451
|
|
|
379
452
|
def previous(
|
|
380
453
|
self,
|
|
@@ -395,6 +468,9 @@ class _T:
|
|
|
395
468
|
Be careful, if the 'truncated' value is 0
|
|
396
469
|
this will give you an unexpected negative
|
|
397
470
|
value.
|
|
471
|
+
|
|
472
|
+
The formula:
|
|
473
|
+
- `t - n * (1 / fps)`
|
|
398
474
|
"""
|
|
399
475
|
t = (
|
|
400
476
|
self.truncated(t)
|
|
@@ -402,7 +478,7 @@ class _T:
|
|
|
402
478
|
self.rounded(t)
|
|
403
479
|
)
|
|
404
480
|
|
|
405
|
-
return t - n * self._t_handler.
|
|
481
|
+
return t - n * (1 / self._t_handler.fps)
|
|
406
482
|
|
|
407
483
|
class _Pts:
|
|
408
484
|
"""
|
|
@@ -442,6 +518,9 @@ class _Pts:
|
|
|
442
518
|
None, we will not make any conversion
|
|
443
519
|
and the value received could be useless
|
|
444
520
|
because it is in the middle of a range.
|
|
521
|
+
|
|
522
|
+
The formula:
|
|
523
|
+
- `int(t / time_base)`
|
|
445
524
|
"""
|
|
446
525
|
t = (
|
|
447
526
|
self._t_handler.t.truncated(t)
|
|
@@ -462,14 +541,70 @@ class _Pts:
|
|
|
462
541
|
Transform the given 'pts' to a 't' value
|
|
463
542
|
truncating, rounding or applying no
|
|
464
543
|
variation.
|
|
544
|
+
|
|
545
|
+
The formula:
|
|
546
|
+
- `pts * time_base`
|
|
465
547
|
"""
|
|
466
548
|
return self._t_handler.t.from_pts(pts, do_truncate)
|
|
549
|
+
|
|
550
|
+
def to_index(
|
|
551
|
+
self,
|
|
552
|
+
pts: int,
|
|
553
|
+
do_truncate: Union[bool, None] = True
|
|
554
|
+
) -> int:
|
|
555
|
+
"""
|
|
556
|
+
Transform the given 'pts' to a index value
|
|
557
|
+
truncating, rounding or applying no
|
|
558
|
+
variation.
|
|
559
|
+
|
|
560
|
+
The formula:
|
|
561
|
+
- `int(round((pts * time_base) * fps))`
|
|
562
|
+
"""
|
|
563
|
+
return self._t_handler.t.to_index(
|
|
564
|
+
self.to_t(pts, do_truncate = None),
|
|
565
|
+
do_truncate = do_truncate
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
def from_index(
|
|
569
|
+
self,
|
|
570
|
+
index: int
|
|
571
|
+
) -> Fraction:
|
|
572
|
+
"""
|
|
573
|
+
Transform the given index to a 't' time
|
|
574
|
+
moment value.
|
|
575
|
+
|
|
576
|
+
The formula:
|
|
577
|
+
- `int((frame_index * (1 / fps)) * time_base)`
|
|
578
|
+
"""
|
|
579
|
+
return self.from_t(
|
|
580
|
+
t = self._t_handler.t.from_index(index),
|
|
581
|
+
do_truncate = True
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
"""
|
|
585
|
+
These 2 methods below are here because they
|
|
586
|
+
seem to work for videos, but I think they
|
|
587
|
+
could work not if the video has dynamic frame
|
|
588
|
+
rate or in some other situations, thats why
|
|
589
|
+
this is here as a reminder.
|
|
590
|
+
|
|
591
|
+
I found one video that had audio_fps=44100
|
|
592
|
+
and time_base=256/11025, so it was impossible
|
|
593
|
+
to make a conversion using this formula with
|
|
594
|
+
the audio. With video seems to be ok, but...
|
|
595
|
+
|
|
596
|
+
Use these methods below at your own risk.
|
|
597
|
+
"""
|
|
467
598
|
|
|
468
599
|
def truncated(
|
|
469
600
|
self,
|
|
470
601
|
pts: int
|
|
471
602
|
):
|
|
472
603
|
"""
|
|
604
|
+
(!) This is valid only for video and/or
|
|
605
|
+
could work not properly. Use it at your
|
|
606
|
+
own risk.
|
|
607
|
+
|
|
473
608
|
Get the 'pts' value provided but truncated.
|
|
474
609
|
|
|
475
610
|
This means that if 't' is in a
|
|
@@ -488,6 +623,10 @@ class _Pts:
|
|
|
488
623
|
pts: int
|
|
489
624
|
) -> int:
|
|
490
625
|
"""
|
|
626
|
+
(!) This is valid only for video and/or
|
|
627
|
+
could work not properly. Use it at your
|
|
628
|
+
own risk.
|
|
629
|
+
|
|
491
630
|
Get the 'pts' value provided but rounded.
|
|
492
631
|
|
|
493
632
|
This means that if 't' is in a
|
|
@@ -510,6 +649,10 @@ class _Pts:
|
|
|
510
649
|
do_truncate: bool = True
|
|
511
650
|
) -> int:
|
|
512
651
|
"""
|
|
652
|
+
(!) This is valid only for video and/or
|
|
653
|
+
could work not properly. Use it at your
|
|
654
|
+
own risk.
|
|
655
|
+
|
|
513
656
|
Get the value that is 'n' times ahead of
|
|
514
657
|
the 'pts' value provided (truncated or
|
|
515
658
|
rounded according to the 'do_truncate'
|
|
@@ -517,6 +660,9 @@ class _Pts:
|
|
|
517
660
|
|
|
518
661
|
Useful when you need the next value for a
|
|
519
662
|
range in an iteration or similar.
|
|
663
|
+
|
|
664
|
+
The formula:
|
|
665
|
+
- `pts + n * ticks_per_frame`
|
|
520
666
|
"""
|
|
521
667
|
pts = (
|
|
522
668
|
self.truncated(pts)
|
|
@@ -533,6 +679,10 @@ class _Pts:
|
|
|
533
679
|
do_truncate: bool = True
|
|
534
680
|
) -> int:
|
|
535
681
|
"""
|
|
682
|
+
(!) This is valid only for video and/or
|
|
683
|
+
could work not properly. Use it at your
|
|
684
|
+
own risk.
|
|
685
|
+
|
|
536
686
|
Get the value that is 'n' times before
|
|
537
687
|
the 't' property of this instance
|
|
538
688
|
(truncated or rounded according to the
|
|
@@ -545,6 +695,9 @@ class _Pts:
|
|
|
545
695
|
Be careful, if the 'truncated' value is 0
|
|
546
696
|
this will give you an unexpected negative
|
|
547
697
|
value.
|
|
698
|
+
|
|
699
|
+
The formula:
|
|
700
|
+
- `pts - n * ticks_per_frame`
|
|
548
701
|
"""
|
|
549
702
|
pts = (
|
|
550
703
|
self.truncated(pts)
|
|
@@ -553,7 +706,7 @@ class _Pts:
|
|
|
553
706
|
)
|
|
554
707
|
|
|
555
708
|
return pts - n * get_ticks_per_frame(self._t_handler.fps, self._t_handler.time_base)
|
|
556
|
-
|
|
709
|
+
|
|
557
710
|
class THandler:
|
|
558
711
|
"""
|
|
559
712
|
Class to simplify the way we work with
|
|
@@ -621,9 +774,9 @@ def frame_t_to_index(
|
|
|
621
774
|
also provided 'fps'.
|
|
622
775
|
|
|
623
776
|
The formula:
|
|
624
|
-
- `int(t * fps)`
|
|
777
|
+
- `int(round(t * fps))`
|
|
625
778
|
"""
|
|
626
|
-
return int(
|
|
779
|
+
return int(round(t * fps))
|
|
627
780
|
|
|
628
781
|
def frame_index_to_t(
|
|
629
782
|
index: int,
|
|
@@ -651,6 +804,8 @@ def frame_t_to_pts(
|
|
|
651
804
|
moment provided, based on the also provided
|
|
652
805
|
'fps' and 'time_base'.
|
|
653
806
|
|
|
807
|
+
(!) This is valid only for videos.
|
|
808
|
+
|
|
654
809
|
The formula:
|
|
655
810
|
- `frame_index * ticks_per_frame`
|
|
656
811
|
"""
|
|
@@ -670,6 +825,23 @@ def frame_pts_to_t(
|
|
|
670
825
|
- `pts * time_base`
|
|
671
826
|
"""
|
|
672
827
|
return parse_fraction(pts * time_base)
|
|
828
|
+
|
|
829
|
+
def get_audio_frame_duration(
|
|
830
|
+
samples: int,
|
|
831
|
+
audio_fps: Fraction
|
|
832
|
+
) -> Fraction:
|
|
833
|
+
"""
|
|
834
|
+
Get the audio frame duration by giving the
|
|
835
|
+
number of '.samples' and also the rate (that
|
|
836
|
+
we call 'audio_fps').
|
|
837
|
+
|
|
838
|
+
This is useful when trying to guess the next
|
|
839
|
+
pts or t.
|
|
840
|
+
|
|
841
|
+
The formula:
|
|
842
|
+
- `samples / audio_fps`
|
|
843
|
+
"""
|
|
844
|
+
return Fraction(samples / audio_fps)
|
|
673
845
|
|
|
674
846
|
def get_ticks_per_frame(
|
|
675
847
|
fps: Union[float, int, Fraction],
|
|
@@ -680,6 +852,9 @@ def get_ticks_per_frame(
|
|
|
680
852
|
tick is the minimum amount of time we
|
|
681
853
|
spend from one frame to the next.
|
|
682
854
|
|
|
855
|
+
(!) This is only valid for video
|
|
856
|
+
apparently.
|
|
857
|
+
|
|
683
858
|
The formula:
|
|
684
859
|
- `1 / (fps * time_base)`
|
|
685
860
|
"""
|
|
@@ -723,3 +898,17 @@ def parse_fraction(
|
|
|
723
898
|
|
|
724
899
|
return fraction
|
|
725
900
|
|
|
901
|
+
def check_values_are_same(
|
|
902
|
+
value_a: Union[Fraction, float],
|
|
903
|
+
value_b: Union[Fraction, float],
|
|
904
|
+
tolerance: float = 1e-6
|
|
905
|
+
) -> bool:
|
|
906
|
+
"""
|
|
907
|
+
Check that the `value_a` and the `value_b` are the same
|
|
908
|
+
by applying the `tolerance` value, that is 0.000001 by
|
|
909
|
+
default.
|
|
910
|
+
|
|
911
|
+
For example, `0.016666666666666666` is the same value as
|
|
912
|
+
`1/60` with `tolerance=0.000001`.
|
|
913
|
+
"""
|
|
914
|
+
return abs(float(value_a) - float(value_b)) < tolerance
|
|
File without changes
|
|
File without changes
|
{yta_video_frame_time-0.0.6 → yta_video_frame_time-0.0.14}/src/yta_video_frame_time/__init__.py
RENAMED
|
File without changes
|