99import click
1010import cv2
1111import numpy as np
12+ from click import Context , Parameter
1213from pydantic import ValidationError
1314from PySide6 .QtCore import Qt , QThread , Signal , Slot
1415from PySide6 .QtGui import QCloseEvent , QIcon , QImage , QKeyEvent , QPixmap , QResizeEvent
@@ -70,8 +71,7 @@ class Presentation:
7071 """Creates presentation from a configuration object."""
7172
7273 def __init__ (self , config : PresentationConfig ) -> None :
73- self .slides : List [SlideConfig ] = config .slides
74- self .files : List [str ] = config .files
74+ self .config = config
7575
7676 self .__current_slide_index : int = 0
7777 self .current_animation : int = self .current_slide .start_animation
@@ -90,23 +90,41 @@ def __init__(self, config: PresentationConfig) -> None:
9090 def __len__ (self ) -> int :
9191 return len (self .slides )
9292
93+ @property
94+ def slides (self ) -> List [SlideConfig ]:
95+ """Returns the list of slides."""
96+ return self .config .slides
97+
98+ @property
99+ def files (self ) -> List [Path ]:
100+ """Returns the list of animation files."""
101+ return self .config .files
102+
103+ @property
104+ def resolution (self ) -> Tuple [int , int ]:
105+ """Returns the resolution."""
106+ return self .config .resolution
107+
93108 @property
94109 def current_slide_index (self ) -> int :
95110 return self .__current_slide_index
96111
97112 @current_slide_index .setter
98- def current_slide_index (self , value : Optional [int ]):
99- if value :
113+ def current_slide_index (self , value : Optional [int ]) -> None :
114+ if value is not None :
100115 if - len (self ) <= value < len (self ):
101116 self .__current_slide_index = value
102117 self .current_animation = self .current_slide .start_animation
118+ logger .debug (f"Set current slide index to { value } " )
103119 else :
104120 logger .error (
105121 f"Could not load slide number { value } , playing first slide instead."
106122 )
107123
108- def set_current_animation_and_update_slide_number (self , value : Optional [int ]):
109- if value :
124+ def set_current_animation_and_update_slide_number (
125+ self , value : Optional [int ]
126+ ) -> None :
127+ if value is not None :
110128 n_files = len (self .files )
111129 if - n_files <= value < n_files :
112130 if value < 0 :
@@ -116,6 +134,8 @@ def set_current_animation_and_update_slide_number(self, value: Optional[int]):
116134 if value < slide .end_animation :
117135 self .current_slide_index = i
118136 self .current_animation = value
137+
138+ logger .debug (f"Playing animation { value } , at slide index { i } " )
119139 return
120140
121141 assert (
@@ -159,7 +179,7 @@ def load_animation_cap(self, animation: int) -> None:
159179
160180 self .release_cap ()
161181
162- file : str = self .files [animation ]
182+ file : str = str ( self .files [animation ])
163183
164184 if self .reverse :
165185 file = "{}_reversed{}" .format (* os .path .splitext (file ))
@@ -178,7 +198,7 @@ def current_cap(self) -> cv2.VideoCapture:
178198
179199 def rewind_current_slide (self ) -> None :
180200 """Rewinds current slide to first frame."""
181- logger .debug ("Rewinding curring slide" )
201+ logger .debug ("Rewinding current slide" )
182202 if self .reverse :
183203 self .current_animation = self .current_slide .end_animation - 1
184204 else :
@@ -216,9 +236,10 @@ def load_next_slide(self) -> None:
216236
217237 def load_previous_slide (self ) -> None :
218238 """Loads previous slide."""
219- logger .debug ("Loading previous slide" )
239+ logger .debug (f "Loading previous slide, current is { self . current_slide_index } " )
220240 self .cancel_reverse ()
221241 self .current_slide_index = max (0 , self .current_slide_index - 1 )
242+ logger .debug (f"Loading slide index { self .current_slide_index } " )
222243 self .rewind_current_slide ()
223244
224245 @property
@@ -229,7 +250,8 @@ def fps(self) -> int:
229250 logger .warn (
230251 f"Something is wrong with video file { self .current_file } , as the fps returned by frame { self .current_frame_number } is 0"
231252 )
232- return max (fps , 1 ) # TODO: understand why we sometimes get 0 fps
253+ # TODO: understand why we sometimes get 0 fps
254+ return max (fps , 1 ) # type: ignore
233255
234256 def reset (self ) -> None :
235257 """Rests current presentation."""
@@ -320,6 +342,7 @@ class Display(QThread): # type: ignore
320342
321343 change_video_signal = Signal (np .ndarray )
322344 change_info_signal = Signal (dict )
345+ change_presentation_sigal = Signal ()
323346 finished = Signal ()
324347
325348 def __init__ (
@@ -365,10 +388,12 @@ def current_presentation_index(self) -> int:
365388 return self .__current_presentation_index
366389
367390 @current_presentation_index .setter
368- def current_presentation_index (self , value : Optional [int ]):
369- if value :
391+ def current_presentation_index (self , value : Optional [int ]) -> None :
392+ if value is not None :
370393 if - len (self ) <= value < len (self ):
371394 self .__current_presentation_index = value
395+ self .current_presentation .release_cap ()
396+ self .change_presentation_sigal .emit ()
372397 else :
373398 logger .error (
374399 f"Could not load scene number { value } , playing first scene instead."
@@ -379,6 +404,11 @@ def current_presentation(self) -> Presentation:
379404 """Returns the current presentation."""
380405 return self .presentations [self .current_presentation_index ]
381406
407+ @property
408+ def current_resolution (self ) -> Tuple [int , int ]:
409+ """Returns the resolution of the current presentation."""
410+ return self .current_presentation .resolution
411+
382412 def run (self ) -> None :
383413 """Runs a series of presentations until end or exit."""
384414 while self .run_flag :
@@ -413,7 +443,7 @@ def run(self) -> None:
413443 if self .record_to is not None :
414444 self .record_movie ()
415445
416- logger .debug ("Closing video thread gracully and exiting" )
446+ logger .debug ("Closing video thread gracefully and exiting" )
417447 self .finished .emit ()
418448
419449 def record_movie (self ) -> None :
@@ -587,7 +617,6 @@ def __init__(
587617 * args : Any ,
588618 config : Config = DEFAULT_CONFIG ,
589619 fullscreen : bool = False ,
590- resolution : Tuple [int , int ] = (1980 , 1080 ),
591620 hide_mouse : bool = False ,
592621 aspect_ratio : AspectRatio = AspectRatio .auto ,
593622 resize_mode : Qt .TransformationMode = Qt .SmoothTransformation ,
@@ -599,7 +628,12 @@ def __init__(
599628 self .setWindowTitle (WINDOW_NAME )
600629 self .icon = QIcon (":/icon.png" )
601630 self .setWindowIcon (self .icon )
602- self .display_width , self .display_height = resolution
631+
632+ # create the video capture thread
633+ kwargs ["config" ] = config
634+ self .thread = Display (* args , ** kwargs )
635+
636+ self .display_width , self .display_height = self .thread .current_resolution
603637 self .aspect_ratio = aspect_ratio
604638 self .resize_mode = resize_mode
605639 self .hide_mouse = hide_mouse
@@ -619,9 +653,6 @@ def __init__(
619653 self .label .setPixmap (self .pixmap )
620654 self .label .setMinimumSize (1 , 1 )
621655
622- # create the video capture thread
623- kwargs ["config" ] = config
624- self .thread = Display (* args , ** kwargs )
625656 # create the info dialog
626657 self .info = Info ()
627658 self .info .show ()
@@ -635,6 +666,7 @@ def __init__(
635666 # connect signals
636667 self .thread .change_video_signal .connect (self .update_image )
637668 self .thread .change_info_signal .connect (self .info .update_info )
669+ self .thread .change_presentation_sigal .connect (self .update_canvas )
638670 self .thread .finished .connect (self .closeAll )
639671 self .send_key_signal .connect (self .thread .set_key )
640672
@@ -688,6 +720,14 @@ def update_image(self, cv_img: np.ndarray) -> None:
688720
689721 self .label .setPixmap (QPixmap .fromImage (qt_img ))
690722
723+ @Slot ()
724+ def update_canvas (self ) -> None :
725+ """Update the canvas when a presentation has changed."""
726+ logger .debug ("Updating canvas" )
727+ self .display_width , self .display_height = self .thread .current_resolution
728+ if not self .isFullScreen ():
729+ self .resize (self .display_width , self .display_height )
730+
691731
692732@click .command ()
693733@click .option (
@@ -757,7 +797,7 @@ def value_proc(value: Optional[str]) -> List[str]:
757797 while True :
758798 try :
759799 scenes = click .prompt ("Choice(s)" , value_proc = value_proc )
760- return scenes
800+ return scenes # type: ignore
761801 except ValueError as e :
762802 raise click .UsageError (str (e ))
763803
@@ -785,7 +825,9 @@ def get_scenes_presentation_config(
785825 return presentation_configs
786826
787827
788- def start_at_callback (ctx , param , values : str ) -> Tuple [Optional [int ], ...]:
828+ def start_at_callback (
829+ ctx : Context , param : Parameter , values : str
830+ ) -> Tuple [Optional [int ], ...]:
789831 if values == "(None, None, None)" :
790832 return (None , None , None )
791833
@@ -838,9 +880,8 @@ def str_to_int_or_none(value: str) -> Optional[int]:
838880 "--resolution" ,
839881 metavar = "<WIDTH HEIGHT>" ,
840882 type = (int , int ),
841- default = ( 1920 , 1080 ) ,
883+ default = None ,
842884 help = "Window resolution WIDTH HEIGHT used if fullscreen is not set. You may manually resize the window afterward." ,
843- show_default = True ,
844885)
845886@click .option (
846887 "--to" ,
@@ -931,7 +972,7 @@ def present(
931972 start_paused : bool ,
932973 fullscreen : bool ,
933974 skip_all : bool ,
934- resolution : Tuple [int , int ],
975+ resolution : Optional [ Tuple [int , int ] ],
935976 record_to : Optional [Path ],
936977 exit_after_last_slide : bool ,
937978 hide_mouse : bool ,
@@ -956,9 +997,15 @@ def present(
956997 if skip_all :
957998 exit_after_last_slide = True
958999
1000+ presentation_configs = get_scenes_presentation_config (scenes , folder )
1001+
1002+ if resolution is not None :
1003+ for presentation_config in presentation_configs :
1004+ presentation_config .resolution = resolution
1005+
9591006 presentations = [
9601007 Presentation (presentation_config )
961- for presentation_config in get_scenes_presentation_config ( scenes , folder )
1008+ for presentation_config in presentation_configs
9621009 ]
9631010
9641011 if config_path .exists ():
@@ -994,7 +1041,6 @@ def present(
9941041 start_paused = start_paused ,
9951042 fullscreen = fullscreen ,
9961043 skip_all = skip_all ,
997- resolution = resolution ,
9981044 record_to = record_to ,
9991045 exit_after_last_slide = exit_after_last_slide ,
10001046 hide_mouse = hide_mouse ,
0 commit comments