cloudinary 1.11.1 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -1
  3. data/.travis.yml +12 -6
  4. data/CHANGELOG.md +30 -0
  5. data/cloudinary.gemspec +2 -0
  6. data/lib/active_storage/blob_key.rb +20 -0
  7. data/lib/active_storage/service/cloudinary_service.rb +181 -0
  8. data/lib/cloudinary.rb +9 -4
  9. data/lib/cloudinary/api.rb +17 -4
  10. data/lib/cloudinary/auth_token.rb +6 -4
  11. data/lib/cloudinary/uploader.rb +4 -4
  12. data/lib/cloudinary/utils.rb +62 -29
  13. data/lib/cloudinary/version.rb +1 -1
  14. data/spec/active_storage/Gemfile +12 -0
  15. data/spec/active_storage/application_system_test_case.rb +5 -0
  16. data/spec/active_storage/database/create_users_migration.rb +9 -0
  17. data/spec/active_storage/database/setup.rb +7 -0
  18. data/spec/active_storage/dummy/Rakefile +5 -0
  19. data/spec/active_storage/dummy/app/assets/config/manifest.js +3 -0
  20. data/spec/active_storage/dummy/app/assets/javascripts/application.js +13 -0
  21. data/spec/active_storage/dummy/app/assets/stylesheets/application.css +15 -0
  22. data/spec/active_storage/dummy/app/controllers/application_controller.rb +5 -0
  23. data/spec/active_storage/dummy/app/helpers/application_helper.rb +4 -0
  24. data/spec/active_storage/dummy/app/jobs/application_job.rb +4 -0
  25. data/spec/active_storage/dummy/app/models/application_record.rb +5 -0
  26. data/spec/active_storage/dummy/app/views/layouts/application.html.erb +14 -0
  27. data/spec/active_storage/dummy/bin/bundle +5 -0
  28. data/spec/active_storage/dummy/bin/rails +6 -0
  29. data/spec/active_storage/dummy/bin/rake +6 -0
  30. data/spec/active_storage/dummy/bin/yarn +11 -0
  31. data/spec/active_storage/dummy/config.ru +7 -0
  32. data/spec/active_storage/dummy/config/application.rb +22 -0
  33. data/spec/active_storage/dummy/config/boot.rb +7 -0
  34. data/spec/active_storage/dummy/config/database.yml +25 -0
  35. data/spec/active_storage/dummy/config/environment.rb +7 -0
  36. data/spec/active_storage/dummy/config/environments/development.rb +52 -0
  37. data/spec/active_storage/dummy/config/environments/production.rb +83 -0
  38. data/spec/active_storage/dummy/config/environments/test.rb +38 -0
  39. data/spec/active_storage/dummy/config/initializers/application_controller_renderer.rb +7 -0
  40. data/spec/active_storage/dummy/config/initializers/assets.rb +16 -0
  41. data/spec/active_storage/dummy/config/initializers/backtrace_silencers.rb +8 -0
  42. data/spec/active_storage/dummy/config/initializers/cookies_serializer.rb +7 -0
  43. data/spec/active_storage/dummy/config/initializers/filter_parameter_logging.rb +6 -0
  44. data/spec/active_storage/dummy/config/initializers/inflections.rb +17 -0
  45. data/spec/active_storage/dummy/config/initializers/mime_types.rb +5 -0
  46. data/spec/active_storage/dummy/config/initializers/wrap_parameters.rb +16 -0
  47. data/spec/active_storage/dummy/config/routes.rb +4 -0
  48. data/spec/active_storage/dummy/config/secrets.yml +32 -0
  49. data/spec/active_storage/dummy/config/spring.rb +8 -0
  50. data/spec/active_storage/dummy/config/storage.yml +3 -0
  51. data/spec/active_storage/dummy/config/webpacker.yml +72 -0
  52. data/spec/active_storage/dummy/package.json +5 -0
  53. data/spec/active_storage/dummy/public/404.html +67 -0
  54. data/spec/active_storage/dummy/public/422.html +67 -0
  55. data/spec/active_storage/dummy/public/500.html +66 -0
  56. data/spec/active_storage/dummy/public/apple-touch-icon-precomposed.png +0 -0
  57. data/spec/active_storage/dummy/public/apple-touch-icon.png +0 -0
  58. data/spec/active_storage/dummy/public/favicon.ico +0 -0
  59. data/spec/active_storage/fixtures/files/colors.bmp +0 -0
  60. data/spec/active_storage/fixtures/files/empty_file.txt +0 -0
  61. data/spec/active_storage/fixtures/files/favicon.ico +0 -0
  62. data/spec/active_storage/fixtures/files/icon.psd +0 -0
  63. data/spec/active_storage/fixtures/files/icon.svg +13 -0
  64. data/spec/active_storage/fixtures/files/image.gif +0 -0
  65. data/spec/active_storage/fixtures/files/racecar.jpg +0 -0
  66. data/spec/active_storage/fixtures/files/racecar.tif +0 -0
  67. data/spec/active_storage/fixtures/files/racecar_rotated.jpg +0 -0
  68. data/spec/active_storage/fixtures/files/report.pdf +0 -0
  69. data/spec/active_storage/fixtures/files/rotated_video.mp4 +0 -0
  70. data/spec/active_storage/fixtures/files/video.mp4 +0 -0
  71. data/spec/active_storage/fixtures/files/video_with_rectangular_samples.mp4 +0 -0
  72. data/spec/active_storage/fixtures/files/video_with_undefined_display_aspect_ratio.mp4 +0 -0
  73. data/spec/active_storage/fixtures/files/video_without_video_stream.mp4 +0 -0
  74. data/spec/active_storage/service/cloudinary_service_spec.rb +92 -0
  75. data/spec/active_storage/service/configurations.yml +4 -0
  76. data/spec/active_storage/test_helper.rb +43 -0
  77. data/spec/api_spec.rb +68 -12
  78. data/spec/archive_spec.rb +17 -1
  79. data/spec/cloudinary_helper_spec.rb +2 -0
  80. data/spec/cloudinary_spec.rb +30 -6
  81. data/spec/movie.mp4 +0 -0
  82. data/spec/spec_helper.rb +9 -2
  83. data/spec/uploader_spec.rb +20 -3
  84. data/spec/utils_methods_spec.rb +29 -2
  85. data/spec/utils_spec.rb +93 -11
  86. data/vendor/assets/javascripts/cloudinary/jquery.cloudinary.js +31 -1
  87. data/vendor/assets/javascripts/cloudinary/jquery.fileupload.js +24 -4
  88. data/vendor/assets/javascripts/cloudinary/jquery.ui.widget.js +741 -561
  89. data/vendor/assets/javascripts/cloudinary/load-image.all.min.js +1 -1
  90. metadata +161 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 629a48ffc8543fc7fc8da12f12d88823232fe70a08556c49b9583437bcc24c45
4
- data.tar.gz: 1a34d49c36238a396fc3d1494845f0f97ae71b18bf49175e255c9fbea239031f
3
+ metadata.gz: f3837b9826e6f31a35e478b9d8bbf81027cee43ece65f7df161b748fd5c21dd9
4
+ data.tar.gz: 3b47da18531bbfe2252f6cfe4a071bc5972c286c2f5777758d1b1a7e1a30b07e
5
5
  SHA512:
6
- metadata.gz: 9be4a6c2470fcdbab667610878db7934bff588ceda810e4917caa36b28c9c3e910214ebd4f21baca258ff643f7bd8e7277af4dda89779da356473d49b2cc73b8
7
- data.tar.gz: b308cf1059df2bc58ba103d67b845c0a3a2384a4f53e387f950057abbc502ef425506b2861b1b471689b21cd3e04f1c960fe230559ad8d5830babc203a85b2c0
6
+ metadata.gz: 34d0a6214325094a27683345da51e605b7ba6f6ba5761d4c64478dea618dbf3d4ce14a255bf69d513704d72dca6ef5a27ee4bc6e5e81d66e5548341f9e7c2e8c
7
+ data.tar.gz: 383bbafb8b06b9bda3fdedbde2f6b7248bcee0bacd5576e98b8e9b79d9ff45cd7d1025056fe1edde412fbf4a04855ba4ea3af197c260374827026f7a0b883f55
data/.gitignore CHANGED
@@ -55,4 +55,9 @@ bower.json
55
55
  .powenv
56
56
 
57
57
  !lib/
58
- cloudinary.yml
58
+ cloudinary.yml
59
+ configuration.yml
60
+
61
+ # Developer env
62
+ .idea
63
+ .vscode
@@ -1,9 +1,15 @@
1
1
  language: ruby
2
2
 
3
- before_install:
4
- - gem install bundler
5
-
6
- rvm:
7
- - 1.9.3
8
- - ruby-head
3
+ matrix:
4
+ include:
5
+ - name: "Ruby 1.9"
6
+ dist: precise
7
+ rvm: 1.9.3
8
+ before_install:
9
+ - gem install bundler -v 1.17.3
10
+ - name: "Ruby 2.5.3"
11
+ dist: xenial
12
+ rvm: ruby-2.5.3
13
+ before_install:
14
+ - gem install bundler
9
15
  script: bundle exec rspec
@@ -1,3 +1,33 @@
1
+ 1.12.0 / 2019-10-02
2
+ =============
3
+
4
+ New functionality and features
5
+ ------------------------------
6
+
7
+ * Add Cloudinary service for ActiveStorage
8
+ * Add `create_folder` Admin API method
9
+ * Add `delete_folder` Admin API method
10
+ * Add `cinemagraph_analysis` to `upload`, `explicit` and `resource` API methods
11
+ * Add `font_antialiasing` and `font_hinting` text style parameters
12
+ * Add `derived_next_cursor` parameter to `resource` Admin API
13
+ * Add `next_cursor` and `max_results` for `root_folders` and `subfolders` Admin API functions
14
+ * Add `jpeg` to `IMAGE_FORMATS`
15
+ * Add `pow` transformation operator
16
+ * Add `force_version` to `cloudinary_url`
17
+ * Support per corner values for the `radius` transformation parameter
18
+ * Support using multiple resource types when generating archives
19
+ * Support Google Storage fetch URL
20
+
21
+ Other Changes
22
+ -------------
23
+ * Ensure `CLOUDINARY_URL` starts with `cloudinary://`
24
+ * Reduce memory usage in `Cloudinary::Utils.cloudinary_url`
25
+ * Encode URL in Admin API methods
26
+ * Fix base64 data validation
27
+ * Return `video` as the `resource_type` for audio files
28
+ * Add language and platform version for ruby/rails user agent
29
+ * Fix TravisCI configuration for ruby 1.9
30
+
1
31
 
2
32
  1.11.1 / 2018-12-22
3
33
  ===================
@@ -26,7 +26,9 @@ Gem::Specification.new do |s|
26
26
  s.add_development_dependency "actionpack"
27
27
  s.add_development_dependency "nokogiri"
28
28
  s.add_development_dependency "rake"
29
+ s.add_development_dependency "sqlite3"
29
30
  s.add_development_dependency "rspec", '>=3.5'
31
+ s.add_development_dependency "rails", "~>5.2" if RUBY_VERSION >= "2.2.2"
30
32
  s.add_development_dependency "rspec-rails"
31
33
  s.add_development_dependency "rubyzip", "<=1.2.0" # support testing Ruby 1.9
32
34
  s.add_development_dependency "simplecov"
@@ -0,0 +1,20 @@
1
+ # Allow Blob attributes to be passed down to the service
2
+ # attributes includes
3
+ # - key - the string the BlobKey represents
4
+ # - content_type
5
+ # - filename
6
+ module ActiveStorage
7
+ class BlobKey < String
8
+ attr_reader :attributes
9
+ def initialize(attributes)
10
+ if attributes.is_a? Hash
11
+ attributes.symbolize_keys!
12
+ super(attributes[:key])
13
+ @attributes = attributes
14
+ else
15
+ super(attributes)
16
+ @attributes = {key: attributes} if attributes
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,181 @@
1
+ require 'active_storage/blob_key'
2
+ require 'cloudinary/helper'
3
+
4
+ unless ActiveStorage::Blob.method_defined? :original_key
5
+ class ActiveStorage::Blob
6
+ alias_method :original_key, :key
7
+
8
+ def key
9
+ original_key
10
+ ActiveStorage::BlobKey.new(@attributes.as_json)
11
+ end
12
+ end
13
+ end
14
+
15
+ module ActiveStorage
16
+ class Service::CloudinaryService < Service
17
+ module Headers
18
+ CONTENT_TYPE = "Content-Type".freeze
19
+ CONTENT_MD5 = "Content-MD5".freeze
20
+ end
21
+ attr_reader :upload_options
22
+
23
+ def initialize(**options)
24
+ @options = options
25
+ @helper = ActionView::Base.new
26
+ end
27
+
28
+ def upload(key, io, filename: nil, checksum: nil, **options)
29
+ instrument :upload, key: key, checksum: checksum do
30
+ begin
31
+ extra_headers = checksum.nil? ? {} : {Headers::CONTENT_MD5 => checksum}
32
+ options = @options.merge(options)
33
+ Cloudinary::Uploader.upload(
34
+ io,
35
+ public_id: public_id(key),
36
+ resource_type: resource_type(io, key),
37
+ context: {active_storage_key: key, checksum: checksum},
38
+ extra_headers: extra_headers,
39
+ **options
40
+ )
41
+ rescue CloudinaryException => e
42
+ raise ActiveStorage::IntegrityError, e.message, e.backtrace
43
+ end
44
+ end
45
+ end
46
+
47
+ def url(key, filename: nil, content_type: '', **options)
48
+ instrument :url, key: key do |payload|
49
+ url = Cloudinary::Utils.cloudinary_url(
50
+ public_id(key),
51
+ resource_type: resource_type(nil, key),
52
+ format: ext_for_content_type(content_type),
53
+ **@options.merge(options.symbolize_keys)
54
+ )
55
+ payload[:url] = url
56
+
57
+ url
58
+ end
59
+ end
60
+
61
+ def url_for_direct_upload(key, **options)
62
+ instrument :url, key: key do |payload|
63
+ options = {:resource_type => resource_type(nil, key)}.merge(@options.merge(options.symbolize_keys))
64
+ options[:public_id] = public_id(key)
65
+ options[:context] = {active_storage_key: key}
66
+ options.delete(:file)
67
+ payload[:url] = api_uri("upload", options)
68
+ end
69
+ end
70
+
71
+ def headers_for_direct_upload(key, content_type:, checksum:, **)
72
+ {
73
+ Headers::CONTENT_TYPE => content_type,
74
+ Headers::CONTENT_MD5 => checksum,
75
+ }
76
+ end
77
+
78
+ def delete(key)
79
+ instrument :delete, key: key do
80
+ Cloudinary::Uploader.destroy public_id(key), resource_type: resource_type(nil, key)
81
+ end
82
+ end
83
+
84
+ def delete_prefixed(prefix)
85
+ # This method is used by ActiveStorage to delete derived resources after the main resource was deleted.
86
+ # In Cloudinary, the derived resources are deleted automatically when the main resource is deleted.
87
+ end
88
+
89
+ def exist?(key)
90
+ instrument :exist, key: key do |payload|
91
+ begin
92
+ Cloudinary::Api.resource public_id(key), resource_type: resource_type(nil, key)
93
+ true
94
+ rescue Cloudinary::Api::NotFound => e
95
+ false
96
+ end
97
+ end
98
+ end
99
+
100
+ def download(key, &block)
101
+ url = Cloudinary::Utils.unsigned_download_url(public_id(key), resource_type: resource_type(nil, key))
102
+ uri = URI(url)
103
+ if block_given?
104
+ instrument :streaming_download, key: key do
105
+ Net::HTTP.start(uri.host, uri.port) do |http|
106
+ request = Net::HTTP::Get.new uri
107
+ http.request request do |response|
108
+ response.read_body &block
109
+ end
110
+
111
+ end
112
+ end
113
+ else
114
+ instrument :download, key: key do
115
+ res = Net::HTTP::get_response(uri)
116
+ res.body
117
+ end
118
+ end
119
+ end
120
+
121
+ # Return the partial content in the byte +range+ of the file at the +key+.
122
+ def download_chunk(key, range)
123
+ url = Cloudinary::Utils.unsigned_download_url(public_id(key), resource_type: resource_type(nil, key))
124
+ uri = URI(url)
125
+ instrument :download, key: key do
126
+ req = Net::HTTP::Get.new(uri)
127
+ range_end = case
128
+ when range.end.nil? then ''
129
+ when range.exclude_end? then range.end - 1
130
+ else range.end
131
+ end
132
+ req['range'] = "bytes=#{[range.begin, range_end].join('-')}"
133
+ res = Net::HTTP.start(uri.hostname, uri.port) do |http|
134
+ http.request(req)
135
+ end
136
+ res.body.force_encoding(Encoding::BINARY)
137
+ end
138
+
139
+ end
140
+
141
+ private
142
+
143
+ def api_uri(action, options)
144
+ base_url = Cloudinary::Utils.cloudinary_api_url(action, options)
145
+ upload_params = Cloudinary::Uploader.build_upload_params(options)
146
+
147
+ upload_params.reject! {|k, v| Cloudinary::Utils.safe_blank?(v)}
148
+ unless options[:unsigned]
149
+ upload_params = Cloudinary::Utils.sign_request(upload_params, options)
150
+ end
151
+ "#{base_url}?#{upload_params.to_query}"
152
+ end
153
+
154
+ def ext_for_content_type(content_type)
155
+ @formats ||= Hash.new do |h, key|
156
+ ext = Rack::Mime::MIME_TYPES.invert[key]
157
+ h[key] = ext.slice(1..-1) unless ext.nil?
158
+ end
159
+ @formats[content_type]
160
+ end
161
+
162
+ def public_id(key)
163
+ # TODO: Allow custom manipulation of key to obscure how we store in Cloudinary
164
+ key
165
+ end
166
+
167
+ def resource_type(io, key = "")
168
+ return 'image' unless key.respond_to? :attributes
169
+ options = key.attributes
170
+ content_type = options[:content_type] || (io.nil? ? '' : Marcel::MimeType.for(io))
171
+ case content_type.split('/')[0]
172
+ when 'video'
173
+ 'video'
174
+ when 'text'
175
+ 'raw'
176
+ else
177
+ 'image'
178
+ end
179
+ end
180
+ end
181
+ end
@@ -29,8 +29,8 @@ module Cloudinary
29
29
  OLD_AKAMAI_SHARED_CDN = "cloudinary-a.akamaihd.net"
30
30
  SHARED_CDN = AKAMAI_SHARED_CDN
31
31
 
32
- USER_AGENT = "CloudinaryRuby/" + VERSION
33
- @@user_platform = ""
32
+ USER_AGENT = "CloudinaryRuby/#{VERSION} (Ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL})"
33
+ @@user_platform = defined?(Rails.version) ? "Rails/#{Rails.version}" : ""
34
34
 
35
35
  # Add platform information to the USER_AGENT header
36
36
  # This is intended for platform information and not individual applications!
@@ -44,7 +44,7 @@ module Cloudinary
44
44
 
45
45
  def self.USER_AGENT
46
46
  if @@user_platform.empty?
47
- "#{USER_AGENT}"
47
+ USER_AGENT
48
48
  else
49
49
  "#{@@user_platform} #{USER_AGENT}"
50
50
  end
@@ -86,7 +86,12 @@ module Cloudinary
86
86
 
87
87
  def self.config_from_url(url)
88
88
  @@config ||= OpenStruct.new
89
- uri = URI.parse(url)
89
+ return unless url && !url.empty?
90
+ uri = URI.parse(url)
91
+ if !uri.scheme || "cloudinary" != uri.scheme.downcase
92
+ raise(CloudinaryException,
93
+ "Invalid CLOUDINARY_URL scheme. Expecting to start with 'cloudinary://'")
94
+ end
90
95
  set_config(
91
96
  "cloud_name" => uri.host,
92
97
  "api_key" => uri.user,
@@ -79,7 +79,8 @@ class Cloudinary::Api
79
79
  type = options[:type] || "upload"
80
80
  uri = "resources/#{resource_type}/#{type}/#{public_id}"
81
81
  call_api(:get, uri,
82
- only(options,
82
+ only(options,
83
+ :cinemagraph_analysis,
83
84
  :colors,
84
85
  :coordinates,
85
86
  :exif,
@@ -88,7 +89,8 @@ class Cloudinary::Api
88
89
  :max_results,
89
90
  :pages,
90
91
  :phash,
91
- :quality_analysis
92
+ :quality_analysis,
93
+ :derived_next_cursor
92
94
  ), options)
93
95
  end
94
96
 
@@ -226,11 +228,21 @@ class Cloudinary::Api
226
228
  end
227
229
 
228
230
  def self.root_folders(options={})
229
- call_api(:get, "folders", {}, options)
231
+ params = only(options, :max_results, :next_cursor)
232
+ call_api(:get, "folders", params, options)
230
233
  end
231
234
 
232
235
  def self.subfolders(of_folder_path, options={})
233
- call_api(:get, "folders/#{of_folder_path}", {}, options)
236
+ params = only(options, :max_results, :next_cursor)
237
+ call_api(:get, "folders/#{of_folder_path}", params, options)
238
+ end
239
+
240
+ def self.delete_folder(path, options={})
241
+ call_api(:delete, "folders/#{path}", {}, options)
242
+ end
243
+
244
+ def self.create_folder(folder_name, options={})
245
+ call_api(:post, "folders/#{folder_name}", {}, options)
234
246
  end
235
247
 
236
248
  def self.upload_mappings(options={})
@@ -343,6 +355,7 @@ class Cloudinary::Api
343
355
  api_key = options[:api_key] || Cloudinary.config.api_key || raise("Must supply api_key")
344
356
  api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise("Must supply api_secret")
345
357
  timeout = options[:timeout] || Cloudinary.config.timeout || 60
358
+ uri = Cloudinary::Utils.smart_escape(uri)
346
359
  api_url = [cloudinary, "v1_1", cloud_name, uri].join("/")
347
360
  # Add authentication
348
361
  api_url.sub!(%r(^(https?://)), "\\1#{api_key}:#{api_secret}@")
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'openssl'
2
4
  if RUBY_VERSION > "2"
3
5
  require "ostruct"
@@ -10,6 +12,7 @@ module Cloudinary
10
12
  module AuthToken
11
13
  SEPARATOR = '~'
12
14
  UNSAFE = /[ "#%&\'\/:;<=>?@\[\\\]^`{\|}~]/
15
+ EMPTY_TOKEN = {}.freeze
13
16
 
14
17
  def self.generate(options = {})
15
18
  key = options[:key]
@@ -42,12 +45,11 @@ module Cloudinary
42
45
  "#{name}=#{token.join(SEPARATOR)}"
43
46
  end
44
47
 
45
-
46
48
  # Merge token2 to token1 returning a new
47
49
  # Requires to support Ruby 1.9
48
50
  def self.merge_auth_token(token1, token2)
49
- token1 = token1 || {}
50
- token2 = token2 || {}
51
+ token1 = token1 || EMPTY_TOKEN
52
+ token2 = token2 || EMPTY_TOKEN
51
53
  token1 = token1.respond_to?( :to_h) ? token1.to_h : token1
52
54
  token2 = token2.respond_to?( :to_h) ? token2.to_h : token2
53
55
  token1.merge(token2)
@@ -69,4 +71,4 @@ module Cloudinary
69
71
  OpenSSL::HMAC.hexdigest(digest, bin_key, message)
70
72
  end
71
73
  end
72
- end
74
+ end
@@ -5,8 +5,7 @@ require 'cloudinary/cache'
5
5
 
6
6
  class Cloudinary::Uploader
7
7
 
8
- REMOTE_URL_REGEX = %r(^ftp:|^https?:|^s3:|^data:[^;]*;base64,([a-zA-Z0-9\/+\n=]+)$)
9
-
8
+ REMOTE_URL_REGEX = Cloudinary::Utils::REMOTE_URL_REGEX
10
9
  # @deprecated use {Cloudinary::Utils.build_eager} instead
11
10
  def self.build_eager(eager)
12
11
  Cloudinary::Utils.build_eager(eager)
@@ -28,6 +27,7 @@ class Cloudinary::Uploader
28
27
  :backup => Cloudinary::Utils.as_safe_bool(options[:backup]),
29
28
  :callback => options[:callback],
30
29
  :categorization => options[:categorization],
30
+ :cinemagraph_analysis => Cloudinary::Utils.as_safe_bool(options[:cinemagraph_analysis]),
31
31
  :colors => Cloudinary::Utils.as_safe_bool(options[:colors]),
32
32
  :context => Cloudinary::Utils.encode_context(options[:context]),
33
33
  :custom_coordinates => Cloudinary::Utils.encode_double_array(options[:custom_coordinates]),
@@ -77,7 +77,7 @@ class Cloudinary::Uploader
77
77
  params = build_upload_params(options)
78
78
  if file.is_a?(Pathname)
79
79
  params[:file] = File.open(file, "rb")
80
- elsif file.respond_to?(:read) || file.match(REMOTE_URL_REGEX)
80
+ elsif file.respond_to?(:read) || Cloudinary::Utils.is_remote?(file)
81
81
  params[:file] = file
82
82
  else
83
83
  params[:file] = File.open(file, "rb")
@@ -95,7 +95,7 @@ class Cloudinary::Uploader
95
95
  public_id = public_id_or_options
96
96
  options = old_options
97
97
  end
98
- if file.match(REMOTE_URL_REGEX)
98
+ if Cloudinary::Utils.is_remote?(file)
99
99
  return upload(file, options.merge(:public_id => public_id))
100
100
  elsif file.is_a?(Pathname) || !file.respond_to?(:read)
101
101
  filename = file