image_size 3.0.2 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.markdown +6 -0
- data/README.markdown +51 -1
- data/image_size.gemspec +1 -1
- data/lib/image_size/uri_reader.rb +83 -47
- data/spec/image_size/chunky_reader_spec.rb +5 -3
- data/spec/image_size_spec.rb +52 -15
- data/spec/test_server.rb +61 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b0873a78a9fb9f84f07d0389071379dda288330bd38ee7516b442ffde85a3af8
|
4
|
+
data.tar.gz: 0b9fffd2311efa1c24379686821289951a4cd2ec895c27325148a886a24ff57b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6dede99002af9b9fc32f9776e42019079abcd846370c06b2aeee812a73c9ec46dcb6c703d1ae454f807d532c1d6b740aabb999892a6ef610cd17311f6a9a7f0
|
7
|
+
data.tar.gz: 819d8a3b7797b1f12b59eb608ffdeda9338314a923ca54711143832685853ed36b9ae8d165fcfa242ce4e1d72aecd5a03bd11714aa7aee3b464722ea8bb4688f
|
data/CHANGELOG.markdown
CHANGED
@@ -2,6 +2,11 @@
|
|
2
2
|
|
3
3
|
## unreleased
|
4
4
|
|
5
|
+
## v3.1.0 (2022-09-17)
|
6
|
+
|
7
|
+
* Document experimental fetching from http server [#18](https://github.com/toy/image_size/issues/18) [@toy](https://github.com/toy)
|
8
|
+
* Improve experimental fetching of image meta from http server by reading only required amount of data when server does not support range header [@toy](https://github.com/toy)
|
9
|
+
|
5
10
|
## v3.0.2 (2022-05-19)
|
6
11
|
|
7
12
|
* Fix handling empty files [#20](https://github.com/toy/image_size/issues/20) [@toy](https://github.com/toy)
|
@@ -15,6 +20,7 @@
|
|
15
20
|
* Read only required chunks of data for files and seekable IOs [@toy](https://github.com/toy)
|
16
21
|
* Raise `FormatError` whenever reading data returns less data than expected [#12](https://github.com/toy/image_size/issues/12) [@toy](https://github.com/toy)
|
17
22
|
* Add `w`/`width` and `h`/`height` accessors to `Size` [@toy](https://github.com/toy)
|
23
|
+
* Experimental efficient fetching of image meta from http server supporting range [@toy](https://github.com/toy)
|
18
24
|
|
19
25
|
## v2.1.2 (2021-08-21)
|
20
26
|
|
data/README.markdown
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
[![Gem Version](https://img.shields.io/gem/v/image_size?logo=rubygems)](https://rubygems.org/gems/image_size)
|
2
2
|
[![Build Status](https://img.shields.io/github/workflow/status/toy/image_size/check/master?logo=github)](https://github.com/toy/image_size/actions/workflows/check.yml)
|
3
|
+
[![Rubocop](https://img.shields.io/github/workflow/status/toy/image_size/rubocop/master?label=rubocop&logo=rubocop)](https://github.com/toy/image_size/actions/workflows/rubocop.yml)
|
3
4
|
|
4
5
|
# image_size
|
5
6
|
|
@@ -53,7 +54,7 @@ require 'image_size'
|
|
53
54
|
image_size = ImageSize.new(ARGF)
|
54
55
|
```
|
55
56
|
|
56
|
-
Works with `open-uri
|
57
|
+
Works with `open-uri`, see [experimental HTTP server interface below](#experimental-fetch-image-meta-from-http-server):
|
57
58
|
|
58
59
|
```ruby
|
59
60
|
require 'image_size'
|
@@ -89,6 +90,55 @@ File.open('spec/images/jpeg/436x429.jpeg', 'rb') do |fh|
|
|
89
90
|
end
|
90
91
|
```
|
91
92
|
|
93
|
+
### Experimental: fetch image meta from HTTP server
|
94
|
+
|
95
|
+
If server recognises Range header, only needed chunks will be fetched even for TIFF images, otherwise required amount
|
96
|
+
of data will be fetched, in most cases first few kilobytes (TIFF images is an exception).
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
require 'image_size'
|
100
|
+
require 'image_size/uri_reader'
|
101
|
+
|
102
|
+
url = 'http://upload.wikimedia.org/wikipedia/commons/b/b4/Mardin_1350660_1350692_33_images.jpg'
|
103
|
+
p ImageSize.url(url).size
|
104
|
+
```
|
105
|
+
|
106
|
+
This interface is as fast as dedicated gem fastimage for images with meta information in the header:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
url = 'http://upload.wikimedia.org/wikipedia/commons/b/b4/Mardin_1350660_1350692_33_images.jpg'
|
110
|
+
puts Benchmark.measure{ p FastImage.size(url) }
|
111
|
+
```
|
112
|
+
```
|
113
|
+
[9545, 6623]
|
114
|
+
0.004176 0.001974 0.006150 ( 0.282889)
|
115
|
+
```
|
116
|
+
```ruby
|
117
|
+
puts Benchmark.measure{ p ImageSize.url(url).size }
|
118
|
+
```
|
119
|
+
```
|
120
|
+
[9545, 6623]
|
121
|
+
0.005604 0.001406 0.007010 ( 0.238629)
|
122
|
+
```
|
123
|
+
|
124
|
+
And considerably faster for images with meta information at the end of file:
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
url = "https://upload.wikimedia.org/wikipedia/commons/c/c7/Curiosity%27s_Vehicle_System_Test_Bed_%28VSTB%29_Rover_%28PIA15876%29.tif"
|
128
|
+
puts Benchmark.measure{ p FastImage.size(url) }
|
129
|
+
```
|
130
|
+
```
|
131
|
+
[7360, 4912]
|
132
|
+
0.331284 0.247295 0.578579 ( 6.027051)
|
133
|
+
```
|
134
|
+
```ruby
|
135
|
+
puts Benchmark.measure{ p ImageSize.url(url).size }
|
136
|
+
```
|
137
|
+
```
|
138
|
+
[7360, 4912]
|
139
|
+
0.006247 0.001045 0.007292 ( 0.197631)
|
140
|
+
```
|
141
|
+
|
92
142
|
## Licence
|
93
143
|
|
94
144
|
This code is free to use under the terms of the [Ruby's licence](LICENSE.txt).
|
data/image_size.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = 'image_size'
|
5
|
-
s.version = '3.0
|
5
|
+
s.version = '3.1.0'
|
6
6
|
s.summary = %q{Measure image size using pure Ruby}
|
7
7
|
s.description = %q{Measure following file dimensions: apng, bmp, cur, gif, ico, j2c, jp2, jpeg, jpx, mng, pam, pbm, pcx, pgm, png, ppm, psd, svg, swf, tiff, webp, xbm, xpm}
|
8
8
|
s.homepage = "https://github.com/toy/#{s.name}"
|
@@ -6,67 +6,103 @@ require 'image_size/chunky_reader'
|
|
6
6
|
require 'net/https'
|
7
7
|
require 'uri'
|
8
8
|
|
9
|
-
#
|
9
|
+
# Experimental, not yet part of stable API
|
10
10
|
#
|
11
|
-
# It adds ability to fetch
|
12
|
-
# needed chunks if the server recognises Range header
|
11
|
+
# It adds ability to fetch image meta from HTTP server while downloading only
|
12
|
+
# needed chunks if the server recognises Range header, otherwise fetches only
|
13
|
+
# required amount of data
|
13
14
|
class ImageSize
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
def initialize(uri, redirects = 5)
|
18
|
-
if !@http || @http.address != uri.host || @http.port != uri.port
|
19
|
-
@http.finish if @http
|
20
|
-
@http = Net::HTTP.new(uri.host, uri.port)
|
21
|
-
@http.use_ssl = true if uri.scheme == 'https'
|
22
|
-
@http.start
|
23
|
-
end
|
15
|
+
module URIReader # :nodoc:
|
16
|
+
module HTTPChunkyReader # :nodoc:
|
17
|
+
include ChunkyReader
|
24
18
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
case response
|
29
|
-
when Net::HTTPRedirection
|
30
|
-
raise "Too many redirects: #{response['location']}" unless redirects > 0
|
31
|
-
|
32
|
-
initialize(uri + response['location'], redirects - 1)
|
33
|
-
when Net::HTTPOK
|
34
|
-
@body = response.body
|
35
|
-
when Net::HTTPPartialContent
|
36
|
-
@chunks = { 0 => response.body }
|
37
|
-
when Net::HTTPRequestedRangeNotSatisfiable
|
38
|
-
@body = ''
|
39
|
-
else
|
40
|
-
raise "Unexpected response: #{response}"
|
19
|
+
def chunk_range_header(i)
|
20
|
+
{ 'Range' => "bytes=#{chunk_size * i}-#{(chunk_size * (i + 1)) - 1}" }
|
41
21
|
end
|
42
22
|
end
|
43
23
|
|
44
|
-
|
45
|
-
|
24
|
+
class BodyReader # :nodoc:
|
25
|
+
include ChunkyReader
|
26
|
+
|
27
|
+
def initialize(response)
|
28
|
+
@body = String.new
|
29
|
+
@body_reader = response.to_enum(:read_body)
|
30
|
+
end
|
31
|
+
|
32
|
+
def [](offset, length)
|
33
|
+
if @body_reader
|
34
|
+
begin
|
35
|
+
@body << @body_reader.next while @body.length < offset + length
|
36
|
+
rescue StopIteration, IOError
|
37
|
+
@body_reader = nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
46
41
|
@body[offset, length]
|
47
|
-
else
|
48
|
-
super
|
49
42
|
end
|
50
43
|
end
|
51
44
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
raise "Unexpected response: #{response}"
|
60
|
-
end
|
45
|
+
class RangeReader # :nodoc:
|
46
|
+
include HTTPChunkyReader
|
47
|
+
|
48
|
+
def initialize(http, request_uri, chunk0)
|
49
|
+
@http = http
|
50
|
+
@request_uri = request_uri
|
51
|
+
@chunks = { 0 => chunk0 }
|
61
52
|
end
|
62
53
|
|
63
|
-
|
54
|
+
def chunk(i)
|
55
|
+
unless @chunks.key?(i)
|
56
|
+
response = @http.get(@request_uri, chunk_range_header(i))
|
57
|
+
case response
|
58
|
+
when Net::HTTPPartialContent
|
59
|
+
@chunks[i] = response.body
|
60
|
+
else
|
61
|
+
raise "Unexpected response: #{response}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
@chunks[i]
|
66
|
+
end
|
64
67
|
end
|
65
68
|
|
66
|
-
|
69
|
+
class << self
|
70
|
+
include HTTPChunkyReader
|
71
|
+
|
72
|
+
def open(uri, max_redirects = 5)
|
73
|
+
http = nil
|
74
|
+
(max_redirects + 1).times do
|
75
|
+
unless http && http.address == uri.host && http.port == uri.port
|
76
|
+
http.finish if http
|
77
|
+
|
78
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
79
|
+
http.use_ssl = true if uri.scheme == 'https'
|
80
|
+
http.start
|
81
|
+
end
|
67
82
|
|
68
|
-
|
69
|
-
|
83
|
+
response = http.request_get(uri.request_uri, chunk_range_header(0)) do |response_with_unread_body|
|
84
|
+
case response_with_unread_body
|
85
|
+
when Net::HTTPOK
|
86
|
+
return yield BodyReader.new(response_with_unread_body)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
case response
|
91
|
+
when Net::HTTPRedirection
|
92
|
+
uri += response['location']
|
93
|
+
when Net::HTTPPartialContent
|
94
|
+
return yield RangeReader.new(http, uri.request_uri, response.body)
|
95
|
+
when Net::HTTPRequestedRangeNotSatisfiable
|
96
|
+
return yield StringReader.new('')
|
97
|
+
else
|
98
|
+
raise "Unexpected response: #{response}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
raise "Too many redirects: #{uri}"
|
103
|
+
ensure
|
104
|
+
http.finish if http.started?
|
105
|
+
end
|
70
106
|
end
|
71
107
|
end
|
72
108
|
|
@@ -74,7 +110,7 @@ class ImageSize
|
|
74
110
|
class << self
|
75
111
|
def open_with_uri(input, &block)
|
76
112
|
if input.is_a?(URI)
|
77
|
-
|
113
|
+
URIReader.open(input, &block)
|
78
114
|
else
|
79
115
|
open_without_uri(input, &block)
|
80
116
|
end
|
@@ -56,9 +56,11 @@ describe ImageSize::ChunkyReader do
|
|
56
56
|
offsets.each do |offset_b|
|
57
57
|
length = offset_b - offset
|
58
58
|
expect(reader[offset, length]).to eq(data[offset, length]),
|
59
|
-
|
60
|
-
|
61
|
-
|
59
|
+
[
|
60
|
+
"for offset #{offset} and length #{length}",
|
61
|
+
"expected: #{data[offset, length].inspect}",
|
62
|
+
" got: #{reader[offset, length].inspect}",
|
63
|
+
].join("\n")
|
62
64
|
end
|
63
65
|
end
|
64
66
|
end
|
data/spec/image_size_spec.rb
CHANGED
@@ -7,24 +7,16 @@ require 'image_size/uri_reader'
|
|
7
7
|
|
8
8
|
require 'tempfile'
|
9
9
|
require 'shellwords'
|
10
|
-
|
10
|
+
|
11
|
+
require 'test_server'
|
11
12
|
|
12
13
|
describe ImageSize do
|
13
14
|
before :all do
|
14
|
-
@server =
|
15
|
-
:Logger => WEBrick::Log.new(StringIO.new),
|
16
|
-
:AccessLog => [],
|
17
|
-
:BindAddress => '127.0.0.1',
|
18
|
-
:Port => 0, # get the next available port
|
19
|
-
:DocumentRoot => '.',
|
20
|
-
})
|
21
|
-
@server_thread = Thread.new{ @server.start }
|
22
|
-
@server_base_url = URI("http://127.0.0.1:#{@server.config[:Port]}/")
|
15
|
+
@server = TestServer.new
|
23
16
|
end
|
24
17
|
|
25
18
|
after :all do
|
26
|
-
@server.
|
27
|
-
@server_thread.join
|
19
|
+
@server.finish
|
28
20
|
end
|
29
21
|
|
30
22
|
Dir['spec/**/*'].each do |path|
|
@@ -131,9 +123,54 @@ describe ImageSize do
|
|
131
123
|
end
|
132
124
|
|
133
125
|
context 'fetching from webserver' do
|
134
|
-
|
135
|
-
|
136
|
-
|
126
|
+
let(:file_url){ @server.base_url + path }
|
127
|
+
|
128
|
+
context 'supporting range' do
|
129
|
+
context 'without redirects' do
|
130
|
+
it 'gets format and dimensions' do
|
131
|
+
image_size = ImageSize.url(file_url)
|
132
|
+
expect(image_size).to have_attributes(attributes)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'with redirects' do
|
137
|
+
it 'gets format and dimensions' do
|
138
|
+
image_size = ImageSize.url("#{file_url}?redirect=5")
|
139
|
+
expect(image_size).to have_attributes(attributes)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'with too many redirects' do
|
144
|
+
it 'gets format and dimensions' do
|
145
|
+
expect do
|
146
|
+
ImageSize.url("#{file_url}?redirect=6")
|
147
|
+
end.to raise_error(/Too many redirects/)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'not supporting range' do
|
153
|
+
context 'without redirects' do
|
154
|
+
it 'gets format and dimensions' do
|
155
|
+
image_size = ImageSize.url("#{file_url}?ignore_range")
|
156
|
+
expect(image_size).to have_attributes(attributes)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context 'with redirects' do
|
161
|
+
it 'gets format and dimensions' do
|
162
|
+
image_size = ImageSize.url("#{file_url}?ignore_range&redirect=5")
|
163
|
+
expect(image_size).to have_attributes(attributes)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context 'with too many redirects' do
|
168
|
+
it 'gets format and dimensions' do
|
169
|
+
expect do
|
170
|
+
ImageSize.url("#{file_url}?ignore_range&redirect=6")
|
171
|
+
end.to raise_error(/Too many redirects/)
|
172
|
+
end
|
173
|
+
end
|
137
174
|
end
|
138
175
|
end
|
139
176
|
end
|
data/spec/test_server.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'webrick'
|
4
|
+
|
5
|
+
class TestServer
|
6
|
+
attr_reader :base_url
|
7
|
+
|
8
|
+
def initialize(host = '127.0.0.1')
|
9
|
+
server_options = {
|
10
|
+
:Logger => WEBrick::Log.new(StringIO.new),
|
11
|
+
:AccessLog => [],
|
12
|
+
:BindAddress => host,
|
13
|
+
:Port => 0, # get the next available port
|
14
|
+
:DocumentRoot => '.',
|
15
|
+
:RequestCallback => proc do |req, res|
|
16
|
+
redirect = req.query['redirect'].to_i
|
17
|
+
if redirect > 0
|
18
|
+
res.set_redirect(
|
19
|
+
WEBrick::HTTPStatus::TemporaryRedirect,
|
20
|
+
[
|
21
|
+
req.request_uri.port == @base_url.port ? @second_url : @base_url,
|
22
|
+
req.request_uri.request_uri,
|
23
|
+
"?#{encode_www_form(req.query.merge('redirect' => redirect - 1))}",
|
24
|
+
].inject(:+)
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
req.header.delete('range') if req.query['ignore_range']
|
29
|
+
end,
|
30
|
+
}
|
31
|
+
|
32
|
+
@server = WEBrick::HTTPServer.new(server_options)
|
33
|
+
@server.listen(host, 0) # listen on second port
|
34
|
+
|
35
|
+
@base_url = URI("http://#{host}:#{@server.listeners[0].addr[1]}/")
|
36
|
+
@second_url = URI("http://#{host}:#{@server.listeners[1].addr[1]}/")
|
37
|
+
|
38
|
+
@thread = Thread.new{ @server.start }
|
39
|
+
end
|
40
|
+
|
41
|
+
def finish
|
42
|
+
@server.shutdown
|
43
|
+
@thread.join
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
if URI.respond_to?(:encode_www_form)
|
49
|
+
def encode_www_form(h)
|
50
|
+
URI.encode_www_form(h)
|
51
|
+
end
|
52
|
+
else
|
53
|
+
require 'cgi'
|
54
|
+
|
55
|
+
def encode_www_form(h)
|
56
|
+
h.map do |k, v|
|
57
|
+
"#{CGI.escape(k)}=#{CGI.escape(v.to_s)}"
|
58
|
+
end.join('&')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: image_size
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Keisuke Minami
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2022-
|
12
|
+
date: 2022-09-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -120,13 +120,14 @@ files:
|
|
120
120
|
- spec/images/xbm/crlf.16x32.xbm
|
121
121
|
- spec/images/xpm/24x32.xpm
|
122
122
|
- spec/images/xpm/crlf.24x32.xpm
|
123
|
+
- spec/test_server.rb
|
123
124
|
homepage: https://github.com/toy/image_size
|
124
125
|
licenses:
|
125
126
|
- Ruby
|
126
127
|
metadata:
|
127
128
|
bug_tracker_uri: https://github.com/toy/image_size/issues
|
128
129
|
changelog_uri: https://github.com/toy/image_size/blob/master/CHANGELOG.markdown
|
129
|
-
documentation_uri: https://www.rubydoc.info/gems/image_size/3.0
|
130
|
+
documentation_uri: https://www.rubydoc.info/gems/image_size/3.1.0
|
130
131
|
source_code_uri: https://github.com/toy/image_size
|
131
132
|
post_install_message:
|
132
133
|
rdoc_options: []
|
@@ -190,3 +191,4 @@ test_files:
|
|
190
191
|
- spec/images/xbm/crlf.16x32.xbm
|
191
192
|
- spec/images/xpm/24x32.xpm
|
192
193
|
- spec/images/xpm/crlf.24x32.xpm
|
194
|
+
- spec/test_server.rb
|