thumbshooter 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of thumbshooter might be problematic. Click here for more details.

data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Thumbshooter
2
2
  ============
3
3
 
4
- Generates thumbshots of URLs by using webkit und python.
4
+ Generates thumbshots of URLs by using Webkit and QT4.
5
5
 
6
6
 
7
7
  Requirements
@@ -9,7 +9,7 @@ Requirements
9
9
 
10
10
  Please ensure python-qt4 and qt4-webkit is installed.
11
11
 
12
- apt-get install libqt4-webkit python-qt4
12
+ apt-get install libqt4-ruby libqt4-webkit
13
13
 
14
14
  You do also need a running x server. You can use a lightweight
15
15
  x server by doing "apt-get install xvfb" and enabling it:
@@ -32,4 +32,5 @@ Example
32
32
 
33
33
 
34
34
 
35
- Copyright (c) 2009 Julian Kornberger, released under the GNU license
35
+ Copyright (c) 2009 Julian Kornberger | Digineo GmbH Germany
36
+ released under the GNU license
data/lib/thumbshooter.rb CHANGED
@@ -4,9 +4,16 @@
4
4
  class Thumbshooter
5
5
 
6
6
  # use X window virtual framebuffer?
7
- cattr_accessor :use_xvfb
7
+ def self.use_xvfb=(value)
8
+ @use_xvfb = value
9
+ end
10
+
11
+ # use X window virtual framebuffer?
12
+ def self.use_xvfb
13
+ @use_xvfb
14
+ end
8
15
 
9
- WEBKIT2PNG = File.dirname(__FILE__) + '/webkit2png.py'
16
+ WEBKIT2PNG = File.dirname(__FILE__) + '/webkit2png.rb'
10
17
 
11
18
  #
12
19
  # screen: dimension of the view part (w * h) i.e. 800x800
@@ -21,11 +28,10 @@ class Thumbshooter
21
28
  when :screen
22
29
  # 123x124 (width x height)
23
30
  raise ArgumentError, "invalid value for #{key}: #{value}" unless value =~ /^\d+x\d+$/
24
- @args << " --geometry=" + value.sub('x',' ')
31
+ @screen = value
32
+ @args << " --size=" + value
25
33
  when :timeout
26
34
  @args << " --timeout=#{value}"
27
- when :format
28
- @args << " --#{key}=#{value}"
29
35
  when :resize
30
36
  raise ArgumentError, "invalid value for #{key}: #{value}" unless value =~ /^\d+x\d+$/
31
37
  @resize = value
@@ -43,15 +49,26 @@ class Thumbshooter
43
49
 
44
50
  # execute webkit2png-script and save stdout
45
51
  command = ''
46
- command << 'xvfb-run --server-args="-screen 0, 640x480x24" ' if use_xvfb
52
+ if self.class.use_xvfb
53
+ # calculate screen size
54
+ screen = @screen ? @screen.split('x').collect{|i|i.to_i+100}.join("x") : '1024x768'
55
+ # add xvfb wrapper
56
+ command << "xvfb-run -n 14 --server-args='-screen 0, #{screen}x24' "
57
+ end
58
+
47
59
  command << "#{WEBKIT2PNG} '#{url}' #{args}"
48
60
 
49
- img = `#{command}`
61
+ img = `#{command} 2>&1`
50
62
  status = $?.to_i
51
- if status != 0
52
- raise "webkitpng failed with status #{status}: #{img}"
63
+ pos = img.index("\211PNG")
64
+
65
+ if status != 0 || !pos
66
+ raise "#{WEBKIT2PNG} failed with status #{status}: #{img}"
53
67
  end
54
68
 
69
+ # strip beginning rubish
70
+ img = img[pos..-1]
71
+
55
72
  if @resize
56
73
  width,height = @resize.split("x")
57
74
  img = resize(img,width,height)
data/lib/webkit2png.rb ADDED
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Example call:
4
+ #
5
+ # ./webkit2png.rb --size 800x600 --timeout 15 http://www.example.com/ > thumbshot.png
6
+ #
7
+
8
+ require 'optparse'
9
+
10
+ # Load dependencies
11
+ begin
12
+ require "Qt4"
13
+ require "qtwebkit"
14
+ rescue LoadError => e
15
+ STDERR.puts e
16
+ STDERR.puts 'try: apt-get install libqt4-ruby libqt4-webkit'
17
+ exit -2
18
+ end
19
+
20
+
21
+ class Thumbshooter
22
+
23
+ IMAGE_TYPE = 'png'
24
+ CHECK_LOADING_STATUS_INTERVAL = 0.1
25
+
26
+ attr_accessor :timeout
27
+
28
+ def initialize
29
+ @timeout = 30
30
+ @state = 'waiting'
31
+ @progress = 0
32
+ @started_at = nil
33
+ @output = nil
34
+ self.screen_size = '1024x768'
35
+ end
36
+
37
+ def screen_size=(value)
38
+ raise ArgumentError, "invalid size: #{value}" unless value =~ /^(\d+)x(\d+$)/
39
+ @screen_size = Qt::Size.new($~[1].to_i, $~[2].to_i)
40
+ end
41
+
42
+ def generate(url)
43
+ app = Qt::Application.new(ARGV)
44
+ webview = Qt::WebView.new()
45
+
46
+ webview.connect(SIGNAL("loadStarted()")) do
47
+ @started_at = Time.now.to_i
48
+ end
49
+
50
+ webview.connect(SIGNAL('loadFinished(bool)')) do |result|
51
+ if result
52
+ @state = 'finished-success'
53
+ else
54
+ @state = 'finished-fail'
55
+ @progress = false
56
+ end
57
+ suspend_thread # Give it enough time to switch to the sentinel thread and avoid an empty exec loop
58
+ end
59
+
60
+ webview.connect(SIGNAL("loadProgress(int)")) do |progress|
61
+ @progress = progress
62
+ suspend_thread if has_reached_time_out?
63
+ end
64
+
65
+ # Hide the scrollbars
66
+ mainFrame = webview.page.mainFrame
67
+ mainFrame.setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff)
68
+ mainFrame.setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff)
69
+
70
+ webview.load(Qt::Url.new(url))
71
+ webview.resize(@screen_size)
72
+ webview.show
73
+ render_page_thread = Thread.new do
74
+ app.exec
75
+ end
76
+
77
+ check_status_thread = Thread.new do
78
+ while true do
79
+ sleep CHECK_LOADING_STATUS_INTERVAL
80
+ if @state =~ /^finished/ || has_reached_time_out?
81
+ # Save a screenshot if page finished loaded or it has timed out with 50%+ completion
82
+ if @state == 'finished-success' || (@progress && @progress >= 50)
83
+ save_screenshot(webview)
84
+ end
85
+ render_page_thread.kill
86
+ break
87
+ end
88
+ end
89
+ end
90
+
91
+ check_status_thread.join
92
+ render_page_thread.join
93
+
94
+ # percentage of completion
95
+ @progress
96
+ end
97
+
98
+ def output
99
+ @output
100
+ end
101
+
102
+ def progress
103
+ @progress
104
+ end
105
+
106
+ private
107
+
108
+ def has_reached_time_out?
109
+ Time.now.to_i >= (@started_at + timeout)
110
+ end
111
+
112
+ def suspend_thread
113
+ sleep(20)
114
+ end
115
+
116
+ def save_screenshot(webview)
117
+ pixmap = Qt::Pixmap.grabWindow(webview.window.winId)
118
+
119
+ # save directly to file
120
+ # pixmap.save('thumbshot.png', 'png')
121
+
122
+ byteArray = Qt::ByteArray.new
123
+ buffer = Qt::Buffer.new(byteArray)
124
+ buffer.open(Qt::IODevice::WriteOnly)
125
+
126
+ pixmap.save(buffer, IMAGE_TYPE)
127
+ @output = byteArray
128
+ end
129
+
130
+ end
131
+
132
+
133
+ shooter = Thumbshooter.new
134
+
135
+ # Parse arguments
136
+ opts = OptionParser.new
137
+ opts.on('--size WIDTHxHEIGHT') do |arg|
138
+ shooter.screen_size = arg
139
+ end
140
+
141
+ opts.on('--timeout SECONDS', Integer) do |arg|
142
+ shooter.timeout = arg
143
+ end
144
+ opts.parse!
145
+
146
+ # generate thumbshot
147
+ shooter.generate(ARGV.last)
148
+
149
+ # return result
150
+ if shooter.output
151
+ puts shooter.output
152
+ else
153
+ STDERR.puts "failed at progress: #{shooter.progress}"
154
+ exit -1
155
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thumbshooter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julian Kornberger
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-07 00:00:00 +02:00
12
+ date: 2009-11-25 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -22,7 +22,7 @@ dependencies:
22
22
  - !ruby/object:Gem::Version
23
23
  version: "2"
24
24
  version:
25
- description: Thumbshooter creates thumbshots of websites using webkit, qt4 and python.
25
+ description: Thumbshooter creates thumbshots of websites using webkit and qt4.
26
26
  email: kontakt@digineo.de
27
27
  executables: []
28
28
 
@@ -37,7 +37,7 @@ files:
37
37
  - LICENSE
38
38
  - README.md
39
39
  - lib/thumbshooter.rb
40
- - lib/webkit2png.py
40
+ - lib/webkit2png.rb
41
41
  - rails/init.rb
42
42
  has_rdoc: true
43
43
  homepage: http://github.com/digineo/thumbshooter
@@ -62,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
62
62
  version: "0"
63
63
  version:
64
64
  requirements:
65
- - :"rmagick libqt4-webkit python-qt4"
65
+ - :"rmagick libqt4-ruby libqt4-webkit"
66
66
  rubyforge_project:
67
67
  rubygems_version: 1.3.5
68
68
  signing_key:
data/lib/webkit2png.py DELETED
@@ -1,210 +0,0 @@
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
- import sys
24
- import signal
25
- import os
26
- import logging
27
- import time
28
-
29
- from optparse import OptionParser
30
-
31
- from PyQt4.QtCore import *
32
- from PyQt4.QtGui import *
33
- from PyQt4.QtWebKit import QWebPage
34
-
35
- # Class for Website-Rendering. Uses QWebPage, which
36
- # requires a running QtGui to work.
37
- class WebkitRenderer(QObject):
38
-
39
- # Initializes the QWebPage object and registers some slots
40
- def __init__(self):
41
- logging.debug("Initializing class %s", self.__class__.__name__)
42
- self._page = QWebPage()
43
- self.connect(self._page, SIGNAL("loadFinished(bool)"), self.__on_load_finished)
44
- self.connect(self._page, SIGNAL("loadStarted()"), self.__on_load_started)
45
-
46
- # The way we will use this, it seems to be unesseccary to have Scrollbars enabled
47
- self._page.mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
48
- self._page.mainFrame().setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAlwaysOff)
49
-
50
- # Helper for multithreaded communication through signals
51
- self.__loading = False
52
- self.__loading_result = False
53
-
54
- # Loads "url" and renders it.
55
- # Returns QImage-object on success.
56
- def render(self, url, width=0, height=0, timeout=0):
57
- logging.debug("render(%s, timeout=%d)", url, timeout)
58
-
59
- # This is an event-based application. So we have to wait until
60
- # "loadFinished(bool)" raised.
61
- cancelAt = time.time() + timeout
62
- self._page.mainFrame().load(QUrl(url))
63
- while self.__loading:
64
- if timeout > 0 and time.time() >= cancelAt:
65
- raise RuntimeError("Request timed out")
66
- QCoreApplication.processEvents()
67
-
68
- logging.debug("Processing result")
69
-
70
- if self.__loading_result == False:
71
- raise RuntimeError("Failed to load %s" % url)
72
-
73
- # Set initial viewport (the size of the "window")
74
- size = self._page.mainFrame().contentsSize()
75
- if width > 0:
76
- size.setWidth(width)
77
- if height > 0:
78
- size.setHeight(height)
79
- self._page.setViewportSize(size)
80
-
81
- # Paint this frame into an image
82
- image = QImage(self._page.viewportSize(), QImage.Format_ARGB32)
83
- painter = QPainter(image)
84
- self._page.mainFrame().render(painter)
85
- painter.end()
86
-
87
- return image
88
-
89
-
90
- # Eventhandler for "loadStarted()" signal
91
- def __on_load_started(self):
92
- logging.debug("loading started")
93
- self.__loading = True
94
-
95
- # Eventhandler for "loadFinished(bool)" signal
96
- def __on_load_finished(self, result):
97
- logging.debug("loading finished with result %s", result)
98
- self.__loading = False
99
- self.__loading_result = result
100
-
101
-
102
- if __name__ == '__main__':
103
- # Parse command line arguments.
104
- # Syntax:
105
- # $0 [--xvfb|--display=DISPLAY] [--debug] [--output=FILENAME] <URL>
106
- qtargs = [sys.argv[0]]
107
-
108
- description = "Creates a screenshot of a website using QtWebkit." \
109
- + "This program comes with ABSOLUTELY NO WARRANTY. " \
110
- + "This is free software, and you are welcome to redistribute " \
111
- + "it under the terms of the GNU General Public License v2."
112
-
113
- parser = OptionParser(usage="usage: %prog [options] <URL>",
114
- version="%prog 0.1, Copyright (c) 2008 Roland Tapken",
115
- description=description)
116
- parser.add_option("-x", "--xvfb", action="store_true", dest="xvfb",
117
- help="Start an 'xvfb' instance.", default=False)
118
- parser.add_option("-g", "--geometry", dest="geometry", nargs=2, default=(0, 0), type="int",
119
- help="Geometry of the virtual browser window (0 means 'autodetect') [default: %default].", metavar="WIDTH HEIGHT")
120
- parser.add_option("-o", "--output", dest="output",
121
- help="Write output to FILE instead of STDOUT.", metavar="FILE")
122
- parser.add_option("-f", "--format", dest="format", default="png",
123
- help="Output image format [default: %default]", metavar="FORMAT")
124
- parser.add_option("--scale", dest="scale", nargs=2, type="int",
125
- help="Scale the image to this size", metavar="WIDTH HEIGHT")
126
- parser.add_option("--aspect-ratio", dest="ratio", type="choice", choices=["ignore", "keep", "expand"],
127
- help="One of 'ignore', 'keep' or 'expand' [default: %default]")
128
- parser.add_option("-t", "--timeout", dest="timeout", default=0, type="int",
129
- help="Time before the request will be canceled [default: %default]", metavar="SECONDS")
130
- parser.add_option("-d", "--display", dest="display",
131
- help="Connect to X server at DISPLAY.", metavar="DISPLAY")
132
- parser.add_option("--debug", action="store_true", dest="debug",
133
- help="Show debugging information.", default=False)
134
-
135
- # Parse command line arguments and validate them (as far as we can)
136
- (options,args) = parser.parse_args()
137
- if len(args) != 1:
138
- parser.error("incorrect number of arguments")
139
- if options.display and options.xvfb:
140
- parser.error("options -x and -d are mutually exclusive")
141
- options.url = args[0]
142
-
143
- # Enable output of debugging information
144
- if options.debug:
145
- logging.basicConfig(level=logging.DEBUG)
146
-
147
- # Add display information for qt (you may also use the environment var DISPLAY)
148
- if options.display:
149
- qtargs.append("-display")
150
- qtargs.append(options.display)
151
-
152
- if options.xvfb:
153
- # Start 'xvfb' instance by replacing the current process
154
- newArgs = ["xvfb-run", "--server-args=-screen 0, 640x480x24", sys.argv[0]]
155
- for i in range(1, len(sys.argv)):
156
- if sys.argv[i] not in ["-x", "--xvfb"]:
157
- newArgs.append(sys.argv[i])
158
- logging.debug("Executing %s" % " ".join(newArgs))
159
- os.execvp(newArgs[0], newArgs)
160
- raise RuntimeError("Failed to execute '%s'" % newArgs[0])
161
-
162
- # Prepare outout ("1" means STDOUT)
163
- if options.output == None:
164
- qfile = QFile()
165
- qfile.open(1, QIODevice.WriteOnly)
166
- options.output = qfile
167
-
168
- # Initialize Qt-Application, but make this script
169
- # abortable via CTRL-C
170
- app = QApplication(qtargs)
171
- signal.signal(signal.SIGINT, signal.SIG_DFL)
172
-
173
- # Initialize WebkitRenderer object
174
- renderer = WebkitRenderer()
175
-
176
- # Technically, this is a QtGui application, because QWebPage requires it
177
- # to be. But because we will have no user interaction, and rendering can
178
- # not start before 'app.exec_()' is called, we have to trigger our "main"
179
- # by a timer event.
180
- def __on_exec():
181
- # Render the page.
182
- # If this method times out or loading failed, a
183
- # RuntimeException is thrown
184
- try:
185
- image = renderer.render(options.url,
186
- width=options.geometry[0],
187
- height=options.geometry[1],
188
- timeout=options.timeout)
189
-
190
- if options.scale:
191
- # Scale this image
192
- if options.ratio == 'keep':
193
- ratio = Qt.KeepAspectRatio
194
- elif options.ratio == 'expand':
195
- ratio = Qt.KeepAspectRatioByExpanding
196
- else:
197
- ratio = Qt.IgnoreAspectRatio
198
- image = image.scaled(options.scale[0], options.scale[1], ratio)
199
-
200
- image.save(options.output, options.format)
201
- if isinstance(options.output, QFile):
202
- options.output.close()
203
- sys.exit(0)
204
- except RuntimeError, e:
205
- logging.error(e.msg)
206
- sys.exit(1)
207
-
208
- # Go to main loop (required)
209
- QTimer().singleShot(0, __on_exec)
210
- sys.exit(app.exec_())