yta-video-opengl 0.0.13__py3-none-any.whl → 0.0.14__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 +1 -0
- yta_video_opengl/complete/track.py +1 -1
- yta_video_opengl/complete/video_on_track.py +1 -4
- yta_video_opengl/reader/__init__.py +7 -25
- yta_video_opengl/reader/cache.py +225 -242
- yta_video_opengl/t.py +55 -7
- yta_video_opengl/video.py +8 -3
- {yta_video_opengl-0.0.13.dist-info → yta_video_opengl-0.0.14.dist-info}/METADATA +1 -1
- {yta_video_opengl-0.0.13.dist-info → yta_video_opengl-0.0.14.dist-info}/RECORD +11 -11
- {yta_video_opengl-0.0.13.dist-info → yta_video_opengl-0.0.14.dist-info}/LICENSE +0 -0
- {yta_video_opengl-0.0.13.dist-info → yta_video_opengl-0.0.14.dist-info}/WHEEL +0 -0
@@ -94,7 +94,7 @@ class _Part:
|
|
94
94
|
# TODO: By now I'm raising exception to check if
|
95
95
|
# this happens or not because I think it would
|
96
96
|
# be malfunctioning
|
97
|
-
raise Exception(f'Video is returning None frame at t={str(t)}.')
|
97
|
+
raise Exception(f'Video is returning None video frame at t={str(t)}.')
|
98
98
|
|
99
99
|
return frame
|
100
100
|
|
@@ -150,10 +150,7 @@ class VideoOnTrack:
|
|
150
150
|
)
|
151
151
|
|
152
152
|
for frame in frames:
|
153
|
-
|
154
|
-
# src\yta_video_opengl\reader\cache.py
|
155
|
-
# get_frames method... maybe remove it (?)
|
156
|
-
yield frame[0]
|
153
|
+
yield frame
|
157
154
|
|
158
155
|
# # TODO: This was a simple return before
|
159
156
|
# return (
|
@@ -620,17 +620,6 @@ class VideoReader:
|
|
620
620
|
):
|
621
621
|
yield frame
|
622
622
|
|
623
|
-
# TODO: Will we use this (?)
|
624
|
-
def get_frame(
|
625
|
-
self,
|
626
|
-
index: int
|
627
|
-
) -> 'VideoFrame':
|
628
|
-
"""
|
629
|
-
Get the video frame with the given 'index',
|
630
|
-
using the video cache system.
|
631
|
-
"""
|
632
|
-
return self.video_cache.get_frame(index)
|
633
|
-
|
634
623
|
def get_frame_from_t(
|
635
624
|
self,
|
636
625
|
t: Union[int, float, Fraction]
|
@@ -639,18 +628,8 @@ class VideoReader:
|
|
639
628
|
Get the video frame with the given 't' time
|
640
629
|
moment, using the video cache system.
|
641
630
|
"""
|
642
|
-
return self.video_cache.
|
631
|
+
return self.video_cache.get_video_frame(t)
|
643
632
|
|
644
|
-
def get_audio_frame(
|
645
|
-
self,
|
646
|
-
index: int
|
647
|
-
) -> 'AudioFrame':
|
648
|
-
"""
|
649
|
-
Get the audio frame with the given 'index',
|
650
|
-
using the audio cache system.
|
651
|
-
"""
|
652
|
-
return self.audio_cache.get_frame(index)
|
653
|
-
|
654
633
|
def get_audio_frame_from_t(
|
655
634
|
self,
|
656
635
|
t: Union[int, float, Fraction]
|
@@ -659,7 +638,7 @@ class VideoReader:
|
|
659
638
|
Get the audio frame with the given 't' time
|
660
639
|
moment, using the audio cache system.
|
661
640
|
"""
|
662
|
-
return self.audio_cache.
|
641
|
+
return self.audio_cache.get_audio_frame_from_t(t)
|
663
642
|
|
664
643
|
def get_audio_frames_from_t(
|
665
644
|
self,
|
@@ -677,7 +656,10 @@ class VideoReader:
|
|
677
656
|
with more than 1 audio frame).
|
678
657
|
"""
|
679
658
|
t: T = T.from_fps(t, self.fps)
|
680
|
-
|
659
|
+
# We want all the audios that must be played
|
660
|
+
# during the video frame that starts in the
|
661
|
+
# 't' time moment
|
662
|
+
for frame in self.audio_cache.get_audio_frames(t.truncated, t.next(1).truncated):
|
681
663
|
yield frame
|
682
664
|
|
683
665
|
def get_frames(
|
@@ -701,7 +683,7 @@ class VideoReader:
|
|
701
683
|
Iterator to get the audio frames in between
|
702
684
|
the provided 'start' and 'end' time moments.
|
703
685
|
"""
|
704
|
-
for frame in self.audio_cache.
|
686
|
+
for frame in self.audio_cache.get_audio_frames(start, end):
|
705
687
|
yield frame
|
706
688
|
|
707
689
|
def close(
|
yta_video_opengl/reader/cache.py
CHANGED
@@ -20,14 +20,25 @@ A stream can have 'fps = 60' but use another
|
|
20
20
|
different time base that make the pts values go 0,
|
21
21
|
256, 512... for example. The 'time_base' is the
|
22
22
|
only accurate way to obtain the pts.
|
23
|
+
|
24
|
+
Feel free to move this explanation to other
|
25
|
+
place, its about the duration.
|
26
|
+
|
27
|
+
The stream 'duration' parameter is measured
|
28
|
+
on ticks, the amount of ticks that the
|
29
|
+
stream lasts. Here below is an example:
|
30
|
+
|
31
|
+
- Duration raw: 529200
|
32
|
+
- Time base: 1/44100
|
33
|
+
- Duration (seconds): 12.0
|
23
34
|
"""
|
24
|
-
from yta_video_opengl.utils import t_to_pts, pts_to_t, pts_to_index, index_to_pts
|
25
35
|
from yta_video_opengl.t import T
|
26
36
|
from av.container import InputContainer
|
27
37
|
from av.video.stream import VideoStream
|
28
38
|
from av.audio.stream import AudioStream
|
29
39
|
from av.video.frame import VideoFrame
|
30
40
|
from av.audio.frame import AudioFrame
|
41
|
+
from av.packet import Packet
|
31
42
|
from yta_validation.parameter import ParameterValidator
|
32
43
|
from yta_validation import PythonValidator
|
33
44
|
from quicktions import Fraction
|
@@ -35,7 +46,6 @@ from collections import OrderedDict
|
|
35
46
|
from typing import Union
|
36
47
|
|
37
48
|
import numpy as np
|
38
|
-
import av
|
39
49
|
import math
|
40
50
|
|
41
51
|
|
@@ -52,14 +62,14 @@ class VideoFrameCache:
|
|
52
62
|
@property
|
53
63
|
def fps(
|
54
64
|
self
|
55
|
-
) ->
|
65
|
+
) -> Union[int, Fraction, None]:
|
56
66
|
"""
|
57
|
-
The frames per second
|
67
|
+
The frames per second.
|
58
68
|
"""
|
59
69
|
return (
|
60
|
-
|
70
|
+
self.stream.average_rate
|
61
71
|
if self.stream.type == 'video' else
|
62
|
-
|
72
|
+
self.stream.rate
|
63
73
|
)
|
64
74
|
|
65
75
|
@property
|
@@ -104,6 +114,31 @@ class VideoFrameCache:
|
|
104
114
|
end.
|
105
115
|
"""
|
106
116
|
|
117
|
+
# TODO: This is new, remove this comment if
|
118
|
+
# it is ok
|
119
|
+
# TODO: This way of obtaining the duration
|
120
|
+
# in ticks must be a utils
|
121
|
+
self.frame_duration: int = (
|
122
|
+
self.stream.duration / self.stream.frames
|
123
|
+
if PythonValidator.is_instance_of(stream, VideoStream) else
|
124
|
+
# TODO: Is this below ok (?)
|
125
|
+
self.stream.frames
|
126
|
+
)
|
127
|
+
"""
|
128
|
+
The duration (in ticks) of the frame, that
|
129
|
+
is the step between the different pts.
|
130
|
+
"""
|
131
|
+
self._last_packet_accessed: Union[Packet, None] = None
|
132
|
+
"""
|
133
|
+
The last packet that has been accessed
|
134
|
+
"""
|
135
|
+
self._last_frame_read: Union[VideoFrame, AudioFrame, None] = None
|
136
|
+
"""
|
137
|
+
The last frame we have read when decoding.
|
138
|
+
Useful to avoid seeking all the time when we
|
139
|
+
don't need it.
|
140
|
+
"""
|
141
|
+
|
107
142
|
self._prepare()
|
108
143
|
|
109
144
|
def _prepare(
|
@@ -175,166 +210,161 @@ class VideoFrameCache:
|
|
175
210
|
self.cache.popitem(last = False)
|
176
211
|
|
177
212
|
return frame
|
178
|
-
|
179
|
-
def
|
213
|
+
|
214
|
+
def _seek(
|
180
215
|
self,
|
181
216
|
pts: int
|
182
|
-
)
|
217
|
+
):
|
183
218
|
"""
|
184
|
-
|
219
|
+
Seek to the given 'pts' only if it is not
|
220
|
+
the next 'pts' to the last read, and it
|
221
|
+
will also apply a pad to avoid problems
|
222
|
+
when reading audio frames.
|
185
223
|
|
186
|
-
|
187
|
-
|
188
|
-
pts) until the one requested is found. All those
|
189
|
-
frames will be stored in cache.
|
190
|
-
|
191
|
-
This method must be called when the frame
|
192
|
-
requested is not stored in the caché.
|
224
|
+
TODO: Apply the padding only to audio
|
225
|
+
frame reading (?)
|
193
226
|
"""
|
194
|
-
|
195
|
-
return self.cache[pts]
|
196
|
-
|
197
|
-
# Look for the most near key frame
|
198
|
-
key_frame_pts = self._get_nearest_keyframe_pts(pts)
|
199
|
-
|
200
|
-
# Go to the key frame that includes it
|
201
|
-
# but I read that it is recommended to
|
227
|
+
# I found that it is recommended to
|
202
228
|
# read ~100ms before the pts we want to
|
203
229
|
# actually read so we obtain the frames
|
204
230
|
# clean (this is important in audio)
|
205
|
-
# TODO: This
|
231
|
+
# TODO: This is maybe too much for a
|
232
|
+
# video and not needed
|
206
233
|
pts_pad = int(0.1 / self.time_base)
|
207
234
|
self.container.seek(
|
208
|
-
offset = max(0,
|
235
|
+
offset = max(0, pts - pts_pad),
|
209
236
|
stream = self.stream
|
210
237
|
)
|
211
238
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
239
|
+
def get_video_frame(
|
240
|
+
self,
|
241
|
+
t: Union[int, float, Fraction]
|
242
|
+
) -> VideoFrame:
|
243
|
+
"""
|
244
|
+
Get the video frame that is in the 't'
|
245
|
+
time moment provided.
|
246
|
+
"""
|
247
|
+
for frame in self.get_video_frames(t):
|
248
|
+
return frame
|
249
|
+
|
250
|
+
def get_video_frames(
|
251
|
+
self,
|
252
|
+
start: Union[int, float, Fraction] = 0,
|
253
|
+
end: Union[int, float, Fraction, None] = None
|
254
|
+
):
|
255
|
+
"""
|
256
|
+
Get all the frames in the range between
|
257
|
+
the provided 'start' and 'end' time in
|
258
|
+
seconds.
|
259
|
+
|
260
|
+
This method is an iterator that yields
|
261
|
+
the frame, its t and its index.
|
262
|
+
"""
|
263
|
+
start = T(start, self.time_base).truncated
|
264
|
+
end = (
|
265
|
+
T(end, self.time_base).truncated
|
266
|
+
if end is not None else
|
267
|
+
# The next frame
|
268
|
+
start + (1 / self.fps)
|
269
|
+
)
|
270
|
+
|
271
|
+
key_frame_pts = self._get_nearest_keyframe_pts(start / self.time_base)
|
272
|
+
|
273
|
+
if (
|
274
|
+
self._last_packet_accessed is None or
|
275
|
+
self._last_packet_accessed.pts != key_frame_pts
|
276
|
+
):
|
277
|
+
self._seek(key_frame_pts)
|
278
|
+
|
279
|
+
for packet in self.container.demux(self.stream):
|
280
|
+
if packet.pts is None:
|
216
281
|
continue
|
217
282
|
|
218
|
-
|
219
|
-
self._store_frame_in_cache(frame)
|
283
|
+
self._last_packet_accessed = packet
|
220
284
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
'pts' que are looking for seems to be the
|
225
|
-
index and not a pts.
|
285
|
+
for frame in packet.decode():
|
286
|
+
if frame.pts is None:
|
287
|
+
continue
|
226
288
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
289
|
+
# We store all the frames in cache
|
290
|
+
self._store_frame_in_cache(frame)
|
291
|
+
|
292
|
+
current_frame_time = frame.pts * self.time_base
|
293
|
+
|
294
|
+
# We want the range [start, end)
|
295
|
+
if start <= current_frame_time < end:
|
296
|
+
yield frame
|
233
297
|
|
234
|
-
|
235
|
-
|
236
|
-
return decoded
|
298
|
+
if current_frame_time >= end:
|
299
|
+
break
|
237
300
|
|
238
|
-
|
239
|
-
def get_frame(
|
301
|
+
def get_audio_frame_from_t(
|
240
302
|
self,
|
241
|
-
|
242
|
-
)
|
303
|
+
t: Union[int, float, Fraction]
|
304
|
+
):
|
243
305
|
"""
|
244
|
-
Get the
|
245
|
-
the
|
306
|
+
Get the single audio frame that must be
|
307
|
+
played at the 't' time moment provided.
|
308
|
+
This method is useful to get the single
|
309
|
+
audio frame that we need to combine
|
310
|
+
when using it in a composition.
|
311
|
+
|
312
|
+
TODO: Are we actually using this method (?)
|
246
313
|
"""
|
247
|
-
|
248
|
-
|
314
|
+
t: T = T(t, self.time_base)
|
315
|
+
# We need the just one audio frame
|
316
|
+
for frame in self.get_audio_frames(t.truncated, t.next(1).truncated):
|
317
|
+
return frame
|
249
318
|
|
250
|
-
|
251
|
-
self.cache[pts]
|
252
|
-
if pts in self.cache else
|
253
|
-
self.get_frame_from_pts(pts)
|
254
|
-
)
|
255
|
-
|
256
|
-
def get_frame_from_t(
|
319
|
+
def get_audio_frames_from_t(
|
257
320
|
self,
|
258
321
|
t: Union[int, float, Fraction]
|
259
|
-
)
|
322
|
+
):
|
260
323
|
"""
|
261
|
-
Get the
|
262
|
-
|
324
|
+
Get all the audio frames that must be
|
325
|
+
played at the 't' time moment provided.
|
263
326
|
"""
|
264
|
-
|
327
|
+
for frame in self.get_audio_frames(t):
|
328
|
+
yield frame
|
265
329
|
|
266
|
-
def
|
330
|
+
def get_audio_frames(
|
267
331
|
self,
|
268
332
|
start: Union[int, float, Fraction] = 0,
|
269
333
|
end: Union[int, float, Fraction, None] = None
|
270
334
|
):
|
271
335
|
"""
|
272
|
-
Get all the frames in the range
|
273
|
-
the provided 'start' and 'end'
|
274
|
-
seconds.
|
336
|
+
Get all the audio frames in the range
|
337
|
+
between the provided 'start' and 'end'
|
338
|
+
time (in seconds).
|
275
339
|
|
276
340
|
This method is an iterator that yields
|
277
341
|
the frame, its t and its index.
|
278
342
|
"""
|
279
|
-
#
|
280
|
-
#
|
281
|
-
#
|
282
|
-
#
|
283
|
-
|
284
|
-
|
285
|
-
"""
|
286
|
-
Feel free to move this explanation to other
|
287
|
-
place, its about the duration.
|
288
|
-
|
289
|
-
The stream 'duration' parameter is measured
|
290
|
-
on ticks, the amount of ticks that the
|
291
|
-
stream lasts. Here below is an example:
|
292
|
-
|
293
|
-
- Duration raw: 529200
|
294
|
-
- Time base: 1/44100
|
295
|
-
- Duration (seconds): 12.0
|
296
|
-
"""
|
297
|
-
|
298
|
-
# The 'duration' is on pts ticks
|
299
|
-
duration = float(self.stream.duration * self.time_base)
|
300
|
-
# TODO: I think it would be better to
|
301
|
-
# receive and work with pts instead of
|
302
|
-
# 't' time moments...
|
303
|
-
# pts_list = [
|
304
|
-
# t_to_pts(t, self.time_base)
|
305
|
-
# for t in T.get_frame_indexes(duration, self.fps, start, end)
|
306
|
-
# ]
|
307
|
-
|
308
|
-
# if all(
|
309
|
-
# pts in self.cache
|
310
|
-
# for pts in pts_list
|
311
|
-
# ):
|
312
|
-
# for pts in pts_list:
|
313
|
-
# yield self.cache[pts]
|
314
|
-
|
315
|
-
# If not all, we ignore the cache because we
|
316
|
-
# need to decode and they are all consecutive
|
317
|
-
start = T(start, self.time_base).truncated_pts
|
343
|
+
# TODO: Is this ok? We are trying to obtain
|
344
|
+
# the audio frames for a video frame, so
|
345
|
+
# should we use the 'self.time_base' to
|
346
|
+
# truncate (?)
|
347
|
+
start = T(start, self.time_base).truncated
|
318
348
|
end = (
|
319
|
-
T(end, self.time_base).
|
349
|
+
T(end, self.time_base).truncated
|
320
350
|
if end is not None else
|
321
|
-
|
351
|
+
start + (1 / self.fps)
|
322
352
|
)
|
323
|
-
key_frame_pts = self._get_nearest_keyframe_pts(start)
|
324
353
|
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
self.container.seek(
|
333
|
-
offset = max(0, key_frame_pts - pts_pad),
|
334
|
-
stream = self.stream
|
335
|
-
)
|
354
|
+
key_frame_pts = self._get_nearest_keyframe_pts(start / self.time_base)
|
355
|
+
|
356
|
+
if (
|
357
|
+
self._last_packet_accessed is None or
|
358
|
+
self._last_packet_accessed.pts != key_frame_pts
|
359
|
+
):
|
360
|
+
self._seek(key_frame_pts)
|
336
361
|
|
337
362
|
for packet in self.container.demux(self.stream):
|
363
|
+
if packet.pts is None:
|
364
|
+
continue
|
365
|
+
|
366
|
+
self._last_packet_accessed = packet
|
367
|
+
|
338
368
|
for frame in packet.decode():
|
339
369
|
if frame.pts is None:
|
340
370
|
continue
|
@@ -342,27 +372,24 @@ class VideoFrameCache:
|
|
342
372
|
# We store all the frames in cache
|
343
373
|
self._store_frame_in_cache(frame)
|
344
374
|
|
345
|
-
|
346
|
-
#
|
347
|
-
#
|
375
|
+
current_frame_time = frame.pts * self.time_base
|
376
|
+
# End is not included, its the start of the
|
377
|
+
# next frame actually
|
378
|
+
frame_end = current_frame_time + (frame.samples / self.stream.sample_rate)
|
348
379
|
|
349
380
|
# For the next comments imagine we are looking
|
350
381
|
# for the [1.0, 2.0) audio time range
|
351
382
|
# Previous frame and nothing is inside
|
352
|
-
if
|
383
|
+
if frame_end <= start:
|
353
384
|
# From 0.25 to 1.0
|
354
385
|
continue
|
355
|
-
|
386
|
+
|
356
387
|
# We finished, nothing is inside and its after
|
357
|
-
if
|
358
|
-
end is not None and
|
359
|
-
frame.pts >= end
|
360
|
-
):
|
388
|
+
if current_frame_time >= end:
|
361
389
|
# From 2.0 to 2.75
|
362
390
|
return
|
363
391
|
|
364
|
-
#
|
365
|
-
# Audio is:
|
392
|
+
# If we need audio from 1 to 2, audio is:
|
366
393
|
# - from 0 to 0.75 (Not included, omit)
|
367
394
|
# - from 0.5 to 1.5 (Included, take 1.0 to 1.5)
|
368
395
|
# - from 0.5 to 2.5 (Included, take 1.0 to 2.0)
|
@@ -372,55 +399,46 @@ class VideoFrameCache:
|
|
372
399
|
|
373
400
|
# Here below, at least a part is inside
|
374
401
|
if (
|
375
|
-
|
376
|
-
|
402
|
+
current_frame_time < start and
|
403
|
+
frame_end > start
|
377
404
|
):
|
378
405
|
# A part at the end is included
|
379
406
|
end_time = (
|
380
407
|
# From 0.5 to 1.5 0> take 1.0 to 1.5
|
381
|
-
|
382
|
-
if
|
408
|
+
frame_end
|
409
|
+
if frame_end <= end else
|
383
410
|
# From 0.5 to 2.5 => take 1.0 to 2.0
|
384
411
|
end
|
385
412
|
)
|
386
413
|
#print('A part at the end is included.')
|
387
|
-
|
388
|
-
frame = trim_audio_frame_pts(
|
414
|
+
frame = trim_audio_frame(
|
389
415
|
frame = frame,
|
390
|
-
|
391
|
-
|
416
|
+
start = start,
|
417
|
+
end = end_time,
|
392
418
|
time_base = self.time_base
|
393
419
|
)
|
394
420
|
elif (
|
395
|
-
|
396
|
-
|
421
|
+
current_frame_time >= start and
|
422
|
+
current_frame_time < end
|
397
423
|
):
|
398
424
|
end_time = (
|
399
425
|
# From 1.25 to 1.5 => take 1.25 to 1.5
|
400
|
-
|
401
|
-
if
|
426
|
+
frame_end
|
427
|
+
if frame_end <= end else
|
402
428
|
# From 1.25 to 2.5 => take 1.25 to 2.0
|
403
429
|
end
|
404
430
|
)
|
405
431
|
# A part at the begining is included
|
406
432
|
#print('A part at the begining is included.')
|
407
|
-
|
408
|
-
frame = trim_audio_frame_pts(
|
433
|
+
frame = trim_audio_frame(
|
409
434
|
frame = frame,
|
410
|
-
|
411
|
-
|
435
|
+
start = current_frame_time,
|
436
|
+
end = end_time,
|
412
437
|
time_base = self.time_base
|
413
438
|
)
|
414
439
|
|
415
440
|
# If the whole frame is in, past as it is
|
416
|
-
|
417
|
-
# TODO: Maybe send a @dataclass instead (?)
|
418
|
-
# TODO: Do I really need these 't' and 'index' (?)
|
419
|
-
yield (
|
420
|
-
frame,
|
421
|
-
pts_to_t(frame.pts, self.time_base),
|
422
|
-
pts_to_index(frame.pts, self.time_base, self.fps)
|
423
|
-
)
|
441
|
+
yield frame
|
424
442
|
|
425
443
|
def clear(
|
426
444
|
self
|
@@ -431,99 +449,64 @@ class VideoFrameCache:
|
|
431
449
|
self.cache.clear()
|
432
450
|
|
433
451
|
return self
|
434
|
-
|
435
452
|
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
) -> av.AudioFrame:
|
453
|
+
def trim_audio_frame(
|
454
|
+
frame: AudioFrame,
|
455
|
+
start: Union[int, float, Fraction],
|
456
|
+
end: Union[int, float, Fraction],
|
457
|
+
time_base: Fraction
|
458
|
+
) -> AudioFrame:
|
443
459
|
"""
|
444
|
-
|
460
|
+
Trim an audio frame to obtain the part between
|
461
|
+
[start, end), that is provided in seconds.
|
445
462
|
"""
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
#frame_end_pts = frame.pts + int((n_samples / sr) / time_base)
|
451
|
-
# TODO: This could be wrong
|
452
|
-
frame_end_pts = frame.pts + int(frame.samples)
|
453
|
-
|
454
|
-
# solapamiento en PTS
|
455
|
-
cut_start_pts = max(frame.pts, start_pts)
|
456
|
-
cut_end_pts = min(frame_end_pts, end_pts)
|
457
|
-
|
458
|
-
if cut_start_pts >= cut_end_pts:
|
459
|
-
raise Exception('Oops...')
|
460
|
-
return None # no hay solapamiento
|
463
|
+
# (channels, n_samples)
|
464
|
+
samples = frame.to_ndarray()
|
465
|
+
n_samples = samples.shape[1]
|
461
466
|
|
462
|
-
#
|
463
|
-
|
464
|
-
|
467
|
+
# In seconds
|
468
|
+
frame_start = frame.pts * float(time_base)
|
469
|
+
frame_end = frame_start + (n_samples / frame.sample_rate)
|
465
470
|
|
466
|
-
|
467
|
-
|
471
|
+
# Overlapping
|
472
|
+
cut_start = max(frame_start, float(start))
|
473
|
+
cut_end = min(frame_end, float(end))
|
468
474
|
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
#
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
475
|
+
if cut_start >= cut_end:
|
476
|
+
# No overlapping
|
477
|
+
return None
|
478
|
+
|
479
|
+
# To sample indexes
|
480
|
+
start_index = int(round((cut_start - frame_start) * frame.sample_rate))
|
481
|
+
end_index = int(round((cut_end - frame_start) * frame.sample_rate))
|
482
|
+
|
483
|
+
new_frame = AudioFrame.from_ndarray(
|
484
|
+
# end_index is not included: so [start, end)
|
485
|
+
array = samples[:, start_index:end_index],
|
486
|
+
format = frame.format,
|
487
|
+
layout = frame.layout
|
488
|
+
)
|
489
|
+
|
490
|
+
# Set attributes
|
491
|
+
new_frame.sample_rate = frame.sample_rate
|
484
492
|
new_frame.time_base = time_base
|
493
|
+
new_frame.pts = int(round(cut_start / float(time_base)))
|
485
494
|
|
486
495
|
return new_frame
|
487
496
|
|
488
497
|
|
489
498
|
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
frame_end = frame_start + (n_samples / sr)
|
505
|
-
|
506
|
-
# calcular solapamiento en segundos
|
507
|
-
cut_start = max(frame_start, start_time)
|
508
|
-
cut_end = min(frame_end, end_time)
|
509
|
-
|
510
|
-
if cut_start >= cut_end:
|
511
|
-
return None # no hay solapamiento
|
512
|
-
|
513
|
-
# convertir a índices de samples
|
514
|
-
start_idx = int((cut_start - frame_start) * sr)
|
515
|
-
end_idx = int((cut_end - frame_start) * sr)
|
516
|
-
|
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))}')
|
518
|
-
cut_samples = samples[:, start_idx:end_idx]
|
519
|
-
|
520
|
-
# crear nuevo AudioFrame
|
521
|
-
new_frame = av.AudioFrame.from_ndarray(cut_samples, format = frame.format, layout = frame.layout)
|
522
|
-
new_frame.sample_rate = sr
|
523
|
-
|
524
|
-
# ajustar PTS → corresponde al inicio real del recorte
|
525
|
-
new_pts = int(cut_start / time_base)
|
526
|
-
new_frame.pts = new_pts
|
527
|
-
new_frame.time_base = time_base
|
528
|
-
|
529
|
-
return new_frame
|
499
|
+
"""
|
500
|
+
There is a way of editing videos being
|
501
|
+
able to arbitrary access to frames, that
|
502
|
+
is transforming the source videos to
|
503
|
+
intra-frame videos. This is a ffmpeg
|
504
|
+
command that can do it:
|
505
|
+
|
506
|
+
- `ffmpeg -i input.mp4 -c:v libx264 -x264opts keyint=1 -preset fast -crf 18 -c:a copy output_intra.mp4`
|
507
|
+
|
508
|
+
Once you have the 'output_intra.mp4',
|
509
|
+
each packet can decodify its frame
|
510
|
+
depending not on the previous one, being
|
511
|
+
able to seek and jump easy.
|
512
|
+
"""
|
yta_video_opengl/t.py
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
"""
|
2
|
+
This is an example of what a video has:
|
3
|
+
- fps = 60
|
4
|
+
- time_base = 1 / 15360
|
5
|
+
- tick = fps * time_base = 256
|
6
|
+
|
7
|
+
So, the first pts is 0 and the second
|
8
|
+
one is 256. The frame 16 will be 3840,
|
9
|
+
that is 256 * 15 (because first index
|
10
|
+
is 0).
|
11
|
+
"""
|
1
12
|
from yta_validation.parameter import ParameterValidator
|
2
13
|
from yta_validation import PythonValidator
|
3
14
|
from yta_validation.number import NumberValidator
|
@@ -36,7 +47,7 @@ class T:
|
|
36
47
|
The 't' but as a Fraction that is multiple
|
37
48
|
of the given 'time_base' and rounded (the
|
38
49
|
value could be the same as truncated if it
|
39
|
-
is closer to the
|
50
|
+
is closer to the previous value).
|
40
51
|
"""
|
41
52
|
return round_t(self._t, self.time_base, do_truncate = False)
|
42
53
|
|
@@ -96,20 +107,57 @@ class T:
|
|
96
107
|
"""
|
97
108
|
return T(self.truncated + n * self.time_base, self.time_base)
|
98
109
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
110
|
+
def previous(
|
111
|
+
self,
|
112
|
+
n: int = 1
|
113
|
+
) -> 'T':
|
114
|
+
"""
|
115
|
+
Get the value that is 'n' times before the
|
116
|
+
'truncated' property of this instance.
|
117
|
+
|
118
|
+
Useful when you need the previous value to
|
119
|
+
check if the current is the next one or
|
120
|
+
similar.
|
121
|
+
|
122
|
+
Be careful, if the 'truncated' value is 0
|
123
|
+
this will give you an unexpected negative
|
124
|
+
value.
|
125
|
+
"""
|
126
|
+
return T(self.truncated - n * self.time_base, self.time_base)
|
127
|
+
|
103
128
|
@staticmethod
|
104
129
|
def from_fps(
|
105
130
|
t: Union[int, float, Fraction],
|
106
131
|
fps: Union[int, float, Fraction]
|
107
|
-
):
|
132
|
+
) -> 'T':
|
108
133
|
"""
|
109
134
|
Get the instance but providing the 'fps'
|
110
|
-
(or sample rate) value directly
|
135
|
+
(or sample rate) value directly, that will
|
136
|
+
be turned into a time base.
|
111
137
|
"""
|
112
138
|
return T(t, fps_to_time_base(fps))
|
139
|
+
|
140
|
+
@staticmethod
|
141
|
+
def from_pts(
|
142
|
+
pts: int,
|
143
|
+
time_base: Fraction
|
144
|
+
) -> 'T':
|
145
|
+
"""
|
146
|
+
Get the instance but providing the 'pts'
|
147
|
+
and the 'time_base'.
|
148
|
+
"""
|
149
|
+
return T(pts * time_base, time_base)
|
150
|
+
|
151
|
+
|
152
|
+
# TODO: Careful with this below
|
153
|
+
"""
|
154
|
+
To obtain the pts step, or frame duration in
|
155
|
+
ticks, you need to apply 2 formulas that are
|
156
|
+
different according to if the frame is video
|
157
|
+
or audio:
|
158
|
+
- Audio: .samples
|
159
|
+
- Video: int(round((1 / .fps) / .time_base))
|
160
|
+
"""
|
113
161
|
|
114
162
|
def get_ts(
|
115
163
|
start: Union[int, float, Fraction],
|
yta_video_opengl/video.py
CHANGED
@@ -182,15 +182,20 @@ class Video:
|
|
182
182
|
Get the video frame with the given 't' time
|
183
183
|
moment, using the video cache system.
|
184
184
|
"""
|
185
|
-
return self.reader.video_cache.
|
186
|
-
|
185
|
+
return self.reader.video_cache.get_video_frame(self._get_real_t(t))
|
186
|
+
|
187
187
|
def get_audio_frame_from_t(
|
188
188
|
self,
|
189
189
|
t: Union[int, float, Fraction]
|
190
190
|
) -> 'AudioFrame':
|
191
191
|
"""
|
192
192
|
Get the audio frame with the given 't' time
|
193
|
-
moment, using the audio cache system.
|
193
|
+
moment, using the audio cache system. This
|
194
|
+
method is useful when we need to combine
|
195
|
+
many different frames so we can obtain them
|
196
|
+
one by one.
|
197
|
+
|
198
|
+
TODO: Is this actually necessary (?)
|
194
199
|
"""
|
195
200
|
return self.reader.audio_cache.get_frame_from_t(self._get_real_t(t))
|
196
201
|
|
@@ -1,21 +1,21 @@
|
|
1
1
|
yta_video_opengl/__init__.py,sha256=ycAx_XYMVDfkuObSvtW6irQ0Wo-fgxEz3fjIRMe8PpY,205
|
2
2
|
yta_video_opengl/classes.py,sha256=t5-Tfc7ecvHl8JlVBp_FVzZT6ole6Ly5-FeBBH7wcxo,37742
|
3
3
|
yta_video_opengl/complete/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
-
yta_video_opengl/complete/timeline.py,sha256=
|
5
|
-
yta_video_opengl/complete/track.py,sha256=
|
6
|
-
yta_video_opengl/complete/video_on_track.py,sha256=
|
4
|
+
yta_video_opengl/complete/timeline.py,sha256=tMYeAFrOY3M7DAREOYnPT_RzHoFtQnCxk68DusKmJDU,9433
|
5
|
+
yta_video_opengl/complete/track.py,sha256=qIJd3RLizutmCtqk8pkyW40xr6Vz0Aub5_CDJZ0KORY,13735
|
6
|
+
yta_video_opengl/complete/video_on_track.py,sha256=oBWlSFumP1khpWE-z3MEBihTxdnjDvdWHbtFrQCjJgE,4964
|
7
7
|
yta_video_opengl/nodes/__init__.py,sha256=TZ-ZO05PZ0_ABq675E22_PngLWOe-_w5s1cLlV3NbWM,3469
|
8
8
|
yta_video_opengl/nodes/audio/__init__.py,sha256=4nKkC70k1UgLcCSPqFWm3cKdaJM0KUmQTwGWv1xFarQ,2926
|
9
9
|
yta_video_opengl/nodes/video/__init__.py,sha256=gSoaoEmjdQmyRwH18mf5z3NAhap3S0RgbeBbfBXi4jc,132
|
10
10
|
yta_video_opengl/nodes/video/opengl.py,sha256=K2pyCJEd9z4gnZqJetKyGPbtHuBzFsx74ZYyzhSqYPo,8510
|
11
|
-
yta_video_opengl/reader/__init__.py,sha256=
|
12
|
-
yta_video_opengl/reader/cache.py,sha256=
|
13
|
-
yta_video_opengl/t.py,sha256=
|
11
|
+
yta_video_opengl/reader/__init__.py,sha256=Go2rp9flUIBXuo5d_3eqB5CyIE9SqB8_pKsESyZXO-A,19648
|
12
|
+
yta_video_opengl/reader/cache.py,sha256=vGb1JgrTAoChw5n-F24Z2Dmgadt0Wa4PVRRDYMy63Q0,16414
|
13
|
+
yta_video_opengl/t.py,sha256=xOhT1xBEwChlXf-Tuy-WxA_08iRJWVlnL_Hyzr-9-sk,6633
|
14
14
|
yta_video_opengl/tests.py,sha256=EdTyYtTUd_mj6geWnrvnF-wZSHCKKvhYgiLclkV73O0,26576
|
15
15
|
yta_video_opengl/utils.py,sha256=yUi17EjNR4SVpvdDUwUaKl4mBCb1uyFCSGoIX3Zr2F0,15586
|
16
|
-
yta_video_opengl/video.py,sha256=
|
16
|
+
yta_video_opengl/video.py,sha256=3jBuBW0IRpHrl8wgSoSit2x5pdoi_Q98ZVAg8hK_59I,8638
|
17
17
|
yta_video_opengl/writer.py,sha256=QwvjQcEkzn1WAVqVTFiI6tYIXJO67LKKUTJGO_eflFM,8893
|
18
|
-
yta_video_opengl-0.0.
|
19
|
-
yta_video_opengl-0.0.
|
20
|
-
yta_video_opengl-0.0.
|
21
|
-
yta_video_opengl-0.0.
|
18
|
+
yta_video_opengl-0.0.14.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
19
|
+
yta_video_opengl-0.0.14.dist-info/METADATA,sha256=gto3fNuFhgs_NWWM8dmqNiNRNk826zlK6YFMkd0EkfM,714
|
20
|
+
yta_video_opengl-0.0.14.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
21
|
+
yta_video_opengl-0.0.14.dist-info/RECORD,,
|
File without changes
|
File without changes
|