screencap 0.0.3 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9b7624dd53d821906855cf7ae58dd10655313d48
4
+ data.tar.gz: c8200b69557b17fb185aafe9b60caf9c45c9fb5e
5
+ SHA512:
6
+ metadata.gz: 3e7760a49fb7eb9a09b82b0d2b064ca1d53da0ac74464c6f711c6d4f41824326e9578c2e283723efa383e54228a9259c6af4de127704d72500b785ffab2605e4
7
+ data.tar.gz: 7a0ac0b9fe08fedcfec07d11b91fd955dfe4d7e6c34c7d6066309a9344bd037a23a87451ebefe752df11ef86ee59656d3ba0184a675340dac98394d039faf56f
data/Gemfile CHANGED
@@ -4,5 +4,4 @@ source 'https://rubygems.org'
4
4
  gem 'guard'
5
5
  gem 'guard-rspec'
6
6
  gem 'guard-bundler'
7
- gem 'phantomjs.rb', :git => 'git://github.com/westoque/phantomjs.rb.git'
8
7
  gemspec
data/README.md CHANGED
@@ -29,14 +29,17 @@ it also currently supports a couple of options
29
29
 
30
30
  ```ruby
31
31
  f = Screencap::Fetcher.new('http://google.com')
32
- screenshot = f.fetch(:div => '.header', :output => '~/my_directory.png') #dont forget the extension!
33
- ```
32
+ screenshot = f.fetch(
33
+ :output => '~/my_directory.png', # don't forget the extension!
34
+ # optional:
35
+ :div => '.header', # selector for a specific element to take screenshot of
36
+ :width => 1024,
37
+ :height => 768,
38
+ :top => 0, :left => 0, :width => 100, :height => 100 # dimensions for a specific area
39
+ )
34
40
 
35
- ## To-do
41
+ ```
36
42
 
37
- more tests
38
- better configuration
39
- expose more options
40
43
  ## Contributing
41
44
 
42
45
  1. Fork it
@@ -5,14 +5,9 @@ require 'pathname'
5
5
 
6
6
  module Screencap
7
7
  SCREENCAP_ROOT = Pathname.new(File.dirname(__FILE__))
8
- TMP_DIRECTORY = SCREENCAP_ROOT.join('..', 'tmp')
9
- end
10
-
11
- #config
12
8
 
13
- #tmp directory to store files
14
-
15
- #should return a file handle to tmp director where it is stored
9
+ class Error < StandardError; end
10
+ end
16
11
 
17
12
  require 'screencap/fetcher'
18
13
  require 'screencap/phantom'
@@ -6,7 +6,7 @@ module Screencap
6
6
 
7
7
  def fetch(opts = {})
8
8
  @filename = opts.fetch(:output, clean_filename)
9
- raster(@url, @filename, opts[:div])
9
+ raster(@url, @filename, opts)
10
10
  fetched_file
11
11
  end
12
12
 
@@ -15,7 +15,9 @@ module Screencap
15
15
  end
16
16
 
17
17
  def fetched_file
18
- File.open(filename)
18
+ if File.exists?(filename)
19
+ File.open(filename)
20
+ end
19
21
  end
20
22
 
21
23
  def raster(*args)
@@ -23,7 +25,7 @@ module Screencap
23
25
  end
24
26
 
25
27
  def clean_filename
26
- "#{TMP_DIRECTORY}/#{@url.delete('/.:?!')}.png"
28
+ "#{@url.delete('/.:?!')}.png"
27
29
  end
28
30
  end
29
31
  end
@@ -2,12 +2,17 @@ module Screencap
2
2
  class Phantom
3
3
  RASTERIZE = SCREENCAP_ROOT.join('screencap', 'raster.js')
4
4
 
5
- def self.rasterize(url, path, *args)
6
- # puts RASTERIZE.to_s, url, path, *args # Your code goes here...
7
- Phantomjs.run(RASTERIZE.to_s, url, path, *args)
5
+ def self.rasterize(url, path, args = {})
6
+ params = {
7
+ url: CGI::escape(url),
8
+ output: path
9
+ }.merge(args).collect {|k,v| "#{k}=#{v}"}
10
+ puts RASTERIZE.to_s, params
11
+ result = Phantomjs.run(RASTERIZE.to_s, *params)
12
+ puts result if(args[:debug])
13
+ raise Screencap::Error, "Could not load URL #{url}" if result.match /Unable to load/
8
14
  end
9
15
 
10
-
11
16
  def quoted_args(args)
12
17
  args.map{|x| quoted_arg(x)}
13
18
  end
@@ -1,52 +1,182 @@
1
- var page = new WebPage(),
2
- address, output, div;
1
+ //
2
+ // Takes a screenshot of the given URL, uses named arguments passed in like so: phantomjs raster.js arg=value arg2=value2
3
+ //
4
+ // Arguments:
5
+ // - url - URL to screenshot
6
+ // - output - page to output (e.g. /tmp/output.png)
7
+ // - width [optional] - default 1024 - viewport width
8
+ // - height [optional] - viewport height (see note below on using height)
9
+ // - debug [optional] - default false - whether to do some extra debugging
10
+ // - div [optional] - a selector to use to screenshot to a specific element
11
+ // - resourceWait [optional] - default 300 - the time to wait after the last resource has loaded in MS before taking the screenshot
12
+ // - maxRenderWait [optional] - default 10000 - the maximum time to wait before taking the screenshot, regardless of whether resources are waiting to be loaded
13
+ // - top, left, width, height [optional] - dimensions to use to screenshot a specific area of the screen
14
+ //
15
+ // == Important notice when providing height ==
16
+ //
17
+ // If you provide a height then we resize the html & body tags otherwise render() renders the entire page
18
+ // changing the viewport height does not affect this behaviour of render(), see https://github.com/ariya/phantomjs/issues/10619
19
+ //
20
+ var page = new WebPage(),
21
+ resourceWait = 300,
22
+ maxRenderWait = 10000,
23
+ args = {},
24
+ resourceCount = 0,
25
+ debug = false,
26
+ mask = null,
27
+ forcedRenderTimeout,
28
+ renderTimeout;
3
29
 
4
- if (phantom.args.length < 2 || phantom.args.length > 4) {
5
- console.log('Usage: rasterize.js URL filename div(optional)');
6
- phantom.exit();
7
- }
30
+ //
31
+ // Functions
32
+ //
33
+ function pickupNamedArguments() {
34
+ var i, pair;
35
+ for(i = 0; i < phantom.args.length; i++) {
36
+ pair = phantom.args[i].split(/=(.*)/);
37
+ args[pair[0]] = pair[1];
38
+ }
39
+
40
+ if(!args.width) { args.width = 1024; }
41
+ if(args.url) { args.url = decodeURIComponent(args.url); }
42
+ if(args.debug) { debug = true; }
43
+ if(args.resourceWait) { resourceWait = args.resourceWait; }
44
+ if(args.maxRenderWait) { maxRenderWait = args.maxRenderWait; }
45
+ }
46
+
47
+ function setupMask() {
48
+ // if given settings for an area to take create a mask for that
49
+ if( args.top && args.left && args.width && args.height) {
50
+ mask = {
51
+ top: args.top,
52
+ left: args.left,
53
+ width: args.width,
54
+ height: args.height
55
+ };
56
+ }
57
+ }
8
58
 
9
- address = phantom.args[0];
10
- output = phantom.args[1];
11
- div = phantom.args[2];
59
+ function doRender() {
60
+ clearTimeout(renderTimeout);
61
+ clearTimeout(forcedRenderTimeout);
62
+ page.render(args.output);
63
+ phantom.exit();
64
+ }
12
65
 
13
- //console.log(address, output, div)
66
+ function delayScreenshotForResources() {
67
+ forcedRenderTimeout = setTimeout(doRender, maxRenderWait);
68
+ }
14
69
 
15
- function evaluate(page, func) {
16
- var args = [].slice.call(arguments, 2);
17
- var fn = "function() { return (" + func.toString() + ").apply(this, " + JSON.stringify(args) + ");}";
70
+ function evaluateWithArgs(func) {
71
+ var args = [].slice.call(arguments, 1);
72
+ var fn = "function() { return (" + func.toString() + ").apply(this, " + JSON.stringify(args) + "); }";
18
73
  return page.evaluate(fn);
19
74
  }
20
75
 
21
- function returnDivDimensions(div){
76
+ function takeScreenshot() {
77
+ page.open(args.url, function(status) {
78
+ if(status !== 'success') {
79
+ console.log('Unable to load: ' + args.url);
80
+ phantom.exit();
81
+ } else {
82
+ page.includeJs(
83
+ "https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js",
84
+ function() {
85
+
86
+ var foundDiv = true;
87
+ page.evaluate(function(){ jQuery.noConflict(); });
88
+
89
+ if(args.div) {
90
+ var clip = evaluateWithArgs(withinPage_GetDivDimensions, args.div);
91
+ foundDiv = clip;
92
+ page.clipRect = clip;
93
+ } else if(mask) {
94
+ page.clipRect = mask;
95
+ } else if(args.height) {
96
+ // have a height resize the html & body to workaround https://github.com/ariya/phantomjs/issues/10619
97
+ evaluateWithArgs(
98
+ function(w,h) {
99
+ jQuery('body, html').css({
100
+ width: w + 'px',
101
+ height: h + 'px',
102
+ overflow: 'hidden'
103
+ });
104
+ },
105
+ page.viewportSize.width,
106
+ page.viewportSize.height
107
+ );
108
+ }
109
+
110
+ if(foundDiv) {
111
+ delayScreenshotForResources();
112
+ } else {
113
+ phantom.exit();
114
+ }
115
+ }
116
+ );
117
+ }
118
+ });
119
+ }
120
+
121
+ //
122
+ // Functions evaluated within the page context
123
+ //
124
+ function withinPage_GetDivDimensions(div){
22
125
  var $el = jQuery(div);
23
- var box = $el.offset()
24
- box.height = $el.height();
25
- box.width = $el.width();
26
- return box;
126
+
127
+ if($el.length === 0){
128
+ console.log(div + ' was not found. exiting');
129
+ return false;
130
+ }
131
+
132
+ var dims = $el.offset();
133
+ dims.height = $el.height();
134
+ dims.width = $el.width();
135
+ return dims;
27
136
  }
28
137
 
29
- page.onConsoleMessage = function (msg) {
30
- //console.log("from page:" + msg);
138
+ //
139
+ // Event handlers
140
+ //
141
+ page.onConsoleMessage = function(msg) {
142
+ console.log('from page: ' + msg);
31
143
  };
32
144
 
33
- page.viewportSize = { width: 1000, height: 550 }
145
+ page.onResourceRequested = function(req) {
146
+ resourceCount += 1;
147
+ if(debug) { console.log('> ' + req.id + ' - ' + req.url); }
148
+ clearTimeout(renderTimeout);
149
+ };
34
150
 
35
- page.open(address, function (status) {
36
- if (status !== 'success') {
37
- console.log('Unable to load:' + address);
38
- phantom.exit();
39
- }
40
- //once page loaded, include jQuery from cdn
41
- page.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js", function() {
42
- page.evaluate(function(){jQuery.noConflict()})
43
-
44
- if(div) {
45
- var clip = evaluate(page, returnDivDimensions, div);
46
- page.clipRect = clip;
151
+ page.onResourceReceived = function(res) {
152
+ if(!res.stage || res.stage == 'end') {
153
+ resourceCount -= 1;
154
+ if(debug) {
155
+ console.log(res.id + ' ' + res.status + ' - ' + res.url);
156
+ console.log(resourceCount + ' resources remaining');
157
+ }
158
+ if(resourceCount === 0) {
159
+ // Once all resources are loaded, we wait a small amount of time
160
+ // (resourceWait) in case these resources load other resources.
161
+ clearTimeout(forcedRenderTimeout);
162
+ renderTimeout = setTimeout(doRender, resourceWait);
47
163
  }
164
+ }
165
+ };
48
166
 
49
- page.render(output);
50
- phantom.exit();
51
- });
52
- });
167
+ //
168
+ // Do the processing
169
+ //
170
+ pickupNamedArguments();
171
+ setupMask();
172
+
173
+ console.log(JSON.stringify(args));
174
+
175
+ if( !args.url || !args.output ) {
176
+ console.log('Usage: raster.js url=URL output=filename width=width[optional] height=height[optional] debug=true/false[optional] (div=div[optional] OR top=top left=left width=width height=height)');
177
+ phantom.exit();
178
+ }
179
+
180
+ page.viewportSize = { width: args.width, height: args.height || 1024 };
181
+
182
+ takeScreenshot();
@@ -1,3 +1,3 @@
1
1
  module Screencap
2
- VERSION = "0.0.3"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -2,7 +2,7 @@
2
2
  require File.expand_path('../lib/screencap/version', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |gem|
5
- gem.authors = ["Maxwell Salzberg"]
5
+ gem.authors = ["Maxwell Salzberg","David Spurr"]
6
6
  gem.email = ["maxwell@joindiaspora.com"]
7
7
  gem.description = %q{a gem to grab screenshots of webpages, or just parts of webpages}
8
8
  gem.summary = %q{uses Phantom.js to grab pages, or parts of pages. Simple API.}
@@ -17,4 +17,7 @@ Gem::Specification.new do |gem|
17
17
 
18
18
  gem.add_development_dependency 'rspec', '~> 2.10'
19
19
  gem.add_development_dependency 'rake'
20
+ gem.add_development_dependency 'phantomjs.rb'
21
+ gem.add_development_dependency 'fastimage'
22
+ gem.add_runtime_dependency 'phantomjs'
20
23
  end
@@ -6,7 +6,28 @@ describe Screencap::Fetcher do
6
6
  end
7
7
 
8
8
  it 'supports a custom filename' do
9
- screenshot = Screencap::Fetcher.new('http://yahoo.com').fetch(:output => Screencap::TMP_DIRECTORY + 'kats.png')
9
+ screenshot = Screencap::Fetcher.new('http://yahoo.com').fetch(:output => TMP_DIRECTORY + 'custom_filename.png')
10
10
  File.exists?(screenshot).should == true
11
11
  end
12
+
13
+ it 'supports a custom width' do
14
+ screenshot = Screencap::Fetcher.new('http://google.com').fetch(:output => TMP_DIRECTORY + 'custom_width.jpg', :width => 800)
15
+ FastImage.size(screenshot)[0].should == 800
16
+ end
17
+
18
+ it 'supports a custom height' do
19
+ # note using stackoverflow.com as google.com implements x-frame-options header meaning that it won't load in the object element
20
+ screenshot = Screencap::Fetcher.new('http://stackoverflow.com').fetch(:output => TMP_DIRECTORY + 'custom_height.jpg', :height => 600)
21
+ FastImage.size(screenshot)[1].should == 600
22
+ end
23
+
24
+ it 'captures a given element' do
25
+ screenshot = Screencap::Fetcher.new('http://placehold.it').fetch(:output => TMP_DIRECTORY + 'given_element.jpg', :div => 'img.image')
26
+ FastImage.size(screenshot)[0].should == 140
27
+ end
28
+
29
+ it 'should work when given a query string with ampersand in it' do
30
+ screenshot = Screencap::Fetcher.new('http://google.com?1=2&3=4').fetch(:output => TMP_DIRECTORY + 'ampersand.jpg', :width => 800)
31
+ FastImage.size(screenshot)[0].should == 800
32
+ end
12
33
  end
@@ -2,6 +2,13 @@ require 'spec_helper'
2
2
 
3
3
  describe Screencap do
4
4
  it 'works' do
5
- Screencap::Fetcher.new('http://google.com').fetch
5
+ screenshot = Screencap::Fetcher.new('http://google.com').fetch(output: TMP_DIRECTORY + 'google.png')
6
+ FastImage.size(screenshot)[0].should == 1024
7
+ end
8
+
9
+ it 'throws error when phantom could not load page' do
10
+ expect {
11
+ Screencap::Fetcher.new('http://doesnotexistatallipromise.com/').fetch(output: TMP_DIRECTORY + 'foo.png')
12
+ }.to raise_error Screencap::Error, "Could not load URL http://doesnotexistatallipromise.com/"
6
13
  end
7
14
  end
@@ -1,5 +1,8 @@
1
1
  $:.unshift File.dirname(__FILE__) + '/../lib'
2
2
  require 'screencap'
3
+ require 'fastimage'
4
+
5
+ TMP_DIRECTORY = Screencap::SCREENCAP_ROOT.join('..', 'tmp')
3
6
 
4
7
  RSpec.configure do |config|
5
8
  config.treat_symbols_as_metadata_keys_with_true_values = true
@@ -7,6 +10,9 @@ RSpec.configure do |config|
7
10
  config.filter_run :focus
8
11
 
9
12
  config.before(:all) do
10
- system("rm #{Screencap::TMP_DIRECTORY}/*.png")
13
+ unless ENV['KEEP_OUTPUT']
14
+ system("rm #{TMP_DIRECTORY}/*.png")
15
+ system("rm #{TMP_DIRECTORY}/*.jpg")
16
+ end
11
17
  end
12
18
  end
metadata CHANGED
@@ -1,38 +1,86 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: screencap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
5
- prerelease:
4
+ version: 0.1.1
6
5
  platform: ruby
7
6
  authors:
8
7
  - Maxwell Salzberg
8
+ - David Spurr
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-06 00:00:00.000000000 Z
12
+ date: 2014-04-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &70117782109160 !ruby/object:Gem::Requirement
17
- none: false
16
+ requirement: !ruby/object:Gem::Requirement
18
17
  requirements:
19
- - - ~>
18
+ - - "~>"
20
19
  - !ruby/object:Gem::Version
21
20
  version: '2.10'
22
21
  type: :development
23
22
  prerelease: false
24
- version_requirements: *70117782109160
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '2.10'
25
28
  - !ruby/object:Gem::Dependency
26
29
  name: rake
27
- requirement: &70117782124980 !ruby/object:Gem::Requirement
28
- none: false
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: phantomjs.rb
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: fastimage
58
+ requirement: !ruby/object:Gem::Requirement
29
59
  requirements:
30
- - - ! '>='
60
+ - - ">="
31
61
  - !ruby/object:Gem::Version
32
62
  version: '0'
33
63
  type: :development
34
64
  prerelease: false
35
- version_requirements: *70117782124980
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: phantomjs
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
36
84
  description: a gem to grab screenshots of webpages, or just parts of webpages
37
85
  email:
38
86
  - maxwell@joindiaspora.com
@@ -40,9 +88,9 @@ executables: []
40
88
  extensions: []
41
89
  extra_rdoc_files: []
42
90
  files:
43
- - .gitignore
44
- - .rspec
45
- - .travis.yml
91
+ - ".gitignore"
92
+ - ".rspec"
93
+ - ".travis.yml"
46
94
  - Gemfile
47
95
  - Guardfile
48
96
  - LICENSE
@@ -59,27 +107,26 @@ files:
59
107
  - spec/spec_helper.rb
60
108
  homepage: http://github.com/maxwell/screencap
61
109
  licenses: []
110
+ metadata: {}
62
111
  post_install_message:
63
112
  rdoc_options: []
64
113
  require_paths:
65
114
  - lib
66
115
  required_ruby_version: !ruby/object:Gem::Requirement
67
- none: false
68
116
  requirements:
69
- - - ! '>='
117
+ - - ">="
70
118
  - !ruby/object:Gem::Version
71
119
  version: '0'
72
120
  required_rubygems_version: !ruby/object:Gem::Requirement
73
- none: false
74
121
  requirements:
75
- - - ! '>='
122
+ - - ">="
76
123
  - !ruby/object:Gem::Version
77
124
  version: '0'
78
125
  requirements: []
79
126
  rubyforge_project:
80
- rubygems_version: 1.8.17
127
+ rubygems_version: 2.0.2
81
128
  signing_key:
82
- specification_version: 3
129
+ specification_version: 4
83
130
  summary: uses Phantom.js to grab pages, or parts of pages. Simple API.
84
131
  test_files:
85
132
  - spec/fetcher_spec.rb