@@ -174,29 +174,38 @@ def seek(self, target: Union[FrameTimecode, float, int]):
174174 SeekError: An error occurs while seeking, or seeking is not supported.
175175 ValueError: `target` is not a valid value (i.e. it is negative).
176176 """
177+ success = False
177178 if not isinstance (target , FrameTimecode ):
178179 target = FrameTimecode (target , self .frame_rate )
179180 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
181189 except OSError as ex :
182- # Leave the object in a valid state.
183- self .reset ()
184190 # TODO(#380): Other backends do not currently throw an exception if attempting to seek
185191 # past EOF. We need to ensure consistency for seeking past end of video with respect to
186192 # errors and behaviour, and should probably gracefully stop at the last frame instead
187193 # of throwing an exception.
188194 if target >= self .duration :
189195 raise SeekError ("Target frame is beyond end of video!" ) from ex
190196 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 ()
193201
194- def reset (self ):
202+ def reset (self , print_infos = False ):
195203 """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
198206 self ._frame_number = 0
199207 self ._eof = False
208+ self ._reader = FFMPEG_VideoReader (self ._path , print_infos = print_infos )
200209
201210 def read (self , decode : bool = True , advance : bool = True ) -> Union [np .ndarray , bool ]:
202211 """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
210219 If decode = False, a bool indicating if advancing to the the next frame succeeded.
211220 """
212221 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
213225 if self ._last_frame_rgb is None :
214226 self ._last_frame_rgb = cv2 .cvtColor (self ._last_frame , cv2 .COLOR_BGR2RGB )
215227 return self ._last_frame_rgb
216- if not hasattr (self ._reader , "lastread" ):
228+ if not hasattr (self ._reader , "lastread" ) or self . _eof :
217229 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 :
222236 if self ._eof :
223237 return False
224238 self ._eof = True
225239 self ._frame_number += 1
226240 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 :
228243 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