Skip to content

Commit

Permalink
implement @damies13's fix for more frames
Browse files Browse the repository at this point in the history
  • Loading branch information
shravanasati committed Oct 12, 2024
1 parent 64690fe commit 4ae4443
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 30 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"python.analysis.typeCheckingMode": "standard"
}
100 changes: 70 additions & 30 deletions pyscreenrec/__init__.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
# type: ignore
from pyscreeze import screenshot
from time import sleep

# type: ignore
import cv2
import os
from threading import Thread

# type: ignore
from natsort import natsorted


class InvalidCodec(Exception):
pass

class InvalidStartMode(Exception):
pass

class InvalidFPS(Exception):
class InvalidStartMode(Exception):
pass

class HighFPSWarning(Warning):
pass

class ScreenRecorder:
"""
Expand All @@ -31,38 +30,37 @@ def __init__(self) -> None:
"""
self.__running = False
self.__start_mode = "start"
self.screenshot_folder = os.path.join(os.path.expanduser("~"), "pyscreenrec_data")
self.screenshot_folder = os.path.join(
os.path.expanduser("~"), "pyscreenrec_data"
)

# making the screenshot directory if not exists
if not os.path.exists(self.screenshot_folder):
os.mkdir(self.screenshot_folder)
# clearing all the previous data if last session ended unsuccessfully
self._clear_data()


def _start_recording(self, video_name:str, fps:int) -> None:
def _start_recording(self, video_name: str, fps: int) -> None:
"""
(Protected) Starts screen recording.
@params
@params
video_name (str) --> The name of the screen recording video.
fps (int) --> The Frames Per Second for the screen recording. Implies how much screenshots will be taken in a second.
"""
# checking for video extension and fps
if not video_name.endswith(".mp4"):
raise InvalidCodec("The video's extension can only be '.mp4'.")
if fps > 60:
raise InvalidFPS("The FPS for the screen recording can be maximum 60 FPS.")
if fps > 30:
raise HighFPSWarning(f"The FPS you have used for screen recording, {fps}, is pretty high and may result in delayed execution of other programs in most systems. It is advised to keep it under 20.")

self.fps = fps
self.video_name = video_name

# checking if screen is already being recorded
if self.__running:
# todo decide the behavior on such cases
# whether an error/warning should be raised or ignored
print("Screen recording is already running.")

else:
Expand All @@ -72,41 +70,65 @@ def _start_recording(self, video_name:str, fps:int) -> None:

# starting screenshotting
while self.__running:
st_start = time.perf_counter()
screenshot(os.path.join(self.screenshot_folder, f"s{i}.jpg"))
sleep(1/self.fps)
st_end = time.perf_counter()
st_total = st_end - st_start
# not sleeping for exactly 1/self.fps seconds because
# otherwise time is lost in sleeping which could be used in
# capturing frames
# since due to thread context-switching, this screenshotter
# thread doesn't get all the time that it needs
# thus, if more than required time has been spent just on
# screenshotting, don't sleep at all
sleep(max(0, 1 / self.fps - st_total))
i += 1

elif self.__start_mode == "resume":
self.__running = True
i = len(natsorted([img for img in os.listdir(self.screenshot_folder) if img.endswith(".jpg")])) + 1
i = (
len(
natsorted(
[
img
for img in os.listdir(self.screenshot_folder)
if img.endswith(".jpg")
]
)
)
+ 1
)

while self.__running:
screenshot(os.path.join(self.screenshot_folder, f"s{i}.jpg"))
sleep(1/self.fps)
sleep(1 / self.fps)
i += 1

else:
raise InvalidStartMode("The `self.__start_mode` can only be 'start' or 'resume'.")

raise InvalidStartMode(
"The `self.__start_mode` can only be 'start' or 'resume'."
)

def start_recording(self, video_name:str="Recording.mp4", fps:int=15) -> None:
def start_recording(self, video_name: str = "Recording.mp4", fps: int = 15) -> None:
"""
Starts screen recording.
@params
@params
video_name (str) --> The name of the output screen recording.
fps (int) --> The Frames Per Second for the screen recording. Implies how much screenshots will be taken in a second.
"""
t = Thread(target=self._start_recording, args=(video_name,fps))
t = Thread(target=self._start_recording, args=(video_name, fps))
t.start()

def stop_recording(self) -> None:
"""
Stops screen recording.
"""
if not self.__running:
# todo decide the behavior on such cases
# whether an error/warning should be raised or ignored
print("No screen recording session is going on.")
return None
self.__running = False
Expand All @@ -115,12 +137,13 @@ def stop_recording(self) -> None:
self._save_video(self.video_name)
self._clear_data()


def pause_recording(self) -> None:
"""
Pauses screen recording.
"""
if not self.__running:
# todo decide the behavior on such cases
# whether an error/warning should be raised or ignored
print("No screen recording session is going on.")
return None

Expand All @@ -131,14 +154,15 @@ def resume_recording(self) -> None:
Resumes screen recording.
"""
if self.__running:
# todo decide the behavior on such cases
# whether an error/warning should be raised or ignored
print("Screen recording is already running.")
return None

self.__start_mode = "resume"
self.start_recording(self.video_name)


def _save_video(self, video_name:str) -> None:
def _save_video(self, video_name: str) -> None:
"""
(Protected) Makes a video out of the screenshots.
Expand All @@ -147,12 +171,17 @@ def _save_video(self, video_name:str) -> None:
video_name (str) --> Name or path to the output video.
"""
# fetching image info
images = natsorted([img for img in os.listdir(self.screenshot_folder) if img.endswith(".jpg")])
images = natsorted(
[img for img in os.listdir(self.screenshot_folder) if img.endswith(".jpg")]
)
# print(f"{len(images)=}")
frame = cv2.imread(os.path.join(self.screenshot_folder, images[0]))
height, width, _ = frame.shape

# making a videowriter object
video = cv2.VideoWriter(video_name, cv2.VideoWriter_fourcc(*'mp4v'), self.fps, (width,height))
# making a videowriter object
video = cv2.VideoWriter(
video_name, cv2.VideoWriter_fourcc(*"mp4v"), self.fps, (width, height)
)

# writing all the images to a video
for image in images:
Expand All @@ -171,4 +200,15 @@ def _clear_data(self) -> None:
os.remove(os.path.join(self.screenshot_folder, image))

def __repr__(self) -> str:
return "pyscreenrec is a small and cross-platform python library that can be used to record screen. \nFor more info, visit https://github.com/shravanasati/pyscreenrec#readme."
return "pyscreenrec is a small and cross-platform python library that can be used to record screen. \nFor more info, visit https://github.com/shravanasati/pyscreenrec#readme."


if __name__ == "__main__":
import time

rec = ScreenRecorder()
print("recording started")
rec.start_recording(fps=30)
time.sleep(10)
print("recording ended")
rec.stop_recording()

0 comments on commit 4ae4443

Please sign in to comment.