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.
@@ -218,6 +218,7 @@ class Timeline:
218
218
  for t in get_ts(start, end, self.fps):
219
219
  frame = self.get_frame_at(t)
220
220
 
221
+ print(f'Getting t:{str(float(t))}')
221
222
  #print(frame)
222
223
 
223
224
  # We need to adjust our output elements to be
@@ -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
- # TODO: I am generating a tuple in the
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.get_frame_from_t(t)
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.get_frame_from_t(t)
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
- for frame in self.audio_cache.get_frames(t.truncated, t.next(1).truncated):
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.get_frames(start, end):
686
+ for frame in self.audio_cache.get_audio_frames(start, end):
705
687
  yield frame
706
688
 
707
689
  def close(
@@ -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
- ) -> float:
65
+ ) -> Union[int, Fraction, None]:
56
66
  """
57
- The frames per second as a float.
67
+ The frames per second.
58
68
  """
59
69
  return (
60
- float(self.stream.average_rate)
70
+ self.stream.average_rate
61
71
  if self.stream.type == 'video' else
62
- float(self.stream.rate)
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 get_frame_from_pts(
213
+
214
+ def _seek(
180
215
  self,
181
216
  pts: int
182
- ) -> Union[VideoFrame, AudioFrame, None]:
217
+ ):
183
218
  """
184
- Get the frame that has the provided 'pts'.
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
- 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é.
224
+ TODO: Apply the padding only to audio
225
+ frame reading (?)
193
226
  """
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
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 code is repeated, refactor
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, key_frame_pts - pts_pad),
235
+ offset = max(0, pts - pts_pad),
209
236
  stream = self.stream
210
237
  )
211
238
 
212
- decoded = None
213
- for frame in self.container.decode(self.stream):
214
- # TODO: Could 'frame' be None (?)
215
- if frame.pts is None:
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
- # Store in cache if needed
219
- self._store_frame_in_cache(frame)
283
+ self._last_packet_accessed = packet
220
284
 
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.
285
+ for frame in packet.decode():
286
+ if frame.pts is None:
287
+ continue
226
288
 
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
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
- # TODO: Is this working? We need previous
235
- # frames to be able to decode...
236
- return decoded
298
+ if current_frame_time >= end:
299
+ break
237
300
 
238
- # TODO: I'm not using this method...
239
- def get_frame(
301
+ def get_audio_frame_from_t(
240
302
  self,
241
- index: int
242
- ) -> Union[VideoFrame, AudioFrame]:
303
+ t: Union[int, float, Fraction]
304
+ ):
243
305
  """
244
- Get the frame with the given 'index' from
245
- the cache.
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
- # TODO: Maybe we can accept 'pts' also
248
- pts = index_to_pts(index, self.time_base, self.fps)
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
- 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(
319
+ def get_audio_frames_from_t(
257
320
  self,
258
321
  t: Union[int, float, Fraction]
259
- ) -> Union[VideoFrame, AudioFrame]:
322
+ ):
260
323
  """
261
- Get the frame with the given 't' time moment
262
- from the cache.
324
+ Get all the audio frames that must be
325
+ played at the 't' time moment provided.
263
326
  """
264
- return self.get_frame_from_pts(T(t, self.time_base).truncated_pts)
327
+ for frame in self.get_audio_frames(t):
328
+ yield frame
265
329
 
266
- def get_frames(
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 between
273
- the provided 'start' and 'end' time in
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
- # 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
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).truncated_pts
349
+ T(end, self.time_base).truncated
320
350
  if end is not None else
321
- None
351
+ start + (1 / self.fps)
322
352
  )
323
- key_frame_pts = self._get_nearest_keyframe_pts(start)
324
353
 
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
- )
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
- 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))
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 frame_end_pts <= start:
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
- # We need: from 1 to 2
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
- frame.pts < start and
376
- frame_end_pts > start
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
- frame_end_pts
382
- if frame_end_pts <= end else
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
- # TODO: I'm using too much 'pts_to_t'
388
- frame = trim_audio_frame_pts(
414
+ frame = trim_audio_frame(
389
415
  frame = frame,
390
- start_pts = start,
391
- end_pts = end_time,
416
+ start = start,
417
+ end = end_time,
392
418
  time_base = self.time_base
393
419
  )
394
420
  elif (
395
- frame.pts >= start and
396
- frame.pts < end
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
- frame_end_pts
401
- if frame_end_pts <= end else
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
- # TODO: I'm using too much 'pts_to_t'
408
- frame = trim_audio_frame_pts(
433
+ frame = trim_audio_frame(
409
434
  frame = frame,
410
- start_pts = frame.pts,
411
- end_pts = end_time,
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
- # 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:
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
- Recorta un AudioFrame para quedarse solo con la parte entre [start_pts, end_pts] en ticks (PTS).
460
+ Trim an audio frame to obtain the part between
461
+ [start, end), that is provided in seconds.
445
462
  """
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
463
+ # (channels, n_samples)
464
+ samples = frame.to_ndarray()
465
+ n_samples = samples.shape[1]
461
466
 
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
467
+ # In seconds
468
+ frame_start = frame.pts * float(time_base)
469
+ frame_end = frame_start + (n_samples / frame.sample_rate)
465
470
 
466
- start_idx = int(cut_start_time * sr)
467
- end_idx = int(cut_end_time * sr)
471
+ # Overlapping
472
+ cut_start = max(frame_start, float(start))
473
+ cut_end = min(frame_end, float(end))
468
474
 
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
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
- 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
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 previou value).
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
- # 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
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.get_frame_from_t(self._get_real_t(t))
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,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: yta-video-opengl
3
- Version: 0.0.13
3
+ Version: 0.0.14
4
4
  Summary: Youtube Autonomous Video OpenGL Module
5
5
  Author: danialcala94
6
6
  Author-email: danielalcalavalera@gmail.com
@@ -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=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
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=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
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=ap3H-aA29Yj-SAsTKsNq2gotcBUatQVQ2jmplgKMm9Q,8465
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.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,,
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,,