imgproxy 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +100 -0
- data/lib/imgproxy/builder.rb +113 -0
- data/lib/imgproxy/config.rb +41 -0
- data/lib/imgproxy/options.rb +118 -0
- data/lib/imgproxy/version.rb +3 -0
- data/lib/imgproxy.rb +72 -0
- metadata +49 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0b6f43dc8ff8bc89edf3015f8ebac74c4c8b4f785a0c886209a66133d44c7754
|
4
|
+
data.tar.gz: 68749943510a2c6b41608ebb9fdd132d5339ad35486019710588f5ec3aa49d35
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a320137aa833b9af94bb9de211b297e49fe2bb515a2b6a08d6cfae9b512173653ec82c1bf85964e126a56e39fe92073049e4d382d789d6a0c70e8d89d551ca20
|
7
|
+
data.tar.gz: 9851452726dcadfd6b84fa6018b4d3791650a69c52860fb79a5d9d2ee48b1f275d9bebfd6e8174da69acb0968d411e03ad890c26979ab2ec1b6007c412b06d29
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2019 imgproxy
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# imgproxy.rb
|
2
|
+
|
3
|
+
[![Gem](https://img.shields.io/gem/v/imgproxy.svg?style=for-the-badge)](https://rubygems.org/gems/imgproxy) [![rubydoc.org](https://img.shields.io/badge/rubydoc-reference-blue.svg?style=for-the-badge)](https://www.rubydoc.info/gems/imgproxy/)
|
4
|
+
|
5
|
+
Gem for [imgproxy](https://github.com/DarthSim/imgproxy) URLs generation.
|
6
|
+
|
7
|
+
[imgproxy](https://github.com/DarthSim/imgproxy) is a fast and secure standalone server for resizing and converting remote images. The main principles of imgproxy are simplicity, speed, and security.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
```
|
12
|
+
gem install imgproxy
|
13
|
+
```
|
14
|
+
|
15
|
+
or add it to your `Gemfile`:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem "imgproxy"
|
19
|
+
```
|
20
|
+
|
21
|
+
## Configuration
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
# config/initializers/imgproxy.rb
|
25
|
+
|
26
|
+
Imgproxy.configure do |config|
|
27
|
+
# imgproxy endpoint
|
28
|
+
config.endpoint = "http://imgproxy.example.com"
|
29
|
+
# hex-encoded signature key
|
30
|
+
config.hex_key = "your_key"
|
31
|
+
# hex-encoded signature salt
|
32
|
+
config.hex_salt = "your_salt"
|
33
|
+
# signature size. Defaults to 32
|
34
|
+
config.signature_size = 5
|
35
|
+
# use short processing option names (`rs` for `resize`, `g` for `gravity`, etc).
|
36
|
+
# Defaults to true
|
37
|
+
config.use_short_options = false
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
## Usage
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
Imgproxy.url_for(
|
45
|
+
"http://images.example.com/images/image.jpg",
|
46
|
+
width: 500,
|
47
|
+
height: 400,
|
48
|
+
resizing_type: :fill,
|
49
|
+
sharpen: 0.5
|
50
|
+
)
|
51
|
+
# => http://imgproxy.example.com/2tjGMpWqjO/rs:fill:500:400/sh:0.5/plain/http://images.example.com/images/image.jpg
|
52
|
+
```
|
53
|
+
|
54
|
+
You can reuse processing options by using `Imgproxy::Builder`:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
builder = Imgproxy::Builder.new(
|
58
|
+
width: 500,
|
59
|
+
height: 400,
|
60
|
+
resizing_type: :fill,
|
61
|
+
sharpen: 0.5
|
62
|
+
)
|
63
|
+
|
64
|
+
builder.url_for("http://images.example.com/images/image1.jpg")
|
65
|
+
builder.url_for("http://images.example.com/images/image2.jpg")
|
66
|
+
```
|
67
|
+
|
68
|
+
Available options are:
|
69
|
+
|
70
|
+
* `resizing_type`
|
71
|
+
* `width`
|
72
|
+
* `height`
|
73
|
+
* `dpr`
|
74
|
+
* `enlarge`
|
75
|
+
* `extend`
|
76
|
+
* `gravity`
|
77
|
+
* `gravity_x`
|
78
|
+
* `gravity_y`
|
79
|
+
* `quality`
|
80
|
+
* `background`
|
81
|
+
* `blur`
|
82
|
+
* `sharpen`
|
83
|
+
* `watermark_opacity`
|
84
|
+
* `watermark_position`
|
85
|
+
* `watermark_x_offset`
|
86
|
+
* `watermark_y_offset`
|
87
|
+
* `watermark_scale`
|
88
|
+
* `preset`
|
89
|
+
* `cachebuster`
|
90
|
+
* `format`
|
91
|
+
* `use_short_options`
|
92
|
+
|
93
|
+
_See [imgproxy URL format guide](https://github.com/DarthSim/imgproxy/blob/master/docs/generating_the_url_advanced.md) for more info_
|
94
|
+
|
95
|
+
## Contributing
|
96
|
+
|
97
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/imgproxy/imgproxy.rb.
|
98
|
+
|
99
|
+
## License
|
100
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
require 'imgproxy/options'
|
5
|
+
|
6
|
+
module Imgproxy
|
7
|
+
# Builds imgproxy URL
|
8
|
+
#
|
9
|
+
# builder = Imgproxy::Builder.new(
|
10
|
+
# width: 500,
|
11
|
+
# height: 400,
|
12
|
+
# resizing_type: :fill,
|
13
|
+
# sharpen: 0.5
|
14
|
+
# )
|
15
|
+
#
|
16
|
+
# builder.url_for("http://images.example.com/images/image1.jpg")
|
17
|
+
# builder.url_for("http://images.example.com/images/image2.jpg")
|
18
|
+
class Builder
|
19
|
+
# @param [Hash] options Processing options
|
20
|
+
# @see Imgproxy.url_for
|
21
|
+
def initialize(options = {})
|
22
|
+
@use_short_options = options.delete(:use_short_options)
|
23
|
+
@use_short_options = config.use_short_options if @use_short_options.nil?
|
24
|
+
|
25
|
+
@options = Imgproxy::Options.new(options)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Genrates imgproxy URL
|
29
|
+
#
|
30
|
+
# @return [String] imgproxy URL
|
31
|
+
# @param [String] image Source image URL
|
32
|
+
# @see Imgproxy.url_for
|
33
|
+
def url_for(image)
|
34
|
+
path = [*processing_options, 'plain', escape_url(image)].join('/')
|
35
|
+
path = "#{path}@#{options[:format]}" if @options[:format]
|
36
|
+
|
37
|
+
signature = sign_path(path)
|
38
|
+
|
39
|
+
"#{Imgproxy.config.endpoint}/#{signature}/#{path}"
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
OPTIONS_ALIASES = {
|
45
|
+
resize: :rs,
|
46
|
+
size: :s,
|
47
|
+
resizing_type: :rt,
|
48
|
+
width: :w,
|
49
|
+
height: :h,
|
50
|
+
enlarge: :en,
|
51
|
+
extend: :ex,
|
52
|
+
gravity: :g,
|
53
|
+
quality: :q,
|
54
|
+
background: :bg,
|
55
|
+
blur: :bl,
|
56
|
+
sharpen: :sh,
|
57
|
+
watermark: :wm,
|
58
|
+
preset: :pr,
|
59
|
+
cachebuster: :cb
|
60
|
+
}.freeze
|
61
|
+
|
62
|
+
NEED_ESCAPE_RE = /@\?%/.freeze
|
63
|
+
|
64
|
+
def processing_options
|
65
|
+
@processing_options ||=
|
66
|
+
@options.build.map do |key, value|
|
67
|
+
"#{option_alias(key)}:#{wrap_array(value).join(':')}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def option_alias(name)
|
72
|
+
return name unless config.use_short_options
|
73
|
+
|
74
|
+
OPTIONS_ALIASES.fetch(name, name)
|
75
|
+
end
|
76
|
+
|
77
|
+
def wrap_array(value)
|
78
|
+
value.is_a?(Array) ? value : [value]
|
79
|
+
end
|
80
|
+
|
81
|
+
def escape_url(url)
|
82
|
+
url =~ NEED_ESCAPE_RE ? CGI.escape(url) : url
|
83
|
+
end
|
84
|
+
|
85
|
+
def sign_path(path)
|
86
|
+
return 'unsafe' if signature_key.nil? || signature_salt.nil?
|
87
|
+
|
88
|
+
digest = OpenSSL::HMAC.digest(
|
89
|
+
OpenSSL::Digest.new('sha256'),
|
90
|
+
signature_key,
|
91
|
+
"#{signature_salt}/#{path}"
|
92
|
+
)[0, signature_size]
|
93
|
+
|
94
|
+
Base64.urlsafe_encode64(digest).tr('=', '')
|
95
|
+
end
|
96
|
+
|
97
|
+
def signature_key
|
98
|
+
config.key
|
99
|
+
end
|
100
|
+
|
101
|
+
def signature_salt
|
102
|
+
config.salt
|
103
|
+
end
|
104
|
+
|
105
|
+
def signature_size
|
106
|
+
config.signature_size
|
107
|
+
end
|
108
|
+
|
109
|
+
def config
|
110
|
+
Imgproxy.config
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Imgproxy
|
2
|
+
# Imgproxy config
|
3
|
+
# @see Imgproxy.configure
|
4
|
+
class Config
|
5
|
+
# @return [String] imgproxy endpoint
|
6
|
+
attr_reader :endpoint
|
7
|
+
# @return [String] imgproxy signature key
|
8
|
+
attr_accessor :key
|
9
|
+
# @return [String] imgproxy signature salt
|
10
|
+
attr_accessor :salt
|
11
|
+
# @return [Integer] imgproxy signature size. Defaults to 32
|
12
|
+
attr_accessor :signature_size
|
13
|
+
# @return [Boolean] use short processing option names
|
14
|
+
# (`rs` for `resize`, `g` for `gravity`, etc).
|
15
|
+
# Defaults to true
|
16
|
+
attr_accessor :use_short_options
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
self.signature_size = 32
|
20
|
+
self.use_short_options = true
|
21
|
+
end
|
22
|
+
|
23
|
+
# Decodes hex-encoded key and sets it to {#key}
|
24
|
+
#
|
25
|
+
# @param value [String] hex-encoded signature key
|
26
|
+
def hex_key=(value)
|
27
|
+
self.key = [value].pack('H*')
|
28
|
+
end
|
29
|
+
|
30
|
+
# Decodes hex-encoded salt and sets it to {#salt}
|
31
|
+
#
|
32
|
+
# @param value [String] hex-encoded signature salt
|
33
|
+
def hex_salt=(value)
|
34
|
+
self.salt = [value].pack('H*')
|
35
|
+
end
|
36
|
+
|
37
|
+
def endpoint=(value)
|
38
|
+
@endpoint = value.end_with?('/') ? value[0..-2] : value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Imgproxy
|
4
|
+
# Formats and regroups processing options
|
5
|
+
class Options < Hash
|
6
|
+
STRING_OPTS = %i[resizing_type gravity watermark_position style cachebuster
|
7
|
+
format].freeze
|
8
|
+
|
9
|
+
INT_OPTS = %i[width height quality watermark_x_offset
|
10
|
+
watermark_y_offset].freeze
|
11
|
+
|
12
|
+
FLOAT_OPTS = %i[dpr gravity_x gravity_y blur sharpen watermark_opacity
|
13
|
+
watermark_scale].freeze
|
14
|
+
|
15
|
+
BOOL_OPTS = %i[enlarge extend].freeze
|
16
|
+
|
17
|
+
ARRAY_OPTS = %i[background preset].freeze
|
18
|
+
|
19
|
+
ALL_OPTS =
|
20
|
+
(STRING_OPTS + INT_OPTS + FLOAT_OPTS + BOOL_OPTS + ARRAY_OPTS).freeze
|
21
|
+
|
22
|
+
OPTS_PRIORITY = { resize: 1, size: 2 }.freeze
|
23
|
+
|
24
|
+
# @param options [Hash] raw processing options
|
25
|
+
def initialize(options)
|
26
|
+
merge!(options.slice(*ALL_OPTS))
|
27
|
+
typecast
|
28
|
+
freeze
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Hash] formatted and regrouped processing options
|
32
|
+
def build
|
33
|
+
opts = dup
|
34
|
+
|
35
|
+
group_resizing_opts(opts)
|
36
|
+
group_gravity_opts(opts)
|
37
|
+
group_watermark_opts(opts)
|
38
|
+
encode_style(opts)
|
39
|
+
|
40
|
+
Hash[opts.sort_by { |k, _| OPTS_PRIORITY.fetch(k, 99) }]
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def typecast
|
46
|
+
compact.each do |key, value|
|
47
|
+
self[key] =
|
48
|
+
case key
|
49
|
+
when *STRING_OPTS then value.to_s
|
50
|
+
when *INT_OPTS then value.to_i
|
51
|
+
when *FLOAT_OPTS then value.to_f
|
52
|
+
when *BOOL_OPTS then bool(value)
|
53
|
+
when *ARRAY_OPTS then wrap_array(value)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def bool(value)
|
59
|
+
value && value != 0 && value != '0' ? 1 : 0
|
60
|
+
end
|
61
|
+
|
62
|
+
def wrap_array(value)
|
63
|
+
value.is_a?(Array) ? value : [value]
|
64
|
+
end
|
65
|
+
|
66
|
+
def group_resizing_opts(opts)
|
67
|
+
return opts unless opts[:width] && opts[:height]
|
68
|
+
|
69
|
+
opts[:size] = trim_nils(
|
70
|
+
[opts.delete(:width), opts.delete(:height),
|
71
|
+
opts.delete(:enlarge), opts.delete(:extend)]
|
72
|
+
)
|
73
|
+
|
74
|
+
if opts[:resizing_type]
|
75
|
+
opts[:resize] = [opts.delete(:resizing_type), *opts.delete(:size)]
|
76
|
+
end
|
77
|
+
|
78
|
+
opts
|
79
|
+
end
|
80
|
+
|
81
|
+
def group_gravity_opts(opts)
|
82
|
+
gravity = trim_nils(
|
83
|
+
[
|
84
|
+
opts.delete(:gravity),
|
85
|
+
opts.delete(:gravity_x),
|
86
|
+
opts.delete(:gravity_y)
|
87
|
+
]
|
88
|
+
)
|
89
|
+
|
90
|
+
opts[:gravity] = gravity unless gravity[0].nil?
|
91
|
+
end
|
92
|
+
|
93
|
+
def group_watermark_opts(opts)
|
94
|
+
watermark = trim_nils(
|
95
|
+
[
|
96
|
+
opts.delete(:watermark_opacity),
|
97
|
+
opts.delete(:watermark_position),
|
98
|
+
opts.delete(:watermark_x_offset),
|
99
|
+
opts.delete(:watermark_y_offset),
|
100
|
+
opts.delete(:watermark_scale)
|
101
|
+
]
|
102
|
+
)
|
103
|
+
|
104
|
+
opts[:watermark] = watermark unless watermark[0].nil?
|
105
|
+
end
|
106
|
+
|
107
|
+
def encode_style(opts)
|
108
|
+
return if opts[:style].nil?
|
109
|
+
|
110
|
+
opts[:style] = Base64.urlsafe_encode64(opts[:style]).tr('=', '')
|
111
|
+
end
|
112
|
+
|
113
|
+
def trim_nils(value)
|
114
|
+
value.delete_at(-1) while !value.empty? && value[-1].nil?
|
115
|
+
value
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/lib/imgproxy.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'imgproxy/version'
|
2
|
+
require 'imgproxy/config'
|
3
|
+
require 'imgproxy/builder'
|
4
|
+
|
5
|
+
# @see Imgproxy::ClassMethods
|
6
|
+
module Imgproxy
|
7
|
+
class << self
|
8
|
+
# Imgproxy config
|
9
|
+
#
|
10
|
+
# @return [Config]
|
11
|
+
def config
|
12
|
+
@config ||= Imgproxy::Config.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# Yields Imgproxy config
|
16
|
+
#
|
17
|
+
# Imgproxy.configure do |config|
|
18
|
+
# config.endpoint = "http://imgproxy.example.com"
|
19
|
+
# config.hex_key = "your_key"
|
20
|
+
# config.hex_salt = "your_salt"
|
21
|
+
# config.use_short_options = true
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# @yieldparam config [Config]
|
25
|
+
# @return [Config]
|
26
|
+
def configure
|
27
|
+
yield config
|
28
|
+
config
|
29
|
+
end
|
30
|
+
|
31
|
+
# Genrates imgproxy URL
|
32
|
+
#
|
33
|
+
# Imgproxy.url_for(
|
34
|
+
# "http://images.example.com/images/image.jpg",
|
35
|
+
# width: 500,
|
36
|
+
# height: 400,
|
37
|
+
# resizing_type: :fill,
|
38
|
+
# sharpen: 0.5
|
39
|
+
# )
|
40
|
+
#
|
41
|
+
# @return [String] imgproxy URL
|
42
|
+
# @param [String] image Source image URL
|
43
|
+
# @param [Hash] options Processing options
|
44
|
+
# @option options [String] :resizing_type
|
45
|
+
# @option options [Integer] :width
|
46
|
+
# @option options [Integer] :height
|
47
|
+
# @option options [Float] :dpr
|
48
|
+
# @option options [Boolean] :enlarge
|
49
|
+
# @option options [Boolean] :extend
|
50
|
+
# @option options [String] :gravity
|
51
|
+
# @option options [Float] :gravity_x
|
52
|
+
# @option options [Float] :gravity_y
|
53
|
+
# @option options [Integer] :quality
|
54
|
+
# @option options [Array] :background
|
55
|
+
# @option options [Float] :blur
|
56
|
+
# @option options [Float] :sharpen
|
57
|
+
# @option options [Float] :watermark_opacity
|
58
|
+
# @option options [String] :watermark_position
|
59
|
+
# @option options [Integer] :watermark_x_offset
|
60
|
+
# @option options [Integer] :watermark_y_offset
|
61
|
+
# @option options [Float] :watermark_scale
|
62
|
+
# @option options [Array] :preset
|
63
|
+
# @option options [String] :cachebuster
|
64
|
+
# @option options [String] :format
|
65
|
+
# @option options [Boolean] :use_short_options
|
66
|
+
# @see https://github.com/DarthSim/imgproxy/blob/master/docs/generating_the_url_advanced.md
|
67
|
+
# imgproxy URL format documentation
|
68
|
+
def url_for(image, options = {})
|
69
|
+
Imgproxy::Builder.new(options).url_for(image)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: imgproxy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sergey Alexandrovich
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-03-25 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A gem that easily generates imgproxy URLs for your images
|
14
|
+
email: darthsim@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- LICENSE
|
20
|
+
- README.md
|
21
|
+
- lib/imgproxy.rb
|
22
|
+
- lib/imgproxy/builder.rb
|
23
|
+
- lib/imgproxy/config.rb
|
24
|
+
- lib/imgproxy/options.rb
|
25
|
+
- lib/imgproxy/version.rb
|
26
|
+
homepage: https://github.com/imgproxy/imgproxy.rb
|
27
|
+
licenses:
|
28
|
+
- MIT
|
29
|
+
metadata: {}
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubygems_version: 3.0.2
|
46
|
+
signing_key:
|
47
|
+
specification_version: 4
|
48
|
+
summary: imgproxy URL generator
|
49
|
+
test_files: []
|