cloudinary 1.17.1 → 1.18.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e74e0f92ef1143c670e497a22d7b78c638f57d7a35a51585c7a895e654bb95e1
4
- data.tar.gz: 64c6b47d7fe482f4cbc7a82a7024a3c18ab51529dc52224067bd120d29e239c1
3
+ metadata.gz: c9179fe1c8105281b33780687c2bed49db41a35fca2d33ff15bd7fd4635d119f
4
+ data.tar.gz: 7b3bd01f71f1012bc7f315f707450ebb65537064e004fad615d713f489c4316c
5
5
  SHA512:
6
- metadata.gz: 784f4de3203258629ee5843fd80d105e6ad59727a43299c5742ae9af25bdd02389b5cc990c563d542a5463970a9d4ae27c9abe99747518962b0a017273c14c85
7
- data.tar.gz: da26474dc9fa48717859e648f3620416c02717169a7bd1347e7d2636e6e86b9e39313a8ae5077b9f39c994c465a0d887b5d90d16d0ee0aa58e210757607f1dd3
6
+ metadata.gz: 347fc122e9a31aa241afa194c09cdc3c0d19e63c5a206cc7751f4939de7c273769d648c7fd33af2cc4e7505fba0a5be9d555f1c3db1e2b41d45cfb58082e1cc3
7
+ data.tar.gz: 4f04b59256afe8f23c27625d94d761e9802e2ccd91d3c3dc992eb263e3430a7a053467c203d416d379815e317924f042c9310ac0f2b4ca185630307268aa66ca
@@ -1,4 +1,18 @@
1
1
 
2
+ 1.18.0 / 2020-09-27
3
+ ===================
4
+
5
+ New functionality and features
6
+ ------------------------------
7
+ * Add `download_folder` helper
8
+ * Add support for `sources` in `video` tag
9
+ * Add structured metadata to Admin and Upload API
10
+
11
+ Other Changes
12
+ -------------
13
+ * Fix download of a raw file in ActiveStorage
14
+ * Update embedded `jquery.cloudinary.js` to fix ES5 compatibility issue
15
+
2
16
  1.17.1 / 2020-08-25
3
17
  ===================
4
18
 
@@ -1,5 +1,6 @@
1
1
  require 'active_storage/blob_key'
2
2
  require 'cloudinary/helper'
3
+ require 'net/http'
3
4
 
4
5
  unless ActiveStorage::Blob.method_defined? :original_key
5
6
  class ActiveStorage::Blob
@@ -57,7 +58,7 @@ module ActiveStorage
57
58
  url = Cloudinary::Utils.cloudinary_url(
58
59
  public_id(key),
59
60
  resource_type: resource_type(nil, key),
60
- format: ext_for_file(filename, content_type),
61
+ format: ext_for_file(key, filename, content_type),
61
62
  **@options.merge(options.symbolize_keys)
62
63
  )
63
64
 
@@ -107,8 +108,7 @@ module ActiveStorage
107
108
  end
108
109
 
109
110
  def download(key, &block)
110
- url = Cloudinary::Utils.unsigned_download_url(public_id(key), resource_type: resource_type(nil, key))
111
- uri = URI(url)
111
+ uri = URI(url(key))
112
112
  if block_given?
113
113
  instrument :streaming_download, key: key do
114
114
  Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
@@ -169,12 +169,18 @@ module ActiveStorage
169
169
  #
170
170
  # It does the best effort when original filename does not include extension, but we know the mime-type.
171
171
  #
172
+ # @param [ActiveStorage::BlobKey] key The blob key with attributes.
172
173
  # @param [ActiveStorage::Filename] filename The original filename.
173
174
  # @param [string] content_type The content type of the file.
174
175
  #
175
176
  # @return [string] The extension of the filename.
176
- def ext_for_file(filename, content_type)
177
+ def ext_for_file(key, filename, content_type)
178
+ if filename.blank?
179
+ options = key.respond_to?(:attributes) ? key.attributes : {}
180
+ filename = ActiveStorage::Filename.new(options[:filename]) if options.has_key?(:filename)
181
+ end
177
182
  ext = filename.respond_to?(:extension_without_delimiter) ? filename.extension_without_delimiter : nil
183
+
178
184
  return ext unless ext.blank?
179
185
 
180
186
  # Raw files are not convertible, no extension guessing for them
@@ -348,6 +348,144 @@ class Cloudinary::Api
348
348
  call_json_api('GET', json_url, {}, 60, {})
349
349
  end
350
350
 
351
+ # Returns a list of all metadata field definitions.
352
+ #
353
+ # @see https://cloudinary.com/documentation/admin_api#get_metadata_fields Get metadata fields API reference
354
+ #
355
+ # @param [Hash] options Additional options
356
+ # @return [Cloudinary::Api::Response]
357
+ # @raise [Cloudinary::Api::Error]
358
+ def self.list_metadata_fields(options = {})
359
+ call_metadata_api(:get, [], {}, options)
360
+ end
361
+
362
+ # Gets a metadata field by external id.
363
+ #
364
+ # @see https://cloudinary.com/documentation/admin_api#get_a_metadata_field_by_external_id Get metadata field by external ID API reference
365
+ #
366
+ # @param [String] field_external_id The ID of the metadata field to retrieve
367
+ # @param [Hash] options Additional options
368
+ # @return [Cloudinary::Api::Response]
369
+ # @raise [Cloudinary::Api::Error]
370
+ def self.metadata_field_by_field_id(field_external_id, options = {})
371
+ uri = [field_external_id]
372
+
373
+ call_metadata_api(:get, uri, {}, options)
374
+ end
375
+
376
+ # Creates a new metadata field definition.
377
+ #
378
+ # @see https://cloudinary.com/documentation/admin_api#create_a_metadata_field Create metadata field API reference
379
+ #
380
+ # @param [Hash] field The field to add
381
+ # @param [Hash] options Additional options
382
+ # @return [Cloudinary::Api::Response]
383
+ # @raise [Cloudinary::Api::Error]
384
+ def self.add_metadata_field(field, options = {})
385
+ params = only(field, :type, :external_id, :label, :mandatory, :default_value, :validation, :datasource)
386
+
387
+ call_metadata_api(:post, [], params, options)
388
+ end
389
+
390
+ # Updates a metadata field by external id.
391
+ #
392
+ # Updates a metadata field definition (partially, no need to pass the entire object) passed as JSON data.
393
+ # See https://cloudinary.com/documentation/admin_api#generic_structure_of_a_metadata_field for the generic structure
394
+ # of a metadata field.
395
+ #
396
+ # @see https://cloudinary.com/documentation/admin_api#update_a_metadata_field_by_external_id Update metadata field API reference
397
+ #
398
+ # @param [String] field_external_id The id of the metadata field to update
399
+ # @param [Hash] field The field definition
400
+ # @param [Hash] options Additional options
401
+ # @return [Cloudinary::Api::Response]
402
+ # @raise [Cloudinary::Api::Error]
403
+ def self.update_metadata_field(field_external_id, field, options = {})
404
+ uri = [field_external_id]
405
+ params = only(field, :label, :mandatory, :default_value, :validation)
406
+
407
+ call_metadata_api(:put, uri, params, options)
408
+ end
409
+
410
+ # Deletes a metadata field definition.
411
+ #
412
+ # The field should no longer be considered a valid candidate for all other endpoints.
413
+ #
414
+ # @see https://cloudinary.com/documentation/admin_api#delete_a_metadata_field_by_external_id Delete metadata field API reference
415
+ #
416
+ # @param [String] field_external_id The external id of the field to delete
417
+ # @param [Hash] options Additional options
418
+ # @return [Cloudinary::Api::Response] A hash with a "message" key. "ok" value indicates a successful deletion
419
+ # @raise [Cloudinary::Api::Error]
420
+ def self.delete_metadata_field(field_external_id, options = {})
421
+ uri = [field_external_id]
422
+
423
+ call_metadata_api(:delete, uri, {}, options)
424
+ end
425
+
426
+ # Deletes entries in a metadata field datasource.
427
+ #
428
+ # Deletes (blocks) the datasource entries for a specified metadata field definition. Sets the state of the
429
+ # entries to inactive. This is a soft delete, the entries still exist under the hood and can be activated
430
+ # again with the restore datasource entries method.
431
+ #
432
+ # @see https://cloudinary.com/documentation/admin_api#delete_entries_in_a_metadata_field_datasource Delete entries in a metadata field datasource API reference
433
+ #
434
+ # @param [String] field_external_id The id of the field to update
435
+ # @param [Array] entries_external_id The ids of all the entries to delete from the datasource
436
+ # @param [Hash] options Additional options
437
+ # @return [Cloudinary::Api::Response] The remaining datasource entries
438
+ # @raise [Cloudinary::Api::Error]
439
+ def self.delete_datasource_entries(field_external_id, entries_external_id, options = {})
440
+ uri = [field_external_id, "datasource"]
441
+ params = {:external_ids => entries_external_id }
442
+
443
+ call_metadata_api(:delete, uri, params, options)
444
+ end
445
+
446
+ # Updates a metadata field datasource.
447
+ #
448
+ # Updates the datasource of a supported field type (currently only enum and set), passed as JSON data. The
449
+ # update is partial: datasource entries with an existing external_id will be updated and entries with new
450
+ # external_id’s (or without external_id’s) will be appended.
451
+ #
452
+ # @see https://cloudinary.com/documentation/admin_api#update_a_metadata_field_datasource Update a metadata field datasource API reference
453
+ #
454
+ # @param [String] field_external_id The external id of the field to update
455
+ # @param [Array] entries_external_id
456
+ # @param [Hash] options Additional options
457
+ # @return [Cloudinary::Api::Response]
458
+ # @raise [Cloudinary::Api::Error]
459
+ def self.update_metadata_field_datasource(field_external_id, entries_external_id, options = {})
460
+ uri = [field_external_id, "datasource"]
461
+
462
+ params = entries_external_id.each_with_object({:values => [] }) do |item, hash|
463
+ item = only(item, :external_id, :value)
464
+ hash[:values ] << item if item.present?
465
+ end
466
+
467
+ call_metadata_api(:put, uri, params, options)
468
+ end
469
+
470
+ # Restores entries in a metadata field datasource.
471
+ #
472
+ # Restores (unblocks) any previously deleted datasource entries for a specified metadata field definition.
473
+ # Sets the state of the entries to active.
474
+ #
475
+ # @see https://cloudinary.com/documentation/admin_api#restore_entries_in_a_metadata_field_datasource Restore entries in a metadata field datasource API reference
476
+ #
477
+ # @param [String] field_external_id The ID of the metadata field
478
+ # @param [Array] entries_external_ids An array of IDs of datasource entries to restore (unblock)
479
+ # @param [Hash] options Additional options
480
+ # @return [Cloudinary::Api::Response]
481
+ # @raise [Cloudinary::Api::Error]
482
+ def self.restore_metadata_field_datasource(field_external_id, entries_external_ids, options = {})
483
+ uri = [field_external_id, "datasource_restore"]
484
+ params = {:external_ids => entries_external_ids }
485
+
486
+ call_metadata_api(:post, uri, params, options)
487
+ end
488
+
351
489
  protected
352
490
 
353
491
  def self.call_api(method, uri, params, options)
@@ -396,6 +534,22 @@ class Cloudinary::Api
396
534
  raise GeneralError.new("Error parsing server response (#{response.code}) - #{response.body}. Got - #{e}")
397
535
  end
398
536
 
537
+ # Protected function that assists with performing an API call to the metadata_fields part of the Admin API.
538
+ #
539
+ # @protected
540
+ # @param [Symbol] method The HTTP method. Valid methods: get, post, put, delete
541
+ # @param [Array] uri REST endpoint of the API (without 'metadata_fields')
542
+ # @param [Hash] params Query/body parameters passed to the method
543
+ # @param [Hash] options Additional options. Can be an override of the configuration, headers, etc.
544
+ # @return [Cloudinary::Api::Response]
545
+ # @raise [Cloudinary::Api::Error]
546
+ def self.call_metadata_api(method, uri, params, options)
547
+ options[:content_type] = :json
548
+ uri = ["metadata_fields", uri].reject(&:empty?).join("/")
549
+
550
+ call_api(method, uri, params, options)
551
+ end
552
+
399
553
  def self.only(hash, *keys)
400
554
  result = {}
401
555
  keys.each do |key|
@@ -65,7 +65,8 @@ class Cloudinary::Uploader
65
65
  :unique_filename => Cloudinary::Utils.as_safe_bool(options[:unique_filename]),
66
66
  :upload_preset => options[:upload_preset],
67
67
  :use_filename => Cloudinary::Utils.as_safe_bool(options[:use_filename]),
68
- :accessibility_analysis => Cloudinary::Utils.as_safe_bool(options[:accessibility_analysis])
68
+ :accessibility_analysis => Cloudinary::Utils.as_safe_bool(options[:accessibility_analysis]),
69
+ :metadata => Cloudinary::Utils.encode_context(options[:metadata])
69
70
  }
70
71
  params
71
72
  end
@@ -272,6 +273,29 @@ class Cloudinary::Uploader
272
273
  return self.call_tags_api(nil, "remove_all", public_ids, options)
273
274
  end
274
275
 
276
+ # Populates metadata fields with the given values. Existing values will be overwritten.
277
+ #
278
+ # Any metadata-value pairs given are merged with any existing metadata-value pairs
279
+ # (an empty value for an existing metadata field clears the value).
280
+ #
281
+ # @param [Hash] metadata A list of custom metadata fields (by external_id) and the values to assign to each of them.
282
+ # @param [Array] public_ids An array of Public IDs of assets uploaded to Cloudinary.
283
+ # @param [Hash] options
284
+ # @option options [String] :resource_type The type of file. Default: image. Valid values: image, raw, video.
285
+ # @option options [String] :type The storage type. Default: upload. Valid values: upload, private, authenticated
286
+ # @return mixed a list of public IDs that were updated
287
+ # @raise [Cloudinary::Api:Error]
288
+ def self.update_metadata(metadata, public_ids, options = {})
289
+ self.call_api("metadata", options) do
290
+ {
291
+ timestamp: (options[:timestamp] || Time.now.to_i),
292
+ type: options[:type],
293
+ public_ids: Cloudinary::Utils.build_array(public_ids),
294
+ metadata: Cloudinary::Utils.encode_context(metadata)
295
+ }
296
+ end
297
+ end
298
+
275
299
  private
276
300
 
277
301
  def self.call_tags_api(tag, command, public_ids = [], options = {})
@@ -736,6 +736,18 @@ class Cloudinary::Utils
736
736
  download_archive_url(options.merge(:target_format => "zip"))
737
737
  end
738
738
 
739
+ # Creates and returns a URL that when invoked creates an archive of a folder.
740
+ #
741
+ # @param [Object] folder_path Full path (from the root) of the folder to download.
742
+ # @param [Hash] options Additional options.
743
+ #
744
+ # @return [String]
745
+ def self.download_folder(folder_path, options = {})
746
+ resource_type = options[:resource_type] || "all"
747
+
748
+ download_archive_url(options.merge(:resource_type => resource_type, :prefixes => folder_path))
749
+ end
750
+
739
751
  def self.signed_download_url(public_id, options = {})
740
752
  aws_private_key_path = options[:aws_private_key_path] || Cloudinary.config.aws_private_key_path
741
753
  if aws_private_key_path
@@ -1,4 +1,4 @@
1
1
  # Copyright Cloudinary
2
2
  module Cloudinary
3
- VERSION = "1.17.1"
3
+ VERSION = "1.18.0"
4
4
  end
@@ -3,12 +3,33 @@ module CloudinaryHelper
3
3
  DEFAULT_POSTER_OPTIONS = { :format => 'jpg', :resource_type => 'video' }
4
4
  DEFAULT_SOURCE_TYPES = %w(webm mp4 ogv)
5
5
  DEFAULT_VIDEO_OPTIONS = { :resource_type => 'video' }
6
+ DEFAULT_SOURCES = [
7
+ {
8
+ :type => "mp4",
9
+ :codecs => "hev1",
10
+ :transformations => { :video_codec => "h265" }
11
+ },
12
+ {
13
+ :type => "webm",
14
+ :codecs => "vp9",
15
+ :transformations => { :video_codec => "vp9" }
16
+ },
17
+ {
18
+ :type => "mp4",
19
+ :transformations => { :video_codec => "auto" }
20
+ },
21
+ {
22
+ :type => "webm",
23
+ :transformations => { :video_codec => "auto" }
24
+ }
25
+ ]
6
26
 
7
27
  # Creates an HTML video tag for the provided +source+
8
28
  #
9
29
  # ==== Options
10
30
  # * <tt>:source_types</tt> - Specify which source type the tag should include. defaults to webm, mp4 and ogv.
11
31
  # * <tt>:source_transformation</tt> - specific transformations to use for a specific source type.
32
+ # * <tt>:sources</tt> - list of sources (overrides :source_types when present)
12
33
  # * <tt>:poster</tt> - override default thumbnail:
13
34
  # * url: provide an ad hoc url
14
35
  # * options: with specific poster transformations and/or Cloudinary +:public_id+
@@ -20,6 +41,17 @@ module CloudinaryHelper
20
41
  # cl_video_tag("mymovie.webm", :source_types => [:webm, :mp4], :poster => {:effect => 'sepia'}) do
21
42
  # content_tag( :span, "Cannot present video!")
22
43
  # end
44
+ # cl_video_tag("mymovie", :sources => [
45
+ # {
46
+ # :type => "mp4",
47
+ # :codecs => "hev1",
48
+ # :transformations => { :video_codec => "h265" }
49
+ # },
50
+ # {
51
+ # :type => "webm",
52
+ # :transformations => { :video_codec => "auto" }
53
+ # }
54
+ # ])
23
55
  def cl_video_tag(source, options = {}, &block)
24
56
  source = strip_known_ext(source)
25
57
  video_attributes = [:autoplay,:controls,:loop,:muted,:poster, :preload]
@@ -48,30 +80,12 @@ module CloudinaryHelper
48
80
  video_options[:poster] = cl_video_thumbnail_path(source, options)
49
81
  end
50
82
 
51
- source_transformation = options.delete(:source_transformation) || {}
52
- source_types = Array(options.delete(:source_types))
53
- fallback = (capture(&block) if block_given?) || options.delete(:fallback_content)
83
+ fallback = (capture(&block) if block_given?) || options.delete(:fallback_content)
54
84
 
55
- if source_types.size > 1
56
- cloudinary_tag(source, options) do |_source, tag_options|
57
- content_tag('video', tag_options.merge(video_options)) do
58
- source_tags = source_types.map do |type|
59
- transformation = source_transformation[type.to_sym] || {}
60
- cloudinary_tag("#{source}.#{type}", options.merge(transformation)) do |url, _tag_options|
61
- mime_type = "video/#{(type == 'ogv' ? 'ogg' : type)}"
62
- tag("source", :src => url, :type => mime_type)
63
- end
64
- end
65
- source_tags.push(fallback.html_safe) unless fallback.blank?
66
- safe_join(source_tags)
67
- end
68
- end
85
+ if options[:sources]
86
+ video_tag_from_sources(source, options, video_options, fallback)
69
87
  else
70
- transformation = source_transformation[source_types.first.to_sym] || {}
71
- video_options[:src] = cl_video_path("#{source}.#{source_types.first.to_sym}", transformation.merge(options))
72
- cloudinary_tag(source, options) do |_source, tag_options|
73
- content_tag('video', fallback, tag_options.merge(video_options))
74
- end
88
+ video_tag_from_source_types(source, options, video_options, fallback)
75
89
  end
76
90
  end
77
91
 
@@ -96,6 +110,66 @@ module CloudinaryHelper
96
110
  name.sub(/\.(#{DEFAULT_SOURCE_TYPES.join("|")})$/, '')
97
111
  end
98
112
 
113
+ private
114
+
115
+ def video_tag_from_source_types(source_name, options, video_options, fallback)
116
+ source_transformation = options.delete(:source_transformation) || {}
117
+ source_types = Array(options.delete(:source_types))
118
+
119
+ if source_types.size > 1
120
+ sources = source_types.map do |type|
121
+ {
122
+ :type => type,
123
+ :transformations => source_transformation[type.to_sym] || {}
124
+ }
125
+ end
126
+
127
+ generate_tag_from_sources(:source_name => source_name,
128
+ :sources => sources,
129
+ :options => options,
130
+ :video_options => video_options,
131
+ :fallback => fallback)
132
+ else
133
+ transformation = source_transformation[source_types.first.to_sym] || {}
134
+ video_options[:src] = cl_video_path("#{source_name}.#{source_types.first.to_sym}", transformation.merge(options))
135
+ cloudinary_tag(source_name, options) do |_source, tag_options|
136
+ content_tag('video', fallback, tag_options.merge(video_options))
137
+ end
138
+ end
139
+ end
140
+
141
+ def video_tag_from_sources(source_name, options, video_options, fallback)
142
+ sources = options.delete(:sources)
143
+
144
+ generate_tag_from_sources(:source_name => source_name,
145
+ :sources => sources,
146
+ :options => options,
147
+ :video_options => video_options,
148
+ :fallback => fallback)
149
+ end
150
+
151
+ def generate_tag_from_sources(params)
152
+ source_name, sources, options, video_options, fallback = params.values_at(:source_name, :sources, :options, :video_options, :fallback)
153
+
154
+ cloudinary_tag(source_name, options) do |_source, tag_options|
155
+ content_tag('video', tag_options.merge(video_options)) do
156
+ source_tags = sources.map do |source|
157
+ type = source[:type]
158
+ transformation = source[:transformations] || {}
159
+ cloudinary_tag("#{source_name}.#{type}", options.merge(transformation)) do |url, _tag_options|
160
+ mime_type = "video/#{(type == 'ogv' ? 'ogg' : type)}"
161
+ if source[:codecs]
162
+ codecs = source[:codecs].is_a?(Array) ? source[:codecs].join(", ") : source[:codecs]
163
+ mime_type = "#{mime_type}; codecs=#{codecs}"
164
+ end
165
+ tag("source", :src => url, :type => mime_type)
166
+ end
167
+ end
168
+ source_tags.push(fallback.html_safe) unless fallback.blank?
169
+ safe_join(source_tags)
170
+ end
171
+ end
172
+ end
99
173
  end
100
174
 
101
175
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloudinary
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.17.1
4
+ version: 1.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nadav Soferman
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2020-08-25 00:00:00.000000000 Z
13
+ date: 2020-09-27 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: aws_cf_signer