screencap 0.0.3 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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