yta-video-opengl 0.0.12__py3-none-any.whl → 0.0.13__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_opengl/complete/timeline.py +40 -44
- yta_video_opengl/complete/track.py +39 -31
- yta_video_opengl/complete/video_on_track.py +26 -12
- yta_video_opengl/reader/__init__.py +21 -58
- yta_video_opengl/reader/cache.py +65 -43
- yta_video_opengl/t.py +185 -0
- yta_video_opengl/tests.py +4 -2
- yta_video_opengl/utils.py +108 -86
- yta_video_opengl/video.py +85 -12
- yta_video_opengl/writer.py +13 -14
- {yta_video_opengl-0.0.12.dist-info → yta_video_opengl-0.0.13.dist-info}/METADATA +2 -1
- yta_video_opengl-0.0.13.dist-info/RECORD +21 -0
- yta_video_opengl-0.0.12.dist-info/RECORD +0 -20
- {yta_video_opengl-0.0.12.dist-info → yta_video_opengl-0.0.13.dist-info}/LICENSE +0 -0
- {yta_video_opengl-0.0.12.dist-info → yta_video_opengl-0.0.13.dist-info}/WHEEL +0 -0
@@ -4,6 +4,7 @@ that, using ffmpeg, detects the video.
|
|
4
4
|
"""
|
5
5
|
from yta_video_opengl.reader.cache import VideoFrameCache
|
6
6
|
from yta_video_opengl.utils import iterate_stream_frames_demuxing
|
7
|
+
from yta_video_opengl.t import T
|
7
8
|
from yta_validation import PythonValidator
|
8
9
|
from av.video.frame import VideoFrame
|
9
10
|
from av.audio.frame import AudioFrame
|
@@ -11,7 +12,7 @@ from av.packet import Packet
|
|
11
12
|
from av.video.stream import VideoStream
|
12
13
|
from av.audio.stream import AudioStream
|
13
14
|
from av.container.input import InputContainer
|
14
|
-
from
|
15
|
+
from quicktions import Fraction
|
15
16
|
from av import open as av_open
|
16
17
|
from typing import Union
|
17
18
|
from dataclasses import dataclass
|
@@ -58,7 +59,7 @@ class VideoReaderFrame:
|
|
58
59
|
self,
|
59
60
|
# TODO: Add the type, please
|
60
61
|
frame: any,
|
61
|
-
t: float = None,
|
62
|
+
t: Union[int, float, Fraction] = None,
|
62
63
|
pixel_format: str = 'rgb24'
|
63
64
|
):
|
64
65
|
self.value: Union[AudioFrame, VideoFrame] = frame
|
@@ -66,7 +67,7 @@ class VideoReaderFrame:
|
|
66
67
|
The frame content, that can be audio or video
|
67
68
|
frame.
|
68
69
|
"""
|
69
|
-
self.t:
|
70
|
+
self.t: Fraction = Fraction(t)
|
70
71
|
"""
|
71
72
|
The 't' time moment of the frame.
|
72
73
|
"""
|
@@ -139,6 +140,8 @@ class VideoReader:
|
|
139
140
|
"""
|
140
141
|
return self.container.decode(self.video_stream)
|
141
142
|
|
143
|
+
# TODO: Remove this when possible, we are not
|
144
|
+
# using 'next' properties
|
142
145
|
@property
|
143
146
|
def next_frame(
|
144
147
|
self
|
@@ -159,16 +162,6 @@ class VideoReader:
|
|
159
162
|
"""
|
160
163
|
return self.container.decode(self.audio_stream)
|
161
164
|
|
162
|
-
@property
|
163
|
-
def next_audio_frame(
|
164
|
-
self
|
165
|
-
) -> Union[AudioFrame, None]:
|
166
|
-
"""
|
167
|
-
Get the next audio frame (decoded) from the
|
168
|
-
iterator.
|
169
|
-
"""
|
170
|
-
return next(self.audio_frame_iterator)
|
171
|
-
|
172
165
|
@property
|
173
166
|
def packet_iterator(
|
174
167
|
self
|
@@ -179,16 +172,6 @@ class VideoReader:
|
|
179
172
|
"""
|
180
173
|
return self.container.demux(self.video_stream)
|
181
174
|
|
182
|
-
@property
|
183
|
-
def next_packet(
|
184
|
-
self
|
185
|
-
) -> Union[Packet, None]:
|
186
|
-
"""
|
187
|
-
Get the next video packet (not decoded) from
|
188
|
-
the iterator.
|
189
|
-
"""
|
190
|
-
return next(self.packet_iterator)
|
191
|
-
|
192
175
|
@property
|
193
176
|
def audio_packet_iterator(
|
194
177
|
self
|
@@ -199,16 +182,6 @@ class VideoReader:
|
|
199
182
|
"""
|
200
183
|
return self.container.demux(self.audio_stream)
|
201
184
|
|
202
|
-
@property
|
203
|
-
def next_audio_packet(
|
204
|
-
self
|
205
|
-
) -> Union[Packet, None]:
|
206
|
-
"""
|
207
|
-
Get the next audio packet (not decoded) from
|
208
|
-
the iterator.
|
209
|
-
"""
|
210
|
-
return next(self.packet_iterator)
|
211
|
-
|
212
185
|
@property
|
213
186
|
def packet_with_audio_iterator(
|
214
187
|
self
|
@@ -220,18 +193,6 @@ class VideoReader:
|
|
220
193
|
"""
|
221
194
|
return self.container.demux((self.video_stream, self.audio_stream))
|
222
195
|
|
223
|
-
@property
|
224
|
-
def next_packet_with_audio(
|
225
|
-
self
|
226
|
-
) -> Union[Packet, None]:
|
227
|
-
"""
|
228
|
-
Get the next video frames packet (or audio
|
229
|
-
frames packet) from the iterator. Depending
|
230
|
-
on the position, the packet can be video or
|
231
|
-
audio.
|
232
|
-
"""
|
233
|
-
return next(self.packet_with_audio_iterator)
|
234
|
-
|
235
196
|
@property
|
236
197
|
def has_video(
|
237
198
|
self
|
@@ -312,8 +273,6 @@ class VideoReader:
|
|
312
273
|
The fps of the video.
|
313
274
|
"""
|
314
275
|
return (
|
315
|
-
# They return it as a Fraction but we usually
|
316
|
-
# use it as float or even int
|
317
276
|
self.video_stream.average_rate
|
318
277
|
if self.has_video else
|
319
278
|
None
|
@@ -515,6 +474,7 @@ class VideoReader:
|
|
515
474
|
)
|
516
475
|
# TODO: Should this be 'AUTO' (?)
|
517
476
|
self.video_stream.thread_type = 'AUTO'
|
477
|
+
|
518
478
|
self.audio_stream = (
|
519
479
|
self.container.streams.audio[0]
|
520
480
|
if self.container.streams.audio else
|
@@ -534,7 +494,7 @@ class VideoReader:
|
|
534
494
|
|
535
495
|
def seek(
|
536
496
|
self,
|
537
|
-
pts,
|
497
|
+
pts: int,
|
538
498
|
stream = None
|
539
499
|
) -> 'VideoReader':
|
540
500
|
"""
|
@@ -567,7 +527,8 @@ class VideoReader:
|
|
567
527
|
for frame in self.frame_iterator:
|
568
528
|
yield VideoReaderFrame(
|
569
529
|
frame = frame,
|
570
|
-
|
530
|
+
# TODO: Maybe use util to transform it (?)
|
531
|
+
t = frame.pts * self.time_base,
|
571
532
|
pixel_format = self.pixel_format
|
572
533
|
)
|
573
534
|
|
@@ -672,7 +633,7 @@ class VideoReader:
|
|
672
633
|
|
673
634
|
def get_frame_from_t(
|
674
635
|
self,
|
675
|
-
t: float
|
636
|
+
t: Union[int, float, Fraction]
|
676
637
|
) -> 'VideoFrame':
|
677
638
|
"""
|
678
639
|
Get the video frame with the given 't' time
|
@@ -692,7 +653,7 @@ class VideoReader:
|
|
692
653
|
|
693
654
|
def get_audio_frame_from_t(
|
694
655
|
self,
|
695
|
-
t: float
|
656
|
+
t: Union[int, float, Fraction]
|
696
657
|
) -> 'AudioFrame':
|
697
658
|
"""
|
698
659
|
Get the audio frame with the given 't' time
|
@@ -702,7 +663,7 @@ class VideoReader:
|
|
702
663
|
|
703
664
|
def get_audio_frames_from_t(
|
704
665
|
self,
|
705
|
-
t: float
|
666
|
+
t: Union[int, float, Fraction]
|
706
667
|
):
|
707
668
|
"""
|
708
669
|
Get the sequence of audio frames for the
|
@@ -715,14 +676,14 @@ class VideoReader:
|
|
715
676
|
(remember that a video frame is associated
|
716
677
|
with more than 1 audio frame).
|
717
678
|
"""
|
718
|
-
|
719
|
-
for frame in self.audio_cache.get_frames(t, t
|
679
|
+
t: T = T.from_fps(t, self.fps)
|
680
|
+
for frame in self.audio_cache.get_frames(t.truncated, t.next(1).truncated):
|
720
681
|
yield frame
|
721
682
|
|
722
683
|
def get_frames(
|
723
684
|
self,
|
724
|
-
start: float = 0.0,
|
725
|
-
end: Union[float, None] = None
|
685
|
+
start: Union[int, float, Fraction] = 0.0,
|
686
|
+
end: Union[int, float, Fraction, None] = None
|
726
687
|
):
|
727
688
|
"""
|
728
689
|
Iterator to get the video frames in between
|
@@ -733,8 +694,8 @@ class VideoReader:
|
|
733
694
|
|
734
695
|
def get_audio_frames(
|
735
696
|
self,
|
736
|
-
start: float = 0.0,
|
737
|
-
end: Union[float, None] = None
|
697
|
+
start: Union[int, float, Fraction] = 0.0,
|
698
|
+
end: Union[int, float, Fraction, None] = None
|
738
699
|
):
|
739
700
|
"""
|
740
701
|
Iterator to get the audio frames in between
|
@@ -752,6 +713,8 @@ class VideoReader:
|
|
752
713
|
self.container.close()
|
753
714
|
|
754
715
|
|
716
|
+
# TODO: I think I'm not using this...
|
717
|
+
# Remove it please
|
755
718
|
def audio_ts_for_video_t(
|
756
719
|
t: float,
|
757
720
|
video_fps: float,
|
yta_video_opengl/reader/cache.py
CHANGED
@@ -15,9 +15,14 @@ frame we are requesting in the moment, keeping in
|
|
15
15
|
memory all those frames to be handled fast. It
|
16
16
|
will remove the old frames if needed to use only
|
17
17
|
the 'size' we set when creating it.
|
18
|
+
|
19
|
+
A stream can have 'fps = 60' but use another
|
20
|
+
different time base that make the pts values go 0,
|
21
|
+
256, 512... for example. The 'time_base' is the
|
22
|
+
only accurate way to obtain the pts.
|
18
23
|
"""
|
19
24
|
from yta_video_opengl.utils import t_to_pts, pts_to_t, pts_to_index, index_to_pts
|
20
|
-
from
|
25
|
+
from yta_video_opengl.t import T
|
21
26
|
from av.container import InputContainer
|
22
27
|
from av.video.stream import VideoStream
|
23
28
|
from av.audio.stream import AudioStream
|
@@ -25,14 +30,19 @@ from av.video.frame import VideoFrame
|
|
25
30
|
from av.audio.frame import AudioFrame
|
26
31
|
from yta_validation.parameter import ParameterValidator
|
27
32
|
from yta_validation import PythonValidator
|
28
|
-
from
|
33
|
+
from quicktions import Fraction
|
29
34
|
from collections import OrderedDict
|
30
35
|
from typing import Union
|
31
36
|
|
32
37
|
import numpy as np
|
38
|
+
import av
|
33
39
|
import math
|
34
40
|
|
35
41
|
|
42
|
+
# TODO: This is not actually a Video
|
43
|
+
# cache, is a FrameCache because we
|
44
|
+
# create one for video but another
|
45
|
+
# one for audio. Rename it please.
|
36
46
|
class VideoFrameCache:
|
37
47
|
"""
|
38
48
|
Class to manage the frames cache of a video
|
@@ -108,6 +118,7 @@ class VideoFrameCache:
|
|
108
118
|
# use the amount of frames of the biggest
|
109
119
|
# interval of frames that belongs to a key
|
110
120
|
# frame, or a value by default
|
121
|
+
# TODO: Careful if this is too big
|
111
122
|
fps = (
|
112
123
|
float(self.stream.average_rate)
|
113
124
|
if PythonValidator.is_instance_of(self.stream, VideoStream) else
|
@@ -116,7 +127,7 @@ class VideoFrameCache:
|
|
116
127
|
# Intervals, but in number of frames
|
117
128
|
intervals = np.diff(
|
118
129
|
# Intervals of time between keyframes
|
119
|
-
np.array(self.key_frames_pts) * self.
|
130
|
+
np.array(self.key_frames_pts) * self.time_base
|
120
131
|
) * fps
|
121
132
|
|
122
133
|
self.size = (
|
@@ -131,7 +142,7 @@ class VideoFrameCache:
|
|
131
142
|
|
132
143
|
self.container.seek(0)
|
133
144
|
|
134
|
-
def
|
145
|
+
def _get_nearest_keyframe_pts(
|
135
146
|
self,
|
136
147
|
pts: int
|
137
148
|
):
|
@@ -157,7 +168,6 @@ class VideoFrameCache:
|
|
157
168
|
the cache if full.
|
158
169
|
"""
|
159
170
|
if frame.pts not in self.cache:
|
160
|
-
# TODO: The 'format' must be dynamic
|
161
171
|
self.cache[frame.pts] = frame
|
162
172
|
|
163
173
|
# Clean cache if full
|
@@ -185,10 +195,19 @@ class VideoFrameCache:
|
|
185
195
|
return self.cache[pts]
|
186
196
|
|
187
197
|
# Look for the most near key frame
|
188
|
-
key_frame_pts = self.
|
198
|
+
key_frame_pts = self._get_nearest_keyframe_pts(pts)
|
189
199
|
|
190
200
|
# Go to the key frame that includes it
|
191
|
-
|
201
|
+
# but I read that it is recommended to
|
202
|
+
# read ~100ms before the pts we want to
|
203
|
+
# actually read so we obtain the frames
|
204
|
+
# clean (this is important in audio)
|
205
|
+
# TODO: This code is repeated, refactor
|
206
|
+
pts_pad = int(0.1 / self.time_base)
|
207
|
+
self.container.seek(
|
208
|
+
offset = max(0, key_frame_pts - pts_pad),
|
209
|
+
stream = self.stream
|
210
|
+
)
|
192
211
|
|
193
212
|
decoded = None
|
194
213
|
for frame in self.container.decode(self.stream):
|
@@ -199,6 +218,15 @@ class VideoFrameCache:
|
|
199
218
|
# Store in cache if needed
|
200
219
|
self._store_frame_in_cache(frame)
|
201
220
|
|
221
|
+
"""
|
222
|
+
The 'frame.pts * frame.time_base' will give
|
223
|
+
us the index of the frame, and actually the
|
224
|
+
'pts' que are looking for seems to be the
|
225
|
+
index and not a pts.
|
226
|
+
|
227
|
+
TODO: Review all this in all the logic
|
228
|
+
please.
|
229
|
+
"""
|
202
230
|
if frame.pts >= pts:
|
203
231
|
decoded = self.cache[frame.pts]
|
204
232
|
break
|
@@ -207,6 +235,7 @@ class VideoFrameCache:
|
|
207
235
|
# frames to be able to decode...
|
208
236
|
return decoded
|
209
237
|
|
238
|
+
# TODO: I'm not using this method...
|
210
239
|
def get_frame(
|
211
240
|
self,
|
212
241
|
index: int
|
@@ -226,24 +255,18 @@ class VideoFrameCache:
|
|
226
255
|
|
227
256
|
def get_frame_from_t(
|
228
257
|
self,
|
229
|
-
t: float
|
258
|
+
t: Union[int, float, Fraction]
|
230
259
|
) -> Union[VideoFrame, AudioFrame]:
|
231
260
|
"""
|
232
261
|
Get the frame with the given 't' time moment
|
233
262
|
from the cache.
|
234
263
|
"""
|
235
|
-
|
236
|
-
|
237
|
-
return (
|
238
|
-
self.cache[pts]
|
239
|
-
if pts in self.cache else
|
240
|
-
self.get_frame_from_pts(pts)
|
241
|
-
)
|
264
|
+
return self.get_frame_from_pts(T(t, self.time_base).truncated_pts)
|
242
265
|
|
243
266
|
def get_frames(
|
244
267
|
self,
|
245
|
-
start: float = 0,
|
246
|
-
end: Union[float, None] = None
|
268
|
+
start: Union[int, float, Fraction] = 0,
|
269
|
+
end: Union[int, float, Fraction, None] = None
|
247
270
|
):
|
248
271
|
"""
|
249
272
|
Get all the frames in the range between
|
@@ -273,8 +296,7 @@ class VideoFrameCache:
|
|
273
296
|
"""
|
274
297
|
|
275
298
|
# The 'duration' is on pts ticks
|
276
|
-
duration = float(self.stream.duration * self.
|
277
|
-
print(f'duration of the whole stream: {str(duration)}s, asking for [{str(start)}, {str(end)})')
|
299
|
+
duration = float(self.stream.duration * self.time_base)
|
278
300
|
# TODO: I think it would be better to
|
279
301
|
# receive and work with pts instead of
|
280
302
|
# 't' time moments...
|
@@ -292,16 +314,25 @@ class VideoFrameCache:
|
|
292
314
|
|
293
315
|
# If not all, we ignore the cache because we
|
294
316
|
# need to decode and they are all consecutive
|
295
|
-
start =
|
317
|
+
start = T(start, self.time_base).truncated_pts
|
296
318
|
end = (
|
297
|
-
|
319
|
+
T(end, self.time_base).truncated_pts
|
298
320
|
if end is not None else
|
299
321
|
None
|
300
322
|
)
|
301
|
-
key_frame_pts = self.
|
323
|
+
key_frame_pts = self._get_nearest_keyframe_pts(start)
|
302
324
|
|
303
|
-
# Go to the
|
304
|
-
|
325
|
+
# Go to the key frame that includes it
|
326
|
+
# but I read that it is recommended to
|
327
|
+
# read ~100ms before the pts we want to
|
328
|
+
# actually read so we obtain the frames
|
329
|
+
# clean (this is important in audio)
|
330
|
+
# TODO: This code is repeated, refactor
|
331
|
+
pts_pad = int(0.1 / self.time_base)
|
332
|
+
self.container.seek(
|
333
|
+
offset = max(0, key_frame_pts - pts_pad),
|
334
|
+
stream = self.stream
|
335
|
+
)
|
305
336
|
|
306
337
|
for packet in self.container.demux(self.stream):
|
307
338
|
for frame in packet.decode():
|
@@ -311,11 +342,9 @@ class VideoFrameCache:
|
|
311
342
|
# We store all the frames in cache
|
312
343
|
self._store_frame_in_cache(frame)
|
313
344
|
|
314
|
-
print(frame)
|
315
345
|
frame_end_pts = frame.pts + int(frame.samples * (1 / self.stream.sample_rate) / self.time_base)
|
316
346
|
#frame_end_pts = frame.pts + int(frame.samples)
|
317
347
|
#frame_end_pts = frame.pts + int(frame.samples / (self.stream.sample_rate * self.time_base))
|
318
|
-
print(f' Frame from [{str(frame.pts)}, {str(frame_end_pts)}] and looking for [{str(start)}, {str(end)}]')
|
319
348
|
|
320
349
|
# For the next comments imagine we are looking
|
321
350
|
# for the [1.0, 2.0) audio time range
|
@@ -354,7 +383,7 @@ class VideoFrameCache:
|
|
354
383
|
# From 0.5 to 2.5 => take 1.0 to 2.0
|
355
384
|
end
|
356
385
|
)
|
357
|
-
print('A part at the end is included.')
|
386
|
+
#print('A part at the end is included.')
|
358
387
|
# TODO: I'm using too much 'pts_to_t'
|
359
388
|
frame = trim_audio_frame_pts(
|
360
389
|
frame = frame,
|
@@ -374,7 +403,7 @@ class VideoFrameCache:
|
|
374
403
|
end
|
375
404
|
)
|
376
405
|
# A part at the begining is included
|
377
|
-
print('A part at the begining is included.')
|
406
|
+
#print('A part at the begining is included.')
|
378
407
|
# TODO: I'm using too much 'pts_to_t'
|
379
408
|
frame = trim_audio_frame_pts(
|
380
409
|
frame = frame,
|
@@ -404,14 +433,7 @@ class VideoFrameCache:
|
|
404
433
|
return self
|
405
434
|
|
406
435
|
|
407
|
-
|
408
|
-
import av
|
409
|
-
import numpy as np
|
410
|
-
|
411
|
-
import av
|
412
|
-
|
413
|
-
|
414
|
-
|
436
|
+
# TODO: Move this to a utils when refactored
|
415
437
|
def trim_audio_frame_pts(
|
416
438
|
frame: av.AudioFrame,
|
417
439
|
start_pts: int,
|
@@ -444,12 +466,12 @@ def trim_audio_frame_pts(
|
|
444
466
|
start_idx = int(cut_start_time * sr)
|
445
467
|
end_idx = int(cut_end_time * sr)
|
446
468
|
|
447
|
-
print(
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
)
|
469
|
+
# print(
|
470
|
+
# f"cutting [{frame.pts}, {frame_end_pts}] "
|
471
|
+
# f"to [{cut_start_pts}, {cut_end_pts}] "
|
472
|
+
# f"({start_idx}:{end_idx} / {frame.samples})"
|
473
|
+
# #f"({start_idx}:{end_idx} / {n_samples})"
|
474
|
+
# )
|
453
475
|
|
454
476
|
cut_samples = samples[:, start_idx:end_idx]
|
455
477
|
|
@@ -492,7 +514,7 @@ def trim_audio_frame_t(
|
|
492
514
|
start_idx = int((cut_start - frame_start) * sr)
|
493
515
|
end_idx = int((cut_end - frame_start) * sr)
|
494
516
|
|
495
|
-
print(f'cutting [{str(frame_start)}, {str(frame_end)}] to [{str(float(start_time))}, {str(float(end_time))}] from {str(start_idx)} to {str(end_idx)} of {str(int((frame_end - frame_start) * sr))}')
|
517
|
+
# print(f'cutting [{str(frame_start)}, {str(frame_end)}] to [{str(float(start_time))}, {str(float(end_time))}] from {str(start_idx)} to {str(end_idx)} of {str(int((frame_end - frame_start) * sr))}')
|
496
518
|
cut_samples = samples[:, start_idx:end_idx]
|
497
519
|
|
498
520
|
# crear nuevo AudioFrame
|
yta_video_opengl/t.py
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
from yta_validation.parameter import ParameterValidator
|
2
|
+
from yta_validation import PythonValidator
|
3
|
+
from yta_validation.number import NumberValidator
|
4
|
+
from quicktions import Fraction
|
5
|
+
from typing import Union
|
6
|
+
|
7
|
+
|
8
|
+
class T:
|
9
|
+
"""
|
10
|
+
Class to simplify the way we work with a
|
11
|
+
't' time moment but using the fractions
|
12
|
+
library to be precise and avoid any issue
|
13
|
+
related with commas.
|
14
|
+
|
15
|
+
This class must be used when trying to
|
16
|
+
apply a specific 't' time moment for a
|
17
|
+
video or audio frame, using the fps or
|
18
|
+
sample rate as time_base to be precise.
|
19
|
+
"""
|
20
|
+
|
21
|
+
@property
|
22
|
+
def truncated(
|
23
|
+
self
|
24
|
+
) -> Fraction:
|
25
|
+
"""
|
26
|
+
The 't' but as a Fraction that is multiple
|
27
|
+
of the given 'time_base' and truncated.
|
28
|
+
"""
|
29
|
+
return round_t(self._t, self.time_base)
|
30
|
+
|
31
|
+
@property
|
32
|
+
def rounded(
|
33
|
+
self
|
34
|
+
) -> Fraction:
|
35
|
+
"""
|
36
|
+
The 't' but as a Fraction that is multiple
|
37
|
+
of the given 'time_base' and rounded (the
|
38
|
+
value could be the same as truncated if it
|
39
|
+
is closer to the previou value).
|
40
|
+
"""
|
41
|
+
return round_t(self._t, self.time_base, do_truncate = False)
|
42
|
+
|
43
|
+
@property
|
44
|
+
def truncated_pts(
|
45
|
+
self
|
46
|
+
) -> int:
|
47
|
+
"""
|
48
|
+
The 'truncated' value but as a pts, which
|
49
|
+
is the int value to be set in audio and
|
50
|
+
video frames in the pyav library to be
|
51
|
+
displayed in that moment.
|
52
|
+
"""
|
53
|
+
return int(self.truncated / self.time_base)
|
54
|
+
|
55
|
+
@property
|
56
|
+
def rounded_pts(
|
57
|
+
self
|
58
|
+
) -> int:
|
59
|
+
"""
|
60
|
+
The 'rounded' value but as a pts, which
|
61
|
+
is the int value to be set in audio and
|
62
|
+
video frames in the pyav library to be
|
63
|
+
displayed in that moment.
|
64
|
+
"""
|
65
|
+
return int(self.rounded / self.time_base)
|
66
|
+
|
67
|
+
def __init__(
|
68
|
+
self,
|
69
|
+
t: Union[int, float, Fraction],
|
70
|
+
time_base: Fraction
|
71
|
+
):
|
72
|
+
ParameterValidator.validate_mandatory_instance_of('t', t, [int, float, 'Fraction'])
|
73
|
+
ParameterValidator.validate_mandatory_instance_of('time_base', time_base, 'Fraction')
|
74
|
+
|
75
|
+
self._t: Union[int, float, Fraction] = t
|
76
|
+
"""
|
77
|
+
The 't' time moment as it was passed as
|
78
|
+
parameter.
|
79
|
+
"""
|
80
|
+
self.time_base: Fraction = time_base
|
81
|
+
"""
|
82
|
+
The time_base that will used to round the
|
83
|
+
values to be multiples of it.
|
84
|
+
"""
|
85
|
+
|
86
|
+
def next(
|
87
|
+
self,
|
88
|
+
n: int = 1
|
89
|
+
) -> 'T':
|
90
|
+
"""
|
91
|
+
Get the value that is 'n' times ahead of
|
92
|
+
the 'truncated' property of this instance.
|
93
|
+
|
94
|
+
Useful when you need the next value for a
|
95
|
+
range in an iteration or similar.
|
96
|
+
"""
|
97
|
+
return T(self.truncated + n * self.time_base, self.time_base)
|
98
|
+
|
99
|
+
# TODO: Maybe its better to make the '__init__'
|
100
|
+
# receive the fps and create the 'from_time_base'
|
101
|
+
# because I think we will provide the fps or the
|
102
|
+
# sample rate more often
|
103
|
+
@staticmethod
|
104
|
+
def from_fps(
|
105
|
+
t: Union[int, float, Fraction],
|
106
|
+
fps: Union[int, float, Fraction]
|
107
|
+
):
|
108
|
+
"""
|
109
|
+
Get the instance but providing the 'fps'
|
110
|
+
(or sample rate) value directly.
|
111
|
+
"""
|
112
|
+
return T(t, fps_to_time_base(fps))
|
113
|
+
|
114
|
+
def get_ts(
|
115
|
+
start: Union[int, float, Fraction],
|
116
|
+
end: Union[int, float, Fraction],
|
117
|
+
fps: Fraction
|
118
|
+
) -> list[Fraction]:
|
119
|
+
"""
|
120
|
+
Get all the 't' time moments between the given
|
121
|
+
'start' and the given 'end', using the provided
|
122
|
+
'time_base' for precision.
|
123
|
+
|
124
|
+
The 'end' is not included, we return a range
|
125
|
+
[start, end) because the last frame is the
|
126
|
+
start of another time range.
|
127
|
+
"""
|
128
|
+
start = T.from_fps(start, fps).truncated
|
129
|
+
end = T.from_fps(end, fps).truncated
|
130
|
+
|
131
|
+
time_base = fps_to_time_base(fps)
|
132
|
+
return [
|
133
|
+
start + i * time_base
|
134
|
+
for i in range((end - start) // time_base)
|
135
|
+
]
|
136
|
+
|
137
|
+
def round_t(
|
138
|
+
t: Union[int, float, Fraction],
|
139
|
+
time_base = Fraction(1, 60),
|
140
|
+
do_truncate: bool = True
|
141
|
+
):
|
142
|
+
"""
|
143
|
+
Round the given 't' time moment to the most
|
144
|
+
near multiple of the given 'time_base' (or
|
145
|
+
the previous one if 'do_truncate' is True)
|
146
|
+
using fractions module to be precise.
|
147
|
+
|
148
|
+
This method is very useful to truncate 't'
|
149
|
+
time moments in order to get the frames or
|
150
|
+
samples for the specific and exact time
|
151
|
+
moments according to their fps or sample
|
152
|
+
rate (that should be passed as the
|
153
|
+
'time_base' parameter).
|
154
|
+
|
155
|
+
Examples below, with `time_base = 1/5`:
|
156
|
+
- `t = 0.25` => `0.2` (truncated or rounded)
|
157
|
+
- `t = 0.35` => `0.2` (truncated)
|
158
|
+
- `t = 0.45` => `0.4` (truncated or rounded)
|
159
|
+
- `t = 0.55` => `0.6` (rounded)
|
160
|
+
"""
|
161
|
+
t = Fraction(t).limit_denominator()
|
162
|
+
steps = t / time_base
|
163
|
+
|
164
|
+
snapped_steps = (
|
165
|
+
steps.numerator // steps.denominator
|
166
|
+
if do_truncate else
|
167
|
+
round(steps) # round(float(steps))
|
168
|
+
)
|
169
|
+
|
170
|
+
return snapped_steps * time_base
|
171
|
+
|
172
|
+
def fps_to_time_base(
|
173
|
+
fps: Union[int, float, Fraction]
|
174
|
+
) -> Fraction:
|
175
|
+
"""
|
176
|
+
Get the pyav time base from the given
|
177
|
+
'fps'.
|
178
|
+
"""
|
179
|
+
return (
|
180
|
+
Fraction(1, fps)
|
181
|
+
if NumberValidator.is_int(fps) else
|
182
|
+
Fraction(1, 1) / fps
|
183
|
+
if PythonValidator.is_instance_of(fps, 'Fraction') else
|
184
|
+
Fraction(1, 1) / Fraction.from_float(fps).limit_denominator(1000000) # if float
|
185
|
+
)
|
yta_video_opengl/tests.py
CHANGED
@@ -586,10 +586,12 @@ def video_modified_stored():
|
|
586
586
|
|
587
587
|
video = Video(VIDEO_PATH, 0.25, 0.75)
|
588
588
|
timeline = Timeline()
|
589
|
-
timeline.add_video(Video(VIDEO_PATH, 0.25, 0
|
589
|
+
timeline.add_video(Video(VIDEO_PATH, 0.25, 1.0), 0.5)
|
590
590
|
# This is successfully raising an exception
|
591
591
|
#timeline.add_video(Video(VIDEO_PATH, 0.25, 0.75), 0.6)
|
592
|
-
timeline.add_video(Video(VIDEO_PATH, 0.25, 0.75), 1.
|
592
|
+
timeline.add_video(Video(VIDEO_PATH, 0.25, 0.75), 1.75)
|
593
|
+
timeline.add_video(Video('C:/Users/dania/Downloads/Y2meta.app-TOP 12 SIMPLE LIQUID TRANSITION _ GREEN SCREEN TRANSITION PACK-(1080p60).mp4', 4.0, 5.0), 3)
|
594
|
+
# timeline.add_video(Video('C:/Users/dania/Downloads/Y2meta.app-10 Smooth Transitions Green Screen Template For Kinemaster, Alight Motion, Filmora, premiere pro-(1080p).mp4', 2.25, 3.0), 3)
|
593
595
|
timeline.render(OUTPUT_PATH)
|
594
596
|
|
595
597
|
return
|