@@ -315,9 +315,9 @@ def __init__(self, id, *args, display='frame', delay=None, quit=ord('q'),
315315
316316 self ._api_preference = kwargs .get ('apiPreference' , None )
317317
318- def __enter__ (self ):
318+ def __enter__ (self , force = False ):
319319 ''' Enter a re-entrant context for this camera. '''
320- if not self .isOpened ():
320+ if force or not self .isOpened ():
321321 if self ._api_preference :
322322 self .open (self ._id , self ._api_preference )
323323 else :
@@ -353,7 +353,7 @@ def __exit__(self, exc_type, exc_value, exc_traceback):
353353 except cv2 .error as e :
354354 print ('Failed to destroy window(s)' , e )
355355
356- waitKey (1 ) # allow the GUI manager to update
356+ waitKey (3 ) # allow the GUI manager to update
357357
358358 def __iter__ (self ):
359359 return self
@@ -577,11 +577,10 @@ def _grabber(self):
577577 self ._wait_until_needed ()
578578 # read the latest frame
579579 read_success , frame = super (ContextualVideoCapture , self ).read ()
580- if not read_success :
581- raise IOError ('Failure to read frame from camera.' )
582580
583581 # apply any desired pre-processing and store for main thread
584- self ._preprocessed = self ._preprocess (frame )
582+ self ._preprocessed = self ._preprocess (frame ) if read_success \
583+ else None
585584 # inform that image is ready for access/main processing
586585 self ._inform_image_ready ()
587586
@@ -619,10 +618,13 @@ def read(self, image=None):
619618 self ._wait_for_camera_image ()
620619 preprocessed = self ._preprocessed
621620 self ._get_latest_image ()
622- self .image = self ._process (preprocessed )
623- if image is not None :
624- image = self .image
625- self .status = True
621+ if preprocessed is None :
622+ self .status , self .image = False , None
623+ else :
624+ self .image = self ._process (preprocessed )
625+ if image is not None :
626+ image = self .image
627+ self .status = True
626628 return self .status , self .image
627629
628630
@@ -637,7 +639,8 @@ class VideoReader(LockedCamera):
637639 'proportion' : cv2 .CAP_PROP_POS_AVI_RATIO ,
638640 }
639641
640- FASTER , SLOWER , REWIND , FORWARD , RESET = (ord (key ) for key in 'wsadr' )
642+ FASTER , SLOWER , REWIND , FORWARD , RESET , RESTART = \
643+ (ord (key ) for key in 'wsadrb' )
641644 FORWARD_DIRECTION , REVERSE_DIRECTION = 1 , - 1
642645 MIN_DELAY = 1 # integer milliseconds
643646
@@ -646,6 +649,8 @@ def __init__(self, filename, *args, start=None, end=None, auto_delay=True,
646649 ** kwargs ):
647650 ''' Initialise a video reader from the given file.
648651
652+ For default key-bindings see 'auto_delay' details.
653+
649654 'filename' is the string path of a video file. Depending on the file
650655 format some features may not be available.
651656 'start' and 'end' denote the respective times of playback, according
@@ -664,12 +669,14 @@ def __init__(self, filename, *args, start=None, end=None, auto_delay=True,
664669 'skip_frames' is True), and 'd' returning to forwards playback.
665670 The 'r' key can be pressed to reset to 1x speed and forwards
666671 direction playback. 'a' and 'd' can be used while paused to step
667- back and forwards, regardless of skip_frames. These defaults can be
668- overridden using the 'play_commands' and 'pause_effects' keyword
669- arguments, supplying a dictionary of key ordinals that sets the
670- desired behaviour. Note that the defaults are set internally, so to
671- turn them off the dictionary must be used, with e.g.
672- play_commands={ord('a'):lambda vid:None} to disable rewinding.
672+ back and forwards, regardless of skip_frames. 'b' can be used while
673+ playing or paused to jump the video back to its starting point.
674+ These defaults can be overridden using the 'play_commands' and
675+ 'pause_effects' keyword arguments, supplying a dictionary of key
676+ ordinals that sets the desired behaviour. Note that the defaults
677+ are set internally, so to turn them off the dictionary must be
678+ used, with e.g. play_commands={ord('a'):lambda vid:None} to disable
679+ rewinding.
673680 'fps' is a float specifying the desired frames per second for playback.
674681 If left as None the fps is read from file, or if that fails is set
675682 to 25 by default. Value is ignored if 'auto_delay' is False.
@@ -684,8 +691,9 @@ def __init__(self, filename, *args, start=None, end=None, auto_delay=True,
684691 formats with slow video frame setting times, and inconsistent
685692 skipping amounts with 'auto_delay' may cause issues with
686693 time-dependent processing.
687- 'verbose' is a boolean determining if playback speed and direction
688- changes are printed to the terminal. Defaults to True.
694+ 'verbose' is a boolean determining if status updates (e.g. initial fps,
695+ and playback speed and direction changes) are printed. Defaults to
696+ True.
689697
690698 *args and **kwargs get passed up the inheritance chain, with notable
691699 keywords including the 'preprocess' and 'process' functions which
@@ -701,35 +709,36 @@ def __init__(self, filename, *args, start=None, end=None, auto_delay=True,
701709 self .filename = filename
702710 self ._fps = fps or self .fps or 25 # user-specified or auto-retrieved
703711 self ._period = 1e3 / self ._fps
712+ self ._verbose = verbose
713+ self .status = True
704714 self ._initialise_delay (auto_delay )
705- self ._initialise_playback (start , end , skip_frames , verbose )
715+ self ._initialise_playback (start , end , skip_frames )
706716
707717 def _initialise_delay (self , auto_delay ):
708718 ''' Determines the delay automatically, or leaves as None. '''
709719 if auto_delay :
710720 if self ._fps == 0 or self ._fps >= 1e3 :
711- print ('failed to determine fps, setting to 25' )
721+ self . verbos_print ('failed to determine fps, setting to 25' )
712722 self ._period = 1e3 / 25
713723 # set a bit low to allow image read times
714- self ._delay = self ._period - 5
724+ self ._delay = self ._period - 5
715725 else :
716726 self ._delay = int (self ._period )
717- print ('delay set automatically to' ,
718- f'{ self ._delay } ms from fps={ self ._fps } ' )
727+ self . verbose_print ('delay set automatically to' ,
728+ f'{ self ._delay } ms from fps={ self ._fps } ' )
719729 else :
720730 self ._delay = None
721731 if self ._destroy == - 1 :
722732 self ._destroy = None
723733
724- def _initialise_playback (self , start , end , skip_frames , verbose ):
734+ def _initialise_playback (self , start , end , skip_frames ):
725735 ''' Set up playback settings as specified. '''
726736 self ._wait_for_camera_image () # don't set frame while grabber running
727737
728738 self ._set_start (start )
729739 self ._set_end (end )
730740
731741 self ._skip_frames = skip_frames
732- self ._verbose = verbose
733742 self ._direction = self .FORWARD_DIRECTION
734743 self ._speed = 1
735744 self ._adjusted_period = self ._period
@@ -741,20 +750,22 @@ def _initialise_playback(self, start, end, skip_frames, verbose):
741750 self .REWIND : self ._go_back ,
742751 self .FORWARD : self ._go_forward ,
743752 self .RESET : self ._reset ,
753+ self .RESTART : self .restart ,
744754 ** self ._play_commands
745755 }
746756
747757 # add step back and forward functionality if keys not already used
748758 self ._pause_effects = {
749759 self .REWIND : self .step_back ,
750760 self .FORWARD : self .step_forward ,
761+ self .RESTART : self .restart ,
751762 ** self ._pause_effects
752763 }
753764
754765 # ensure time between frames is ignored while paused
755766 class LogDict (dict ):
756767 def get (this , * args , ** kwargs ):
757- self ._prev = perf_counter () - ( self . _period - self . MIN_DELAY ) / 1e3
768+ self .reset_delay ()
758769 return dict .get (this , * args , ** kwargs )
759770
760771 self ._pause_effects = LogDict (self ._pause_effects )
@@ -763,14 +774,14 @@ def get(this, *args, **kwargs):
763774
764775 def _set_start (self , start ):
765776 ''' Set the start of the video to user specification, if possible. '''
777+ self ._frame = 0
766778 if start is not None :
767779 if self .set_timestamp (start ):
768- print (f'starting at { start } ' )
780+ self . verbose_print (f'starting at { start } ' )
769781 else :
770- print ('start specification failed, starting at 0:00' )
771- self ._frame = 0
772- else :
773- self ._frame = 0
782+ self .verbose_print ('start specification failed, '
783+ 'starting at 0:00' )
784+ self ._start = self ._frame
774785
775786 def _set_end (self , end ):
776787 ''' Set playback to end where specified by user. '''
@@ -781,7 +792,15 @@ def _set_end(self, end):
781792 self ._end = end
782793 self ._end /= self ._period # convert to number of frames
783794 else :
784- self ._end = np .inf
795+ self ._end = self .get ('num_frames' ) or np .inf
796+
797+ def verbose_print (self , * args , ** kwargs ):
798+ if self ._verbose :
799+ print (* args , ** kwargs )
800+
801+ # NOTE: key callbacks set as static methods for clarity/ease of reference
802+ # VideoReader to be modified gets passed in (so that external functions
803+ # can be used), so also having a reference to self would be confusing.
785804
786805 @staticmethod
787806 def _speed_up (vid ):
@@ -798,8 +817,7 @@ def _slow_down(vid):
798817 def _register_speed_change (self ):
799818 ''' Update internals and print new speed. '''
800819 self ._calculate_period ()
801- if self ._verbose :
802- print (f'speed set to { self ._speed :.1f} x starting fps' )
820+ self .verbose_print (f'speed set to { self ._speed :.1f} x starting fps' )
803821
804822 def _calculate_period (self ):
805823 ''' Determine the adjusted period given the speed. '''
@@ -817,32 +835,37 @@ def _calculate_frames(self):
817835 else 1 )
818836 self ._calculate_timestep ()
819837
838+ def reset_delay (self ):
839+ ''' Resets the delay between frames.
840+
841+ Use to avoid fast playback/frame skipping after pauses.
842+
843+ '''
844+ self ._prev = perf_counter () - (self ._period - self .MIN_DELAY ) / 1e3
845+
820846 @staticmethod
821847 def _go_back (vid ):
822848 ''' Set playback to backwards. '''
823849 if vid ._skip_frames is not None :
824850 vid ._direction = vid .REVERSE_DIRECTION
825- if vid ._verbose :
826- print ('Rewinding' )
851+ vid .verbose_print ('Rewinding' )
827852 else :
828- if vid ._verbose :
829- print ('Cannot go backwards without skip_frames=True' )
853+ vid .verbose_print ('Cannot go backwards without skip_frames=True' )
830854
831855 @staticmethod
832856 def _go_forward (vid ):
833857 ''' Set playback to go forwards. '''
834858 vid ._direction = vid .FORWARD_DIRECTION
835- if vid ._verbose :
836- print ('Going forwards' )
859+ vid .verbose_print ('Going forwards' )
837860
838861 @staticmethod
839862 def _reset (vid ):
840863 ''' Restore playback to 1x speed and forwards. '''
841864 vid ._speed = 1
842865 vid ._direction = vid .FORWARD_DIRECTION
843866 vid ._calculate_period ()
844- if vid ._verbose :
845- print ( f'Going forwards with speed set to 1x starting fps ' )
867+ vid .verbose_print ( 'Going forwards with speed set to 1x starting fps '
868+ f'( { vid . _fps :.2f } ) ' )
846869
847870 @staticmethod
848871 def step_back (vid ):
@@ -878,6 +901,15 @@ def step_forward(vid):
878901 # restore state
879902 vid ._direction , vid ._verbose = old_state
880903
904+ @staticmethod
905+ def restart (vid ):
906+ ''' Attempts to continue playback from the start of the video.
907+
908+ Respects user-defined start-point from initialisation.
909+
910+ '''
911+ vid .set_frame (vid ._start )
912+
881913 @property
882914 def fps (self ):
883915 ''' The constant FPS assumed of the video file. '''
@@ -892,7 +924,7 @@ def frame(self):
892924 def set_frame (self , frame ):
893925 ''' Attempts to set the frame number, returns success.
894926
895- 'frame' is an integer greater than 0. Setting past the last frame
927+ 'frame' is an integer >= 0. Setting past the last frame
896928 either has no effect or ends the playback.
897929
898930 self.set_frame(int) -> bool
@@ -964,9 +996,12 @@ def __next__(self):
964996 self ._update_playback_settings ()
965997
966998 self ._prev = now
967- self ._update_frame_tracking ()
968999
969- return super ().__next__ ()
1000+ self ._update_frame_tracking ()
1001+ read_success , frame = super ().__next__ ()
1002+ if not read_success :
1003+ raise OutOfFrames
1004+ return read_success , frame
9701005
9711006 def _update_playback_settings (self ):
9721007 ''' Adjusts delay/frame skipping if error is sufficiently large. '''
@@ -1008,7 +1043,8 @@ def _update_frame_tracking(self):
10081043 else :
10091044 self ._frame += 1
10101045
1011- if self ._frame > self ._end :
1046+ if self .status == False or self ._frame > self ._end \
1047+ or self ._frame < self ._start :
10121048 raise OutOfFrames
10131049
10141050 def __repr__ (self ):
0 commit comments