remove_bg 1.1.0 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53e08a36b9ec04edb670f7b74247d833adad68018814827a8d245f9999f1703e
4
- data.tar.gz: b75d1b644f844171f96bff2b4c3aaa03d9b8a142f23be296b5a5c26d7049d2b8
3
+ metadata.gz: 728a996008009c54dc10d72f0b0db353b1156182a8379c51ed0cf5f9fbe4b27e
4
+ data.tar.gz: 991a7201af58af739e73af965d082b43a0427d79878e0db2f17d13c3ac575053
5
5
  SHA512:
6
- metadata.gz: f071a1349b53f58f0938299e3302ba07f52aadbf9bb0063f0432e8096a164a35a51a4773a315da4728c58861b9b8224cd56ef1b0d996b90b086d907a923f7c05
7
- data.tar.gz: 85f00a91ac0bdea9b4080213a3bfbd221a66379a8377fc0cad2ed993cf8127dd18ee480f5c1b8bd24ff19503d63a7e9dd7e0b8102d871fe0716ea91378da5eb6
6
+ metadata.gz: 2800e1a449e12e3168c7e15d9fd85b8424434ff568821600464a05d6448529e22514ab1e80cdd7c76c7a6ab91e45db01f99b7a7e7dab617d00b797f6788aa872
7
+ data.tar.gz: 556b0ded824694cffb639cab278eb00ffea8df314c5beee20a2565528671d8548fb856a3d14a52af73c5147f54d016b457268101046ce6c75647fb0a6b606c61
@@ -11,7 +11,16 @@ base_job: &base_job
11
11
 
12
12
  - run:
13
13
  name: install dependencies
14
- command: bundle install --jobs=4 --retry=3 --path vendor/bundle
14
+ command: |
15
+ gem install bundler --version '~> 2.0'
16
+ bundle config set path vendor/bundle
17
+ bundle install --jobs=4 --retry=3
18
+
19
+ - run:
20
+ name: install image processsing dependencies
21
+ command: |
22
+ sudo apt-get update
23
+ sudo apt install imagemagick libvips
15
24
 
16
25
  - save_cache:
17
26
  paths:
@@ -37,10 +46,6 @@ base_job: &base_job
37
46
  ################################################################################
38
47
 
39
48
  jobs:
40
- ruby-2.4:
41
- <<: *base_job
42
- docker:
43
- - image: circleci/ruby:2.4
44
49
  ruby-2.5:
45
50
  <<: *base_job
46
51
  docker:
@@ -49,6 +54,10 @@ jobs:
49
54
  <<: *base_job
50
55
  docker:
51
56
  - image: circleci/ruby:2.6
57
+ ruby-2.7:
58
+ <<: *base_job
59
+ docker:
60
+ - image: circleci/ruby:2.7
52
61
 
53
62
  ################################################################################
54
63
 
@@ -56,6 +65,6 @@ workflows:
56
65
  version: 2
57
66
  multiple-rubies:
58
67
  jobs:
59
- - ruby-2.4
60
68
  - ruby-2.5
61
69
  - ruby-2.6
70
+ - ruby-2.7
data/.gitignore CHANGED
@@ -18,3 +18,5 @@ test-results
18
18
  gemfiles/*.gemfile.lock
19
19
 
20
20
  examples/output
21
+
22
+ .ruby-version
data/Appraisals CHANGED
@@ -1,7 +1,17 @@
1
- appraise "faraday-0-14" do
2
- gem "faraday", "~> 0.14"
1
+ # Oldest supported Faraday version
2
+ appraise "faraday-0-15" do
3
+ gem "faraday", "~> 0.15.0"
3
4
  end
4
5
 
5
- appraise "faraday-0-15" do
6
- gem "faraday", "~> 0.15"
6
+ appraise "faraday-0-16" do
7
+ gem "faraday", "~> 0.16.0"
8
+ end
9
+
10
+ appraise "faraday-0-17" do
11
+ gem "faraday", "~> 0.17.0"
12
+ end
13
+
14
+ # Latest in Faraday 1.x series
15
+ appraise "faraday-1-x" do
16
+ gem "faraday", "~> 1.0"
7
17
  end
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.4.1
4
+
5
+ - Fixes binary encoding issue - via [#15](https://github.com/remove-bg/ruby/pull/15)
6
+
7
+ ## 1.4.0
8
+
9
+ - Adds support for images up to 25 megapixels ([documentation](https://github.com/remove-bg/ruby#processing-images-over-10-megapixels))
10
+ - Requires an image processing library to be configured (ImageMagick, GraphicsMagick or libvips)
11
+
12
+ ## 1.3.0
13
+
14
+ - Add `RemoveBg.account_info` which includes available credits - via [#9](https://github.com/remove-bg/ruby/pull/9)
15
+ - Fix support for Faraday `1.0` - via [#7](https://github.com/remove-bg/ruby/pull/7)
16
+ - Raise minimum Faraday version to `0.15.0`
17
+
18
+ ## 1.2.1
19
+
20
+ - Add `type` attribute to result object (`X-Type` header from API) - via [#2](https://github.com/remove-bg/ruby/pull/2)
21
+
22
+ ## 1.2.0
23
+
24
+ - Support uppercase file extensions (e.g. `.JPG`)
25
+ - Add Faraday 1.0 support (currently RC1)
26
+
3
27
  ## 1.1.0
4
28
 
5
29
  - Support 'credits charged' as a float (API change)
data/Gemfile CHANGED
@@ -1,3 +1,7 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
+
5
+ group :development do
6
+ gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git", ref: "5868643"
7
+ end
@@ -1,70 +1,89 @@
1
+ GIT
2
+ remote: https://github.com/thoughtbot/appraisal.git
3
+ revision: 586864393e405a67b1457b563a4d5adc99e50e2d
4
+ ref: 5868643
5
+ specs:
6
+ appraisal (2.2.0)
7
+ bundler
8
+ rake
9
+ thor (>= 0.14.0)
10
+
1
11
  PATH
2
12
  remote: .
3
13
  specs:
4
- remove_bg (1.1.0)
5
- faraday (~> 0.14)
14
+ remove_bg (1.4.1)
15
+ faraday (>= 0.15, < 2)
16
+ image_processing (>= 1.9, < 2)
17
+ rubyzip (>= 2.0, < 3)
6
18
 
7
19
  GEM
8
20
  remote: https://rubygems.org/
9
21
  specs:
10
- addressable (2.6.0)
11
- public_suffix (>= 2.0.2, < 4.0)
12
- appraisal (2.2.0)
13
- bundler
14
- rake
15
- thor (>= 0.14.0)
22
+ addressable (2.7.0)
23
+ public_suffix (>= 2.0.2, < 5.0)
16
24
  coderay (1.1.2)
17
25
  crack (0.4.3)
18
26
  safe_yaml (~> 1.0.0)
19
27
  diff-lcs (1.3)
20
- dotenv (2.7.1)
21
- faraday (0.15.4)
28
+ dotenv (2.7.5)
29
+ faraday (1.0.1)
22
30
  multipart-post (>= 1.2, < 3)
23
- hashdiff (0.3.8)
24
- method_source (0.9.2)
31
+ ffi (1.12.2)
32
+ hashdiff (1.0.1)
33
+ image_processing (1.11.0)
34
+ mini_magick (>= 4.9.5, < 5)
35
+ ruby-vips (>= 2.0.17, < 3)
36
+ method_source (1.0.0)
37
+ mini_magick (4.10.1)
25
38
  multipart-post (2.1.1)
26
- pry (0.12.2)
27
- coderay (~> 1.1.0)
28
- method_source (~> 0.9.0)
29
- public_suffix (3.0.3)
30
- rake (12.3.2)
31
- rspec (3.8.0)
32
- rspec-core (~> 3.8.0)
33
- rspec-expectations (~> 3.8.0)
34
- rspec-mocks (~> 3.8.0)
35
- rspec-core (3.8.0)
36
- rspec-support (~> 3.8.0)
37
- rspec-expectations (3.8.2)
39
+ pry (0.13.1)
40
+ coderay (~> 1.1)
41
+ method_source (~> 1.0)
42
+ public_suffix (4.0.4)
43
+ rake (13.0.1)
44
+ rspec (3.9.0)
45
+ rspec-core (~> 3.9.0)
46
+ rspec-expectations (~> 3.9.0)
47
+ rspec-mocks (~> 3.9.0)
48
+ rspec-core (3.9.2)
49
+ rspec-support (~> 3.9.3)
50
+ rspec-expectations (3.9.1)
38
51
  diff-lcs (>= 1.2.0, < 2.0)
39
- rspec-support (~> 3.8.0)
40
- rspec-mocks (3.8.0)
52
+ rspec-support (~> 3.9.0)
53
+ rspec-mocks (3.9.1)
41
54
  diff-lcs (>= 1.2.0, < 2.0)
42
- rspec-support (~> 3.8.0)
43
- rspec-support (3.8.0)
55
+ rspec-support (~> 3.9.0)
56
+ rspec-support (3.9.3)
57
+ rspec-with_params (0.2.0)
58
+ rspec (~> 3.0)
44
59
  rspec_junit_formatter (0.4.1)
45
60
  rspec-core (>= 2, < 4, != 2.12.0)
61
+ ruby-vips (2.0.17)
62
+ ffi (~> 1.9)
63
+ rubyzip (2.3.0)
46
64
  safe_yaml (1.0.5)
47
- thor (0.20.3)
48
- vcr (4.0.0)
49
- webmock (3.5.1)
65
+ thor (1.0.1)
66
+ vcr (5.1.0)
67
+ webmock (3.8.3)
50
68
  addressable (>= 2.3.6)
51
69
  crack (>= 0.3.2)
52
- hashdiff
70
+ hashdiff (>= 0.4.0, < 2.0.0)
53
71
 
54
72
  PLATFORMS
55
73
  ruby
56
74
 
57
75
  DEPENDENCIES
58
- appraisal
59
- bundler (~> 1.17)
76
+ appraisal!
77
+ bundler (~> 2.0)
60
78
  dotenv
61
79
  pry
62
- rake (~> 12.0)
80
+ rake
63
81
  remove_bg!
64
82
  rspec (~> 3.8)
83
+ rspec-with_params
65
84
  rspec_junit_formatter
66
85
  vcr
67
86
  webmock
68
87
 
69
88
  BUNDLED WITH
70
- 1.17.3
89
+ 2.1.4
data/README.md CHANGED
@@ -69,6 +69,71 @@ result.save("processed/image.png")
69
69
  result.save("image.png", overwrite: true) # Careful!
70
70
  ```
71
71
 
72
+ #### Processing images over 10 megapixels
73
+
74
+ The Remove.bg API provides a [ZIP format](https://www.remove.bg/api#zip-format)
75
+ which includes all the information required to efficiently composite a large
76
+ image with transparency.
77
+
78
+ The gem can handle this composition for you, but you'll need
79
+ [ImageMagick]/[GraphicsMagick] or [libvips] available on your computer.
80
+
81
+ [ImageMagick]: https://www.imagemagick.org
82
+ [GraphicsMagick]: http://www.graphicsmagick.org
83
+ [libvips]: http://libvips.github.io/libvips/
84
+
85
+ Please configure the image processing library you'd like to use:
86
+
87
+ ```ruby
88
+ RemoveBg.configure do |config|
89
+ config.image_processor = :minimagick # For ImageMagick or GraphicsMagick
90
+ # or
91
+ config.image_processor = :vips
92
+ end
93
+ ```
94
+
95
+ Then process images specifying the `zip` format:
96
+
97
+ ```ruby
98
+ result = RemoveBg.from_file("large-image.jpg", format: "zip")
99
+
100
+ result.save("result-with-transparency.png")
101
+ # or
102
+ result.save_zip("result.zip") # If you want to handle composition yourself
103
+ ```
104
+
105
+ #### Rate limits
106
+
107
+ The [API has rate limits][rate-limits]. Image processing results include the
108
+ rate limit information:
109
+
110
+ ```ruby
111
+ result = RemoveBg.from_file("image.jpg")
112
+ result.rate_limit.to_s
113
+ # => <RateLimit reset_at='2020-05-20T12:00:00Z' total=500 remaining=499 retry_after_seconds=nil>
114
+ ```
115
+
116
+ If you exceed the rate limit a `RemoveBg::RateLimitError` exception will be
117
+ raised. This also contains further information via the `#rate_limit` method.
118
+
119
+ [rate-limits]: https://www.remove.bg/api#rate-limit
120
+
121
+ ### Fetching account information
122
+
123
+ To display the [account information][account-info] for the currently configured
124
+ API key:
125
+
126
+ [account-info]: https://www.remove.bg/api#operations-tag-Fetch_account_info
127
+
128
+ ```ruby
129
+ account = RemoveBg.account_info # If an API key is set via RemoveBg.configuration
130
+ # or
131
+ account = RemoveBg.account_info(api_key: "<api_key>")
132
+
133
+ account.api.free_calls # => 50
134
+ account.credits.total # => 200
135
+ ```
136
+
72
137
  ## Examples
73
138
 
74
139
  - [Bulk processing][bulk-processing] a directory of JPG and PNG files
@@ -2,6 +2,10 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "faraday", "~> 0.15"
5
+ gem "faraday", "~> 0.15.0"
6
+
7
+ group :development do
8
+ gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git", ref: "5868643"
9
+ end
6
10
 
7
11
  gemspec path: "../"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "faraday", "~> 0.16.0"
6
+
7
+ group :development do
8
+ gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git", ref: "5868643"
9
+ end
10
+
11
+ gemspec path: "../"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "faraday", "~> 0.17.0"
6
+
7
+ group :development do
8
+ gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git", ref: "5868643"
9
+ end
10
+
11
+ gemspec path: "../"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "faraday", "~> 1.0"
6
+
7
+ group :development do
8
+ gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git", ref: "5868643"
9
+ end
10
+
11
+ gemspec path: "../"
@@ -14,6 +14,11 @@ module RemoveBg
14
14
  ApiClient.new.remove_from_url(image_url, options)
15
15
  end
16
16
 
17
+ def self.account_info(raw_options = {})
18
+ options = RemoveBg::BaseRequestOptions.new(raw_options)
19
+ ApiClient.new.account_info(options)
20
+ end
21
+
17
22
  def self.configure
18
23
  yield RemoveBg::Configuration.configuration
19
24
  end
@@ -0,0 +1,30 @@
1
+ module RemoveBg
2
+ class AccountInfo
3
+ attr_reader :api, :credits
4
+
5
+ def initialize(attributes)
6
+ @api = ApiInfo.new(**attributes.fetch(:api))
7
+ @credits = CreditsInfo.new(**attributes.fetch(:credits))
8
+ end
9
+
10
+ class ApiInfo
11
+ attr_reader :free_calls, :sizes
12
+
13
+ def initialize(free_calls:, sizes:)
14
+ @free_calls = free_calls
15
+ @sizes = sizes
16
+ end
17
+ end
18
+
19
+ class CreditsInfo
20
+ attr_reader :total, :subscription, :payg, :enterprise
21
+
22
+ def initialize(total:, subscription:, payg:, enterprise:)
23
+ @total = total
24
+ @subscription = subscription
25
+ @payg = payg
26
+ @enterprise = enterprise
27
+ end
28
+ end
29
+ end
30
+ end
@@ -2,19 +2,13 @@ module RemoveBg
2
2
  module Api
3
3
  URL = "https://api.remove.bg"
4
4
 
5
+ V1_ACCOUNT = "/v1.0/account"
6
+ private_constant :V1_ACCOUNT
7
+
5
8
  V1_REMOVE_BG = "/v1.0/removebg"
6
9
  private_constant :V1_REMOVE_BG
7
10
 
8
11
  HEADER_API_KEY = "X-Api-Key"
9
12
  private_constant :HEADER_API_KEY
10
-
11
- HEADER_WIDTH = "X-Width"
12
- private_constant :HEADER_WIDTH
13
-
14
- HEADER_HEIGHT = "X-Height"
15
- private_constant :HEADER_HEIGHT
16
-
17
- HEADER_CREDITS_CHARGED = "X-Credits-Charged"
18
- private_constant :HEADER_CREDITS_CHARGED
19
13
  end
20
14
  end
@@ -1,7 +1,13 @@
1
1
  require "json"
2
+ require "tempfile"
3
+
4
+ require_relative "account_info"
2
5
  require_relative "api"
6
+ require_relative "composite_result"
3
7
  require_relative "error"
4
8
  require_relative "http_connection"
9
+ require_relative "rate_limit_info"
10
+ require_relative "result_metadata"
5
11
  require_relative "result"
6
12
  require_relative "upload"
7
13
  require_relative "url_validator"
@@ -25,44 +31,107 @@ module RemoveBg
25
31
  request_remove_bg(data, options.api_key)
26
32
  end
27
33
 
34
+ def account_info(options)
35
+ request_account_info(options.api_key)
36
+ end
37
+
28
38
  private
29
39
 
30
40
  attr_reader :connection
31
41
 
32
42
  def request_remove_bg(data, api_key)
43
+ download = Tempfile.new("remove-bg-download")
44
+ download.binmode
45
+
46
+ streaming = false
47
+
33
48
  response = connection.post(V1_REMOVE_BG, data) do |req|
34
49
  req.headers[HEADER_API_KEY] = api_key
50
+
51
+ # Faraday v0.16 & v1.0+ support streaming, v0.17 did not (rollback release)
52
+ if req.options.respond_to?(:on_data)
53
+ streaming = true
54
+ req.options.on_data = Proc.new do |chunk, _|
55
+ download.write(chunk)
56
+ end
57
+ end
58
+ end
59
+
60
+ # Faraday v0.15 / v0.17
61
+ if !streaming
62
+ download.write(response.body)
63
+ end
64
+
65
+ download.rewind
66
+
67
+ if response.status == 200
68
+ parse_image_result(headers: response.headers, download: download)
69
+ else
70
+ response_body = download.read
71
+ download.close
72
+ download.unlink
73
+ handle_http_error(response: response, body: response_body)
74
+ end
75
+ end
76
+
77
+ def request_account_info(api_key)
78
+ response = connection.get(V1_ACCOUNT) do |req|
79
+ req.headers[HEADER_API_KEY] = api_key
35
80
  end
36
81
 
82
+ if response.status == 200
83
+ parse_account_result(response)
84
+ else
85
+ handle_http_error(response: response, body: response.body)
86
+ end
87
+ end
88
+
89
+ def handle_http_error(response:, body:)
90
+ error_message = parse_error_message(body)
91
+
37
92
  case response.status
38
- when 200
39
- parse_result(response)
93
+ when 429
94
+ rate_limit = RateLimitInfo.new(response.headers)
95
+ raise RemoveBg::RateLimitError.new(error_message, response, body, rate_limit)
40
96
  when 400..499
41
- error_message = parse_error_message(response)
42
- raise RemoveBg::ClientHttpError.new(error_message, response)
97
+ raise RemoveBg::ClientHttpError.new(error_message, response, body)
43
98
  when 500..599
44
- error_message = parse_error_message(response)
45
- raise RemoveBg::ServerHttpError.new(error_message, response)
99
+ raise RemoveBg::ServerHttpError.new(error_message, response, body)
46
100
  else
47
- raise RemoveBg::HttpError.new("An unknown error occurred", response)
101
+ raise RemoveBg::HttpError.new("An unknown error occurred", response, body)
48
102
  end
49
103
  end
50
104
 
51
- def parse_result(response)
52
- RemoveBg::Result.new(
53
- data: response.body,
54
- width: response.headers[HEADER_WIDTH]&.to_i,
55
- height: response.headers[HEADER_HEIGHT]&.to_i,
56
- credits_charged: response.headers[HEADER_CREDITS_CHARGED]&.to_f,
105
+ def parse_image_result(headers:, download:)
106
+ result_for_content_type(headers["Content-Type"]).new(
107
+ download: download,
108
+ metadata: ResultMetadata.new(headers),
109
+ rate_limit: RateLimitInfo.new(headers)
57
110
  )
58
111
  end
59
112
 
60
- def parse_error_message(response)
61
- parse_errors(response).first["title"]
113
+ def result_for_content_type(content_type)
114
+ if content_type&.include?("application/zip")
115
+ CompositeResult
116
+ else
117
+ Result
118
+ end
119
+ end
120
+
121
+ def parse_account_result(response)
122
+ attributes = JSON.parse(response.body, symbolize_names: true)
123
+ .fetch(:data)
124
+ .fetch(:attributes)
125
+
126
+ RemoveBg::AccountInfo.new(attributes)
127
+ end
128
+
129
+ def parse_error_message(response_body)
130
+ parse_errors(response_body).first["title"]
62
131
  end
63
132
 
64
- def parse_errors(response)
65
- JSON.parse(response.body)["errors"] || []
133
+ def parse_errors(response_body)
134
+ JSON.parse(response_body)["errors"] || []
66
135
  rescue JSON::ParserError
67
136
  [{ "title" => "Unable to parse response" }]
68
137
  end
@@ -0,0 +1,32 @@
1
+ require_relative "error"
2
+
3
+ module RemoveBg
4
+ class BaseRequestOptions
5
+ attr_reader :api_key, :data
6
+
7
+ def initialize(raw_options = {})
8
+ options = raw_options.dup
9
+ @api_key = resolve_api_key(options.delete(:api_key))
10
+ @data = options
11
+ end
12
+
13
+ private
14
+
15
+ def resolve_api_key(request_api_key)
16
+ api_key = request_api_key || global_api_key
17
+
18
+ if api_key.nil? || api_key.empty?
19
+ raise RemoveBg::Error, <<~MSG
20
+ Please configure an API key or specify one per request. API key was:
21
+ #{api_key.inspect}
22
+ MSG
23
+ end
24
+
25
+ api_key
26
+ end
27
+
28
+ def global_api_key
29
+ RemoveBg::Configuration.configuration.api_key
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,55 @@
1
+ require_relative "result"
2
+
3
+ module RemoveBg
4
+ class CompositeResult < Result
5
+ def save_zip(file_path, overwrite: false)
6
+ if File.exist?(file_path) && !overwrite
7
+ raise FileOverwriteError.new(file_path)
8
+ end
9
+
10
+ FileUtils.cp(download, file_path)
11
+ end
12
+
13
+ private
14
+
15
+ def image_file
16
+ composite_file
17
+ end
18
+
19
+ def composite_file
20
+ @composite_file ||= begin
21
+ binary_tempfile(["composed", ".png"])
22
+ .tap { |file| compose_to_file(file) }
23
+ end
24
+ end
25
+
26
+ def color_file
27
+ @color_file ||= binary_tempfile(["color", ".jpg"])
28
+ end
29
+
30
+ def alpha_file
31
+ @alpha_file ||= binary_tempfile(["alpha", ".png"])
32
+ end
33
+
34
+ def compose_to_file(destination)
35
+ extract_parts
36
+
37
+ ImageComposer.new.compose(
38
+ color_file: color_file,
39
+ alpha_file: alpha_file,
40
+ destination_path: destination.path
41
+ )
42
+ end
43
+
44
+ def extract_parts
45
+ Zip::File.open(download) do |zf|
46
+ zf.find_entry("color.jpg").extract(color_file.path) { true }
47
+ zf.find_entry("alpha.png").extract(alpha_file.path) { true }
48
+ end
49
+ end
50
+
51
+ def binary_tempfile(basename)
52
+ Tempfile.new(basename).tap { |file| file.binmode }
53
+ end
54
+ end
55
+ end
@@ -1,6 +1,6 @@
1
1
  module RemoveBg
2
2
  class Configuration
3
- attr_accessor :api_key
3
+ attr_accessor :api_key, :image_processor
4
4
 
5
5
  def self.configuration
6
6
  @configuration ||= Configuration.new
@@ -2,10 +2,11 @@ module RemoveBg
2
2
  class Error < StandardError; end
3
3
 
4
4
  class HttpError < Error
5
- attr_reader :http_response
5
+ attr_reader :http_response, :http_response_body
6
6
 
7
- def initialize(message, http_response)
7
+ def initialize(message, http_response, http_response_body)
8
8
  @http_response = http_response
9
+ @http_response_body = http_response_body
9
10
  super(message)
10
11
  end
11
12
  end
@@ -13,6 +14,15 @@ module RemoveBg
13
14
  class ClientHttpError < HttpError; end
14
15
  class ServerHttpError < HttpError; end
15
16
 
17
+ class RateLimitError < ClientHttpError
18
+ attr_reader :rate_limit
19
+
20
+ def initialize(message, http_response, http_response_body, rate_limit)
21
+ @rate_limit = rate_limit
22
+ super(message, http_response, http_response_body)
23
+ end
24
+ end
25
+
16
26
  class FileError < Error
17
27
  attr_reader :file_path
18
28
 
@@ -0,0 +1,42 @@
1
+ require_relative "error"
2
+
3
+ module RemoveBg
4
+ class ImageComposer
5
+ def compose(color_file:, alpha_file:, destination_path:)
6
+ image = case configured_image_processor
7
+ when :vips
8
+ then vips_compose(color_file: color_file, alpha_file: alpha_file)
9
+ when :minimagick
10
+ then minimagick_compose(color_file: color_file, alpha_file: alpha_file)
11
+ when nil
12
+ raise RemoveBg::Error, "Please configure an image processor to use image composition"
13
+ else
14
+ raise RemoveBg::Error, "Unsupported image processor: #{configured_image_processor.inspect}"
15
+ end
16
+
17
+ image.call(destination: destination_path)
18
+ end
19
+
20
+ private
21
+
22
+ def configured_image_processor
23
+ RemoveBg::Configuration.configuration.image_processor
24
+ end
25
+
26
+ def minimagick_compose(color_file:, alpha_file:)
27
+ require "image_processing/mini_magick"
28
+
29
+ ImageProcessing::MiniMagick
30
+ .source(color_file)
31
+ .composite(alpha_file, mode: "copy-opacity")
32
+ end
33
+
34
+ def vips_compose(color_file:, alpha_file:)
35
+ require "image_processing/vips"
36
+
37
+ ImageProcessing::Vips
38
+ .source(color_file)
39
+ .custom { |image| image.bandjoin(Vips::Image.new_from_file(alpha_file.path)) }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,34 @@
1
+ require "time"
2
+
3
+ module RemoveBg
4
+ class RateLimitInfo
5
+ attr_reader :total, :remaining, :retry_after_seconds
6
+
7
+ def initialize(headers)
8
+ @total = headers["X-RateLimit-Limit"]&.to_i
9
+ @remaining = headers["X-RateLimit-Remaining"]&.to_i
10
+ @reset_timestamp = headers["X-RateLimit-Reset"]&.to_i
11
+
12
+ # Only present if rate limit exceeded
13
+ @retry_after_seconds = headers["Retry-After"]&.to_i
14
+ end
15
+
16
+ def reset_at
17
+ return if reset_timestamp.nil?
18
+ Time.at(reset_timestamp).utc
19
+ end
20
+
21
+ def to_s
22
+ "<RateLimit"\
23
+ " reset_at='#{reset_at.iso8601}'"\
24
+ " retry_after_seconds=#{retry_after_seconds}"\
25
+ " total=#{total}"\
26
+ " remaining=#{remaining}"\
27
+ ">"
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :reset_timestamp
33
+ end
34
+ end
@@ -1,7 +1,7 @@
1
- require_relative "error"
1
+ require_relative "base_request_options"
2
2
 
3
3
  module RemoveBg
4
- class RequestOptions
4
+ class RequestOptions < BaseRequestOptions
5
5
  SIZE_REGULAR = "regular"
6
6
  SIZE_MEDIUM = "medium"
7
7
  SIZE_HD = "hd"
@@ -15,32 +15,10 @@ module RemoveBg
15
15
  CHANNELS_RGBA = "rgba"
16
16
  CHANNELS_ALPHA = "alpha"
17
17
 
18
- attr_reader :api_key, :data
19
-
20
18
  def initialize(raw_options = {})
21
19
  options = raw_options.dup
22
20
  options[:size] ||= SIZE_AUTO
23
- @api_key = resolve_api_key(options.delete(:api_key))
24
- @data = options
25
- end
26
-
27
- private
28
-
29
- def resolve_api_key(request_api_key)
30
- api_key = request_api_key || global_api_key
31
-
32
- if api_key.nil? || api_key.empty?
33
- raise RemoveBg::Error, <<~MSG
34
- Please configure an API key or specify one per request. API key was:
35
- #{api_key.inspect}
36
- MSG
37
- end
38
-
39
- api_key
40
- end
41
-
42
- def global_api_key
43
- RemoveBg::Configuration.configuration.api_key
21
+ super(options)
44
22
  end
45
23
  end
46
24
  end
@@ -1,14 +1,21 @@
1
+ require "fileutils"
2
+ require "forwardable"
3
+ require "zip"
1
4
  require_relative "error"
5
+ require_relative "image_composer"
2
6
 
3
7
  module RemoveBg
4
8
  class Result
5
- attr_reader :data, :width, :height, :credits_charged
9
+ extend ::Forwardable
6
10
 
7
- def initialize(data:, width:, height:, credits_charged:)
8
- @data = data
9
- @width = width
10
- @height = height
11
- @credits_charged = credits_charged
11
+ attr_reader :metadata, :rate_limit
12
+
13
+ def_delegators :metadata, :type, :width, :height, :credits_charged
14
+
15
+ def initialize(download:, metadata:, rate_limit:)
16
+ @download = download
17
+ @metadata = metadata
18
+ @rate_limit = rate_limit
12
19
  end
13
20
 
14
21
  def save(file_path, overwrite: false)
@@ -16,7 +23,20 @@ module RemoveBg
16
23
  raise FileOverwriteError.new(file_path)
17
24
  end
18
25
 
19
- File.write(file_path, data)
26
+ FileUtils.cp(image_file, file_path)
27
+ end
28
+
29
+ def data
30
+ image_file.rewind
31
+ image_file.read
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :download
37
+
38
+ def image_file
39
+ download
20
40
  end
21
41
  end
22
42
  end
@@ -0,0 +1,14 @@
1
+ require_relative "api"
2
+
3
+ module RemoveBg
4
+ class ResultMetadata
5
+ attr_reader :type, :width, :height, :credits_charged
6
+
7
+ def initialize(headers)
8
+ @type = headers["X-Type"]
9
+ @width = headers["X-Width"]&.to_i
10
+ @height = headers["X-Height"]&.to_i
11
+ @credits_charged = headers["X-Credits-Charged"]&.to_f
12
+ end
13
+ end
14
+ end
@@ -1,4 +1,4 @@
1
- require "faraday/upload_io"
1
+ require "faraday"
2
2
  require_relative "error"
3
3
 
4
4
  module RemoveBg
@@ -9,11 +9,11 @@ module RemoveBg
9
9
  end
10
10
 
11
11
  content_type = determine_content_type(file_path)
12
- Faraday::UploadIO.new(file_path, content_type)
12
+ FARADAY_FILE.new(file_path, content_type)
13
13
  end
14
14
 
15
15
  def self.determine_content_type(file_path)
16
- case File.extname(file_path)
16
+ case File.extname(file_path).downcase
17
17
  when ".jpg", ".jpeg" then "image/jpeg"
18
18
  when ".png" then "image/png"
19
19
  else
@@ -22,5 +22,9 @@ module RemoveBg
22
22
  end
23
23
 
24
24
  private_class_method :determine_content_type
25
+
26
+ # UploadIO for Faraday < 0.16.0
27
+ FARADAY_FILE = defined?(Faraday::FilePart) ? Faraday::FilePart : Faraday::UploadIO
28
+ private_constant :FARADAY_FILE
25
29
  end
26
30
  end
@@ -1,3 +1,3 @@
1
1
  module RemoveBg
2
- VERSION = "1.1.0"
2
+ VERSION = "1.4.1"
3
3
  end
@@ -23,14 +23,16 @@ Gem::Specification.new do |spec|
23
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
24
  spec.require_paths = ["lib"]
25
25
 
26
- spec.add_dependency "faraday", "~> 0.14"
26
+ spec.add_dependency "faraday", ">= 0.15", "< 2"
27
+ spec.add_dependency "image_processing", ">= 1.9", "< 2"
28
+ spec.add_dependency "rubyzip", ">= 2.0", "< 3"
27
29
 
28
- spec.add_development_dependency "appraisal"
29
- spec.add_development_dependency "bundler", "~> 1.17"
30
+ spec.add_development_dependency "bundler", "~> 2.0"
30
31
  spec.add_development_dependency "dotenv"
31
32
  spec.add_development_dependency "pry"
32
- spec.add_development_dependency "rake", "~> 12.0"
33
+ spec.add_development_dependency "rake"
33
34
  spec.add_development_dependency "rspec_junit_formatter"
35
+ spec.add_development_dependency "rspec-with_params"
34
36
  spec.add_development_dependency "rspec", "~> 3.8"
35
37
  spec.add_development_dependency "vcr"
36
38
  spec.add_development_dependency "webmock"
metadata CHANGED
@@ -1,57 +1,89 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: remove_bg
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oliver Peate
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-05-29 00:00:00.000000000 Z
11
+ date: 2020-05-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0.15'
20
+ - - "<"
18
21
  - !ruby/object:Gem::Version
19
- version: '0.14'
22
+ version: '2'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '0.15'
30
+ - - "<"
25
31
  - !ruby/object:Gem::Version
26
- version: '0.14'
32
+ version: '2'
27
33
  - !ruby/object:Gem::Dependency
28
- name: appraisal
34
+ name: image_processing
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
37
  - - ">="
32
38
  - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
39
+ version: '1.9'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '2'
43
+ type: :runtime
35
44
  prerelease: false
36
45
  version_requirements: !ruby/object:Gem::Requirement
37
46
  requirements:
38
47
  - - ">="
39
48
  - !ruby/object:Gem::Version
40
- version: '0'
49
+ version: '1.9'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '2'
53
+ - !ruby/object:Gem::Dependency
54
+ name: rubyzip
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '2.0'
60
+ - - "<"
61
+ - !ruby/object:Gem::Version
62
+ version: '3'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '2.0'
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: '3'
41
73
  - !ruby/object:Gem::Dependency
42
74
  name: bundler
43
75
  requirement: !ruby/object:Gem::Requirement
44
76
  requirements:
45
77
  - - "~>"
46
78
  - !ruby/object:Gem::Version
47
- version: '1.17'
79
+ version: '2.0'
48
80
  type: :development
49
81
  prerelease: false
50
82
  version_requirements: !ruby/object:Gem::Requirement
51
83
  requirements:
52
84
  - - "~>"
53
85
  - !ruby/object:Gem::Version
54
- version: '1.17'
86
+ version: '2.0'
55
87
  - !ruby/object:Gem::Dependency
56
88
  name: dotenv
57
89
  requirement: !ruby/object:Gem::Requirement
@@ -84,16 +116,16 @@ dependencies:
84
116
  name: rake
85
117
  requirement: !ruby/object:Gem::Requirement
86
118
  requirements:
87
- - - "~>"
119
+ - - ">="
88
120
  - !ruby/object:Gem::Version
89
- version: '12.0'
121
+ version: '0'
90
122
  type: :development
91
123
  prerelease: false
92
124
  version_requirements: !ruby/object:Gem::Requirement
93
125
  requirements:
94
- - - "~>"
126
+ - - ">="
95
127
  - !ruby/object:Gem::Version
96
- version: '12.0'
128
+ version: '0'
97
129
  - !ruby/object:Gem::Dependency
98
130
  name: rspec_junit_formatter
99
131
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +140,20 @@ dependencies:
108
140
  - - ">="
109
141
  - !ruby/object:Gem::Version
110
142
  version: '0'
143
+ - !ruby/object:Gem::Dependency
144
+ name: rspec-with_params
145
+ requirement: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
111
157
  - !ruby/object:Gem::Dependency
112
158
  name: rspec
113
159
  requirement: !ruby/object:Gem::Requirement
@@ -170,16 +216,24 @@ files:
170
216
  - Rakefile
171
217
  - bin/console
172
218
  - bin/setup
173
- - gemfiles/faraday_0_14.gemfile
174
219
  - gemfiles/faraday_0_15.gemfile
220
+ - gemfiles/faraday_0_16.gemfile
221
+ - gemfiles/faraday_0_17.gemfile
222
+ - gemfiles/faraday_1_x.gemfile
175
223
  - lib/remove_bg.rb
224
+ - lib/remove_bg/account_info.rb
176
225
  - lib/remove_bg/api.rb
177
226
  - lib/remove_bg/api_client.rb
227
+ - lib/remove_bg/base_request_options.rb
228
+ - lib/remove_bg/composite_result.rb
178
229
  - lib/remove_bg/configuration.rb
179
230
  - lib/remove_bg/error.rb
180
231
  - lib/remove_bg/http_connection.rb
232
+ - lib/remove_bg/image_composer.rb
233
+ - lib/remove_bg/rate_limit_info.rb
181
234
  - lib/remove_bg/request_options.rb
182
235
  - lib/remove_bg/result.rb
236
+ - lib/remove_bg/result_metadata.rb
183
237
  - lib/remove_bg/upload.rb
184
238
  - lib/remove_bg/url_validator.rb
185
239
  - lib/remove_bg/version.rb
@@ -206,7 +260,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
260
  - !ruby/object:Gem::Version
207
261
  version: '0'
208
262
  requirements: []
209
- rubygems_version: 3.0.3
263
+ rubygems_version: 3.1.2
210
264
  signing_key:
211
265
  specification_version: 4
212
266
  summary: Remove image background - 100% automatically
@@ -1,7 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "faraday", "~> 0.14"
6
-
7
- gemspec path: "../"