yta-video-opengl 0.0.13__py3-none-any.whl → 0.0.15__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 +3 -4
- yta_video_opengl/complete/track.py +3 -5
- yta_video_opengl/complete/video_on_track.py +2 -8
- yta_video_opengl/reader/__init__.py +22 -39
- yta_video_opengl/reader/cache/__init__.py +249 -0
- yta_video_opengl/reader/cache/audio.py +195 -0
- yta_video_opengl/reader/cache/utils.py +48 -0
- yta_video_opengl/reader/cache/video.py +110 -0
- yta_video_opengl/t.py +55 -7
- yta_video_opengl/video.py +9 -4
- {yta_video_opengl-0.0.13.dist-info → yta_video_opengl-0.0.15.dist-info}/METADATA +1 -1
- yta_video_opengl-0.0.15.dist-info/RECORD +24 -0
- yta_video_opengl/reader/cache.py +0 -529
- yta_video_opengl-0.0.13.dist-info/RECORD +0 -21
- {yta_video_opengl-0.0.13.dist-info → yta_video_opengl-0.0.15.dist-info}/LICENSE +0 -0
- {yta_video_opengl-0.0.13.dist-info → yta_video_opengl-0.0.15.dist-info}/WHEEL +0 -0
yta_video_opengl/reader/cache.py
DELETED
@@ -1,529 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
The pyav container stores the information based
|
3
|
-
on the packets timestamps (called 'pts'). Some
|
4
|
-
of the packets are considered key_frames because
|
5
|
-
they include those key frames.
|
6
|
-
|
7
|
-
Also, this library uses those key frames to start
|
8
|
-
decodifying from there to the next one, obtaining
|
9
|
-
all the frames in between able to be read and
|
10
|
-
modified.
|
11
|
-
|
12
|
-
This cache system will look for the range of
|
13
|
-
frames that belong to the key frame related to the
|
14
|
-
frame we are requesting in the moment, keeping in
|
15
|
-
memory all those frames to be handled fast. It
|
16
|
-
will remove the old frames if needed to use only
|
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.
|
23
|
-
"""
|
24
|
-
from yta_video_opengl.utils import t_to_pts, pts_to_t, pts_to_index, index_to_pts
|
25
|
-
from yta_video_opengl.t import T
|
26
|
-
from av.container import InputContainer
|
27
|
-
from av.video.stream import VideoStream
|
28
|
-
from av.audio.stream import AudioStream
|
29
|
-
from av.video.frame import VideoFrame
|
30
|
-
from av.audio.frame import AudioFrame
|
31
|
-
from yta_validation.parameter import ParameterValidator
|
32
|
-
from yta_validation import PythonValidator
|
33
|
-
from quicktions import Fraction
|
34
|
-
from collections import OrderedDict
|
35
|
-
from typing import Union
|
36
|
-
|
37
|
-
import numpy as np
|
38
|
-
import av
|
39
|
-
import math
|
40
|
-
|
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.
|
46
|
-
class VideoFrameCache:
|
47
|
-
"""
|
48
|
-
Class to manage the frames cache of a video
|
49
|
-
within a video reader instance.
|
50
|
-
"""
|
51
|
-
|
52
|
-
@property
|
53
|
-
def fps(
|
54
|
-
self
|
55
|
-
) -> float:
|
56
|
-
"""
|
57
|
-
The frames per second as a float.
|
58
|
-
"""
|
59
|
-
return (
|
60
|
-
float(self.stream.average_rate)
|
61
|
-
if self.stream.type == 'video' else
|
62
|
-
float(self.stream.rate)
|
63
|
-
)
|
64
|
-
|
65
|
-
@property
|
66
|
-
def time_base(
|
67
|
-
self
|
68
|
-
) -> Union[Fraction, None]:
|
69
|
-
"""
|
70
|
-
The time base of the stream.
|
71
|
-
"""
|
72
|
-
return self.stream.time_base
|
73
|
-
|
74
|
-
def __init__(
|
75
|
-
self,
|
76
|
-
container: InputContainer,
|
77
|
-
stream: Union[VideoStream, AudioStream],
|
78
|
-
size: Union[int, None] = None
|
79
|
-
):
|
80
|
-
ParameterValidator.validate_mandatory_instance_of('container', container, InputContainer)
|
81
|
-
ParameterValidator.validate_mandatory_instance_of('stream', stream, [VideoStream, AudioStream])
|
82
|
-
ParameterValidator.validate_positive_int('size', size)
|
83
|
-
|
84
|
-
self.container: InputContainer = container
|
85
|
-
"""
|
86
|
-
The pyav container.
|
87
|
-
"""
|
88
|
-
self.stream: Union[VideoStream, AudioStream] = stream
|
89
|
-
"""
|
90
|
-
The pyav stream.
|
91
|
-
"""
|
92
|
-
self.cache: OrderedDict = OrderedDict()
|
93
|
-
"""
|
94
|
-
The cache ordered dictionary.
|
95
|
-
"""
|
96
|
-
self.size: Union[int, None] = size
|
97
|
-
"""
|
98
|
-
The size (in number of frames) of the cache.
|
99
|
-
"""
|
100
|
-
self.key_frames_pts: list[int] = []
|
101
|
-
"""
|
102
|
-
The list that contains the timestamps of the
|
103
|
-
key frame packets, ordered from begining to
|
104
|
-
end.
|
105
|
-
"""
|
106
|
-
|
107
|
-
self._prepare()
|
108
|
-
|
109
|
-
def _prepare(
|
110
|
-
self
|
111
|
-
):
|
112
|
-
# Index key frames
|
113
|
-
for packet in self.container.demux(self.stream):
|
114
|
-
if packet.is_keyframe:
|
115
|
-
self.key_frames_pts.append(packet.pts)
|
116
|
-
|
117
|
-
# The cache size will be auto-calculated to
|
118
|
-
# use the amount of frames of the biggest
|
119
|
-
# interval of frames that belongs to a key
|
120
|
-
# frame, or a value by default
|
121
|
-
# TODO: Careful if this is too big
|
122
|
-
fps = (
|
123
|
-
float(self.stream.average_rate)
|
124
|
-
if PythonValidator.is_instance_of(self.stream, VideoStream) else
|
125
|
-
float(self.stream.rate)
|
126
|
-
)
|
127
|
-
# Intervals, but in number of frames
|
128
|
-
intervals = np.diff(
|
129
|
-
# Intervals of time between keyframes
|
130
|
-
np.array(self.key_frames_pts) * self.time_base
|
131
|
-
) * fps
|
132
|
-
|
133
|
-
self.size = (
|
134
|
-
math.ceil(np.max(intervals))
|
135
|
-
if intervals.size > 0 else
|
136
|
-
(
|
137
|
-
self.size or
|
138
|
-
# TODO: Make this 'default_size' a setting or something
|
139
|
-
60
|
140
|
-
)
|
141
|
-
)
|
142
|
-
|
143
|
-
self.container.seek(0)
|
144
|
-
|
145
|
-
def _get_nearest_keyframe_pts(
|
146
|
-
self,
|
147
|
-
pts: int
|
148
|
-
):
|
149
|
-
"""
|
150
|
-
Get the fps of the keyframe that is the
|
151
|
-
nearest to the provided 'pts'. Useful to
|
152
|
-
seek and start decoding frames from that
|
153
|
-
keyframe.
|
154
|
-
"""
|
155
|
-
return max([
|
156
|
-
key_frame_pts
|
157
|
-
for key_frame_pts in self.key_frames_pts
|
158
|
-
if key_frame_pts <= pts
|
159
|
-
])
|
160
|
-
|
161
|
-
def _store_frame_in_cache(
|
162
|
-
self,
|
163
|
-
frame: Union[VideoFrame, AudioFrame]
|
164
|
-
) -> Union[VideoFrame, AudioFrame]:
|
165
|
-
"""
|
166
|
-
Store the provided 'frame' in cache if it
|
167
|
-
is not on it, removing the first item of
|
168
|
-
the cache if full.
|
169
|
-
"""
|
170
|
-
if frame.pts not in self.cache:
|
171
|
-
self.cache[frame.pts] = frame
|
172
|
-
|
173
|
-
# Clean cache if full
|
174
|
-
if len(self.cache) > self.size:
|
175
|
-
self.cache.popitem(last = False)
|
176
|
-
|
177
|
-
return frame
|
178
|
-
|
179
|
-
def get_frame_from_pts(
|
180
|
-
self,
|
181
|
-
pts: int
|
182
|
-
) -> Union[VideoFrame, AudioFrame, None]:
|
183
|
-
"""
|
184
|
-
Get the frame that has the provided 'pts'.
|
185
|
-
|
186
|
-
This method will start decoding frames from the
|
187
|
-
most near key frame (the one with the nearer
|
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é.
|
193
|
-
"""
|
194
|
-
if pts in self.cache:
|
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
|
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
|
-
)
|
211
|
-
|
212
|
-
decoded = None
|
213
|
-
for frame in self.container.decode(self.stream):
|
214
|
-
# TODO: Could 'frame' be None (?)
|
215
|
-
if frame.pts is None:
|
216
|
-
continue
|
217
|
-
|
218
|
-
# Store in cache if needed
|
219
|
-
self._store_frame_in_cache(frame)
|
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
|
-
"""
|
230
|
-
if frame.pts >= pts:
|
231
|
-
decoded = self.cache[frame.pts]
|
232
|
-
break
|
233
|
-
|
234
|
-
# TODO: Is this working? We need previous
|
235
|
-
# frames to be able to decode...
|
236
|
-
return decoded
|
237
|
-
|
238
|
-
# TODO: I'm not using this method...
|
239
|
-
def get_frame(
|
240
|
-
self,
|
241
|
-
index: int
|
242
|
-
) -> Union[VideoFrame, AudioFrame]:
|
243
|
-
"""
|
244
|
-
Get the frame with the given 'index' from
|
245
|
-
the cache.
|
246
|
-
"""
|
247
|
-
# TODO: Maybe we can accept 'pts' also
|
248
|
-
pts = index_to_pts(index, self.time_base, self.fps)
|
249
|
-
|
250
|
-
return (
|
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(
|
257
|
-
self,
|
258
|
-
t: Union[int, float, Fraction]
|
259
|
-
) -> Union[VideoFrame, AudioFrame]:
|
260
|
-
"""
|
261
|
-
Get the frame with the given 't' time moment
|
262
|
-
from the cache.
|
263
|
-
"""
|
264
|
-
return self.get_frame_from_pts(T(t, self.time_base).truncated_pts)
|
265
|
-
|
266
|
-
def get_frames(
|
267
|
-
self,
|
268
|
-
start: Union[int, float, Fraction] = 0,
|
269
|
-
end: Union[int, float, Fraction, None] = None
|
270
|
-
):
|
271
|
-
"""
|
272
|
-
Get all the frames in the range between
|
273
|
-
the provided 'start' and 'end' time in
|
274
|
-
seconds.
|
275
|
-
|
276
|
-
This method is an iterator that yields
|
277
|
-
the frame, its t and its index.
|
278
|
-
"""
|
279
|
-
# We use the cache as iterator if all the frames
|
280
|
-
# requested are stored there
|
281
|
-
# TODO: I think this is not ok... I will never
|
282
|
-
# have all the pts form here stored, as they come
|
283
|
-
# from 't' that is different...
|
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
|
318
|
-
end = (
|
319
|
-
T(end, self.time_base).truncated_pts
|
320
|
-
if end is not None else
|
321
|
-
None
|
322
|
-
)
|
323
|
-
key_frame_pts = self._get_nearest_keyframe_pts(start)
|
324
|
-
|
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
|
-
)
|
336
|
-
|
337
|
-
for packet in self.container.demux(self.stream):
|
338
|
-
for frame in packet.decode():
|
339
|
-
if frame.pts is None:
|
340
|
-
continue
|
341
|
-
|
342
|
-
# We store all the frames in cache
|
343
|
-
self._store_frame_in_cache(frame)
|
344
|
-
|
345
|
-
frame_end_pts = frame.pts + int(frame.samples * (1 / self.stream.sample_rate) / self.time_base)
|
346
|
-
#frame_end_pts = frame.pts + int(frame.samples)
|
347
|
-
#frame_end_pts = frame.pts + int(frame.samples / (self.stream.sample_rate * self.time_base))
|
348
|
-
|
349
|
-
# For the next comments imagine we are looking
|
350
|
-
# for the [1.0, 2.0) audio time range
|
351
|
-
# Previous frame and nothing is inside
|
352
|
-
if frame_end_pts <= start:
|
353
|
-
# From 0.25 to 1.0
|
354
|
-
continue
|
355
|
-
|
356
|
-
# We finished, nothing is inside and its after
|
357
|
-
if (
|
358
|
-
end is not None and
|
359
|
-
frame.pts >= end
|
360
|
-
):
|
361
|
-
# From 2.0 to 2.75
|
362
|
-
return
|
363
|
-
|
364
|
-
# We need: from 1 to 2
|
365
|
-
# Audio is:
|
366
|
-
# - from 0 to 0.75 (Not included, omit)
|
367
|
-
# - from 0.5 to 1.5 (Included, take 1.0 to 1.5)
|
368
|
-
# - from 0.5 to 2.5 (Included, take 1.0 to 2.0)
|
369
|
-
# - from 1.25 to 1.5 (Included, take 1.25 to 1.5)
|
370
|
-
# - from 1.25 to 2.5 (Included, take 1.25 to 2.0)
|
371
|
-
# - from 2.5 to 3.5 (Not included, omit)
|
372
|
-
|
373
|
-
# Here below, at least a part is inside
|
374
|
-
if (
|
375
|
-
frame.pts < start and
|
376
|
-
frame_end_pts > start
|
377
|
-
):
|
378
|
-
# A part at the end is included
|
379
|
-
end_time = (
|
380
|
-
# From 0.5 to 1.5 0> take 1.0 to 1.5
|
381
|
-
frame_end_pts
|
382
|
-
if frame_end_pts <= end else
|
383
|
-
# From 0.5 to 2.5 => take 1.0 to 2.0
|
384
|
-
end
|
385
|
-
)
|
386
|
-
#print('A part at the end is included.')
|
387
|
-
# TODO: I'm using too much 'pts_to_t'
|
388
|
-
frame = trim_audio_frame_pts(
|
389
|
-
frame = frame,
|
390
|
-
start_pts = start,
|
391
|
-
end_pts = end_time,
|
392
|
-
time_base = self.time_base
|
393
|
-
)
|
394
|
-
elif (
|
395
|
-
frame.pts >= start and
|
396
|
-
frame.pts < end
|
397
|
-
):
|
398
|
-
end_time = (
|
399
|
-
# From 1.25 to 1.5 => take 1.25 to 1.5
|
400
|
-
frame_end_pts
|
401
|
-
if frame_end_pts <= end else
|
402
|
-
# From 1.25 to 2.5 => take 1.25 to 2.0
|
403
|
-
end
|
404
|
-
)
|
405
|
-
# A part at the begining is included
|
406
|
-
#print('A part at the begining is included.')
|
407
|
-
# TODO: I'm using too much 'pts_to_t'
|
408
|
-
frame = trim_audio_frame_pts(
|
409
|
-
frame = frame,
|
410
|
-
start_pts = frame.pts,
|
411
|
-
end_pts = end_time,
|
412
|
-
time_base = self.time_base
|
413
|
-
)
|
414
|
-
|
415
|
-
# 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
|
-
)
|
424
|
-
|
425
|
-
def clear(
|
426
|
-
self
|
427
|
-
) -> 'VideoFrameCache':
|
428
|
-
"""
|
429
|
-
Clear the cache by removing all the items.
|
430
|
-
"""
|
431
|
-
self.cache.clear()
|
432
|
-
|
433
|
-
return self
|
434
|
-
|
435
|
-
|
436
|
-
# TODO: Move this to a utils when refactored
|
437
|
-
def trim_audio_frame_pts(
|
438
|
-
frame: av.AudioFrame,
|
439
|
-
start_pts: int,
|
440
|
-
end_pts: int,
|
441
|
-
time_base
|
442
|
-
) -> av.AudioFrame:
|
443
|
-
"""
|
444
|
-
Recorta un AudioFrame para quedarse solo con la parte entre [start_pts, end_pts] en ticks (PTS).
|
445
|
-
"""
|
446
|
-
samples = frame.to_ndarray() # (channels, n_samples)
|
447
|
-
n_channels, n_samples = samples.shape
|
448
|
-
sr = frame.sample_rate
|
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
|
461
|
-
|
462
|
-
# convertir a índices de samples (en ticks → segundos → samples)
|
463
|
-
cut_start_time = (cut_start_pts - frame.pts) * time_base
|
464
|
-
cut_end_time = (cut_end_pts - frame.pts) * time_base
|
465
|
-
|
466
|
-
start_idx = int(cut_start_time * sr)
|
467
|
-
end_idx = int(cut_end_time * sr)
|
468
|
-
|
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
|
-
# )
|
475
|
-
|
476
|
-
cut_samples = samples[:, start_idx:end_idx]
|
477
|
-
|
478
|
-
# crear nuevo AudioFrame
|
479
|
-
new_frame = av.AudioFrame.from_ndarray(cut_samples, format=frame.format, layout=frame.layout)
|
480
|
-
new_frame.sample_rate = sr
|
481
|
-
|
482
|
-
# ajustar PTS → corresponde al inicio real del recorte
|
483
|
-
new_frame.pts = cut_start_pts
|
484
|
-
new_frame.time_base = time_base
|
485
|
-
|
486
|
-
return new_frame
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
def trim_audio_frame_t(
|
491
|
-
frame: av.AudioFrame,
|
492
|
-
start_time: float,
|
493
|
-
end_time: float,
|
494
|
-
time_base
|
495
|
-
) -> av.AudioFrame:
|
496
|
-
"""
|
497
|
-
Recorta un AudioFrame para quedarse solo con la parte entre [start_time, end_time] en segundos.
|
498
|
-
"""
|
499
|
-
samples = frame.to_ndarray() # (channels, n_samples)
|
500
|
-
n_channels, n_samples = samples.shape
|
501
|
-
sr = frame.sample_rate
|
502
|
-
|
503
|
-
frame_start = float(frame.pts * time_base)
|
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
|
@@ -1,21 +0,0 @@
|
|
1
|
-
yta_video_opengl/__init__.py,sha256=ycAx_XYMVDfkuObSvtW6irQ0Wo-fgxEz3fjIRMe8PpY,205
|
2
|
-
yta_video_opengl/classes.py,sha256=t5-Tfc7ecvHl8JlVBp_FVzZT6ole6Ly5-FeBBH7wcxo,37742
|
3
|
-
yta_video_opengl/complete/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
-
yta_video_opengl/complete/timeline.py,sha256=drIeM1xYv-8OM-uwjUifwA5teT5X9LZOEK5Nfzux4FI,9384
|
5
|
-
yta_video_opengl/complete/track.py,sha256=MQJPFyZpl1XFRwYqfq32QzzEESCVtPcnz9yhwm7crng,13729
|
6
|
-
yta_video_opengl/complete/video_on_track.py,sha256=iDD3wyEHblbRxm1e-clMecx104wUfUwKdCq7ZXSq6CU,5127
|
7
|
-
yta_video_opengl/nodes/__init__.py,sha256=TZ-ZO05PZ0_ABq675E22_PngLWOe-_w5s1cLlV3NbWM,3469
|
8
|
-
yta_video_opengl/nodes/audio/__init__.py,sha256=4nKkC70k1UgLcCSPqFWm3cKdaJM0KUmQTwGWv1xFarQ,2926
|
9
|
-
yta_video_opengl/nodes/video/__init__.py,sha256=gSoaoEmjdQmyRwH18mf5z3NAhap3S0RgbeBbfBXi4jc,132
|
10
|
-
yta_video_opengl/nodes/video/opengl.py,sha256=K2pyCJEd9z4gnZqJetKyGPbtHuBzFsx74ZYyzhSqYPo,8510
|
11
|
-
yta_video_opengl/reader/__init__.py,sha256=Cxk838QMjIUOTiO2ZyPGkiPojHlX0c7x6iJkm81_UB4,20039
|
12
|
-
yta_video_opengl/reader/cache.py,sha256=YYx5AX0BkGj1KtiDePHKj-AZlET4ATTqPB0xbr98WOw,17742
|
13
|
-
yta_video_opengl/t.py,sha256=vK23hBQQt_KtBO3ceLQ-SwQbB2jfFsPFX2pYNjYaVwk,5488
|
14
|
-
yta_video_opengl/tests.py,sha256=EdTyYtTUd_mj6geWnrvnF-wZSHCKKvhYgiLclkV73O0,26576
|
15
|
-
yta_video_opengl/utils.py,sha256=yUi17EjNR4SVpvdDUwUaKl4mBCb1uyFCSGoIX3Zr2F0,15586
|
16
|
-
yta_video_opengl/video.py,sha256=ap3H-aA29Yj-SAsTKsNq2gotcBUatQVQ2jmplgKMm9Q,8465
|
17
|
-
yta_video_opengl/writer.py,sha256=QwvjQcEkzn1WAVqVTFiI6tYIXJO67LKKUTJGO_eflFM,8893
|
18
|
-
yta_video_opengl-0.0.13.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
19
|
-
yta_video_opengl-0.0.13.dist-info/METADATA,sha256=DvAvfeMY-o4WMVKdk5d8TiphrseGwMFadDrinfE0wh8,714
|
20
|
-
yta_video_opengl-0.0.13.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
21
|
-
yta_video_opengl-0.0.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|