@@ -174,29 +174,38 @@ def seek(self, target: Union[FrameTimecode, float, int]):
174
174
SeekError: An error occurs while seeking, or seeking is not supported.
175
175
ValueError: `target` is not a valid value (i.e. it is negative).
176
176
"""
177
+ success = False
177
178
if not isinstance (target , FrameTimecode ):
178
179
target = FrameTimecode (target , self .frame_rate )
179
180
try :
180
- self ._reader .get_frame (target .get_seconds ())
181
+ self ._last_frame = self ._reader .get_frame (target .get_seconds ())
182
+ if hasattr (self ._reader , "last_read" ) and target >= self .duration :
183
+ raise SeekError ("MoviePy > 2.0 does not have proper EOF semantics (#461)." )
184
+ self ._frame_number = min (
185
+ target .frame_num ,
186
+ FrameTimecode (self ._reader .infos ["duration" ], self .frame_rate ).frame_num - 1 ,
187
+ )
188
+ success = True
181
189
except OSError as ex :
182
- # Leave the object in a valid state.
183
- self .reset ()
184
190
# TODO(#380): Other backends do not currently throw an exception if attempting to seek
185
191
# past EOF. We need to ensure consistency for seeking past end of video with respect to
186
192
# errors and behaviour, and should probably gracefully stop at the last frame instead
187
193
# of throwing an exception.
188
194
if target >= self .duration :
189
195
raise SeekError ("Target frame is beyond end of video!" ) from ex
190
196
raise
191
- self ._last_frame = self ._reader .lastread
192
- self ._frame_number = target .frame_num
197
+ finally :
198
+ # Leave the object in a valid state on any errors.
199
+ if not success :
200
+ self .reset ()
193
201
194
- def reset (self ):
202
+ def reset (self , print_infos = False ):
195
203
"""Close and re-open the VideoStream (should be equivalent to calling `seek(0)`)."""
196
- self ._reader . initialize ()
197
- self ._last_frame = self . _reader . read_frame ()
204
+ self ._last_frame = False
205
+ self ._last_frame_rgb = None
198
206
self ._frame_number = 0
199
207
self ._eof = False
208
+ self ._reader = FFMPEG_VideoReader (self ._path , print_infos = print_infos )
200
209
201
210
def read (self , decode : bool = True , advance : bool = True ) -> Union [np .ndarray , bool ]:
202
211
"""Read and decode the next frame as a np.ndarray. Returns False when video ends.
@@ -210,21 +219,27 @@ def read(self, decode: bool = True, advance: bool = True) -> Union[np.ndarray, b
210
219
If decode = False, a bool indicating if advancing to the the next frame succeeded.
211
220
"""
212
221
if not advance :
222
+ last_frame_valid = self ._last_frame is not None and self ._last_frame is not False
223
+ if not last_frame_valid :
224
+ return False
213
225
if self ._last_frame_rgb is None :
214
226
self ._last_frame_rgb = cv2 .cvtColor (self ._last_frame , cv2 .COLOR_BGR2RGB )
215
227
return self ._last_frame_rgb
216
- if not hasattr (self ._reader , "lastread" ):
228
+ if not hasattr (self ._reader , "lastread" ) or self . _eof :
217
229
return False
218
- self ._last_frame = self ._reader .lastread
219
- self ._reader .read_frame ()
220
- if self ._last_frame is self ._reader .lastread :
221
- # Didn't decode a new frame, must have hit EOF.
230
+ has_last_read = hasattr (self ._reader , "last_read" )
231
+ # In MoviePy 2.0 there is a separate property we need to read named differently (#461).
232
+ self ._last_frame = self ._reader .last_read if has_last_read else self ._reader .lastread
233
+ # Read the *next* frame for the following call to read, and to check for EOF.
234
+ frame = self ._reader .read_frame ()
235
+ if frame is self ._last_frame :
222
236
if self ._eof :
223
237
return False
224
238
self ._eof = True
225
239
self ._frame_number += 1
226
240
if decode :
227
- if self ._last_frame is not None :
241
+ last_frame_valid = self ._last_frame is not None and self ._last_frame is not False
242
+ if last_frame_valid :
228
243
self ._last_frame_rgb = cv2 .cvtColor (self ._last_frame , cv2 .COLOR_BGR2RGB )
229
- return self ._last_frame_rgb
230
- return True
244
+ return self ._last_frame_rgb
245
+ return not self . _eof
0 commit comments