yta-video-opengl 0.0.8__py3-none-any.whl → 0.0.9__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.
@@ -398,9 +398,14 @@ class VideoReader:
398
398
  """
399
399
  The stream that includes the audio.
400
400
  """
401
- self.cache: VideoFrameCache = None
401
+ self.video_cache: VideoFrameCache = None
402
402
  """
403
- The frame cache system to optimize
403
+ The video frame cache system to optimize
404
+ the way we access to the frames.
405
+ """
406
+ self.audio_cache: VideoFrameCache = None
407
+ """
408
+ The audio frame cache system to optimize
404
409
  the way we access to the frames.
405
410
  """
406
411
 
@@ -434,7 +439,8 @@ class VideoReader:
434
439
  self.video_stream.thread_type = 'AUTO'
435
440
  self.audio_stream = self.container.streams.audio[0]
436
441
  self.audio_stream.thread_type = 'AUTO'
437
- self.cache = VideoFrameCache(self)
442
+ self.video_cache = VideoFrameCache(self.container, self.video_stream)
443
+ self.audio_cache = VideoFrameCache(self.container, self.audio_stream)
438
444
 
439
445
  def seek(
440
446
  self,
@@ -564,10 +570,45 @@ class VideoReader:
564
570
  index: int
565
571
  ) -> 'VideoFrame':
566
572
  """
567
- Get the frame with the given 'index', using
568
- the cache system.
573
+ Get the video frame with the given 'index',
574
+ using the video cache system.
575
+ """
576
+ return self.video_cache.get_frame(index)
577
+
578
+ # TODO: Will we use this (?)
579
+ def get_audio_frame(
580
+ self,
581
+ index: int
582
+ ) -> 'VideoFrame':
583
+ """
584
+ Get the audio frame with the given 'index',
585
+ using the audio cache system.
586
+ """
587
+ return self.video_cache.get_frame(index)
588
+
589
+ def get_frames(
590
+ self,
591
+ start: float = 0.0,
592
+ end: Union[float, None] = None
593
+ ):
569
594
  """
570
- return self.cache.get_frame(index)
595
+ Iterator to get the video frames in between
596
+ the provided 'start' and 'end' time moments.
597
+ """
598
+ for frame in self.video_cache.get_frames(start, end):
599
+ yield frame
600
+
601
+ def get_audio_frames(
602
+ self,
603
+ start: float = 0.0,
604
+ end: Union[float, None] = None
605
+ ):
606
+ """
607
+ Iterator to get the audio frames in between
608
+ the provided 'start' and 'end' time moments.
609
+ """
610
+ for frame in self.audio_cache.get_frames(start, end):
611
+ yield frame
571
612
 
572
613
  def close(
573
614
  self
@@ -16,7 +16,16 @@ 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
18
  """
19
+ from yta_video_opengl.utils import t_to_pts, pts_to_t, pts_to_index
20
+ from av.container import InputContainer
21
+ from av.video.stream import VideoStream
22
+ from av.audio.stream import AudioStream
23
+ from av.video.frame import VideoFrame
24
+ from av.audio.frame import AudioFrame
25
+ from yta_validation.parameter import ParameterValidator
26
+ from fractions import Fraction
19
27
  from collections import OrderedDict
28
+ from typing import Union
20
29
 
21
30
 
22
31
  class VideoFrameCache:
@@ -26,33 +35,44 @@ class VideoFrameCache:
26
35
  """
27
36
 
28
37
  @property
29
- def container(
38
+ def fps(
30
39
  self
31
- ) -> 'InputContainer':
40
+ ) -> float:
32
41
  """
33
- Shortcut to the video reader instance container.
42
+ The frames per second as a float.
34
43
  """
35
- return self.reader_instance.container
44
+ return (
45
+ float(self.stream.average_rate)
46
+ if self.stream.type == 'video' else
47
+ float(self.stream.rate)
48
+ )
36
49
 
37
50
  @property
38
- def stream(
51
+ def time_base(
39
52
  self
40
- ) -> 'VideoStream':
53
+ ) -> Union[Fraction, None]:
41
54
  """
42
- Shortcut to the video reader instance video
43
- stream.
55
+ The time base of the stream.
44
56
  """
45
- return self.reader_instance.video_stream
57
+ return self.stream.time_base
46
58
 
47
59
  def __init__(
48
60
  self,
49
- reader: 'VideoReader',
61
+ container: InputContainer,
62
+ stream: Union[VideoStream, AudioStream],
50
63
  size: int = 50
51
64
  ):
52
- self.reader_instance: 'VideoReader' = reader
65
+ ParameterValidator.validate_mandatory_instance_of('container', container, InputContainer)
66
+ ParameterValidator.validate_mandatory_instance_of('stream', stream, [VideoStream, AudioStream])
67
+ ParameterValidator.validate_mandatory_positive_int('size', size)
68
+
69
+ self.container: InputContainer = container
70
+ """
71
+ The pyav container.
53
72
  """
54
- The video reader instance this cache belongs
55
- to.
73
+ self.stream: Union[VideoStream, AudioStream] = stream
74
+ """
75
+ The pyav stream.
56
76
  """
57
77
  self.cache: OrderedDict = OrderedDict()
58
78
  """
@@ -69,21 +89,40 @@ class VideoFrameCache:
69
89
  end.
70
90
  """
71
91
 
92
+ self._prepare()
93
+
94
+ def _prepare(
95
+ self
96
+ ):
72
97
  # Index key frames
73
98
  for packet in self.container.demux(self.stream):
74
99
  if packet.is_keyframe:
75
100
  self.key_frames_pts.append(packet.pts)
76
101
 
77
102
  self.container.seek(0)
78
- # TODO: Maybe this is better (?)
79
- #self.reader_instance.reset()
103
+
104
+ def _get_nearest_keyframe_fps(
105
+ self,
106
+ pts: int
107
+ ):
108
+ """
109
+ Get the fps of the keyframe that is the
110
+ nearest to the provided 'pts'. Useful to
111
+ seek and start decoding frames from that
112
+ keyframe.
113
+ """
114
+ return max([
115
+ key_frame_pts
116
+ for key_frame_pts in self.key_frames_pts
117
+ if key_frame_pts <= pts
118
+ ])
80
119
 
81
120
  def _get_frame_by_pts(
82
121
  self,
83
- target_pts
122
+ pts: int
84
123
  ):
85
124
  """
86
- Get the frame that has the provided 'target_pts'.
125
+ Get the frame that has the provided 'pts'.
87
126
 
88
127
  This method will start decoding frames from the
89
128
  most near key frame (the one with the nearer
@@ -94,11 +133,7 @@ class VideoFrameCache:
94
133
  requested is not stored in the caché.
95
134
  """
96
135
  # Look for the most near key frame
97
- key_frame_pts = max([
98
- key_frame_pts
99
- for key_frame_pts in self.key_frames_pts
100
- if key_frame_pts <= target_pts
101
- ])
136
+ key_frame_pts = self._get_nearest_keyframe_fps(pts)
102
137
 
103
138
  # Go to the key frame that includes it
104
139
  self.container.seek(key_frame_pts, stream = self.stream)
@@ -106,21 +141,20 @@ class VideoFrameCache:
106
141
  decoded = None
107
142
  for frame in self.container.decode(self.stream):
108
143
  # TODO: Could 'frame' be None (?)
109
- pts = frame.pts
110
- if pts is None:
144
+ if frame.pts is None:
111
145
  continue
112
146
 
113
147
  # Store in cache if needed
114
- if pts not in self.cache:
148
+ if frame.pts not in self.cache:
115
149
  # TODO: The 'format' must be dynamic
116
- self.cache[pts] = frame.to_ndarray(format = "rgb24")
150
+ self.cache[frame.pts] = frame.to_ndarray(format = "rgb24")
117
151
 
118
152
  # Clean cache if full
119
153
  if len(self.cache) > self.size:
120
154
  self.cache.popitem(last = False)
121
155
 
122
- if pts >= target_pts:
123
- decoded = self.cache[pts]
156
+ if frame.pts >= pts:
157
+ decoded = self.cache[frame.pts]
124
158
  break
125
159
 
126
160
  return decoded
@@ -128,21 +162,65 @@ class VideoFrameCache:
128
162
  def get_frame(
129
163
  self,
130
164
  index: int
131
- ) -> 'VideoFrame':
165
+ ) -> Union[VideoFrame, AudioFrame]:
132
166
  """
133
167
  Get the frame with the given 'index' from
134
168
  the cache.
135
169
  """
136
- # convertir frame_number a PTS (timestamps internos)
137
- time_base = self.stream.time_base
138
- fps = float(self.stream.average_rate)
139
- target_pts = int(index / fps / time_base)
170
+ # TODO: Maybe we can accept 't' and 'pts' also
171
+ target_pts = int(index / self.fps / self.time_base)
140
172
 
141
173
  return (
142
174
  self.cache[target_pts]
143
175
  if target_pts in self.cache else
144
176
  self._get_frame_by_pts(target_pts)
145
177
  )
178
+
179
+ def get_frames(
180
+ self,
181
+ start: float = 0,
182
+ end: Union[float, None] = None
183
+ ):
184
+ """
185
+ Get all the frames in the range between
186
+ the provided 'start' and 'end' time in
187
+ seconds.
188
+ """
189
+ # TODO: I create this method by default using
190
+ # the cache. Think about how to implement it
191
+ # and apply it here, please.
192
+ # Go to the nearest key frame
193
+ start = t_to_pts(start, self.time_base)
194
+ end = (
195
+ t_to_pts(end, self.time_base)
196
+ if end is not None else
197
+ None
198
+ )
199
+ key_frame_pts = self._get_nearest_keyframe_fps(start)
200
+
201
+ # Go to the nearest key frame to start decoding
202
+ self.container.seek(key_frame_pts, stream = self.stream)
203
+
204
+ for packet in self.container.demux(self.stream):
205
+ for frame in packet.decode():
206
+ if frame.pts is None:
207
+ continue
208
+
209
+ if frame.pts < start:
210
+ continue
211
+
212
+ if (
213
+ end is not None and
214
+ frame.pts > end
215
+ ):
216
+ return
217
+
218
+ # TODO: Maybe send a @dataclass instead (?)
219
+ yield (
220
+ frame,
221
+ pts_to_t(frame.pts, self.time_base),
222
+ pts_to_index(frame.pts, self.time_base, self.fps)
223
+ )
146
224
 
147
225
  def clear(
148
226
  self
yta_video_opengl/tests.py CHANGED
@@ -583,7 +583,7 @@ def video_modified_stored():
583
583
  from yta_video_opengl.utils import texture_to_frame, frame_to_texture
584
584
  from yta_video_opengl.video import Video
585
585
 
586
- Video(VIDEO_PATH, 0, 0.5).save_as(OUTPUT_PATH)
586
+ Video(VIDEO_PATH, 0.25, 0.75).save_as(OUTPUT_PATH)
587
587
 
588
588
  return
589
589
 
yta_video_opengl/video.py CHANGED
@@ -89,17 +89,23 @@ class Video:
89
89
  - `t` as the frame time moment
90
90
  - `index` as the frame index
91
91
  """
92
- for frame in iterate_stream_frames_demuxing(
93
- container = self.reader.container,
94
- video_stream = self.reader.video_stream,
95
- audio_stream = self.reader.audio_stream,
96
- video_start_pts = self.start_pts,
97
- video_end_pts = self.end_pts,
98
- audio_start_pts = self.audio_start_pts,
99
- audio_end_pts = self.audio_end_pts
100
- ):
92
+ for frame in self.reader.get_frames(self.start, self.end):
101
93
  yield frame
102
94
 
95
+ for frame in self.reader.get_audio_frames(self.start, self.end):
96
+ yield frame
97
+
98
+ # for frame in iterate_stream_frames_demuxing(
99
+ # container = self.reader.container,
100
+ # video_stream = self.reader.video_stream,
101
+ # audio_stream = self.reader.audio_stream,
102
+ # video_start_pts = self.start_pts,
103
+ # video_end_pts = self.end_pts,
104
+ # audio_start_pts = self.audio_start_pts,
105
+ # audio_end_pts = self.audio_end_pts
106
+ # ):
107
+ # yield frame
108
+
103
109
  def __init__(
104
110
  self,
105
111
  filename: str,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: yta-video-opengl
3
- Version: 0.0.8
3
+ Version: 0.0.9
4
4
  Summary: Youtube Autonomous Video OpenGL Module
5
5
  Author: danialcala94
6
6
  Author-email: danielalcalavalera@gmail.com
@@ -0,0 +1,12 @@
1
+ yta_video_opengl/__init__.py,sha256=ycAx_XYMVDfkuObSvtW6irQ0Wo-fgxEz3fjIRMe8PpY,205
2
+ yta_video_opengl/classes.py,sha256=VUw73kfz8kxYLE0x0LxNHqFekF3CklcyofCNN-z57Lg,37706
3
+ yta_video_opengl/reader/__init__.py,sha256=rAWISZ7OzDnzar0At-LCfDA-MmWzax2jT2l5gySv4aw,16911
4
+ yta_video_opengl/reader/cache.py,sha256=UKhZvgY80ySuOYH52ikco6affsm8bjP656EroVR9Utg,6960
5
+ yta_video_opengl/tests.py,sha256=NZ-W1ak-ygwL9wATzEXtlCeCZX74ij_TZhktetMnOD4,25810
6
+ yta_video_opengl/utils.py,sha256=y0N1mS9FjpB4nFnx00K7sIs5EsqMkTe8C0bzLXZe9YM,10479
7
+ yta_video_opengl/video.py,sha256=Y14-Bsq7AH0GenwbPk61giD9eLHZDmWeZvP_iZn0e7w,5182
8
+ yta_video_opengl/writer.py,sha256=7xglz8xHOXMtWkctzuB21Y-e9xWFXYcklt3jVUN4svQ,8198
9
+ yta_video_opengl-0.0.9.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
10
+ yta_video_opengl-0.0.9.dist-info/METADATA,sha256=iYodm7r8DJD5lpPAs92lAClPcJ4dFrS94Dv5WjHcx5Q,670
11
+ yta_video_opengl-0.0.9.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
12
+ yta_video_opengl-0.0.9.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- yta_video_opengl/__init__.py,sha256=ycAx_XYMVDfkuObSvtW6irQ0Wo-fgxEz3fjIRMe8PpY,205
2
- yta_video_opengl/classes.py,sha256=VUw73kfz8kxYLE0x0LxNHqFekF3CklcyofCNN-z57Lg,37706
3
- yta_video_opengl/reader/__init__.py,sha256=95thY3A2_QOPdKInQIN7eOlxwzZanFNi5yePmlsvdXc,15620
4
- yta_video_opengl/reader/cache.py,sha256=Y3lQrirQJz7zFeiJQeJnkzyghYeMahkpKzsouzB90VI,4421
5
- yta_video_opengl/tests.py,sha256=h_7juB8iV-pUj9EUMO_zzPZGj2IGIe60lK7xskI2n9I,25806
6
- yta_video_opengl/utils.py,sha256=y0N1mS9FjpB4nFnx00K7sIs5EsqMkTe8C0bzLXZe9YM,10479
7
- yta_video_opengl/video.py,sha256=xPlWP6ulTKsTqJd_7SwudAGUPFNOS6Hr6IRntdAvfx4,4966
8
- yta_video_opengl/writer.py,sha256=7xglz8xHOXMtWkctzuB21Y-e9xWFXYcklt3jVUN4svQ,8198
9
- yta_video_opengl-0.0.8.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
10
- yta_video_opengl-0.0.8.dist-info/METADATA,sha256=LBw62abHLEFbp1x2bFcqs9CNCmr0a16pQ_UsvlFta2E,670
11
- yta_video_opengl-0.0.8.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
12
- yta_video_opengl-0.0.8.dist-info/RECORD,,