yta-video-frame-time 0.0.12__tar.gz → 0.0.13__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.

Potentially problematic release.


This version of yta-video-frame-time might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yta-video-frame-time
3
- Version: 0.0.12
3
+ Version: 0.0.13
4
4
  Summary: Youtube Autonomous Video Frame Time Module
5
5
  License-File: LICENSE
6
6
  Author: danialcala94
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "yta-video-frame-time"
3
- version = "0.0.12"
3
+ version = "0.0.13"
4
4
  description = "Youtube Autonomous Video Frame Time Module"
5
5
  authors = [
6
6
  {name = "danialcala94",email = "danielalcalavalera@gmail.com"}
@@ -0,0 +1,569 @@
1
+ from yta_validation.parameter import ParameterValidator
2
+ from quicktions import Fraction
3
+ from typing import Union
4
+
5
+
6
+ """
7
+ Tengo un elemento con una duración concreta, por lo que
8
+ el rango es [0, duration):
9
+ - Recortar solo por el principio => 2 segmentos
10
+ - Recortar solo por el final => 2 segmentos
11
+ - Recortar en medio => 3 segmentos
12
+ """
13
+ Number = Union[int, float, Fraction]
14
+ """
15
+ Custom type to represent numbers.
16
+ """
17
+
18
+ """
19
+ TODO: Maybe we can add the possibility of having an
20
+ `fps` value when initializing it to be able to force
21
+ the time interval values to be multiple of `1/fps`.
22
+ But this, if implemented, should be `TimeIntervalFPS`
23
+ or similar, and inheritance from this one but forcing
24
+ the values to be transformed according to that `1/fps`.
25
+ """
26
+ class TimeInterval:
27
+ """
28
+ Class to represent a time interval, which is a tuple
29
+ of time moments representing the time range
30
+ `[start, end)`.
31
+ """
32
+
33
+ @property
34
+ def start_base(
35
+ self
36
+ ) -> float:
37
+ """
38
+ The `start` of the interval but always as 0.
39
+ """
40
+ return 0
41
+
42
+ @property
43
+ def end_base(
44
+ self
45
+ ) -> float:
46
+ """
47
+ The `end` of the interval but adapted to a `start=0`.
48
+ """
49
+ return self.end - self.start
50
+
51
+ @property
52
+ def duration(
53
+ self
54
+ ) -> float:
55
+ """
56
+ The `duration` of the time interval.
57
+ """
58
+ return self.end - self.start
59
+
60
+ @property
61
+ def copy(
62
+ self
63
+ ) -> 'TimeInterval':
64
+ """
65
+ A copy of this instance.
66
+ """
67
+ return TimeInterval(
68
+ start = self.start,
69
+ end = self.end
70
+ )
71
+
72
+ @property
73
+ def as_tuple(
74
+ self
75
+ ) -> tuple[float, float]:
76
+ """
77
+ The time interval but as a `(start, end)` tuple.
78
+ """
79
+ return (self.start, self.end)
80
+
81
+ def __init__(
82
+ self,
83
+ start: Number,
84
+ end: Number,
85
+ ):
86
+ """
87
+ Provide the interval as it actually is, with the `start`
88
+ and `end`. These values will be adjusted to an internal
89
+ interval starting on 0.
90
+
91
+ The `end` value must be greater than the `start` value.
92
+ """
93
+ if start > end:
94
+ raise Exception('The `start` value provided is greater than the `end` value provided.')
95
+
96
+ if start == end:
97
+ raise Exception('The `start` value provided is exactly the `end` value provided.')
98
+
99
+ self.start: float = start
100
+ """
101
+ The original `start` of the time segment.
102
+ """
103
+ self.end: float = end
104
+ """
105
+ The original `end` of the time segment.
106
+ """
107
+
108
+ def _validate_t(
109
+ self,
110
+ t: Number,
111
+ do_include_start: bool = False,
112
+ do_include_end: bool = False
113
+ ) -> None:
114
+ """
115
+ Validate that the provided `t` value is between the `start`
116
+ and the `end` parameters provided, including them or not
117
+ according to the boolean parameters provided.
118
+ """
119
+ ParameterValidator.validate_mandatory_number_between(
120
+ name = 't',
121
+ value = t,
122
+ lower_limit = self.start,
123
+ upper_limit = self.end,
124
+ do_include_lower_limit = do_include_start,
125
+ do_include_upper_limit = do_include_end
126
+ )
127
+
128
+ def _cut(
129
+ self,
130
+ start: Number,
131
+ end: Number
132
+ ) -> tuple[Union['TimeInterval', None], Union['TimeInterval', None], Union['TimeInterval', None]]:
133
+ """
134
+ *For internal use only*
135
+
136
+ Cut a segment with the given `start` and `end` time moments.
137
+
138
+ This method will return a tuple of 3 elements including the
139
+ segments created by cutting this time interval. The tuple
140
+ will include all the segments at the begining and the rest
141
+ will be None.
142
+
143
+ Examples below:
144
+ - A time interval of `[2, 5)` cut with `start=3` and `end=4`
145
+ will generate `((2, 3), (3, 4), (4, 5))`.
146
+ - A time interval of `[2, 5)` cut with `start=2` and `end=4`
147
+ will generate `((2, 4), (4, 5), None)`.
148
+ - A time interval of `[2, 5)` cut with `start=4` and `end=5`
149
+ will generate `((2, 4), (4, 5), None)`.
150
+ - A time interval of `[2, 5)` cut with `start=2` and `end=5`
151
+ will generate `((2, 5), None, None)`.
152
+
153
+ As you can see, the result could be the same in different
154
+ situations, but it's up to you (and the specific method in
155
+ which you are calling to this one) to choose the tuple you
156
+ want to return.
157
+
158
+ (!) This will not modify the original instance.
159
+ """
160
+ self._validate_t(start, do_include_start = True)
161
+ self._validate_t(end, do_include_end = True)
162
+
163
+ return (
164
+ (
165
+ self.copy,
166
+ None,
167
+ None
168
+ )
169
+ if (
170
+ start == self.start and
171
+ end == self.end
172
+ ) else
173
+ (
174
+ TimeInterval(
175
+ start = self.start,
176
+ end = end
177
+ ),
178
+ TimeInterval(
179
+ start = end,
180
+ end = self.end
181
+ ),
182
+ None
183
+ )
184
+ if start == self.start else
185
+ (
186
+ TimeInterval(
187
+ start = self.start,
188
+ end = start
189
+ ),
190
+ TimeInterval(
191
+ start = start,
192
+ end = self.end
193
+ ),
194
+ None
195
+ )
196
+ if end == self.end else
197
+ (
198
+ TimeInterval(
199
+ start = self.start,
200
+ end = start
201
+ ),
202
+ TimeInterval(
203
+ start = start,
204
+ end = end
205
+ ),
206
+ TimeInterval(
207
+ start = end,
208
+ end = self.end
209
+ )
210
+ )
211
+ )
212
+
213
+ def cut_from_start_to(
214
+ self,
215
+ t: Number,
216
+ do_get_cut: bool = False
217
+ ) -> Union['TimeInterval', None]:
218
+ """
219
+ Cut the interval from the start to the `t` value
220
+ provided. The return will be the segment cut if
221
+ `do_get_cut` is False, or the remaining part if
222
+ True.
223
+
224
+ (!) This will not modify the original instance.
225
+ """
226
+ intervals = self._cut(
227
+ start = self.start,
228
+ end = t
229
+ )
230
+
231
+ return (
232
+ intervals[0]
233
+ if (
234
+ len(intervals) == 1 or
235
+ do_get_cut
236
+ ) else
237
+ intervals[1]
238
+ )
239
+
240
+ def cut_to_end_from(
241
+ self,
242
+ t: Number,
243
+ do_get_cut: bool = False
244
+ ) -> Union['TimeInterval', None]:
245
+ """
246
+ Cut the interval from the start to the `t` value
247
+ provided. The return will be the segment cut if
248
+ `do_get_cut` is False, or the remaining part if
249
+ True.
250
+
251
+ (!) This will not modify the original instance.
252
+ """
253
+ intervals = self._cut(
254
+ start = t,
255
+ end = self.end
256
+ )
257
+
258
+ return (
259
+ intervals[0]
260
+ if (
261
+ len(intervals) == 1 or
262
+ not do_get_cut
263
+ ) else
264
+ intervals[1]
265
+ )
266
+
267
+ def cut_from_to(
268
+ self,
269
+ from_t: Number,
270
+ to_t: Number,
271
+ # do_get_cut: bool = False
272
+ ) -> Union['TimeInterval', None]:
273
+ """
274
+ Cut the interval from the `from_t` value provided
275
+ to the `to_t` value given. The return will be the
276
+ segment cut.
277
+
278
+ (!) This will not modify the original instance.
279
+
280
+ TODO: By now we are only getting the cut
281
+ """
282
+ intervals = self._cut(
283
+ start = from_t,
284
+ end = to_t
285
+ )
286
+
287
+ return (
288
+ intervals[0]
289
+ if (
290
+ len(intervals) == 1 or
291
+ (
292
+ len(intervals) == 2 and
293
+ from_t == self.start
294
+ )
295
+ ) else
296
+ intervals[1]
297
+ )
298
+
299
+ def is_t_included(
300
+ self,
301
+ t: float,
302
+ do_include_end: bool = False
303
+ ) -> bool:
304
+ """
305
+ Check if the `t` time moment provided is included in
306
+ this time interval, including the `end` only if the
307
+ `do_include_end` parameter is set as `True`.
308
+ """
309
+ return TimeIntervalUtils.a_includes_t(
310
+ t = t,
311
+ time_interval_a = self,
312
+ do_include_end = do_include_end
313
+ )
314
+
315
+ def is_adjacent_to(
316
+ self,
317
+ time_interval: 'TimeInterval'
318
+ ) -> bool:
319
+ """
320
+ Check if the `time_interval` provided is adjacent
321
+ to this time interval, which means that the `end`
322
+ of one interval is also the `start` of the other
323
+ one.
324
+
325
+ (!) Giving the time intervals inverted will
326
+ provide the same result.
327
+
328
+ Example below:
329
+ - `a=[2, 5)` and `b=[5, 7)` => `True`
330
+ - `a=[5, 7)` and `b=[2, 5)` => `True`
331
+ - `a=[2, 5)` and `b=[3, 4)` => `False`
332
+ - `a=[2, 5)` and `b=[6, 8)` => `False`
333
+ """
334
+ return TimeIntervalUtils.a_is_adjacent_to_b(
335
+ time_interval_a = self,
336
+ time_interval_b = time_interval
337
+ )
338
+
339
+ def do_contains_a(
340
+ self,
341
+ time_interval: 'TimeInterval'
342
+ ) -> bool:
343
+ """
344
+ Check if this time interval includes the `time_interval`
345
+ provided or not, which means that the `time_interval`
346
+ provided is fully contained (included) in this one.
347
+ """
348
+ return TimeIntervalUtils.a_contains_b(
349
+ time_interval_a = self,
350
+ time_interval_b = time_interval
351
+ )
352
+
353
+ def is_contained_in(
354
+ self,
355
+ time_interval: 'TimeInterval'
356
+ ) -> bool:
357
+ """
358
+ Check if this time interval is fully contained in
359
+ the `time_interval` provided, which is a synonim
360
+ of being fully overlapped by that `time_interval`.
361
+ """
362
+ return TimeIntervalUtils.a_is_contained_in_b(
363
+ time_interval_a = self,
364
+ time_interval_b = time_interval
365
+ )
366
+
367
+ def do_intersects_with(
368
+ self,
369
+ time_interval: 'TimeInterval'
370
+ ) -> bool:
371
+ """
372
+ Check if this time interval intersects with the one
373
+ provided as `time_interval`, which means that they
374
+ have at least a part in common.
375
+ """
376
+ return TimeIntervalUtils.a_intersects_with_b(
377
+ time_interval_a = self,
378
+ time_interval_b = time_interval
379
+ )
380
+
381
+ def get_intersection_with_a(
382
+ self,
383
+ time_interval: 'TimeInterval'
384
+ ) -> Union['TimeInterval', None]:
385
+ """
386
+ Get the time interval that intersects this one and the
387
+ one provided as `time_interval`. The result can be `None`
388
+ if there is no intersection in between both.
389
+ """
390
+ return TimeIntervalUtils.get_intersection_of_a_and_b(
391
+ time_interval_a = self,
392
+ time_interval_b = time_interval
393
+ )
394
+
395
+
396
+ class TimeIntervalUtils:
397
+ """
398
+ Static class to wrap the utils related to time intervals.
399
+ """
400
+
401
+ @staticmethod
402
+ def a_includes_t(
403
+ t: float,
404
+ time_interval_a: 'TimeInterval',
405
+ do_include_end: bool = False
406
+ ) -> bool:
407
+ """
408
+ Check if the `t` time moment provided is included in
409
+ the `time_interval_a` given. The `time_interval_a.end`
410
+ is excluded unless the `do_include_end` parameter is
411
+ set as `True`.
412
+
413
+ A time interval is `[start, end)`, thats why the end is
414
+ excluded by default.
415
+ """
416
+ return (
417
+ time_interval_a.start <= t <= time_interval_a.end
418
+ if do_include_end else
419
+ time_interval_a.start <= t < time_interval_a.end
420
+ )
421
+
422
+ @staticmethod
423
+ def a_is_adjacent_to_b(
424
+ time_interval_a: 'TimeInterval',
425
+ time_interval_b: 'TimeInterval',
426
+ ) -> bool:
427
+ """
428
+ Check if the `time_interval_a` provided and the
429
+ also given `time_interval_b` are adjacent, which
430
+ means that the `end` of one interval is also the
431
+ `start` of the other one.
432
+
433
+ (!) Giving the time intervals inverted will
434
+ provide the same result.
435
+
436
+ Examples below:
437
+ - `a=[2, 5)` and `b=[5, 7)` => `True`
438
+ - `a=[5, 7)` and `b=[2, 5)` => `True`
439
+ - `a=[2, 5)` and `b=[3, 4)` => `False`
440
+ - `a=[2, 5)` and `b=[6, 8)` => `False`
441
+ """
442
+ return (
443
+ TimeIntervalUtils.a_is_inmediately_before_b(time_interval_a, time_interval_b) or
444
+ TimeIntervalUtils.a_is_inmediately_after_b(time_interval_a, time_interval_b)
445
+ )
446
+
447
+ @staticmethod
448
+ def a_is_inmediately_before_b(
449
+ time_interval_a: 'TimeInterval',
450
+ time_interval_b: 'TimeInterval',
451
+ ) -> bool:
452
+ """
453
+ Check if the `time_interval_a` provided is inmediately
454
+ before the also given `time_interval_b`, which means
455
+ that the `end` of the first one is also the `start` of
456
+ the second one.
457
+
458
+ Examples below:
459
+ - `a=[2, 5)` and `b=[5, 7)` => `True`
460
+ - `a=[5, 7)` and `b=[2, 5)` => `False`
461
+ - `a=[2, 5)` and `b=[3, 4)` => `False`
462
+ - `a=[2, 5)` and `b=[6, 8)` => `False`
463
+ """
464
+ return time_interval_a.end == time_interval_b.start
465
+
466
+ @staticmethod
467
+ def a_is_inmediately_after_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
+ after the also given `time_interval_b`, which means
474
+ that the `start` of the first one is also the `end` of
475
+ the second one.
476
+
477
+ Examples below:
478
+ - `a=[2, 5)` and `b=[5, 7)` => `False`
479
+ - `a=[5, 7)` and `b=[2, 5)` => `True`
480
+ - `a=[2, 5)` and `b=[3, 4)` => `False`
481
+ - `a=[2, 5)` and `b=[6, 8)` => `False`
482
+ """
483
+ return time_interval_a.start == time_interval_b.end
484
+
485
+ @staticmethod
486
+ def a_contains_b(
487
+ time_interval_a: 'TimeInterval',
488
+ time_interval_b: 'TimeInterval'
489
+ ) -> bool:
490
+ """
491
+ Check if the `time_interval_a` time interval provided
492
+ includes the `time_interval_b` or not, which means that
493
+ the `time_interval_b` is fully contained in the first
494
+ one.
495
+
496
+ Examples below:
497
+ - `a=[2, 5)` and `b=[3, 4)` => `True`
498
+ - `a=[2, 5)` and `b=[2, 4)` => `True`
499
+ - `a=[2, 5)` and `b=[3, 6)` => `False`
500
+ - `a=[2, 5)` and `b=[6, 8)` => `False`
501
+ """
502
+ return (
503
+ time_interval_a.start <= time_interval_b.start and
504
+ time_interval_a.end >= time_interval_b.end
505
+ )
506
+
507
+ @staticmethod
508
+ def a_is_contained_in_b(
509
+ time_interval_a: 'TimeInterval',
510
+ time_interval_b: 'TimeInterval',
511
+ ) -> bool:
512
+ """
513
+ Check if the `time_interval_a` provided is fully
514
+ contained into the also provided `time_interval_b`.
515
+
516
+ Examples below:
517
+ - `a=[2, 5)` and `b=[1, 6)` => `True`
518
+ - `a=[2, 5)` and `b=[0, 9)` => `True`
519
+ - `a=[2, 5)` and `b=[2, 4)` => `False`
520
+ - `a=[2, 5)` and `b=[4, 8)` => `False`
521
+ - `a=[2, 5)` and `b=[7, 8)` => `False`
522
+ """
523
+ return TimeIntervalUtils.a_contains_b(
524
+ time_interval_a = time_interval_b,
525
+ time_interval_b = time_interval_a
526
+ )
527
+
528
+ @staticmethod
529
+ def a_intersects_with_b(
530
+ time_interval_a: 'TimeInterval',
531
+ time_interval_b: 'TimeInterval',
532
+ ) -> bool:
533
+ """
534
+ Check if the `time_interval_a` and the `time_interval_b`
535
+ provided has at least a part in common.
536
+
537
+ Examples below:
538
+ - `a=[2, 5)` and `b=[4, 6)` => `True`
539
+ - `a=[2, 5)` and `b=[1, 3)` => `True`
540
+ - `a=[2, 5)` and `b=[5, 6)` => `False`
541
+ - `a=[2, 5)` and `b=[7, 8)` => `False`
542
+ - `a=[2, 5)` and `b=[1, 2)` => `False`
543
+ """
544
+ return (
545
+ time_interval_b.start < time_interval_a.end and
546
+ time_interval_a.start < time_interval_b.end
547
+ )
548
+
549
+ @staticmethod
550
+ def get_intersection_of_a_and_b(
551
+ time_interval_a: 'TimeInterval',
552
+ time_interval_b: 'TimeInterval'
553
+ ) -> Union['TimeInterval', None]:
554
+ """
555
+ Get the time interval that intersects the two time
556
+ intervals provided, that can be `None` if there is no
557
+ intersection in between both.
558
+ """
559
+ return (
560
+ None
561
+ if not TimeIntervalUtils.a_intersects_with_b(
562
+ time_interval_a = time_interval_a,
563
+ time_interval_b = time_interval_b
564
+ ) else
565
+ TimeInterval(
566
+ start = max(time_interval_a.start, time_interval_b.start),
567
+ end = min(time_interval_a.end, time_interval_b.end)
568
+ )
569
+ )