1
+ #!/usr/bin/env python
2
+ #
3
+ # webkit2png.py
4
+ #
5
+ # Creates screenshots of webpages using by QtWebkit.
6
+ #
7
+ # Copyright (c) 2008 Roland Tapken <roland@dau-sicher.de>
8
+ #
9
+ # This program is free software; you can redistribute it and/or
10
+ # modify it under the terms of the GNU General Public License
11
+ # as published by the Free Software Foundation; either version 2
12
+ # of the License, or (at your option) any later version.
13
+ #
14
+ # This program is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with this program; if not, write to the Free Software
21
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22
+ #
23
+ # Nice ideas "todo":
24
+ # - Add QTcpSocket support to create a "screenshot daemon" that
25
+ # can handle multiple requests at the same time.
26
+
27
+ from webkit2png import WebkitRenderer
28
+
29
+ import sys
30
+ import signal
31
+ import os
32
+ import urlparse
33
+ import logging
34
+ from optparse import OptionParser
35
+
36
+ from PyQt4 .QtCore import *
37
+ from PyQt4 .QtGui import *
38
+ from PyQt4 .QtWebKit import *
39
+ from PyQt4 .QtNetwork import *
40
+
41
+ VERSION = "20091224"
42
+ LOG_FILENAME = 'webkit2png.log'
43
+ logger = logging .getLogger ('webkit2png' );
44
+
45
+ def init_qtgui (display = None , style = None , qtargs = []):
46
+ """Initiates the QApplication environment using the given args."""
47
+ if QApplication .instance ():
48
+ logger .debug ("QApplication has already been instantiated. \
49
+ Ignoring given arguments and returning existing QApplication." )
50
+ return QApplication .instance ()
51
+
52
+ qtargs2 = [sys .argv [0 ]]
53
+
54
+ if display :
55
+ qtargs2 .append ('-display' )
56
+ qtargs2 .append (display )
57
+ # Also export DISPLAY var as this may be used
58
+ # by flash plugin
59
+ os .environ ["DISPLAY" ] = display
60
+
61
+ if style :
62
+ qtargs2 .append ('-style' )
63
+ qtargs2 .append (style )
64
+
65
+ qtargs2 .extend (qtargs )
66
+
67
+ return QApplication (qtargs2 )
68
+
69
+
70
+ if __name__ == '__main__' :
71
+ # This code will be executed if this module is run 'as-is'.
72
+
73
+ # Enable HTTP proxy
74
+ if 'http_proxy' in os .environ :
75
+ proxy_url = urlparse .urlparse (os .environ .get ('http_proxy' ))
76
+ proxy = QNetworkProxy (QNetworkProxy .HttpProxy , proxy_url .hostname , proxy_url .port )
77
+ QNetworkProxy .setApplicationProxy (proxy )
78
+
79
+ # Parse command line arguments.
80
+ # Syntax:
81
+ # $0 [--xvfb|--display=DISPLAY] [--debug] [--output=FILENAME] <URL>
82
+
83
+ description = "Creates a screenshot of a website using QtWebkit." \
84
+ + "This program comes with ABSOLUTELY NO WARRANTY. " \
85
+ + "This is free software, and you are welcome to redistribute " \
86
+ + "it under the terms of the GNU General Public License v2."
87
+
88
+ parser = OptionParser (usage = "usage: %prog [options] <URL>" ,
89
+ version = "%prog " + VERSION + ", Copyright (c) Roland Tapken" ,
90
+ description = description , add_help_option = True )
91
+ parser .add_option ("-x" , "--xvfb" , nargs = 2 , type = "int" , dest = "xvfb" ,
92
+ help = "Start an 'xvfb' instance with the given desktop size." , metavar = "WIDTH HEIGHT" )
93
+ parser .add_option ("-g" , "--geometry" , dest = "geometry" , nargs = 2 , default = (0 , 0 ), type = "int" ,
94
+ help = "Geometry of the virtual browser window (0 means 'autodetect') [default: %default]." , metavar = "WIDTH HEIGHT" )
95
+ parser .add_option ("-o" , "--output" , dest = "output" ,
96
+ help = "Write output to FILE instead of STDOUT." , metavar = "FILE" )
97
+ parser .add_option ("-f" , "--format" , dest = "format" , default = "png" ,
98
+ help = "Output image format [default: %default]" , metavar = "FORMAT" )
99
+ parser .add_option ("--scale" , dest = "scale" , nargs = 2 , type = "int" ,
100
+ help = "Scale the image to this size" , metavar = "WIDTH HEIGHT" )
101
+ parser .add_option ("--aspect-ratio" , dest = "ratio" , type = "choice" , choices = ["ignore" , "keep" , "expand" , "crop" ],
102
+ help = "One of 'ignore', 'keep', 'crop' or 'expand' [default: %default]" )
103
+ parser .add_option ("-F" , "--feature" , dest = "features" , action = "append" , type = "choice" ,
104
+ choices = ["javascript" , "plugins" ],
105
+ help = "Enable additional Webkit features ('javascript', 'plugins')" , metavar = "FEATURE" )
106
+ parser .add_option ("-w" , "--wait" , dest = "wait" , default = 0 , type = "int" ,
107
+ help = "Time to wait after loading before the screenshot is taken [default: %default]" , metavar = "SECONDS" )
108
+ parser .add_option ("-t" , "--timeout" , dest = "timeout" , default = 0 , type = "int" ,
109
+ help = "Time before the request will be canceled [default: %default]" , metavar = "SECONDS" )
110
+ parser .add_option ("-W" , "--window" , dest = "window" , action = "store_true" ,
111
+ help = "Grab whole window instead of frame (may be required for plugins)" , default = False )
112
+ parser .add_option ("-T" , "--transparent" , dest = "transparent" , action = "store_true" ,
113
+ help = "Render output on a transparent background (Be sure to have a transparent background defined in the html)" , default = False )
114
+ parser .add_option ("" , "--style" , dest = "style" ,
115
+ help = "Change the Qt look and feel to STYLE (e.G. 'windows')." , metavar = "STYLE" )
116
+ parser .add_option ("-d" , "--display" , dest = "display" ,
117
+ help = "Connect to X server at DISPLAY." , metavar = "DISPLAY" )
118
+ parser .add_option ("--debug" , action = "store_true" , dest = "debug" ,
119
+ help = "Show debugging information." , default = False )
120
+ parser .add_option ("--log" , action = "store" , dest = "logfile" , default = LOG_FILENAME ,
121
+ help = "Select the log output file" ,)
122
+
123
+ # Parse command line arguments and validate them (as far as we can)
124
+ (options ,args ) = parser .parse_args ()
125
+ if len (args ) != 1 :
126
+ parser .error ("incorrect number of arguments" )
127
+ if options .display and options .xvfb :
128
+ parser .error ("options -x and -d are mutually exclusive" )
129
+ options .url = args [0 ]
130
+
131
+ logging .basicConfig (filename = options .logfile ,level = logging .WARN ,)
132
+
133
+ # Enable output of debugging information
134
+ if options .debug :
135
+ logger .setLevel (logging .DEBUG )
136
+
137
+ if options .xvfb :
138
+ # Start 'xvfb' instance by replacing the current process
139
+ server_num = int (os .getpid () + 1e6 )
140
+ newArgs = ["xvfb-run" , "--auto-servernum" , "--server-num" , str (server_num ), "--server-args=-screen 0, %dx%dx24" % options .xvfb , sys .argv [0 ]]
141
+ skipArgs = 0
142
+ for i in range (1 , len (sys .argv )):
143
+ if skipArgs > 0 :
144
+ skipArgs -= 1
145
+ elif sys .argv [i ] in ["-x" , "--xvfb" ]:
146
+ skipArgs = 2 # following: width and height
147
+ else :
148
+ newArgs .append (sys .argv [i ])
149
+ logger .debug ("Executing %s" % " " .join (newArgs ))
150
+ os .execvp (newArgs [0 ],newArgs [1 :])
151
+
152
+ # Prepare outout ("1" means STDOUT)
153
+ if options .output == None :
154
+ options .output = sys .stdout
155
+ else :
156
+ options .output = open (options .output , "w" )
157
+
158
+ logger .debug ("Version %s, Python %s, Qt %s" , VERSION , sys .version , qVersion ());
159
+
160
+ # Technically, this is a QtGui application, because QWebPage requires it
161
+ # to be. But because we will have no user interaction, and rendering can
162
+ # not start before 'app.exec_()' is called, we have to trigger our "main"
163
+ # by a timer event.
164
+ def __main_qt ():
165
+ # Render the page.
166
+ # If this method times out or loading failed, a
167
+ # RuntimeException is thrown
168
+ try :
169
+ # Initialize WebkitRenderer object
170
+ renderer = WebkitRenderer ()
171
+ renderer .logger = logger
172
+ renderer .width = options .geometry [0 ]
173
+ renderer .height = options .geometry [1 ]
174
+ renderer .timeout = options .timeout
175
+ renderer .wait = options .wait
176
+ renderer .format = options .format
177
+ renderer .grabWholeWindow = options .window
178
+ renderer .renderTransparentBackground = options .transparent
179
+
180
+ if options .scale :
181
+ renderer .scaleRatio = options .ratio
182
+ renderer .scaleToWidth = options .scale [0 ]
183
+ renderer .scaleToHeight = options .scale [1 ]
184
+
185
+ if options .features :
186
+ if "javascript" in options .features :
187
+ renderer .qWebSettings [QWebSettings .JavascriptEnabled ] = True
188
+ if "plugins" in options .features :
189
+ renderer .qWebSettings [QWebSettings .PluginsEnabled ] = True
190
+
191
+ renderer .render_to_file (url = options .url , file_object = options .output )
192
+ options .output .close ()
193
+ QApplication .exit (0 )
194
+ except RuntimeError , e :
195
+ logger .error ("main: %s" % e )
196
+ print >> sys .stderr , e
197
+ QApplication .exit (1 )
198
+
199
+ # Initialize Qt-Application, but make this script
200
+ # abortable via CTRL-C
201
+ app = init_qtgui (display = options .display , style = options .style )
202
+ signal .signal (signal .SIGINT , signal .SIG_DFL )
203
+
204
+ QTimer .singleShot (0 , __main_qt )
205
+ sys .exit (app .exec_ ())
0 commit comments