Skip to content

Commit 4ae4443

Browse files
committed
implement @damies13's fix for more frames
1 parent 64690fe commit 4ae4443

File tree

2 files changed

+73
-30
lines changed

2 files changed

+73
-30
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"python.analysis.typeCheckingMode": "standard"
3+
}

pyscreenrec/__init__.py

Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
11
# type: ignore
22
from pyscreeze import screenshot
33
from time import sleep
4+
45
# type: ignore
56
import cv2
67
import os
78
from threading import Thread
9+
810
# type: ignore
911
from natsort import natsorted
1012

13+
1114
class InvalidCodec(Exception):
1215
pass
1316

14-
class InvalidStartMode(Exception):
15-
pass
1617

17-
class InvalidFPS(Exception):
18+
class InvalidStartMode(Exception):
1819
pass
1920

20-
class HighFPSWarning(Warning):
21-
pass
2221

2322
class ScreenRecorder:
2423
"""
@@ -31,38 +30,37 @@ def __init__(self) -> None:
3130
"""
3231
self.__running = False
3332
self.__start_mode = "start"
34-
self.screenshot_folder = os.path.join(os.path.expanduser("~"), "pyscreenrec_data")
33+
self.screenshot_folder = os.path.join(
34+
os.path.expanduser("~"), "pyscreenrec_data"
35+
)
3536

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

42-
43-
def _start_recording(self, video_name:str, fps:int) -> None:
43+
def _start_recording(self, video_name: str, fps: int) -> None:
4444
"""
4545
(Protected) Starts screen recording.
4646
47-
@params
48-
47+
@params
48+
4949
video_name (str) --> The name of the screen recording video.
5050
5151
fps (int) --> The Frames Per Second for the screen recording. Implies how much screenshots will be taken in a second.
5252
"""
5353
# checking for video extension and fps
5454
if not video_name.endswith(".mp4"):
5555
raise InvalidCodec("The video's extension can only be '.mp4'.")
56-
if fps > 60:
57-
raise InvalidFPS("The FPS for the screen recording can be maximum 60 FPS.")
58-
if fps > 30:
59-
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.")
6056

6157
self.fps = fps
6258
self.video_name = video_name
6359

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

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

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

7987
elif self.__start_mode == "resume":
8088
self.__running = True
81-
i = len(natsorted([img for img in os.listdir(self.screenshot_folder) if img.endswith(".jpg")])) + 1
89+
i = (
90+
len(
91+
natsorted(
92+
[
93+
img
94+
for img in os.listdir(self.screenshot_folder)
95+
if img.endswith(".jpg")
96+
]
97+
)
98+
)
99+
+ 1
100+
)
82101

83102
while self.__running:
84103
screenshot(os.path.join(self.screenshot_folder, f"s{i}.jpg"))
85-
sleep(1/self.fps)
104+
sleep(1 / self.fps)
86105
i += 1
87106

88107
else:
89-
raise InvalidStartMode("The `self.__start_mode` can only be 'start' or 'resume'.")
90-
108+
raise InvalidStartMode(
109+
"The `self.__start_mode` can only be 'start' or 'resume'."
110+
)
91111

92-
def start_recording(self, video_name:str="Recording.mp4", fps:int=15) -> None:
112+
def start_recording(self, video_name: str = "Recording.mp4", fps: int = 15) -> None:
93113
"""
94114
Starts screen recording.
95115
96-
@params
97-
116+
@params
117+
98118
video_name (str) --> The name of the output screen recording.
99119
100120
fps (int) --> The Frames Per Second for the screen recording. Implies how much screenshots will be taken in a second.
101121
"""
102-
t = Thread(target=self._start_recording, args=(video_name,fps))
122+
t = Thread(target=self._start_recording, args=(video_name, fps))
103123
t.start()
104124

105125
def stop_recording(self) -> None:
106126
"""
107127
Stops screen recording.
108128
"""
109129
if not self.__running:
130+
# todo decide the behavior on such cases
131+
# whether an error/warning should be raised or ignored
110132
print("No screen recording session is going on.")
111133
return None
112134
self.__running = False
@@ -115,12 +137,13 @@ def stop_recording(self) -> None:
115137
self._save_video(self.video_name)
116138
self._clear_data()
117139

118-
119140
def pause_recording(self) -> None:
120141
"""
121142
Pauses screen recording.
122143
"""
123144
if not self.__running:
145+
# todo decide the behavior on such cases
146+
# whether an error/warning should be raised or ignored
124147
print("No screen recording session is going on.")
125148
return None
126149

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

137162
self.__start_mode = "resume"
138163
self.start_recording(self.video_name)
139164

140-
141-
def _save_video(self, video_name:str) -> None:
165+
def _save_video(self, video_name: str) -> None:
142166
"""
143167
(Protected) Makes a video out of the screenshots.
144168
@@ -147,12 +171,17 @@ def _save_video(self, video_name:str) -> None:
147171
video_name (str) --> Name or path to the output video.
148172
"""
149173
# fetching image info
150-
images = natsorted([img for img in os.listdir(self.screenshot_folder) if img.endswith(".jpg")])
174+
images = natsorted(
175+
[img for img in os.listdir(self.screenshot_folder) if img.endswith(".jpg")]
176+
)
177+
# print(f"{len(images)=}")
151178
frame = cv2.imread(os.path.join(self.screenshot_folder, images[0]))
152179
height, width, _ = frame.shape
153180

154-
# making a videowriter object
155-
video = cv2.VideoWriter(video_name, cv2.VideoWriter_fourcc(*'mp4v'), self.fps, (width,height))
181+
# making a videowriter object
182+
video = cv2.VideoWriter(
183+
video_name, cv2.VideoWriter_fourcc(*"mp4v"), self.fps, (width, height)
184+
)
156185

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

173202
def __repr__(self) -> str:
174-
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."
203+
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."
204+
205+
206+
if __name__ == "__main__":
207+
import time
208+
209+
rec = ScreenRecorder()
210+
print("recording started")
211+
rec.start_recording(fps=30)
212+
time.sleep(10)
213+
print("recording ended")
214+
rec.stop_recording()

0 commit comments

Comments
 (0)