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
|
@@ -0,0 +1,1818 @@
|
|
|
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, parameter_to_new_time_interval
|
|
7
|
+
from yta_video_frame_time.t_fraction import THandler
|
|
8
|
+
from yta_validation.parameter import ParameterValidator
|
|
9
|
+
from quicktions import Fraction
|
|
10
|
+
from typing import Union
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
Number = Union[int, float, Fraction]
|
|
14
|
+
"""
|
|
15
|
+
Custom type to represent numbers.
|
|
16
|
+
"""
|
|
17
|
+
TimeIntervalType = Union['TimeInterval', tuple[float, float]]
|
|
18
|
+
"""
|
|
19
|
+
The type we accept as time interval, that could be a
|
|
20
|
+
tuple we transform into a time interval.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
TODO: Maybe we can add the possibility of having an
|
|
25
|
+
`fps` value when initializing it to be able to force
|
|
26
|
+
the time interval values to be multiple of `1/fps`.
|
|
27
|
+
But this, if implemented, should be `TimeIntervalFPS`
|
|
28
|
+
or similar, and inheritance from this one but forcing
|
|
29
|
+
the values to be transformed according to that `1/fps`.
|
|
30
|
+
"""
|
|
31
|
+
class TimeInterval:
|
|
32
|
+
"""
|
|
33
|
+
Class to represent a time interval, which is a tuple
|
|
34
|
+
of time moments representing the time range
|
|
35
|
+
`[start, end)`.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def duration(
|
|
40
|
+
self
|
|
41
|
+
) -> float:
|
|
42
|
+
"""
|
|
43
|
+
The `duration` of the time interval.
|
|
44
|
+
"""
|
|
45
|
+
return self.end - self.start
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def copy(
|
|
49
|
+
self
|
|
50
|
+
) -> 'TimeInterval':
|
|
51
|
+
"""
|
|
52
|
+
A copy of this instance.
|
|
53
|
+
"""
|
|
54
|
+
return TimeInterval(
|
|
55
|
+
start = self.start,
|
|
56
|
+
end = self.end
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def as_tuple(
|
|
61
|
+
self
|
|
62
|
+
) -> tuple[float, float]:
|
|
63
|
+
"""
|
|
64
|
+
The time interval but as a `(start, end)` tuple.
|
|
65
|
+
"""
|
|
66
|
+
return (self.start, self.end)
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def cutter(
|
|
70
|
+
self
|
|
71
|
+
) -> 'TimeIntervalCutter':
|
|
72
|
+
"""
|
|
73
|
+
Shortcut to the static class `TimeIntervalCutter` that
|
|
74
|
+
is capable of cutting time intervals.
|
|
75
|
+
"""
|
|
76
|
+
return TimeIntervalCutter
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
start: Number,
|
|
81
|
+
end: Number,
|
|
82
|
+
):
|
|
83
|
+
"""
|
|
84
|
+
The `end` value must be greater than the `start` value.
|
|
85
|
+
"""
|
|
86
|
+
if start > end:
|
|
87
|
+
raise Exception('The `start` value provided is greater than the `end` value provided.')
|
|
88
|
+
|
|
89
|
+
if start == end:
|
|
90
|
+
raise Exception('The `start` value provided is exactly the `end` value provided.')
|
|
91
|
+
|
|
92
|
+
self.start: float = start
|
|
93
|
+
"""
|
|
94
|
+
The original `start` of the time segment.
|
|
95
|
+
"""
|
|
96
|
+
self.end: float = end
|
|
97
|
+
"""
|
|
98
|
+
The original `end` of the time segment.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
# TODO: Is this that necessary (?)
|
|
102
|
+
def _validate_t(
|
|
103
|
+
self,
|
|
104
|
+
t: Number,
|
|
105
|
+
do_include_start: bool = False,
|
|
106
|
+
do_include_end: bool = False
|
|
107
|
+
) -> None:
|
|
108
|
+
"""
|
|
109
|
+
Validate that the provided `t` value is between the `start`
|
|
110
|
+
and the `end` parameters provided, including them or not
|
|
111
|
+
according to the boolean parameters provided.
|
|
112
|
+
"""
|
|
113
|
+
ParameterValidator.validate_mandatory_number_between(
|
|
114
|
+
name = 't',
|
|
115
|
+
value = t,
|
|
116
|
+
lower_limit = self.start,
|
|
117
|
+
upper_limit = self.end,
|
|
118
|
+
do_include_lower_limit = do_include_start,
|
|
119
|
+
do_include_upper_limit = do_include_end
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def cut(
|
|
123
|
+
self,
|
|
124
|
+
start: Number,
|
|
125
|
+
end: Number
|
|
126
|
+
) -> tuple[Union['TimeInterval', None], Union['TimeInterval', None], Union['TimeInterval', None]]:
|
|
127
|
+
"""
|
|
128
|
+
Cut a segment from the given `start` to the also provided
|
|
129
|
+
`end` time moments of this time interval instance.
|
|
130
|
+
|
|
131
|
+
This method will return a tuple of 3 elements including the
|
|
132
|
+
segments created by cutting this time interval in the order
|
|
133
|
+
they were generated, but also having the 4th element always
|
|
134
|
+
as the index of the one specifically requested by the user.
|
|
135
|
+
The tuple will include all the segments at the begining and
|
|
136
|
+
the rest will be None (unless the 4th one, which is the
|
|
137
|
+
index).
|
|
138
|
+
|
|
139
|
+
Examples below:
|
|
140
|
+
- A time interval of `[2, 5)` cut with `start=3` and `end=4`
|
|
141
|
+
will generate `((2, 3), (3, 4), (4, 5), 1)`.
|
|
142
|
+
- A time interval of `[2, 5)` cut with `start=2` and `end=4`
|
|
143
|
+
will generate `((2, 4), (4, 5), None, 0)`.
|
|
144
|
+
- A time interval of `[2, 5)` cut with `start=4` and `end=5`
|
|
145
|
+
will generate `((2, 4), (4, 5), None, 1)`.
|
|
146
|
+
- A time interval of `[2, 5)` cut with `start=2` and `end=5`
|
|
147
|
+
will generate `((2, 5), None, None, 0)`.
|
|
148
|
+
|
|
149
|
+
As you can see, the result could be the same in different
|
|
150
|
+
situations, but it's up to you (and the specific method in
|
|
151
|
+
which you are calling to this one) to choose the tuple you
|
|
152
|
+
want to return.
|
|
153
|
+
"""
|
|
154
|
+
return self.cutter.from_to(
|
|
155
|
+
time_interval = self,
|
|
156
|
+
start = start,
|
|
157
|
+
end = end
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def cutted(
|
|
161
|
+
self,
|
|
162
|
+
start: Number,
|
|
163
|
+
end: Number
|
|
164
|
+
) -> 'TimeInterval':
|
|
165
|
+
"""
|
|
166
|
+
Get this time interval instance but cutted from the `start`
|
|
167
|
+
to the `end` time moments provided.
|
|
168
|
+
|
|
169
|
+
(!) This method doesn't modify the original instance but
|
|
170
|
+
returns a new one.
|
|
171
|
+
"""
|
|
172
|
+
tuples = self.cut(
|
|
173
|
+
start = start,
|
|
174
|
+
end = end
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return tuples[tuples[3]]
|
|
178
|
+
|
|
179
|
+
def trim_start(
|
|
180
|
+
self,
|
|
181
|
+
t_variation: Number,
|
|
182
|
+
limit: Union[Number, None] = None
|
|
183
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
184
|
+
"""
|
|
185
|
+
Get a tuple containing the 2 new `TimeInterval` instances
|
|
186
|
+
generated by trimming this one's start the amount of seconds
|
|
187
|
+
provided as the `t_variation` parameter. The first tuple is
|
|
188
|
+
the remaining, and the second one is the new time interval
|
|
189
|
+
requested by the user.
|
|
190
|
+
|
|
191
|
+
This method will raise an exception if the new `start` value
|
|
192
|
+
becomes a value over the time interval `end` value or the
|
|
193
|
+
`limit`, that must be greater than the `start` and lower
|
|
194
|
+
than the time interval `end` value.
|
|
195
|
+
|
|
196
|
+
The `t_variation` must be a positive value, the amount of
|
|
197
|
+
seconds to be trimmed.
|
|
198
|
+
"""
|
|
199
|
+
return self.cutter.trim_start(
|
|
200
|
+
time_interval = self,
|
|
201
|
+
t_variation = t_variation,
|
|
202
|
+
limit = limit
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
def trimmed_start(
|
|
206
|
+
self,
|
|
207
|
+
t_variation: Number,
|
|
208
|
+
limit: Union[Number, None] = None
|
|
209
|
+
) -> 'TimeInterval':
|
|
210
|
+
"""
|
|
211
|
+
Get this time interval instance but trimmed from the `start`
|
|
212
|
+
the `t_variation` amount of seconds provided.
|
|
213
|
+
|
|
214
|
+
(!) This method doesn't modify the original instance but
|
|
215
|
+
returns a new one.
|
|
216
|
+
"""
|
|
217
|
+
return self.trim_start(
|
|
218
|
+
t_variation = t_variation,
|
|
219
|
+
limit = limit
|
|
220
|
+
)[1]
|
|
221
|
+
|
|
222
|
+
def trim_end(
|
|
223
|
+
self,
|
|
224
|
+
t_variation: Number,
|
|
225
|
+
limit: Union[Number, None] = None
|
|
226
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
227
|
+
"""
|
|
228
|
+
Get a tuple containing the 2 new `TimeInterval` instances
|
|
229
|
+
generated by trimming this one's end the amount of seconds
|
|
230
|
+
provided as the `t_variation` parameter. The first tuple is
|
|
231
|
+
the one requested by the user, and the second one is the
|
|
232
|
+
remaining.
|
|
233
|
+
|
|
234
|
+
This method will raise an exception if the new `end` value
|
|
235
|
+
becomes a value under the time interval `start` value or
|
|
236
|
+
the `limit`, that must be greater than the `start` and lower
|
|
237
|
+
than the time interval `end` value.
|
|
238
|
+
|
|
239
|
+
The `t_variation` must be a positive value, the amount of
|
|
240
|
+
seconds to be trimmed.
|
|
241
|
+
"""
|
|
242
|
+
return self.cutter.trim_end(
|
|
243
|
+
time_interval = self,
|
|
244
|
+
t_variation = t_variation,
|
|
245
|
+
limit = limit
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def trimmed_end(
|
|
249
|
+
self,
|
|
250
|
+
t_variation: Number,
|
|
251
|
+
limit: Union[Number, None] = None
|
|
252
|
+
) -> 'TimeInterval':
|
|
253
|
+
"""
|
|
254
|
+
Get this time interval instance but trimmed from the `end`
|
|
255
|
+
the `t_variation` amount of seconds provided.
|
|
256
|
+
|
|
257
|
+
(!) This method doesn't modify the original instance but
|
|
258
|
+
returns a new one.
|
|
259
|
+
"""
|
|
260
|
+
return self.trim_end(
|
|
261
|
+
t_variation = t_variation,
|
|
262
|
+
limit = limit
|
|
263
|
+
)[0]
|
|
264
|
+
|
|
265
|
+
def split(
|
|
266
|
+
self,
|
|
267
|
+
t: Number
|
|
268
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
269
|
+
"""
|
|
270
|
+
Split the time interval at the provided `t` time moment
|
|
271
|
+
and get the 2 new time intervals as a result (as a tuple).
|
|
272
|
+
|
|
273
|
+
This method will raise an exception if the `t` value
|
|
274
|
+
provided is a limit value (or above).
|
|
275
|
+
|
|
276
|
+
Examples below:
|
|
277
|
+
- A time interval of `[2, 5)` cut with `t=3` will generate
|
|
278
|
+
`((2, 3), (3, 5))`.
|
|
279
|
+
- A time interval of `[2, 5)` cut with `t=4` will generate
|
|
280
|
+
`((2, 4), (4, 5))`.
|
|
281
|
+
- A time interval of `[2, 5)` cut with `t>=5` will raise
|
|
282
|
+
exception.
|
|
283
|
+
- A time interval of `[2, 5)` cut with `t<=2` will raise
|
|
284
|
+
exception.
|
|
285
|
+
"""
|
|
286
|
+
return self.cutter.split(
|
|
287
|
+
time_interval = self,
|
|
288
|
+
t = t
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
def splitted_left(
|
|
292
|
+
self,
|
|
293
|
+
t: Number
|
|
294
|
+
) -> 'TimeInterval':
|
|
295
|
+
"""
|
|
296
|
+
Split this time interval instance by the `t` time moment
|
|
297
|
+
provided and obtain the time interval from the left side,
|
|
298
|
+
which goes from this time interval `start` time moment to
|
|
299
|
+
the `t` provided.
|
|
300
|
+
"""
|
|
301
|
+
return self.split(t)[0]
|
|
302
|
+
|
|
303
|
+
def splitted_right(
|
|
304
|
+
self,
|
|
305
|
+
t: Number
|
|
306
|
+
) -> 'TimeInterval':
|
|
307
|
+
"""
|
|
308
|
+
Split this time interval instance by the `t` time moment
|
|
309
|
+
provided and obtain the time interval from the right side,
|
|
310
|
+
which goes from the `t` provided to the time interval `end`
|
|
311
|
+
time moment.
|
|
312
|
+
"""
|
|
313
|
+
return self.split(t)[1]
|
|
314
|
+
|
|
315
|
+
# Other methods below
|
|
316
|
+
def is_t_included(
|
|
317
|
+
self,
|
|
318
|
+
t: float,
|
|
319
|
+
do_include_end: bool = False
|
|
320
|
+
) -> bool:
|
|
321
|
+
"""
|
|
322
|
+
Check if the `t` time moment provided is included in
|
|
323
|
+
this time interval, including the `end` only if the
|
|
324
|
+
`do_include_end` parameter is set as `True`.
|
|
325
|
+
"""
|
|
326
|
+
return TimeIntervalUtils.a_includes_t(
|
|
327
|
+
t = t,
|
|
328
|
+
time_interval_a = self,
|
|
329
|
+
do_include_end = do_include_end
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
def is_adjacent_to(
|
|
333
|
+
self,
|
|
334
|
+
time_interval: 'TimeInterval'
|
|
335
|
+
) -> bool:
|
|
336
|
+
"""
|
|
337
|
+
Check if the `time_interval` provided is adjacent
|
|
338
|
+
to this time interval, which means that the `end`
|
|
339
|
+
of one interval is also the `start` of the other
|
|
340
|
+
one.
|
|
341
|
+
|
|
342
|
+
(!) Giving the time intervals inverted will
|
|
343
|
+
provide the same result.
|
|
344
|
+
|
|
345
|
+
Example below:
|
|
346
|
+
- `a=[2, 5)` and `b=[5, 7)` => `True`
|
|
347
|
+
- `a=[5, 7)` and `b=[2, 5)` => `True`
|
|
348
|
+
- `a=[2, 5)` and `b=[3, 4)` => `False`
|
|
349
|
+
- `a=[2, 5)` and `b=[6, 8)` => `False`
|
|
350
|
+
"""
|
|
351
|
+
return TimeIntervalUtils.a_is_adjacent_to_b(
|
|
352
|
+
time_interval_a = self,
|
|
353
|
+
time_interval_b = time_interval
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
def do_contains_a(
|
|
357
|
+
self,
|
|
358
|
+
time_interval: 'TimeInterval'
|
|
359
|
+
) -> bool:
|
|
360
|
+
"""
|
|
361
|
+
Check if this time interval includes the `time_interval`
|
|
362
|
+
provided or not, which means that the `time_interval`
|
|
363
|
+
provided is fully contained (included) in this one.
|
|
364
|
+
"""
|
|
365
|
+
return TimeIntervalUtils.a_contains_b(
|
|
366
|
+
time_interval_a = self,
|
|
367
|
+
time_interval_b = time_interval
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
def is_contained_in(
|
|
371
|
+
self,
|
|
372
|
+
time_interval: 'TimeInterval'
|
|
373
|
+
) -> bool:
|
|
374
|
+
"""
|
|
375
|
+
Check if this time interval is fully contained in
|
|
376
|
+
the `time_interval` provided, which is a synonim
|
|
377
|
+
of being fully overlapped by that `time_interval`.
|
|
378
|
+
"""
|
|
379
|
+
return TimeIntervalUtils.a_is_contained_in_b(
|
|
380
|
+
time_interval_a = self,
|
|
381
|
+
time_interval_b = time_interval
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
def do_intersects_with(
|
|
385
|
+
self,
|
|
386
|
+
time_interval: 'TimeInterval'
|
|
387
|
+
) -> bool:
|
|
388
|
+
"""
|
|
389
|
+
Check if this time interval intersects with the one
|
|
390
|
+
provided as `time_interval`, which means that they
|
|
391
|
+
have at least a part in common.
|
|
392
|
+
"""
|
|
393
|
+
return TimeIntervalUtils.a_intersects_with_b(
|
|
394
|
+
time_interval_a = self,
|
|
395
|
+
time_interval_b = time_interval
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
def get_intersection_with_a(
|
|
399
|
+
self,
|
|
400
|
+
time_interval: 'TimeInterval'
|
|
401
|
+
) -> Union['TimeInterval', None]:
|
|
402
|
+
"""
|
|
403
|
+
Get the time interval that intersects this one and the
|
|
404
|
+
one provided as `time_interval`. The result can be `None`
|
|
405
|
+
if there is no intersection in between both.
|
|
406
|
+
"""
|
|
407
|
+
return TimeIntervalUtils.get_intersection_of_a_and_b(
|
|
408
|
+
time_interval_a = self,
|
|
409
|
+
time_interval_b = time_interval
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
class TimeIntervalWithFps(TimeInterval):
|
|
413
|
+
"""
|
|
414
|
+
Class to represent a time interval, which is a tuple
|
|
415
|
+
of time moments representing the time range
|
|
416
|
+
`[start, end)`, but with a `fps` value set to round
|
|
417
|
+
all the time moments to values multiple of `1/fps`.
|
|
418
|
+
|
|
419
|
+
This class is very useful when handling video time
|
|
420
|
+
moments and time ranges.
|
|
421
|
+
"""
|
|
422
|
+
|
|
423
|
+
@property
|
|
424
|
+
def copy(
|
|
425
|
+
self
|
|
426
|
+
) -> 'TimeInterval':
|
|
427
|
+
"""
|
|
428
|
+
A copy of this instance.
|
|
429
|
+
"""
|
|
430
|
+
return TimeIntervalWithFps(
|
|
431
|
+
start = self.start,
|
|
432
|
+
end = self.end,
|
|
433
|
+
fps = self.fps
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
def __init__(
|
|
437
|
+
self,
|
|
438
|
+
start: Number,
|
|
439
|
+
end: Number,
|
|
440
|
+
fps: Number
|
|
441
|
+
):
|
|
442
|
+
"""
|
|
443
|
+
(!) The `start` and `end` values will be truncated
|
|
444
|
+
according to the `fps` provided.
|
|
445
|
+
|
|
446
|
+
The `end` value must be greater than the `start` value.
|
|
447
|
+
"""
|
|
448
|
+
ParameterValidator.validate_mandatory_positive_number('fps', fps, do_include_zero = False)
|
|
449
|
+
|
|
450
|
+
self.fps: float = fps
|
|
451
|
+
"""
|
|
452
|
+
The `fps` for this time interval calculations.
|
|
453
|
+
"""
|
|
454
|
+
self._t_handler = THandler(self.fps)
|
|
455
|
+
"""
|
|
456
|
+
*For internal use only*
|
|
457
|
+
|
|
458
|
+
Shortcut to the `THandler` instance built with the `fps`
|
|
459
|
+
value provided when instantiating this instance.
|
|
460
|
+
"""
|
|
461
|
+
|
|
462
|
+
super().__init__(
|
|
463
|
+
start = self._t_handler.t.truncated(start),
|
|
464
|
+
end = self._t_handler.t.truncated(end)
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
"""
|
|
468
|
+
Apparently, the professional video editors use always
|
|
469
|
+
the `truncate` method to obtain the `start` of the
|
|
470
|
+
frame time interval always. So, why should I change it?
|
|
471
|
+
"""
|
|
472
|
+
def _truncate(
|
|
473
|
+
self,
|
|
474
|
+
t: Number
|
|
475
|
+
) -> float:
|
|
476
|
+
"""
|
|
477
|
+
Get the truncated value of the `t` time moment provided,
|
|
478
|
+
which will be always the `start` of the time interval
|
|
479
|
+
delimited by the `t` and the `fps` of this instance.
|
|
480
|
+
|
|
481
|
+
Some examples below (with `fps=5`):
|
|
482
|
+
- `t=0.2` => `0.2` <=> `interval=[0.2, 0.4)`
|
|
483
|
+
- `t=0.37` => `0.2` <=> `interval=[0.2, 0.4)`
|
|
484
|
+
- `t=0.4` => `0.4` <=> `interval=[0.4, 0.6)`
|
|
485
|
+
- `t=0.87` => `0.8` <=> `interval=[0.8, 1.0)`
|
|
486
|
+
"""
|
|
487
|
+
return self._t_handler.t.truncated(t)
|
|
488
|
+
|
|
489
|
+
def _round(
|
|
490
|
+
self,
|
|
491
|
+
t: Number
|
|
492
|
+
) -> float:
|
|
493
|
+
"""
|
|
494
|
+
Get the rounded value of the `t` time moment provided,
|
|
495
|
+
which will be the `start` or the `end` (depending on which
|
|
496
|
+
one is closer to the `t` value) of the time interval
|
|
497
|
+
delimited by the `t` and the `fps` of this instance.
|
|
498
|
+
|
|
499
|
+
Some examples below (with `fps=5`):
|
|
500
|
+
- `t=0.2` => `0.2` <=> `interval=[0.2, 0.4)`
|
|
501
|
+
- `t=0.29` => `0.2` <=> `interval=[0.2, 0.4)`
|
|
502
|
+
- `t=0.31` => `0.4` <=> `interval=[0.2, 0.4)`
|
|
503
|
+
- `t=0.37` => `0.4` <=> `interval=[0.2, 0.4)`
|
|
504
|
+
- `t=0.4` => `0.4` <=> `interval=[0.4, 0.6)`
|
|
505
|
+
- `t=0.87` => `0.8` <=> `interval=[0.8, 1.0)`
|
|
506
|
+
"""
|
|
507
|
+
return self._t_handler.t.rounded(t)
|
|
508
|
+
|
|
509
|
+
def _round_up(
|
|
510
|
+
self,
|
|
511
|
+
t: Number
|
|
512
|
+
) -> float:
|
|
513
|
+
"""
|
|
514
|
+
Get the rounded up value of the `t` time moment provided,
|
|
515
|
+
which will be the `end` of the time interval (unless it
|
|
516
|
+
is exactly the `start` value of a time interval) delimited
|
|
517
|
+
by the `t` and the `fps` of this instance.
|
|
518
|
+
|
|
519
|
+
Some examples below (with `fps=5`):
|
|
520
|
+
- `t=0.2` => `0.2` <=> `interval=[0.2, 0.4)`
|
|
521
|
+
- `t=0.29` => `0.4` <=> `interval=[0.2, 0.4)`
|
|
522
|
+
- `t=0.31` => `0.4` <=> `interval=[0.2, 0.4)`
|
|
523
|
+
- `t=0.37` => `0.2` <=> `interval=[0.2, 0.4)`
|
|
524
|
+
- `t=0.4` => `0.4` <=> `interval=[0.4, 0.6)`
|
|
525
|
+
- `t=0.87` => `0.8` <=> `interval=[0.8, 1.0)`
|
|
526
|
+
"""
|
|
527
|
+
return self._t_handler.t.rounded_up(t)
|
|
528
|
+
|
|
529
|
+
def cut(
|
|
530
|
+
self,
|
|
531
|
+
start: Number,
|
|
532
|
+
end: Number
|
|
533
|
+
) -> tuple[Union['TimeInterval', None], Union['TimeInterval', None], Union['TimeInterval', None]]:
|
|
534
|
+
"""
|
|
535
|
+
Cut a segment from the given `start` to the also provided
|
|
536
|
+
`end` time moments of this time interval instance.
|
|
537
|
+
|
|
538
|
+
(!) The `start` and `end` values will be truncated to fit the
|
|
539
|
+
`start` value of the time interval they belong to according
|
|
540
|
+
to the `fps` of this instance.
|
|
541
|
+
|
|
542
|
+
This method will return a tuple of 3 elements including the
|
|
543
|
+
segments created by cutting this time interval in the order
|
|
544
|
+
they were generated, but also having the 4th element always
|
|
545
|
+
as the index of the one specifically requested by the user.
|
|
546
|
+
The tuple will include all the segments at the begining and
|
|
547
|
+
the rest will be None (unless the 4th one, which is the
|
|
548
|
+
index).
|
|
549
|
+
|
|
550
|
+
Examples below:
|
|
551
|
+
- A time interval of `[2, 5)` cut with `start=3` and `end=4`
|
|
552
|
+
will generate `((2, 3), (3, 4), (4, 5), 1)`.
|
|
553
|
+
- A time interval of `[2, 5)` cut with `start=2` and `end=4`
|
|
554
|
+
will generate `((2, 4), (4, 5), None, 0)`.
|
|
555
|
+
- A time interval of `[2, 5)` cut with `start=4` and `end=5`
|
|
556
|
+
will generate `((2, 4), (4, 5), None, 1)`.
|
|
557
|
+
- A time interval of `[2, 5)` cut with `start=2` and `end=5`
|
|
558
|
+
will generate `((2, 5), None, None, 0)`.
|
|
559
|
+
|
|
560
|
+
As you can see, the result could be the same in different
|
|
561
|
+
situations, but it's up to you (and the specific method in
|
|
562
|
+
which you are calling to this one) to choose the tuple you
|
|
563
|
+
want to return.
|
|
564
|
+
"""
|
|
565
|
+
# TODO: maybe a 'decorator' to do this '_truncate' (?)
|
|
566
|
+
return super().cut(
|
|
567
|
+
start = self._truncate(start),
|
|
568
|
+
end = self._truncate(end)
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
def cutted(
|
|
572
|
+
self,
|
|
573
|
+
start: Number,
|
|
574
|
+
end: Number
|
|
575
|
+
) -> 'TimeInterval':
|
|
576
|
+
"""
|
|
577
|
+
Get this time interval instance but cutted from the `start`
|
|
578
|
+
to the `end` time moments provided.
|
|
579
|
+
|
|
580
|
+
(!) The `start` and `end` values will be truncated to fit the
|
|
581
|
+
`start` value of the time interval they belong to according
|
|
582
|
+
to the `fps` of this instance.
|
|
583
|
+
|
|
584
|
+
(!) This method doesn't modify the original instance but
|
|
585
|
+
returns a new one.
|
|
586
|
+
"""
|
|
587
|
+
# TODO: maybe a 'decorator' to do this '_truncate' (?)
|
|
588
|
+
return super().cutted(
|
|
589
|
+
start = self._truncate(start),
|
|
590
|
+
end = self._truncate(end)
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
def trim_start(
|
|
594
|
+
self,
|
|
595
|
+
t_variation: Number,
|
|
596
|
+
limit: Union[Number, None] = None
|
|
597
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
598
|
+
"""
|
|
599
|
+
Get a tuple containing the 2 new `TimeInterval` instances
|
|
600
|
+
generated by trimming this one's start the amount of seconds
|
|
601
|
+
provided as the `t_variation` parameter. The first tuple is
|
|
602
|
+
the remaining, and the second one is the new time interval
|
|
603
|
+
requested by the user.
|
|
604
|
+
|
|
605
|
+
(!) The `t_variation` value provided will be transformed into
|
|
606
|
+
a multiple of `1/fps` of this instance, and truncated to fit
|
|
607
|
+
the `start` of the time interval the new segments will belong
|
|
608
|
+
to.
|
|
609
|
+
|
|
610
|
+
This method will raise an exception if the new `start` value
|
|
611
|
+
becomes a value over the time interval `end` value or the
|
|
612
|
+
`limit`, that must be greater than the `start` and lower
|
|
613
|
+
than the time interval `end` value.
|
|
614
|
+
|
|
615
|
+
The `t_variation` must be a positive value, the amount of
|
|
616
|
+
seconds to be trimmed.
|
|
617
|
+
"""
|
|
618
|
+
return super().trim_start(
|
|
619
|
+
# TODO: Is this ok (?)
|
|
620
|
+
# TODO: maybe a 'decorator' to do this '_truncate' (?)
|
|
621
|
+
t_variation = self._truncate(t_variation),
|
|
622
|
+
limit = limit
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
def trimmed_start(
|
|
626
|
+
self,
|
|
627
|
+
t_variation: Number,
|
|
628
|
+
limit: Union[Number, None] = None
|
|
629
|
+
) -> 'TimeInterval':
|
|
630
|
+
"""
|
|
631
|
+
Get this time interval instance but trimmed from the `start`
|
|
632
|
+
the `t_variation` amount of seconds provided.
|
|
633
|
+
|
|
634
|
+
(!) The `t_variation` value provided will be transformed into
|
|
635
|
+
a multiple of `1/fps` of this instance, and truncated to fit
|
|
636
|
+
the `start` of the time interval the new segments will belong
|
|
637
|
+
to.
|
|
638
|
+
|
|
639
|
+
(!) This method doesn't modify the original instance but
|
|
640
|
+
returns a new one.
|
|
641
|
+
"""
|
|
642
|
+
return super().trimmed_start(
|
|
643
|
+
# TODO: maybe a 'decorator' to do this '_truncate' (?)
|
|
644
|
+
t_variation = self._truncate(t_variation),
|
|
645
|
+
limit = limit
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
def trim_end(
|
|
649
|
+
self,
|
|
650
|
+
t_variation: Number,
|
|
651
|
+
limit: Union[Number, None] = None
|
|
652
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
653
|
+
"""
|
|
654
|
+
Get a tuple containing the 2 new `TimeInterval` instances
|
|
655
|
+
generated by trimming this one's end the amount of seconds
|
|
656
|
+
provided as the `t_variation` parameter. The first tuple is
|
|
657
|
+
the one requested by the user, and the second one is the
|
|
658
|
+
remaining.
|
|
659
|
+
|
|
660
|
+
(!) The `t_variation` value provided will be transformed into
|
|
661
|
+
a multiple of `1/fps` of this instance, and truncated to fit
|
|
662
|
+
the `start` of the time interval the new segments will belong
|
|
663
|
+
to.
|
|
664
|
+
|
|
665
|
+
This method will raise an exception if the new `end` value
|
|
666
|
+
becomes a value under the time interval `start` value or
|
|
667
|
+
the `limit`, that must be greater than the `start` and lower
|
|
668
|
+
than the time interval `end` value.
|
|
669
|
+
|
|
670
|
+
The `t_variation` must be a positive value, the amount of
|
|
671
|
+
seconds to be trimmed.
|
|
672
|
+
"""
|
|
673
|
+
return super().trim_end(
|
|
674
|
+
# TODO: maybe a 'decorator' to do this '_truncate' (?)
|
|
675
|
+
t_variation = self._truncate(t_variation),
|
|
676
|
+
limit = limit
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
def trimmed_end(
|
|
680
|
+
self,
|
|
681
|
+
t_variation: Number,
|
|
682
|
+
limit: Union[Number, None] = None
|
|
683
|
+
) -> 'TimeInterval':
|
|
684
|
+
"""
|
|
685
|
+
Get this time interval instance but trimmed from the `end`
|
|
686
|
+
the `t_variation` amount of seconds provided.
|
|
687
|
+
|
|
688
|
+
(!) The `t_variation` value provided will be transformed into
|
|
689
|
+
a multiple of `1/fps` of this instance, and truncated to fit
|
|
690
|
+
the `start` of the time interval the new segments will belong
|
|
691
|
+
to.
|
|
692
|
+
|
|
693
|
+
(!) This method doesn't modify the original instance but
|
|
694
|
+
returns a new one.
|
|
695
|
+
"""
|
|
696
|
+
return super().trimmed_end(
|
|
697
|
+
# TODO: maybe a 'decorator' to do this '_truncate' (?)
|
|
698
|
+
t_variation = self._truncate(t_variation),
|
|
699
|
+
limit = limit
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
def split(
|
|
703
|
+
self,
|
|
704
|
+
t: Number
|
|
705
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
706
|
+
"""
|
|
707
|
+
Split the time interval at the provided `t` time moment
|
|
708
|
+
and get the 2 new time intervals as a result (as a tuple).
|
|
709
|
+
|
|
710
|
+
(!) The `t` value provided will be transformed into a
|
|
711
|
+
multiple of `1/fps` of this instance, and truncated to fit
|
|
712
|
+
the `start` of the time interval the new segments will belong
|
|
713
|
+
to.
|
|
714
|
+
|
|
715
|
+
This method will raise an exception if the `t` value
|
|
716
|
+
provided is a limit value (or above).
|
|
717
|
+
|
|
718
|
+
Examples below:
|
|
719
|
+
- A time interval of `[2, 5)` cut with `t=3` will generate
|
|
720
|
+
`((2, 3), (3, 5))`.
|
|
721
|
+
- A time interval of `[2, 5)` cut with `t=4` will generate
|
|
722
|
+
`((2, 4), (4, 5))`.
|
|
723
|
+
- A time interval of `[2, 5)` cut with `t>=5` will raise
|
|
724
|
+
exception.
|
|
725
|
+
- A time interval of `[2, 5)` cut with `t<=2` will raise
|
|
726
|
+
exception.
|
|
727
|
+
"""
|
|
728
|
+
return super().split(
|
|
729
|
+
# TODO: maybe a 'decorator' to do this '_truncate' (?)
|
|
730
|
+
t = self._truncate(t)
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
# The only thing that changes in 'splitted_left' and
|
|
734
|
+
# 'splitted_right' is the docummentation, but the rest
|
|
735
|
+
# is the same, so I don't overwrite it...
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
class NewTimeInterval:
|
|
739
|
+
"""
|
|
740
|
+
A time interval that has a memory of the original values
|
|
741
|
+
but can be modified and enshorted during its life time.
|
|
742
|
+
|
|
743
|
+
Class to represent a time interval, which is a tuple
|
|
744
|
+
of time moments representing the time range
|
|
745
|
+
`[start, end)`.
|
|
746
|
+
|
|
747
|
+
TODO: This will replace the old two versions by making
|
|
748
|
+
the 'fps' parameter optional.
|
|
749
|
+
"""
|
|
750
|
+
|
|
751
|
+
@property
|
|
752
|
+
def duration(
|
|
753
|
+
self
|
|
754
|
+
) -> float:
|
|
755
|
+
"""
|
|
756
|
+
The `duration` of the time interval.
|
|
757
|
+
"""
|
|
758
|
+
return self.end - self.start
|
|
759
|
+
|
|
760
|
+
@property
|
|
761
|
+
def copy(
|
|
762
|
+
self
|
|
763
|
+
) -> 'NewTimeInterval':
|
|
764
|
+
"""
|
|
765
|
+
A copy of this instance.
|
|
766
|
+
"""
|
|
767
|
+
time_interval = NewTimeInterval(
|
|
768
|
+
start = self.start,
|
|
769
|
+
end = self.end,
|
|
770
|
+
fps = self.fps
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
time_interval.start = self.start
|
|
774
|
+
time_interval.end = self.end
|
|
775
|
+
|
|
776
|
+
return time_interval
|
|
777
|
+
|
|
778
|
+
@property
|
|
779
|
+
def as_tuple(
|
|
780
|
+
self
|
|
781
|
+
) -> tuple[float, float]:
|
|
782
|
+
"""
|
|
783
|
+
The time interval but as a `(start, end)` tuple.
|
|
784
|
+
"""
|
|
785
|
+
return (self.start, self.end)
|
|
786
|
+
|
|
787
|
+
@property
|
|
788
|
+
def cutter(
|
|
789
|
+
self
|
|
790
|
+
) -> 'TimeIntervalCutter':
|
|
791
|
+
"""
|
|
792
|
+
Shortcut to the static class `TimeIntervalCutter` that
|
|
793
|
+
is capable of cutting time intervals.
|
|
794
|
+
"""
|
|
795
|
+
return TimeIntervalCutter
|
|
796
|
+
|
|
797
|
+
def __init__(
|
|
798
|
+
self,
|
|
799
|
+
start: Number,
|
|
800
|
+
end: Number,
|
|
801
|
+
fps: Union[Number, None] = None
|
|
802
|
+
):
|
|
803
|
+
"""
|
|
804
|
+
The `end` value must be greater than the `start` value.
|
|
805
|
+
|
|
806
|
+
(!) If `fps` is provided, the `start` and `end` values
|
|
807
|
+
will be truncated according to the `fps` provided.
|
|
808
|
+
"""
|
|
809
|
+
ParameterValidator.validate_positive_number('fps', fps, do_include_zero = False)
|
|
810
|
+
|
|
811
|
+
self.fps: Union[Number, None] = fps
|
|
812
|
+
"""
|
|
813
|
+
The `fps` for this time interval calculations, if set.
|
|
814
|
+
"""
|
|
815
|
+
self._t_handler: Union[THandler, None] = (
|
|
816
|
+
THandler(self.fps)
|
|
817
|
+
if self.fps is not None else
|
|
818
|
+
None
|
|
819
|
+
)
|
|
820
|
+
"""
|
|
821
|
+
*For internal use only*
|
|
822
|
+
|
|
823
|
+
Shortcut to the `THandler` instance built with the `fps`
|
|
824
|
+
value provided when instantiating this instance, that
|
|
825
|
+
can be `None` if no `fps` are provided.
|
|
826
|
+
"""
|
|
827
|
+
|
|
828
|
+
if start > end:
|
|
829
|
+
raise Exception('The `start` value provided is greater than the `end` value provided.')
|
|
830
|
+
|
|
831
|
+
if start == end:
|
|
832
|
+
raise Exception('The `start` value provided is exactly the `end` value provided.')
|
|
833
|
+
|
|
834
|
+
self.original_start: float = (
|
|
835
|
+
self._t_handler.t.truncated(start)
|
|
836
|
+
if self.fps is not None else
|
|
837
|
+
start
|
|
838
|
+
)
|
|
839
|
+
"""
|
|
840
|
+
The original `start` of the time interval.
|
|
841
|
+
"""
|
|
842
|
+
self.original_end: float = (
|
|
843
|
+
self._t_handler.t.truncated(end)
|
|
844
|
+
if self.fps is not None else
|
|
845
|
+
end
|
|
846
|
+
)
|
|
847
|
+
"""
|
|
848
|
+
The original `end` of the time interval.
|
|
849
|
+
"""
|
|
850
|
+
self.start: float = self.original_start
|
|
851
|
+
"""
|
|
852
|
+
The current `start` value of this time interval.
|
|
853
|
+
"""
|
|
854
|
+
self.end: float = self.original_end
|
|
855
|
+
"""
|
|
856
|
+
The current `end` value of this time interval.
|
|
857
|
+
"""
|
|
858
|
+
|
|
859
|
+
# TODO: Is this that necessary (?)
|
|
860
|
+
def _validate_t(
|
|
861
|
+
self,
|
|
862
|
+
t: Number,
|
|
863
|
+
do_include_start: bool = False,
|
|
864
|
+
do_include_end: bool = False
|
|
865
|
+
) -> None:
|
|
866
|
+
"""
|
|
867
|
+
Validate that the provided `t` value is between the `start`
|
|
868
|
+
and the `end` parameters provided, including them or not
|
|
869
|
+
according to the boolean parameters provided.
|
|
870
|
+
"""
|
|
871
|
+
# TODO: Maybe we should validate also according to the
|
|
872
|
+
# original values
|
|
873
|
+
ParameterValidator.validate_mandatory_number_between(
|
|
874
|
+
name = 't',
|
|
875
|
+
value = t,
|
|
876
|
+
lower_limit = self.start,
|
|
877
|
+
upper_limit = self.end,
|
|
878
|
+
do_include_lower_limit = do_include_start,
|
|
879
|
+
do_include_upper_limit = do_include_end
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
"""
|
|
883
|
+
Apparently, the professional video editors use always
|
|
884
|
+
the `truncate` method to obtain the `start` of the
|
|
885
|
+
frame time interval always. So, why should I change it?
|
|
886
|
+
"""
|
|
887
|
+
def _truncate(
|
|
888
|
+
self,
|
|
889
|
+
t: Number
|
|
890
|
+
) -> float:
|
|
891
|
+
"""
|
|
892
|
+
Get the truncated value of the `t` time moment provided,
|
|
893
|
+
which will be always the `start` of the time interval
|
|
894
|
+
delimited by the `t` and the `fps` of this instance.
|
|
895
|
+
|
|
896
|
+
(!) The value will be truncated only if the `fps`
|
|
897
|
+
attribute is set.
|
|
898
|
+
|
|
899
|
+
Some examples below (with `fps=5`):
|
|
900
|
+
- `t=0.2` => `0.2` <=> `interval=[0.2, 0.4)`
|
|
901
|
+
- `t=0.37` => `0.2` <=> `interval=[0.2, 0.4)`
|
|
902
|
+
- `t=0.4` => `0.4` <=> `interval=[0.4, 0.6)`
|
|
903
|
+
- `t=0.87` => `0.8` <=> `interval=[0.8, 1.0)`
|
|
904
|
+
"""
|
|
905
|
+
return (
|
|
906
|
+
self._t_handler.t.truncated(t)
|
|
907
|
+
if self.fps is not None else
|
|
908
|
+
t
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
def _round(
|
|
912
|
+
self,
|
|
913
|
+
t: Number
|
|
914
|
+
) -> float:
|
|
915
|
+
"""
|
|
916
|
+
Get the rounded value of the `t` time moment provided,
|
|
917
|
+
which will be the `start` or the `end` (depending on which
|
|
918
|
+
one is closer to the `t` value) of the time interval
|
|
919
|
+
delimited by the `t` and the `fps` of this instance.
|
|
920
|
+
|
|
921
|
+
(!) The value will be rounded only if the `fps`
|
|
922
|
+
attribute is set.
|
|
923
|
+
|
|
924
|
+
Some examples below (with `fps=5`):
|
|
925
|
+
- `t=0.2` => `0.2` <=> `interval=[0.2, 0.4)`
|
|
926
|
+
- `t=0.29` => `0.2` <=> `interval=[0.2, 0.4)`
|
|
927
|
+
- `t=0.31` => `0.4` <=> `interval=[0.2, 0.4)`
|
|
928
|
+
- `t=0.37` => `0.4` <=> `interval=[0.2, 0.4)`
|
|
929
|
+
- `t=0.4` => `0.4` <=> `interval=[0.4, 0.6)`
|
|
930
|
+
- `t=0.87` => `0.8` <=> `interval=[0.8, 1.0)`
|
|
931
|
+
"""
|
|
932
|
+
return (
|
|
933
|
+
self._t_handler.t.rounded(t)
|
|
934
|
+
if self.fps is not None else
|
|
935
|
+
t
|
|
936
|
+
)
|
|
937
|
+
|
|
938
|
+
def _round_up(
|
|
939
|
+
self,
|
|
940
|
+
t: Number
|
|
941
|
+
) -> float:
|
|
942
|
+
"""
|
|
943
|
+
Get the rounded up value of the `t` time moment provided,
|
|
944
|
+
which will be the `end` of the time interval (unless it
|
|
945
|
+
is exactly the `start` value of a time interval) delimited
|
|
946
|
+
by the `t` and the `fps` of this instance.
|
|
947
|
+
|
|
948
|
+
(!) The value will be rounded up only if the `fps`
|
|
949
|
+
attribute is set.
|
|
950
|
+
|
|
951
|
+
Some examples below (with `fps=5`):
|
|
952
|
+
- `t=0.2` => `0.2` <=> `interval=[0.2, 0.4)`
|
|
953
|
+
- `t=0.29` => `0.4` <=> `interval=[0.2, 0.4)`
|
|
954
|
+
- `t=0.31` => `0.4` <=> `interval=[0.2, 0.4)`
|
|
955
|
+
- `t=0.37` => `0.2` <=> `interval=[0.2, 0.4)`
|
|
956
|
+
- `t=0.4` => `0.4` <=> `interval=[0.4, 0.6)`
|
|
957
|
+
- `t=0.87` => `0.8` <=> `interval=[0.8, 1.0)`
|
|
958
|
+
"""
|
|
959
|
+
return (
|
|
960
|
+
self._t_handler.t.rounded_up(t)
|
|
961
|
+
if self.fps is not None else
|
|
962
|
+
t
|
|
963
|
+
)
|
|
964
|
+
|
|
965
|
+
def get_cuts(
|
|
966
|
+
self,
|
|
967
|
+
start: Number,
|
|
968
|
+
end: Number
|
|
969
|
+
) -> tuple[Union['NewTimeInterval', None], Union['NewTimeInterval', None], Union['NewTimeInterval', None]]:
|
|
970
|
+
"""
|
|
971
|
+
Cut a segment from the given `start` to the also provided
|
|
972
|
+
`end` time moments of this time interval instance and get
|
|
973
|
+
all the cuts.
|
|
974
|
+
|
|
975
|
+
This method will return a tuple of 3 elements including the
|
|
976
|
+
segments created by cutting this time interval in the order
|
|
977
|
+
they were generated, but also having the 4th element always
|
|
978
|
+
as the index of the one specifically requested by the user.
|
|
979
|
+
The tuple will include all the segments at the begining and
|
|
980
|
+
the rest will be None (unless the 4th one, which is the
|
|
981
|
+
index).
|
|
982
|
+
|
|
983
|
+
Examples below:
|
|
984
|
+
- A time interval of `[2, 5)` cut with `start=3` and `end=4`
|
|
985
|
+
will generate `((2, 3), (3, 4), (4, 5), 1)`.
|
|
986
|
+
- A time interval of `[2, 5)` cut with `start=2` and `end=4`
|
|
987
|
+
will generate `((2, 4), (4, 5), None, 0)`.
|
|
988
|
+
- A time interval of `[2, 5)` cut with `start=4` and `end=5`
|
|
989
|
+
will generate `((2, 4), (4, 5), None, 1)`.
|
|
990
|
+
- A time interval of `[2, 5)` cut with `start=2` and `end=5`
|
|
991
|
+
will generate `((2, 5), None, None, 0)`.
|
|
992
|
+
|
|
993
|
+
As you can see, the result could be the same in different
|
|
994
|
+
situations, but it's up to you (and the specific method in
|
|
995
|
+
which you are calling to this one) to choose the tuple you
|
|
996
|
+
want to return.
|
|
997
|
+
"""
|
|
998
|
+
# TODO: maybe a 'decorator' to do this '_truncate' (?)
|
|
999
|
+
return self.cutter.from_to(
|
|
1000
|
+
time_interval = self,
|
|
1001
|
+
start = self._truncate(start),
|
|
1002
|
+
end = self._truncate(end)
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
def get_cut(
|
|
1006
|
+
self,
|
|
1007
|
+
start: Number,
|
|
1008
|
+
end: Number
|
|
1009
|
+
) -> 'NewTimeInterval':
|
|
1010
|
+
"""
|
|
1011
|
+
Get this time interval instance but cutted from the `start`
|
|
1012
|
+
to the `end` time moments provided.
|
|
1013
|
+
|
|
1014
|
+
(!) This method doesn't modify the original instance but
|
|
1015
|
+
returns a new one.
|
|
1016
|
+
"""
|
|
1017
|
+
tuples = self.get_cuts(
|
|
1018
|
+
start = start,
|
|
1019
|
+
end = end
|
|
1020
|
+
)
|
|
1021
|
+
|
|
1022
|
+
return tuples[tuples[3]]
|
|
1023
|
+
|
|
1024
|
+
def cut(
|
|
1025
|
+
self,
|
|
1026
|
+
start: Number,
|
|
1027
|
+
end: Number
|
|
1028
|
+
) -> 'NewTimeInterval':
|
|
1029
|
+
"""
|
|
1030
|
+
(!) This method will modify this instance.
|
|
1031
|
+
|
|
1032
|
+
Transform this time interval into a new one delimited by
|
|
1033
|
+
the `start` and `end` time moments provided.
|
|
1034
|
+
|
|
1035
|
+
This method returns this same instance but modified.
|
|
1036
|
+
"""
|
|
1037
|
+
cut = self.get_cut(
|
|
1038
|
+
start = start,
|
|
1039
|
+
end = end
|
|
1040
|
+
)
|
|
1041
|
+
|
|
1042
|
+
self.start = cut.start
|
|
1043
|
+
self.end = cut.end
|
|
1044
|
+
|
|
1045
|
+
return self
|
|
1046
|
+
|
|
1047
|
+
# TODO: Rename, please
|
|
1048
|
+
def get_trim_starts(
|
|
1049
|
+
self,
|
|
1050
|
+
t_variation: Number,
|
|
1051
|
+
limit: Union[Number, None] = None
|
|
1052
|
+
) -> tuple['NewTimeInterval', 'NewTimeInterval']:
|
|
1053
|
+
"""
|
|
1054
|
+
Get a tuple containing the 2 new `TimeInterval` instances
|
|
1055
|
+
generated by trimming this one's start the amount of seconds
|
|
1056
|
+
provided as the `t_variation` parameter. The first tuple is
|
|
1057
|
+
the remaining, and the second one is the new time interval
|
|
1058
|
+
requested by the user.
|
|
1059
|
+
|
|
1060
|
+
(!) The `t_variation` value provided will be transformed into
|
|
1061
|
+
a multiple of `1/fps` of this instance, and truncated to fit
|
|
1062
|
+
the `start` of the time interval the new segments will belong
|
|
1063
|
+
to, if the `fps` is set.
|
|
1064
|
+
|
|
1065
|
+
This method will raise an exception if the new `start` value
|
|
1066
|
+
becomes a value over the time interval `end` value or the
|
|
1067
|
+
`limit`, that must be greater than the `start` and lower
|
|
1068
|
+
than the time interval `end` value.
|
|
1069
|
+
|
|
1070
|
+
The `t_variation` must be a positive value, the amount of
|
|
1071
|
+
seconds to be trimmed.
|
|
1072
|
+
"""
|
|
1073
|
+
return self.cutter.trim_start(
|
|
1074
|
+
time_interval = self,
|
|
1075
|
+
# TODO: maybe a 'decorator' to do this '_truncate' (?)
|
|
1076
|
+
t_variation = self._truncate(t_variation),
|
|
1077
|
+
limit = limit
|
|
1078
|
+
)
|
|
1079
|
+
|
|
1080
|
+
# TODO: Rename, please
|
|
1081
|
+
def get_trim_start(
|
|
1082
|
+
self,
|
|
1083
|
+
t_variation: Number,
|
|
1084
|
+
limit: Union[Number, None] = None
|
|
1085
|
+
) -> 'NewTimeInterval':
|
|
1086
|
+
"""
|
|
1087
|
+
Get this time interval instance but trimmed from the `start`
|
|
1088
|
+
the `t_variation` amount of seconds provided.
|
|
1089
|
+
|
|
1090
|
+
(!) The `t_variation` value provided will be transformed into
|
|
1091
|
+
a multiple of `1/fps` of this instance, and truncated to fit
|
|
1092
|
+
the `start` of the time interval the new segments will belong
|
|
1093
|
+
to, if the `fps` is set.
|
|
1094
|
+
|
|
1095
|
+
(!) This method doesn't modify the original instance but
|
|
1096
|
+
returns a new one.
|
|
1097
|
+
"""
|
|
1098
|
+
return self.get_trim_starts(
|
|
1099
|
+
t_variation = t_variation,
|
|
1100
|
+
limit = limit
|
|
1101
|
+
)[1]
|
|
1102
|
+
|
|
1103
|
+
def trim_start(
|
|
1104
|
+
self,
|
|
1105
|
+
t_variation: Number,
|
|
1106
|
+
limit: Union[Number, None] = None
|
|
1107
|
+
) -> 'NewTimeInterval':
|
|
1108
|
+
"""
|
|
1109
|
+
(!) This method will modify this instance.
|
|
1110
|
+
|
|
1111
|
+
Transform this time interval into a new one in which
|
|
1112
|
+
the `start` has been trimmed the `t_variation` provided
|
|
1113
|
+
if the result respected the also given `limit`.
|
|
1114
|
+
|
|
1115
|
+
This method returns this same instance but modified.
|
|
1116
|
+
"""
|
|
1117
|
+
cut = self.get_trim_start(
|
|
1118
|
+
t_variation = t_variation,
|
|
1119
|
+
limit = limit
|
|
1120
|
+
)
|
|
1121
|
+
|
|
1122
|
+
self.start = cut.start
|
|
1123
|
+
self.end = cut.end
|
|
1124
|
+
|
|
1125
|
+
return self
|
|
1126
|
+
|
|
1127
|
+
def get_trim_ends(
|
|
1128
|
+
self,
|
|
1129
|
+
t_variation: Number,
|
|
1130
|
+
limit: Union[Number, None] = None
|
|
1131
|
+
) -> tuple['NewTimeInterval', 'NewTimeInterval']:
|
|
1132
|
+
"""
|
|
1133
|
+
Get a tuple containing the 2 new `TimeInterval` instances
|
|
1134
|
+
generated by trimming this one's end the amount of seconds
|
|
1135
|
+
provided as the `t_variation` parameter. The first tuple is
|
|
1136
|
+
the one requested by the user, and the second one is the
|
|
1137
|
+
remaining.
|
|
1138
|
+
|
|
1139
|
+
(!) The `t_variation` value provided will be transformed into
|
|
1140
|
+
a multiple of `1/fps` of this instance, and truncated to fit
|
|
1141
|
+
the `start` of the time interval the new segments will belong
|
|
1142
|
+
to, if the `fps` is set.
|
|
1143
|
+
|
|
1144
|
+
This method will raise an exception if the new `end` value
|
|
1145
|
+
becomes a value under the time interval `start` value or
|
|
1146
|
+
the `limit`, that must be greater than the `start` and lower
|
|
1147
|
+
than the time interval `end` value.
|
|
1148
|
+
|
|
1149
|
+
The `t_variation` must be a positive value, the amount of
|
|
1150
|
+
seconds to be trimmed.
|
|
1151
|
+
"""
|
|
1152
|
+
return self.cutter.trim_end(
|
|
1153
|
+
time_interval = self,
|
|
1154
|
+
# TODO: maybe a 'decorator' to do this '_truncate' (?)
|
|
1155
|
+
t_variation = self._truncate(t_variation),
|
|
1156
|
+
limit = limit
|
|
1157
|
+
)
|
|
1158
|
+
|
|
1159
|
+
# TODO: Rename, please
|
|
1160
|
+
def get_trim_end(
|
|
1161
|
+
self,
|
|
1162
|
+
t_variation: Number,
|
|
1163
|
+
limit: Union[Number, None] = None
|
|
1164
|
+
) -> 'NewTimeInterval':
|
|
1165
|
+
"""
|
|
1166
|
+
Get this time interval instance but trimmed from the `end`
|
|
1167
|
+
the `t_variation` amount of seconds provided.
|
|
1168
|
+
|
|
1169
|
+
(!) The `t_variation` value provided will be transformed into
|
|
1170
|
+
a multiple of `1/fps` of this instance, and truncated to fit
|
|
1171
|
+
the `end` of the time interval the new segments will belong
|
|
1172
|
+
to, if the `fps` is set.
|
|
1173
|
+
|
|
1174
|
+
(!) This method doesn't modify the original instance but
|
|
1175
|
+
returns a new one.
|
|
1176
|
+
"""
|
|
1177
|
+
return self.get_trim_ends(
|
|
1178
|
+
t_variation = t_variation,
|
|
1179
|
+
limit = limit
|
|
1180
|
+
)[0]
|
|
1181
|
+
|
|
1182
|
+
def trim_end(
|
|
1183
|
+
self,
|
|
1184
|
+
t_variation: Number,
|
|
1185
|
+
limit: Union[Number, None] = None
|
|
1186
|
+
) -> 'NewTimeInterval':
|
|
1187
|
+
"""
|
|
1188
|
+
(!) This method will modify this instance.
|
|
1189
|
+
|
|
1190
|
+
Transform this time interval into a new one in which
|
|
1191
|
+
the `end` has been trimmed the `t_variation` provided
|
|
1192
|
+
if the result respected the also given `limit`.
|
|
1193
|
+
|
|
1194
|
+
This method returns this same instance but modified.
|
|
1195
|
+
"""
|
|
1196
|
+
cut = self.get_trim_end(
|
|
1197
|
+
t_variation = t_variation,
|
|
1198
|
+
limit = limit
|
|
1199
|
+
)
|
|
1200
|
+
|
|
1201
|
+
self.start = cut.start
|
|
1202
|
+
self.end = cut.end
|
|
1203
|
+
|
|
1204
|
+
return self
|
|
1205
|
+
|
|
1206
|
+
def get_splits(
|
|
1207
|
+
self,
|
|
1208
|
+
t: Number
|
|
1209
|
+
) -> tuple['NewTimeInterval', 'NewTimeInterval']:
|
|
1210
|
+
"""
|
|
1211
|
+
Split the time interval at the provided `t` time moment
|
|
1212
|
+
and get the 2 new time intervals as a result (as a tuple).
|
|
1213
|
+
|
|
1214
|
+
(!) The `t` value provided will be transformed into a
|
|
1215
|
+
multiple of `1/fps` of this instance, and truncated to fit
|
|
1216
|
+
the `start` of the time interval the new segments will belong
|
|
1217
|
+
to if the `fps` value is set.
|
|
1218
|
+
|
|
1219
|
+
This method will raise an exception if the `t` value
|
|
1220
|
+
provided is a limit value (or above).
|
|
1221
|
+
|
|
1222
|
+
Examples below:
|
|
1223
|
+
- A time interval of `[2, 5)` cut with `t=3` will generate
|
|
1224
|
+
`((2, 3), (3, 5))`.
|
|
1225
|
+
- A time interval of `[2, 5)` cut with `t=4` will generate
|
|
1226
|
+
`((2, 4), (4, 5))`.
|
|
1227
|
+
- A time interval of `[2, 5)` cut with `t>=5` will raise
|
|
1228
|
+
exception.
|
|
1229
|
+
- A time interval of `[2, 5)` cut with `t<=2` will raise
|
|
1230
|
+
exception.
|
|
1231
|
+
"""
|
|
1232
|
+
return self.cutter.split(
|
|
1233
|
+
time_interval = self,
|
|
1234
|
+
# TODO: maybe a 'decorator' to do this '_truncate' (?)
|
|
1235
|
+
t = self._truncate(t)
|
|
1236
|
+
)
|
|
1237
|
+
|
|
1238
|
+
def split(
|
|
1239
|
+
self,
|
|
1240
|
+
t: Number
|
|
1241
|
+
) -> tuple['NewTimeInterval', 'NewTimeInterval']:
|
|
1242
|
+
"""
|
|
1243
|
+
Split the time interval at the provided `t` time moment
|
|
1244
|
+
and get the 2 new time intervals as a result (as a tuple),
|
|
1245
|
+
that will be copies of this instance (with their original
|
|
1246
|
+
`start` and `end` values) but the new ones according to
|
|
1247
|
+
the split result.
|
|
1248
|
+
|
|
1249
|
+
(!) The `t` value provided will be transformed into a
|
|
1250
|
+
multiple of `1/fps` of this instance, and truncated to fit
|
|
1251
|
+
the `start` of the time interval the new segments will belong
|
|
1252
|
+
to if the `fps` value is set.
|
|
1253
|
+
|
|
1254
|
+
This method will raise an exception if the `t` value
|
|
1255
|
+
provided is a limit value (or above).
|
|
1256
|
+
"""
|
|
1257
|
+
splits = self.get_splits(t)
|
|
1258
|
+
|
|
1259
|
+
split_left = self.copy
|
|
1260
|
+
split_left.start = splits[0].start
|
|
1261
|
+
split_left.end = splits[0].end
|
|
1262
|
+
|
|
1263
|
+
split_right = self.copy
|
|
1264
|
+
split_right.start = splits[1].start
|
|
1265
|
+
split_right.end = splits[1].end
|
|
1266
|
+
|
|
1267
|
+
return (
|
|
1268
|
+
split_left, split_right
|
|
1269
|
+
)
|
|
1270
|
+
|
|
1271
|
+
# TODO: Implement 'extend' methods using the 'original'
|
|
1272
|
+
# values
|
|
1273
|
+
|
|
1274
|
+
class TimeIntervalUtils:
|
|
1275
|
+
"""
|
|
1276
|
+
Static class to wrap the utils related to time intervals.
|
|
1277
|
+
"""
|
|
1278
|
+
|
|
1279
|
+
@staticmethod
|
|
1280
|
+
def a_includes_t(
|
|
1281
|
+
t: float,
|
|
1282
|
+
time_interval_a: 'TimeInterval',
|
|
1283
|
+
do_include_end: bool = False
|
|
1284
|
+
) -> bool:
|
|
1285
|
+
"""
|
|
1286
|
+
Check if the `t` time moment provided is included in
|
|
1287
|
+
the `time_interval_a` given. The `time_interval_a.end`
|
|
1288
|
+
is excluded unless the `do_include_end` parameter is
|
|
1289
|
+
set as `True`.
|
|
1290
|
+
|
|
1291
|
+
A time interval is `[start, end)`, thats why the end is
|
|
1292
|
+
excluded by default.
|
|
1293
|
+
"""
|
|
1294
|
+
return (
|
|
1295
|
+
time_interval_a.start <= t <= time_interval_a.end
|
|
1296
|
+
if do_include_end else
|
|
1297
|
+
time_interval_a.start <= t < time_interval_a.end
|
|
1298
|
+
)
|
|
1299
|
+
|
|
1300
|
+
@staticmethod
|
|
1301
|
+
def a_is_adjacent_to_b(
|
|
1302
|
+
time_interval_a: 'TimeInterval',
|
|
1303
|
+
time_interval_b: 'TimeInterval',
|
|
1304
|
+
) -> bool:
|
|
1305
|
+
"""
|
|
1306
|
+
Check if the `time_interval_a` provided and the
|
|
1307
|
+
also given `time_interval_b` are adjacent, which
|
|
1308
|
+
means that the `end` of one interval is also the
|
|
1309
|
+
`start` of the other one.
|
|
1310
|
+
|
|
1311
|
+
(!) Giving the time intervals inverted will
|
|
1312
|
+
provide the same result.
|
|
1313
|
+
|
|
1314
|
+
Examples below:
|
|
1315
|
+
- `a=[2, 5)` and `b=[5, 7)` => `True`
|
|
1316
|
+
- `a=[5, 7)` and `b=[2, 5)` => `True`
|
|
1317
|
+
- `a=[2, 5)` and `b=[3, 4)` => `False`
|
|
1318
|
+
- `a=[2, 5)` and `b=[6, 8)` => `False`
|
|
1319
|
+
"""
|
|
1320
|
+
return (
|
|
1321
|
+
TimeIntervalUtils.a_is_inmediately_before_b(time_interval_a, time_interval_b) or
|
|
1322
|
+
TimeIntervalUtils.a_is_inmediately_after_b(time_interval_a, time_interval_b)
|
|
1323
|
+
)
|
|
1324
|
+
|
|
1325
|
+
@staticmethod
|
|
1326
|
+
def a_is_inmediately_before_b(
|
|
1327
|
+
time_interval_a: 'TimeInterval',
|
|
1328
|
+
time_interval_b: 'TimeInterval',
|
|
1329
|
+
) -> bool:
|
|
1330
|
+
"""
|
|
1331
|
+
Check if the `time_interval_a` provided is inmediately
|
|
1332
|
+
before the also given `time_interval_b`, which means
|
|
1333
|
+
that the `end` of the first one is also the `start` of
|
|
1334
|
+
the second one.
|
|
1335
|
+
|
|
1336
|
+
Examples below:
|
|
1337
|
+
- `a=[2, 5)` and `b=[5, 7)` => `True`
|
|
1338
|
+
- `a=[5, 7)` and `b=[2, 5)` => `False`
|
|
1339
|
+
- `a=[2, 5)` and `b=[3, 4)` => `False`
|
|
1340
|
+
- `a=[2, 5)` and `b=[6, 8)` => `False`
|
|
1341
|
+
"""
|
|
1342
|
+
return time_interval_a.end == time_interval_b.start
|
|
1343
|
+
|
|
1344
|
+
@staticmethod
|
|
1345
|
+
def a_is_inmediately_after_b(
|
|
1346
|
+
time_interval_a: 'TimeInterval',
|
|
1347
|
+
time_interval_b: 'TimeInterval',
|
|
1348
|
+
) -> bool:
|
|
1349
|
+
"""
|
|
1350
|
+
Check if the `time_interval_a` provided is inmediately
|
|
1351
|
+
after the also given `time_interval_b`, which means
|
|
1352
|
+
that the `start` of the first one is also the `end` of
|
|
1353
|
+
the second one.
|
|
1354
|
+
|
|
1355
|
+
Examples below:
|
|
1356
|
+
- `a=[2, 5)` and `b=[5, 7)` => `False`
|
|
1357
|
+
- `a=[5, 7)` and `b=[2, 5)` => `True`
|
|
1358
|
+
- `a=[2, 5)` and `b=[3, 4)` => `False`
|
|
1359
|
+
- `a=[2, 5)` and `b=[6, 8)` => `False`
|
|
1360
|
+
"""
|
|
1361
|
+
return time_interval_a.start == time_interval_b.end
|
|
1362
|
+
|
|
1363
|
+
@staticmethod
|
|
1364
|
+
def a_contains_b(
|
|
1365
|
+
time_interval_a: 'TimeInterval',
|
|
1366
|
+
time_interval_b: 'TimeInterval'
|
|
1367
|
+
) -> bool:
|
|
1368
|
+
"""
|
|
1369
|
+
Check if the `time_interval_a` time interval provided
|
|
1370
|
+
includes the `time_interval_b` or not, which means that
|
|
1371
|
+
the `time_interval_b` is fully contained in the first
|
|
1372
|
+
one.
|
|
1373
|
+
|
|
1374
|
+
Examples below:
|
|
1375
|
+
- `a=[2, 5)` and `b=[3, 4)` => `True`
|
|
1376
|
+
- `a=[2, 5)` and `b=[2, 4)` => `True`
|
|
1377
|
+
- `a=[2, 5)` and `b=[3, 6)` => `False`
|
|
1378
|
+
- `a=[2, 5)` and `b=[6, 8)` => `False`
|
|
1379
|
+
"""
|
|
1380
|
+
return (
|
|
1381
|
+
time_interval_a.start <= time_interval_b.start and
|
|
1382
|
+
time_interval_a.end >= time_interval_b.end
|
|
1383
|
+
)
|
|
1384
|
+
|
|
1385
|
+
@staticmethod
|
|
1386
|
+
def a_is_contained_in_b(
|
|
1387
|
+
time_interval_a: 'TimeInterval',
|
|
1388
|
+
time_interval_b: 'TimeInterval',
|
|
1389
|
+
) -> bool:
|
|
1390
|
+
"""
|
|
1391
|
+
Check if the `time_interval_a` provided is fully
|
|
1392
|
+
contained into the also provided `time_interval_b`.
|
|
1393
|
+
|
|
1394
|
+
Examples below:
|
|
1395
|
+
- `a=[2, 5)` and `b=[1, 6)` => `True`
|
|
1396
|
+
- `a=[2, 5)` and `b=[0, 9)` => `True`
|
|
1397
|
+
- `a=[2, 5)` and `b=[2, 4)` => `False`
|
|
1398
|
+
- `a=[2, 5)` and `b=[4, 8)` => `False`
|
|
1399
|
+
- `a=[2, 5)` and `b=[7, 8)` => `False`
|
|
1400
|
+
"""
|
|
1401
|
+
return TimeIntervalUtils.a_contains_b(
|
|
1402
|
+
time_interval_a = time_interval_b,
|
|
1403
|
+
time_interval_b = time_interval_a
|
|
1404
|
+
)
|
|
1405
|
+
|
|
1406
|
+
@staticmethod
|
|
1407
|
+
def a_intersects_with_b(
|
|
1408
|
+
time_interval_a: 'TimeInterval',
|
|
1409
|
+
time_interval_b: 'TimeInterval',
|
|
1410
|
+
) -> bool:
|
|
1411
|
+
"""
|
|
1412
|
+
Check if the `time_interval_a` and the `time_interval_b`
|
|
1413
|
+
provided has at least a part in common.
|
|
1414
|
+
|
|
1415
|
+
Examples below:
|
|
1416
|
+
- `a=[2, 5)` and `b=[4, 6)` => `True`
|
|
1417
|
+
- `a=[2, 5)` and `b=[1, 3)` => `True`
|
|
1418
|
+
- `a=[2, 5)` and `b=[5, 6)` => `False`
|
|
1419
|
+
- `a=[2, 5)` and `b=[7, 8)` => `False`
|
|
1420
|
+
- `a=[2, 5)` and `b=[1, 2)` => `False`
|
|
1421
|
+
"""
|
|
1422
|
+
return (
|
|
1423
|
+
time_interval_b.start < time_interval_a.end and
|
|
1424
|
+
time_interval_a.start < time_interval_b.end
|
|
1425
|
+
)
|
|
1426
|
+
|
|
1427
|
+
@staticmethod
|
|
1428
|
+
def get_intersection_of_a_and_b(
|
|
1429
|
+
time_interval_a: 'TimeInterval',
|
|
1430
|
+
time_interval_b: 'TimeInterval'
|
|
1431
|
+
) -> Union['TimeInterval', None]:
|
|
1432
|
+
"""
|
|
1433
|
+
Get the time interval that intersects the two time
|
|
1434
|
+
intervals provided, that can be `None` if there is no
|
|
1435
|
+
intersection in between both.
|
|
1436
|
+
"""
|
|
1437
|
+
return (
|
|
1438
|
+
None
|
|
1439
|
+
if not TimeIntervalUtils.a_intersects_with_b(
|
|
1440
|
+
time_interval_a = time_interval_a,
|
|
1441
|
+
time_interval_b = time_interval_b
|
|
1442
|
+
) else
|
|
1443
|
+
TimeInterval(
|
|
1444
|
+
start = max(time_interval_a.start, time_interval_b.start),
|
|
1445
|
+
end = min(time_interval_a.end, time_interval_b.end)
|
|
1446
|
+
)
|
|
1447
|
+
)
|
|
1448
|
+
|
|
1449
|
+
class TimeIntervalCutter:
|
|
1450
|
+
"""
|
|
1451
|
+
Class to wrap the functionality related to cutting
|
|
1452
|
+
time intervals.
|
|
1453
|
+
"""
|
|
1454
|
+
|
|
1455
|
+
"""
|
|
1456
|
+
TODO: Methods I have to implement:
|
|
1457
|
+
trim_end → recorta la parte final
|
|
1458
|
+
trim_start → recorta la parte inicial
|
|
1459
|
+
cut_segment → elimina un tramo [a, b)
|
|
1460
|
+
split_at → divide en dos en un punto dado
|
|
1461
|
+
"""
|
|
1462
|
+
|
|
1463
|
+
@staticmethod
|
|
1464
|
+
# @parameter_to_time_interval('time_interval')
|
|
1465
|
+
@parameter_to_new_time_interval('time_interval')
|
|
1466
|
+
def trim_end_to(
|
|
1467
|
+
time_interval: TimeIntervalType,
|
|
1468
|
+
t: Number
|
|
1469
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
1470
|
+
"""
|
|
1471
|
+
Get a tuple containing the 2 new `TimeInterval` instances
|
|
1472
|
+
generated by trimming the `time_interval` end to the `t`
|
|
1473
|
+
time moment provided. The first tuple is the requested by
|
|
1474
|
+
the user, and the second one is the remaining.
|
|
1475
|
+
|
|
1476
|
+
The `t` time moment provided must be a value between the
|
|
1477
|
+
`start` and `end` of the `time_interval` provided.
|
|
1478
|
+
"""
|
|
1479
|
+
time_interval._validate_t(t)
|
|
1480
|
+
|
|
1481
|
+
return (
|
|
1482
|
+
TimeInterval(
|
|
1483
|
+
start = time_interval.start,
|
|
1484
|
+
end = t
|
|
1485
|
+
),
|
|
1486
|
+
TimeInterval(
|
|
1487
|
+
start = t,
|
|
1488
|
+
end = time_interval.end
|
|
1489
|
+
)
|
|
1490
|
+
)
|
|
1491
|
+
|
|
1492
|
+
@staticmethod
|
|
1493
|
+
# @parameter_to_time_interval('time_interval')
|
|
1494
|
+
@parameter_to_new_time_interval('time_interval')
|
|
1495
|
+
def trim_end(
|
|
1496
|
+
time_interval: TimeIntervalType,
|
|
1497
|
+
t_variation: Number,
|
|
1498
|
+
limit: Union[Number, None] = None,
|
|
1499
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
1500
|
+
"""
|
|
1501
|
+
Get a tuple containing the 2 new `TimeInterval` instances
|
|
1502
|
+
generated by trimming the `time_interval` end the amount
|
|
1503
|
+
of seconds provided as the `t_variation` parameter. The
|
|
1504
|
+
first tuple is the requested by the user, and the second one
|
|
1505
|
+
is the remaining.
|
|
1506
|
+
|
|
1507
|
+
This method will raise an exception if the new `end` value
|
|
1508
|
+
becomes a value under the time interval `start` value or
|
|
1509
|
+
the `limit`, that must be greater than the `start` and lower
|
|
1510
|
+
than the time interval `end` value.
|
|
1511
|
+
|
|
1512
|
+
The `t_variation` must be a positive value, the amount of
|
|
1513
|
+
seconds to be trimmed.
|
|
1514
|
+
"""
|
|
1515
|
+
ParameterValidator.validate_mandatory_positive_number('t_variation', t_variation, do_include_zero = False)
|
|
1516
|
+
ParameterValidator.validate_number_between('limit', limit, time_interval.start, time_interval.end, False, False)
|
|
1517
|
+
|
|
1518
|
+
new_end = time_interval.end - t_variation
|
|
1519
|
+
limit = (
|
|
1520
|
+
time_interval.start
|
|
1521
|
+
if limit is None else
|
|
1522
|
+
limit
|
|
1523
|
+
)
|
|
1524
|
+
|
|
1525
|
+
if new_end <= time_interval.start:
|
|
1526
|
+
raise Exception('The "t_variation" value provided makes the new end value be lower than the "start" value.')
|
|
1527
|
+
|
|
1528
|
+
if new_end <= limit:
|
|
1529
|
+
raise Exception('The "t_variation" value provided makes the new end value be lower than the "limit" value provided.')
|
|
1530
|
+
|
|
1531
|
+
return (
|
|
1532
|
+
TimeInterval(
|
|
1533
|
+
start = time_interval.start,
|
|
1534
|
+
end = new_end
|
|
1535
|
+
),
|
|
1536
|
+
TimeInterval(
|
|
1537
|
+
start = new_end,
|
|
1538
|
+
end = time_interval.end
|
|
1539
|
+
)
|
|
1540
|
+
)
|
|
1541
|
+
|
|
1542
|
+
@staticmethod
|
|
1543
|
+
# @parameter_to_time_interval('time_interval')
|
|
1544
|
+
@parameter_to_new_time_interval('time_interval')
|
|
1545
|
+
def trim_start_to(
|
|
1546
|
+
time_interval: TimeIntervalType,
|
|
1547
|
+
t: Number
|
|
1548
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
1549
|
+
"""
|
|
1550
|
+
Get a tuple containing the 2 new `TimeInterval` instances
|
|
1551
|
+
generated by trimming the `time_interval` start to the `t`
|
|
1552
|
+
time moment provided. The first tuple is the remaining, and
|
|
1553
|
+
the second one is the requested by the user.
|
|
1554
|
+
|
|
1555
|
+
The `t` time moment provided must be a value between the
|
|
1556
|
+
`start` and `end` of the `time_interval` provided.
|
|
1557
|
+
"""
|
|
1558
|
+
time_interval._validate_t(t)
|
|
1559
|
+
|
|
1560
|
+
return (
|
|
1561
|
+
TimeInterval(
|
|
1562
|
+
start = time_interval.start,
|
|
1563
|
+
end = t
|
|
1564
|
+
),
|
|
1565
|
+
TimeInterval(
|
|
1566
|
+
start = t,
|
|
1567
|
+
end = time_interval.end
|
|
1568
|
+
)
|
|
1569
|
+
)
|
|
1570
|
+
|
|
1571
|
+
@staticmethod
|
|
1572
|
+
# @parameter_to_time_interval('time_interval')
|
|
1573
|
+
@parameter_to_new_time_interval('time_interval')
|
|
1574
|
+
def trim_start(
|
|
1575
|
+
time_interval: TimeIntervalType,
|
|
1576
|
+
t_variation: Number,
|
|
1577
|
+
limit: Union[Number, None] = None,
|
|
1578
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
1579
|
+
"""
|
|
1580
|
+
Get a tuple containing the 2 new `TimeInterval` instances
|
|
1581
|
+
generated by trimming the `time_interval` start the amount
|
|
1582
|
+
of seconds provided as the `t_variation` parameter. The
|
|
1583
|
+
first tuple is the remaining, and the second one is the
|
|
1584
|
+
new time interval requested by the user.
|
|
1585
|
+
|
|
1586
|
+
This method will raise an exception if the new `end` value
|
|
1587
|
+
becomes a value under the time interval `start` value or
|
|
1588
|
+
the `limit`, that must be greater than the `start` and lower
|
|
1589
|
+
than the time interval `end` value.
|
|
1590
|
+
|
|
1591
|
+
The `t_variation` must be a positive value, the amount of
|
|
1592
|
+
seconds to be trimmed.
|
|
1593
|
+
"""
|
|
1594
|
+
ParameterValidator.validate_mandatory_positive_number('t_variation', t_variation, do_include_zero = False)
|
|
1595
|
+
ParameterValidator.validate_number_between('limit', limit, time_interval.start, time_interval.end, False, False)
|
|
1596
|
+
|
|
1597
|
+
new_start = time_interval.start + t_variation
|
|
1598
|
+
limit = (
|
|
1599
|
+
time_interval.end
|
|
1600
|
+
if limit is None else
|
|
1601
|
+
limit
|
|
1602
|
+
)
|
|
1603
|
+
|
|
1604
|
+
if new_start >= time_interval.end:
|
|
1605
|
+
raise Exception('The "t_variation" value provided makes the new end value be lower than the "end" value.')
|
|
1606
|
+
|
|
1607
|
+
if new_start >= limit:
|
|
1608
|
+
raise Exception('The "t_variation" value provided makes the new end value be greater than the "limit" value provided.')
|
|
1609
|
+
|
|
1610
|
+
return (
|
|
1611
|
+
TimeInterval(
|
|
1612
|
+
start = time_interval.start,
|
|
1613
|
+
end = new_start
|
|
1614
|
+
),
|
|
1615
|
+
TimeInterval(
|
|
1616
|
+
start = new_start,
|
|
1617
|
+
end = time_interval.end
|
|
1618
|
+
)
|
|
1619
|
+
)
|
|
1620
|
+
|
|
1621
|
+
@staticmethod
|
|
1622
|
+
# @parameter_to_time_interval('time_interval')
|
|
1623
|
+
@parameter_to_new_time_interval('time_interval')
|
|
1624
|
+
def from_to(
|
|
1625
|
+
time_interval: 'TimeInterval',
|
|
1626
|
+
start: Number,
|
|
1627
|
+
end: Number
|
|
1628
|
+
) -> tuple[Union['TimeInterval', None], Union['TimeInterval', None], Union['TimeInterval', None], int]:
|
|
1629
|
+
"""
|
|
1630
|
+
Cut a segment from the given `start` to the also provided
|
|
1631
|
+
`end` time moments of the `time_interval` passed as
|
|
1632
|
+
parameter.
|
|
1633
|
+
|
|
1634
|
+
This method will return a tuple of 3 elements including the
|
|
1635
|
+
segments created by cutting this time interval in the order
|
|
1636
|
+
they were generated, but also having the 4th element always
|
|
1637
|
+
as the index of the one specifically requested by the user.
|
|
1638
|
+
The tuple will include all the segments at the begining and
|
|
1639
|
+
the rest will be None (unless the 4th one, which is the
|
|
1640
|
+
index).
|
|
1641
|
+
|
|
1642
|
+
Examples below:
|
|
1643
|
+
- A time interval of `[2, 5)` cut with `start=3` and `end=4`
|
|
1644
|
+
will generate `((2, 3), (3, 4), (4, 5), 1)`.
|
|
1645
|
+
- A time interval of `[2, 5)` cut with `start=2` and `end=4`
|
|
1646
|
+
will generate `((2, 4), (4, 5), None, 0)`.
|
|
1647
|
+
- A time interval of `[2, 5)` cut with `start=4` and `end=5`
|
|
1648
|
+
will generate `((2, 4), (4, 5), None, 1)`.
|
|
1649
|
+
- A time interval of `[2, 5)` cut with `start=2` and `end=5`
|
|
1650
|
+
will generate `((2, 5), None, None, 0)`.
|
|
1651
|
+
|
|
1652
|
+
As you can see, the result could be the same in different
|
|
1653
|
+
situations, but it's up to you (and the specific method in
|
|
1654
|
+
which you are calling to this one) to choose the tuple you
|
|
1655
|
+
want to return.
|
|
1656
|
+
"""
|
|
1657
|
+
time_interval._validate_t(start, do_include_start = True)
|
|
1658
|
+
time_interval._validate_t(end, do_include_end = True)
|
|
1659
|
+
|
|
1660
|
+
return (
|
|
1661
|
+
# TODO: What about this case, should we raise except (?)
|
|
1662
|
+
(
|
|
1663
|
+
time_interval.copy,
|
|
1664
|
+
None,
|
|
1665
|
+
None,
|
|
1666
|
+
0
|
|
1667
|
+
)
|
|
1668
|
+
if (
|
|
1669
|
+
start == time_interval.start and
|
|
1670
|
+
end == time_interval.end
|
|
1671
|
+
) else
|
|
1672
|
+
(
|
|
1673
|
+
TimeInterval(
|
|
1674
|
+
start = time_interval.start,
|
|
1675
|
+
end = end
|
|
1676
|
+
),
|
|
1677
|
+
TimeInterval(
|
|
1678
|
+
start = end,
|
|
1679
|
+
end = time_interval.end
|
|
1680
|
+
),
|
|
1681
|
+
None,
|
|
1682
|
+
0
|
|
1683
|
+
)
|
|
1684
|
+
if start == time_interval.start else
|
|
1685
|
+
(
|
|
1686
|
+
TimeInterval(
|
|
1687
|
+
start = time_interval.start,
|
|
1688
|
+
end = start
|
|
1689
|
+
),
|
|
1690
|
+
TimeInterval(
|
|
1691
|
+
start = start,
|
|
1692
|
+
end = time_interval.end
|
|
1693
|
+
),
|
|
1694
|
+
None,
|
|
1695
|
+
1
|
|
1696
|
+
)
|
|
1697
|
+
if end == time_interval.end else
|
|
1698
|
+
(
|
|
1699
|
+
TimeInterval(
|
|
1700
|
+
start = time_interval.start,
|
|
1701
|
+
end = start
|
|
1702
|
+
),
|
|
1703
|
+
TimeInterval(
|
|
1704
|
+
start = start,
|
|
1705
|
+
end = end
|
|
1706
|
+
),
|
|
1707
|
+
TimeInterval(
|
|
1708
|
+
start = end,
|
|
1709
|
+
end = time_interval.end
|
|
1710
|
+
),
|
|
1711
|
+
1
|
|
1712
|
+
)
|
|
1713
|
+
)
|
|
1714
|
+
|
|
1715
|
+
@staticmethod
|
|
1716
|
+
# @parameter_to_time_interval('time_interval')
|
|
1717
|
+
@parameter_to_new_time_interval('time_interval')
|
|
1718
|
+
def split(
|
|
1719
|
+
time_interval: 'TimeInterval',
|
|
1720
|
+
t: Number,
|
|
1721
|
+
) -> tuple['TimeInterval', 'TimeInterval']:
|
|
1722
|
+
"""
|
|
1723
|
+
Split the interval at the provided `t` time moment and
|
|
1724
|
+
get the 2 new time intervals as a result (as a tuple).
|
|
1725
|
+
|
|
1726
|
+
This method will raise an exception if the `t` value
|
|
1727
|
+
provided is a limit value (or above).
|
|
1728
|
+
|
|
1729
|
+
Examples below:
|
|
1730
|
+
- A time interval of `[2, 5)` cut with `t=3` will generate
|
|
1731
|
+
`((2, 3), (3, 5))`.
|
|
1732
|
+
- A time interval of `[2, 5)` cut with `t=4` will generate
|
|
1733
|
+
`((2, 4), (4, 5))`.
|
|
1734
|
+
- A time interval of `[2, 5)` cut with `t>=5` will raise
|
|
1735
|
+
exception.
|
|
1736
|
+
- A time interval of `[2, 5)` cut with `t<=2` will raise
|
|
1737
|
+
exception.
|
|
1738
|
+
"""
|
|
1739
|
+
if (
|
|
1740
|
+
t <= time_interval.start or
|
|
1741
|
+
t >= time_interval.end
|
|
1742
|
+
):
|
|
1743
|
+
raise Exception('The "t" value provided is not a valid value as it is a limit (or more than a limit).')
|
|
1744
|
+
|
|
1745
|
+
return (
|
|
1746
|
+
TimeInterval(
|
|
1747
|
+
start = time_interval.start,
|
|
1748
|
+
end = t
|
|
1749
|
+
),
|
|
1750
|
+
TimeInterval(
|
|
1751
|
+
start = t,
|
|
1752
|
+
end = time_interval.end
|
|
1753
|
+
)
|
|
1754
|
+
)
|
|
1755
|
+
|
|
1756
|
+
# TODO: Maybe add some 'extend' functionality
|
|
1757
|
+
|
|
1758
|
+
# # TODO: This method is interesting if we don't want only to
|
|
1759
|
+
# # trim but also to extend, so we can use the limits.
|
|
1760
|
+
# @staticmethod
|
|
1761
|
+
# def trim_end(
|
|
1762
|
+
# time_interval: TimeIntervalType,
|
|
1763
|
+
# t_variation: Number,
|
|
1764
|
+
# lower_limit: Number = 0.0,
|
|
1765
|
+
# upper_limit: Union[Number, None] = None,
|
|
1766
|
+
# do_include_lower_limit: bool = True,
|
|
1767
|
+
# do_include_upper_limit: bool = True
|
|
1768
|
+
# ) -> Union['TimeInterval', 'TimeInterval']:
|
|
1769
|
+
# """
|
|
1770
|
+
# Get a tuple containing the 2 new `TimeInterval` instances
|
|
1771
|
+
# generated by trimming the `time_interval` provided from
|
|
1772
|
+
# the `end` and at the `t` time moment provided. The first
|
|
1773
|
+
# value is the remaining, and the second one is the requested
|
|
1774
|
+
# by the user.
|
|
1775
|
+
|
|
1776
|
+
# This will raise an exception if the new `end` becomes a
|
|
1777
|
+
# value under the `lower_limit` provided, above the
|
|
1778
|
+
# `upper_limit` given (if given) or even below the time
|
|
1779
|
+
# interval `start` value. The limits will be included or not
|
|
1780
|
+
# according to the boolean parameters.
|
|
1781
|
+
# """
|
|
1782
|
+
# new_end = time_interval.end + t_variation
|
|
1783
|
+
|
|
1784
|
+
# if new_end <= time_interval.start:
|
|
1785
|
+
# raise Exception('The "t_variation" value provided makes the new end value be lower than the "start" value.')
|
|
1786
|
+
|
|
1787
|
+
# is_under_lower_limit = (
|
|
1788
|
+
# new_end < lower_limit
|
|
1789
|
+
# if do_include_lower_limit else
|
|
1790
|
+
# new_end <= lower_limit
|
|
1791
|
+
# )
|
|
1792
|
+
|
|
1793
|
+
# is_over_upper_limit = (
|
|
1794
|
+
# upper_limit is None and
|
|
1795
|
+
# (
|
|
1796
|
+
# new_end > upper_limit
|
|
1797
|
+
# if do_include_upper_limit else
|
|
1798
|
+
# new_end >= upper_limit
|
|
1799
|
+
# )
|
|
1800
|
+
# )
|
|
1801
|
+
|
|
1802
|
+
# if (
|
|
1803
|
+
# is_under_lower_limit or
|
|
1804
|
+
# is_over_upper_limit
|
|
1805
|
+
# ):
|
|
1806
|
+
# raise Exception('The "t_variation" value provided makes the new end value be out of the limits.')
|
|
1807
|
+
|
|
1808
|
+
# return (
|
|
1809
|
+
# TimeInterval(
|
|
1810
|
+
# start = time_interval.start,
|
|
1811
|
+
# end = new_end
|
|
1812
|
+
# ),
|
|
1813
|
+
# TimeInterval(
|
|
1814
|
+
# start = new_end,
|
|
1815
|
+
# end = time_interval.end
|
|
1816
|
+
# )
|
|
1817
|
+
# )
|
|
1818
|
+
|