-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcapture.py
215 lines (184 loc) · 7.4 KB
/
capture.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
"""
Example of using CEF browser in off-screen rendering mode
(windowless) to create a screenshot of a web page. This
example doesn't depend on any third party GUI framework.
This example is discussed in Tutorial in the Off-screen
rendering section.
With optionl arguments to this script you can resize viewport
so that screenshot includes whole page with height like 5000px
which would be an equivalent of scrolling down multiple pages.
By default when no arguments are provided will load cefpython
project page on Github with 5000px height.
Usage:
python capture.py
python capture.py https://github.com/cztomczak/cefpython 1024 5000 25
python capture.py https://www.google.com/ncr 1024 768 25
Tested configurations:
- CEF Python v57.0+
"""
from cefpython3 import cefpython as cef
import os
import platform
import signal
import subprocess
import sys
import time
# Config
URL = "https://github.com/cztomczak/cefpython"
VIEWPORT_SIZE = (1080, 720)
FPS = 25
OUTFILE = os.path.join(os.path.abspath(os.path.dirname(__file__)), "fifo.bgra")
def main():
check_versions()
sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error
command_line_arguments()
# Off-screen-rendering requires setting "windowless_rendering_enabled"
# option, so that RenderHandler callbacks are called.
cef.Initialize(settings={"windowless_rendering_enabled": True},
commandLineSwitches={"disable-gpu": ""})
print("[capture.py] Open write {}".format(OUTFILE))
global fifo
fifo = open(OUTFILE, 'w')
global browser
browser = create_browser()
# SIGINT or SIGTERM can't be caught due to a bug in CEF framework
# See https://github.com/cztomczak/cefpython/issues/400
# signal.signal(signal.SIGINT, exit_gracefully)
# signal.signal(signal.SIGTERM, exit_gracefully)
cef.MessageLoop()
print("[capture.py] Shutting down")
cef.Shutdown()
def exit_gracefully(signum, frame):
global browser
print("")
# print(os.linesep) # "[capture.py] Signal " + str(signum))
cef.PostTask(cef.TID_UI, exit_app, browser)
def check_versions():
print("[capture.py] CEF Python {ver}".format(ver=cef.__version__))
print("[capture.py] Python {ver} {arch}".format(
ver=platform.python_version(), arch=platform.architecture()[0]))
assert cef.__version__ >= "57.0", "CEF Python v57.0+ required to run this"
def command_line_arguments():
if len(sys.argv) == 6:
url = sys.argv[1]
width = int(sys.argv[2])
height = int(sys.argv[3])
fps = int(sys.argv[4])
outfile = sys.argv[5]
if url.startswith("http://") or url.startswith("https://"):
global URL
URL = url
else:
print("[capture.py] Error: Invalid url argument")
sys.exit(1)
if width > 0 and height > 0:
global VIEWPORT_SIZE
VIEWPORT_SIZE = (width, height)
else:
print("[capture.py] Error: Invalid width and height")
sys.exit(1)
if fps > 0:
global FPS
FPS = fps
else:
print("[capture.py] Error: invalid fps")
if len(outfile) > 0:
global OUTFILE
OUTFILE = outfile
else:
print("[capture.py] Error: invalid outfile")
elif len(sys.argv) > 1:
print("[capture.py] Error: Expected arguments: url width height fps outfile")
sys.exit(1)
def create_browser():
# Create browser in off-screen-rendering mode (windowless mode)
# by calling SetAsOffscreen method. In such mode parent window
# handle can be NULL (0).
parent_window_handle = 0
window_info = cef.WindowInfo()
window_info.SetAsOffscreen(parent_window_handle)
window_info.SetTransparentPainting(True)
print("[capture.py] Viewport size: {size}"
.format(size=str(VIEWPORT_SIZE)))
print("[capture.py] Max frames per second: {fps}"
.format(fps=FPS))
print("[capture.py] Loading url: {url}"
.format(url=URL))
browser = cef.CreateBrowserSync(window_info=window_info,
settings={"windowless_frame_rate": FPS},
url=URL)
browser.SetClientHandler(LoadHandler())
browser.SetClientHandler(RenderHandler())
browser.SendFocusEvent(True)
# You must call WasResized at least once to let know CEF that
# viewport size is available and that OnPaint may be called.
browser.WasResized()
return browser
def exit_app(browser):
# Important note:
# Do not close browser nor exit app from OnLoadingStateChange
# OnLoadError or OnPaint events. Closing browser during these
# events may result in unexpected behavior. Use cef.PostTask
# function to call exit_app from these events.
print("[capture.py] Close browser and exit app")
browser.CloseBrowser()
cef.QuitMessageLoop()
global fifo
if fifo:
fifo.close()
class LoadHandler(object):
def OnLoadingStateChange(self, browser, is_loading, **_):
"""Called when the loading state has changed."""
if not is_loading:
# Loading is complete
# sys.stdout.write(os.linesep)
print("[capture.py] Web page loading is complete")
# See comments in exit_app() why PostTask must be used
# cef.PostTask(cef.TID_UI, exit_app, browser)
def OnLoadError(self, browser, frame, error_code, failed_url, **_):
"""Called when the resource load for a navigation fails
or is canceled."""
if not frame.IsMain():
# We are interested only in loading main url.
# Ignore any errors during loading of other frames.
return
print("[capture.py] ERROR: Failed to load url: {url}"
.format(url=failed_url))
print("[capture.py] Error code: {code}"
.format(code=error_code))
# See comments in exit_app() why PostTask must be used
cef.PostTask(cef.TID_UI, exit_app, browser)
current_milli_time = lambda: int(round(time.time() * 1000))
class RenderHandler(object):
def __init__(self):
self.frameCount = 0
self.lastTime = current_milli_time()
def GetViewRect(self, rect_out, **_):
"""Called to retrieve the view rectangle which is relative
to screen coordinates. Return True if the rectangle was
provided."""
# rect_out --> [x, y, width, height]
rect_out.extend([0, 0, VIEWPORT_SIZE[0], VIEWPORT_SIZE[1]])
return True
def OnPaint(self, browser, element_type, paint_buffer, **_):
"""Called when an element should be painted."""
self.frameCount = self.frameCount + 1
time = current_milli_time()
fps = 1000.0 / (time - self.lastTime)
self.lastTime = time
sys.stdout.write("[capture.py] frame={number: >5} fps={fps: >6.2f}\r"
.format(number=self.frameCount, fps=fps))
sys.stdout.flush()
if element_type == cef.PET_VIEW:
try:
global fifo
fifo.write(paint_buffer.GetString())
fifo.flush()
except Exception as ex:
print("[capture.py] Error {0}".format(str(ex)))
# See comments in exit_app() why PostTask must be used
cef.PostTask(cef.TID_UI, exit_app, browser)
else:
raise Exception("Unsupported element_type in OnPaint")
if __name__ == '__main__':
main()