@@ -177,29 +177,32 @@ def seek(self, target: Union[FrameTimecode, float, int]):
177177 if not isinstance (target , FrameTimecode ):
178178 target = FrameTimecode (target , self .frame_rate )
179179 try :
180- self ._reader .get_frame (target .get_seconds ())
180+ self ._last_frame = self ._reader .get_frame (target .get_seconds ())
181+ if hasattr (self ._reader , "last_read" ) and target >= self .duration :
182+ raise SeekError ("MoviePy > 2.0 does not have proper EOF semantics." )
183+ self ._frame_number = min (
184+ target .frame_num ,
185+ FrameTimecode (self ._reader .infos ["duration" ], self .frame_rate ).frame_num - 1 ,
186+ )
181187 except OSError as ex :
182- # Leave the object in a valid state.
183- self .reset ()
184188 # TODO(#380): Other backends do not currently throw an exception if attempting to seek
185189 # past EOF. We need to ensure consistency for seeking past end of video with respect to
186190 # errors and behaviour, and should probably gracefully stop at the last frame instead
187191 # of throwing an exception.
188192 if target >= self .duration :
189193 raise SeekError ("Target frame is beyond end of video!" ) from ex
190194 raise
191- self ._last_frame = self ._reader .lastread
192- self ._frame_number = min (
193- target .frame_num ,
194- FrameTimecode (self ._reader .infos ["duration" ], self .frame_rate ).frame_num - 1 ,
195- )
195+ finally :
196+ # Leave the object in a valid state.
197+ self .reset ()
196198
197- def reset (self ):
199+ def reset (self , print_infos = False ):
198200 """Close and re-open the VideoStream (should be equivalent to calling `seek(0)`)."""
199- self ._reader . initialize ()
200- self ._last_frame = self . _reader . read_frame ()
201+ self ._last_frame = False
202+ self ._last_frame_rgb = None
201203 self ._frame_number = 0
202204 self ._eof = False
205+ self ._reader = FFMPEG_VideoReader (self ._path , print_infos = print_infos )
203206
204207 def read (self , decode : bool = True , advance : bool = True ) -> Union [np .ndarray , bool ]:
205208 """Read and decode the next frame as a np.ndarray. Returns False when video ends.
@@ -213,21 +216,26 @@ def read(self, decode: bool = True, advance: bool = True) -> Union[np.ndarray, b
213216 If decode = False, a bool indicating if advancing to the the next frame succeeded.
214217 """
215218 if not advance :
219+ last_frame_valid = self ._last_frame is not None and self ._last_frame is not False
220+ if not last_frame_valid :
221+ return False
216222 if self ._last_frame_rgb is None :
217223 self ._last_frame_rgb = cv2 .cvtColor (self ._last_frame , cv2 .COLOR_BGR2RGB )
218224 return self ._last_frame_rgb
219- if not hasattr (self ._reader , "lastread" ):
225+ if not hasattr (self ._reader , "lastread" ) or self . _eof :
220226 return False
221- # TODO: In Moviepy2.0 this is broken - lastread is updated in-place in some cases.
222- self ._last_frame = self ._reader .lastread
227+ has_last_read = hasattr (self ._reader , "last_read" )
228+ self ._last_frame = self ._reader .last_read if has_last_read else self ._reader .lastread
229+ # Read the *next* frame for the following call to read, and to check for EOF.
223230 frame = self ._reader .read_frame ()
224231 if frame is self ._last_frame :
225232 if self ._eof :
226233 return False
227234 self ._eof = True
228235 self ._frame_number += 1
229236 if decode :
230- if self ._last_frame is not None :
237+ last_frame_valid = self ._last_frame is not None and self ._last_frame is not False
238+ if last_frame_valid :
231239 self ._last_frame_rgb = cv2 .cvtColor (self ._last_frame , cv2 .COLOR_BGR2RGB )
232- return self . _last_frame_rgb if not self ._eof else False
240+ return self ._last_frame_rgb
233241 return not self ._eof
0 commit comments