1
1
# type: ignore
2
2
from pyscreeze import screenshot
3
3
from time import sleep
4
+
4
5
# type: ignore
5
6
import cv2
6
7
import os
7
8
from threading import Thread
9
+
8
10
# type: ignore
9
11
from natsort import natsorted
10
12
13
+
11
14
class InvalidCodec (Exception ):
12
15
pass
13
16
14
- class InvalidStartMode (Exception ):
15
- pass
16
17
17
- class InvalidFPS (Exception ):
18
+ class InvalidStartMode (Exception ):
18
19
pass
19
20
20
- class HighFPSWarning (Warning ):
21
- pass
22
21
23
22
class ScreenRecorder :
24
23
"""
@@ -31,38 +30,37 @@ def __init__(self) -> None:
31
30
"""
32
31
self .__running = False
33
32
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
+ )
35
36
36
37
# making the screenshot directory if not exists
37
38
if not os .path .exists (self .screenshot_folder ):
38
39
os .mkdir (self .screenshot_folder )
39
40
# clearing all the previous data if last session ended unsuccessfully
40
41
self ._clear_data ()
41
42
42
-
43
- def _start_recording (self , video_name :str , fps :int ) -> None :
43
+ def _start_recording (self , video_name : str , fps : int ) -> None :
44
44
"""
45
45
(Protected) Starts screen recording.
46
46
47
- @params
48
-
47
+ @params
48
+
49
49
video_name (str) --> The name of the screen recording video.
50
50
51
51
fps (int) --> The Frames Per Second for the screen recording. Implies how much screenshots will be taken in a second.
52
52
"""
53
53
# checking for video extension and fps
54
54
if not video_name .endswith (".mp4" ):
55
55
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." )
60
56
61
57
self .fps = fps
62
58
self .video_name = video_name
63
59
64
60
# checking if screen is already being recorded
65
61
if self .__running :
62
+ # todo decide the behavior on such cases
63
+ # whether an error/warning should be raised or ignored
66
64
print ("Screen recording is already running." )
67
65
68
66
else :
@@ -72,41 +70,65 @@ def _start_recording(self, video_name:str, fps:int) -> None:
72
70
73
71
# starting screenshotting
74
72
while self .__running :
73
+ st_start = time .perf_counter ()
75
74
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 ))
77
85
i += 1
78
86
79
87
elif self .__start_mode == "resume" :
80
88
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
+ )
82
101
83
102
while self .__running :
84
103
screenshot (os .path .join (self .screenshot_folder , f"s{ i } .jpg" ))
85
- sleep (1 / self .fps )
104
+ sleep (1 / self .fps )
86
105
i += 1
87
106
88
107
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
+ )
91
111
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 :
93
113
"""
94
114
Starts screen recording.
95
115
96
- @params
97
-
116
+ @params
117
+
98
118
video_name (str) --> The name of the output screen recording.
99
119
100
120
fps (int) --> The Frames Per Second for the screen recording. Implies how much screenshots will be taken in a second.
101
121
"""
102
- t = Thread (target = self ._start_recording , args = (video_name ,fps ))
122
+ t = Thread (target = self ._start_recording , args = (video_name , fps ))
103
123
t .start ()
104
124
105
125
def stop_recording (self ) -> None :
106
126
"""
107
127
Stops screen recording.
108
128
"""
109
129
if not self .__running :
130
+ # todo decide the behavior on such cases
131
+ # whether an error/warning should be raised or ignored
110
132
print ("No screen recording session is going on." )
111
133
return None
112
134
self .__running = False
@@ -115,12 +137,13 @@ def stop_recording(self) -> None:
115
137
self ._save_video (self .video_name )
116
138
self ._clear_data ()
117
139
118
-
119
140
def pause_recording (self ) -> None :
120
141
"""
121
142
Pauses screen recording.
122
143
"""
123
144
if not self .__running :
145
+ # todo decide the behavior on such cases
146
+ # whether an error/warning should be raised or ignored
124
147
print ("No screen recording session is going on." )
125
148
return None
126
149
@@ -131,14 +154,15 @@ def resume_recording(self) -> None:
131
154
Resumes screen recording.
132
155
"""
133
156
if self .__running :
157
+ # todo decide the behavior on such cases
158
+ # whether an error/warning should be raised or ignored
134
159
print ("Screen recording is already running." )
135
160
return None
136
161
137
162
self .__start_mode = "resume"
138
163
self .start_recording (self .video_name )
139
164
140
-
141
- def _save_video (self , video_name :str ) -> None :
165
+ def _save_video (self , video_name : str ) -> None :
142
166
"""
143
167
(Protected) Makes a video out of the screenshots.
144
168
@@ -147,12 +171,17 @@ def _save_video(self, video_name:str) -> None:
147
171
video_name (str) --> Name or path to the output video.
148
172
"""
149
173
# 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)=}")
151
178
frame = cv2 .imread (os .path .join (self .screenshot_folder , images [0 ]))
152
179
height , width , _ = frame .shape
153
180
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
+ )
156
185
157
186
# writing all the images to a video
158
187
for image in images :
@@ -171,4 +200,15 @@ def _clear_data(self) -> None:
171
200
os .remove (os .path .join (self .screenshot_folder , image ))
172
201
173
202
def __repr__ (self ) -> str :
174
- return "pyscreenrec is a small and cross-platform python library that can be used to record screen. \n For 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. \n For 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