diff --git a/timelapse/__main__.py b/timelapse/__main__.py index a6dc7ae..056d083 100644 --- a/timelapse/__main__.py +++ b/timelapse/__main__.py @@ -11,35 +11,35 @@ from Foundation import NSUserDefaults -def dark_mode(): +def dark_mode() -> bool: return NSUserDefaults.standardUserDefaults().stringForKey_('AppleInterfaceStyle') == "Dark" # Configuration -start_recording = False # Start recording on launch -encode = True # Create video after recording -screenshot_interval = 1.5 # Number of seconds between screenshots -dir_base = os.path.expanduser("~") # Base directory -dir_app = "timelapse" # Output directory -dir_pictures = "Pictures" # Place for pictures in filesystem -dir_movies = "Movies" # Place for movies in filesystem -dir_resources = "./resources/" +start_recording: bool = False # Start recording on launch +encode: bool = True # Create video after recording +screenshot_interval: float = 1.5 # Number of seconds between screenshots +dir_base: str = os.path.expanduser("~") # Base directory +dir_app: str = "timelapse" # Output directory +dir_pictures: str = "Pictures" # Place for pictures in filesystem +dir_movies: str = "Movies" # Place for movies in filesystem +dir_resources: str = "./resources/" if dark_mode(): dir_resources += "white" else: dir_resources += "black" -subdir_suffix = "Session-" + time.strftime("%Y%m%d") # Name of subdirectory -image_recording = "record.gif" # App icon recording -image_idle = "stop.gif" # App icon idle -create_session_subdir = True # New screenshot directory for each session -create_movies = True # Create movies from screenshots after recording +subdir_suffix: str = "Session-" + time.strftime("%Y%m%d") # Name of subdirectory +image_recording: str = "record.gif" # App icon recording +image_idle: str = "stop.gif" # App icon idle +create_session_subdir: str = True # New screenshot directory for each session +create_movies: bool = True # Create movies from screenshots after recording # Menu item text when recorder is running -text_recorder_running = "Stop recording" -text_recorder_idle = "Start recording" # Menu item text when recorder is idle +text_recorder_running: str = "Stop recording" +text_recorder_idle: str = "Start recording" # Menu item text when recorder is idle # Tooltip of menu icon when not recording -tooltip_idle = "Timelapse screen recorder" -tooltip_running = "Recording | " + tooltip_idle # Tooltip when recording +tooltip_idle: str = "Timelapse screen recorder" +tooltip_running: str = "Recording | " + tooltip_idle # Tooltip when recording ############### @@ -47,19 +47,19 @@ def dark_mode(): class Timelapse(NSObject): """ Creates a timelapse video """ - def applicationDidFinishLaunching_(self, notification): + def applicationDidFinishLaunching_(self, notification) -> None: self.check_dependencies() # Initialize recording - self.recording = start_recording + self.recording: bool = start_recording self.recorder = None # Set correct output paths - self.recorder_output_basedir = os.path.join( + self.recorder_output_basedir: str = os.path.join( dir_base, dir_pictures, dir_app) - self.encoder_output_basedir = os.path.join(dir_base, dir_movies) + self.encoder_output_basedir: str = os.path.join(dir_base, dir_movies) - self.image_dir = self.create_dir(self.recorder_output_basedir) + self.image_dir: str = self.create_dir(self.recorder_output_basedir) # Create a reference to the statusbar (menubar) self.statusbar = NSStatusBar.systemStatusBar() @@ -77,13 +77,13 @@ def applicationDidFinishLaunching_(self, notification): self.loadIcons() self.setStatus() - def loadIcons(self): + def loadIcons(self) -> None: self.icon_recording = NSImage.alloc().initWithContentsOfFile_( os.path.join(dir_resources, image_recording)) self.icon_idle = NSImage.alloc().initWithContentsOfFile_( os.path.join(dir_resources, image_idle)) - def setStatus(self): + def setStatus(self) -> None: """ Sets the image and menu text according to recording status """ if self.recording: self.statusitem.setImage_(self.icon_recording) @@ -94,7 +94,7 @@ def setStatus(self): self.recordButton.setTitle_(text_recorder_idle) self.statusitem.setToolTip_(tooltip_idle) - def createMenu(self): + def createMenu(self) -> menu: """ Status bar menu """ menu = NSMenu.alloc().init() # Bind record event @@ -107,7 +107,7 @@ def createMenu(self): menu.addItem_(menuitem) return menu - def startStopRecording_(self, notification): + def startStopRecording_(self, notification) -> None: if self.recording: self.recorder.join(timeout=screenshot_interval*2) # Create timelapse after recording? @@ -119,18 +119,18 @@ def startStopRecording_(self, notification): notify("Timelapse started", "The recording has started") self.recorder = Recorder(self.image_dir, screenshot_interval) self.recorder.start() - self.recording = not self.recording + self.recording: bool = not self.recording self.setStatus() @objc.python_method - def create_dir(self, base_dir): + def create_dir(self, base_dir: str) -> str: """ Creates a specified directory and the path to it if necessary """ if create_session_subdir: # Create a new subdirectory - output_dir = os.path.join(base_dir, self.get_sub_dir(base_dir)) + output_dir: str = os.path.join(base_dir, self.get_sub_dir(base_dir)) else: # Don't create a subdirectory. Use base directory for output - output_dir = base_dir + output_dir: str = base_dir # Create path if it doesn't exist try: print(f"Output directory: {output_dir}") @@ -144,20 +144,20 @@ def create_dir(self, base_dir): return output_dir @objc.python_method - def get_sub_dir(self, base_dir): + def get_sub_dir(self, base_dir: str) -> str: """ Returns the next nonexistend subdirectory to base_dir """ - subdir_base = os.path.join(base_dir, subdir_suffix) + subdir_base: str = os.path.join(base_dir, subdir_suffix) # Check if we can use subdir without any session id - subdir = subdir_base + subdir: str = subdir_base # Use a session id only if subdir already exists - session_number = 0 + session_number: int = 0 while os.path.exists(subdir): # We can't use subdir. Create directory with session id session_number += 1 - subdir = subdir_base + "-" + str(session_number) + subdir: str = subdir_base + "-" + str(session_number) return subdir - def check_dependencies(self): + def check_dependencies(self) -> None: try: subprocess.run(['ffmpeg'], check=True, capture_output=True, timeout=10.0) diff --git a/timelapse/encoder.py b/timelapse/encoder.py index ddc480d..b78bbed 100644 --- a/timelapse/encoder.py +++ b/timelapse/encoder.py @@ -6,6 +6,7 @@ import fnmatch import shutil from notify import notify # Shows notifications/alerts +from typing import List not_found_msg = """ The ffmpeg command was not found; @@ -18,22 +19,22 @@ class Encoder(Thread): """Create a video file from images""" - def __init__(self, input_dir, output_dir): + def __init__(self, input_dir: str, output_dir: str) -> None: # Initialize the thread Thread.__init__(self) # Set config options - self.input = f"{input_dir}/%d.png" - timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S") - self.output = f"{output_dir}/timelapse-{timestamp}.mov" + self.input: str = f"{input_dir}/%d.png" + timestamp: str = datetime.now().strftime("%Y-%m-%d-%H-%M-%S") + self.output: str = f"{output_dir}/timelapse-{timestamp}.mov" print("Encoder started") - def join(self, timeout=None): + def join(self, timeout=None) -> None: """ Hard shutdown """ Thread.join(self) - def run(self): + def run(self) -> None: """ Now that we have screenshots of the user's desktop, we will stitch them together using `ffmpeg` to create a movie. Each image will become @@ -41,7 +42,7 @@ def run(self): """ # Call ffmpeg with settings compatible with QuickTime. # https://superuser.com/a/820137 - command = ["/usr/local/bin/ffmpeg", "-y", + command: List[str]= ["/usr/local/bin/ffmpeg", "-y", "-framerate", "30", "-i", self.input, "-vf", "format=yuv420p", diff --git a/timelapse/notify.py b/timelapse/notify.py index 12d0289..89b6275 100644 --- a/timelapse/notify.py +++ b/timelapse/notify.py @@ -1,7 +1,7 @@ import os -def notify(title, text): +def notify(title: str, text: str) -> int: os.system(""" osascript -e 'display notification "{}" with title "{}"' """.format(text, title)) diff --git a/timelapse/recorder.py b/timelapse/recorder.py index cc55d0d..ce221ef 100644 --- a/timelapse/recorder.py +++ b/timelapse/recorder.py @@ -6,7 +6,7 @@ from AppKit import NSEvent, NSScreen, NSMouseInRect -def get_screen_with_mouse_index(): +def get_screen_with_mouse_index() ->int: mouseLocation = NSEvent.mouseLocation() screens = NSScreen.screens() for i, screen in enumerate(screens): @@ -20,7 +20,7 @@ class Recorder(Process): Takes a screenshot every 'interval' seconds and saves it into output_dir or a subdirectory thereof. """ - def __init__(self, output_dir, interval=4): + def __init__(self, output_dir: str, interval: int=4) -> None: # Initialize the thread Process.__init__(self) @@ -29,15 +29,15 @@ def __init__(self, output_dir, interval=4): self._stopped = Event() # Set config options - self.output_dir = output_dir - self.interval = interval - self.format = "png" + self.output_dir: str = output_dir + self.interval: int = interval + self.format: str = "png" # Number of screenshots taken - self.screenshot_counter = 0 + self.screenshot_counter: int = 0 print("Recorder started") - def join(self, timeout=None): + def join(self, timeout=None) -> None: """ Stop recording """ self._stop.set() self._stopped.wait() @@ -45,7 +45,7 @@ def join(self, timeout=None): self.get_recording_time() + ".") Process.join(self, timeout=timeout) - def run(self): + def run(self) -> None: """ Periodically take a screenshots of the screen """ while not self._stop.is_set(): self.screenshot() @@ -53,18 +53,18 @@ def run(self): self._stopped.set() - def get_recording_time(self): + def get_recording_time(self) ->str: return str(self.screenshot_counter * self.interval) + " seconds" - def get_filename(self): + def get_filename(self) ->str: """ Call screencapture for mac screenshot """ - filename = os.path.join( + filename: str = os.path.join( self.output_dir, f"{self.screenshot_counter}.{self.format}") return filename - def screenshot(self): + def screenshot(self) -> None: """ This method uses Mac OSX screencapture from the commandline """ - filename = self.get_filename() + filename: str = self.get_filename() subprocess.run( ['screencapture', '-S', '-o', '-x', '-D', str(get_screen_with_mouse_index() + 1), '-t', self.format, filename],