kithe 2.4.0 → 2.6.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: 6f3d3c39a3bdccd9fc97f8f5e16c94cf12d4926e7ca50cc5e3343406ad3044d3
4
- data.tar.gz: 2bd6fc9c175d4ec213e58460fbb95b5c40b49ca1ae5808e77df26932cf60474a
3
+ metadata.gz: b4f768036d2b89fb4fedb4220b007e87f23967f9b81177cf4818fbd967f0b0d2
4
+ data.tar.gz: 358cf6e4531c022160b4877e8ea7931a10930e71e2714f095bc0e52499fc6eee
5
5
  SHA512:
6
- metadata.gz: 55c9e6eb14e975a46927fa01b4c7a8a67ca1b804ae7662f2e34e7878ee0a3247b944395edf93e311f7c39b640392ddd5b74a3df77381ba5f761e78f883e48dfa
7
- data.tar.gz: 730f6f1d20d45d7973d2c340e15889d6f2b56295f70eec8703a1e1b666ba078357b1d1ec2e34fa14ca5575ff17032f52340ffba3070e2b16e3da35012c5e8d9e
6
+ metadata.gz: 44139f3684a3609b16f972dffc58656bc2b110c6f6b5aefd635fc303089b30824a1351918ff704bcd48850bfb6cc95978bf2732ecda4166e8d9715ce9ab5ffee
7
+ data.tar.gz: 6be86d9e1b234832bced241f1b4bf7a9badcc446fd3d1c00b617395864e833ddc2894e702b6ce2b41789d87fc310e1725818bc8b44d786666a5f3fe549fea6f5
@@ -22,6 +22,13 @@ module Kithe
22
22
  #
23
23
  # ffprobe_results = FfprobeCharacterization.new(url).ffprobe_hash
24
24
  #
25
+ # The class method .characterize_from_uploader can usefully extract a URL if possible
26
+ # or else execute with a file, such as from a shrine `add_metadata` block.
27
+ #
28
+ # add_metadata do |source_io, **context|
29
+ # Kithe::FfprobeCharacterization.characterize_from_uploader(source_io, context)
30
+ # end
31
+ #
25
32
  class FfprobeCharacterization
26
33
  class_attribute :ffprobe_command, default: "ffprobe"
27
34
  class_attribute :ffprobe_timeout, default: 10
@@ -0,0 +1,106 @@
1
+ require 'tty/command'
2
+
3
+ module Kithe
4
+ # Creates a JPG screen capture using ffmpeg, by default with the `thumbnail`
5
+ # filter to choose a representative frame from the first minute or so.
6
+ #
7
+ # @example tempfile = FfmpegExtractJpg.new.call(shrine_uploaded_file)
8
+ # @example tempfile = FfmpegExtractJpg.new.call(url)
9
+ # @example tempfile = FfmpegExtractJpg.new(start_seconds: 60).call(shrine_uploaded_file)
10
+ # @example tempfile = FfmpegExtractJpg.new(start_seconds: 10, width_pixels: 420).call(shrine_uploaded_file)
11
+ class FfmpegExtractJpg
12
+ class_attribute :ffmpeg_command, default: "ffmpeg"
13
+ attr_reader :start_seconds, :frame_sample_size, :width_pixels
14
+
15
+ # @param start_seconds [Integer] seek to this point to find thumbnail. If it's
16
+ # after the end of the video, you won't get a thumb back though! [Default 0]
17
+ #
18
+ # @param frame_sample_size [Integer,false,nil] argument passed to ffmpeg thumbnail filter,
19
+ # how many frames to sample, starting at start_seconds, to choose representative
20
+ # thumbnail. If set to false, thumbnail filter won't be used. If this one
21
+ # goes past the end of the video, ffmpeg is fine with it. Set to `false` to
22
+ # disable use of ffmpeg sample feature, and just use exact frame at start_seconds.
23
+ #
24
+ # NOTE: This can consume significant RAM depending on value and video resolution.
25
+ #
26
+ # [Default false, not operative]
27
+ #
28
+ # @width_pixels [Integer] output thumb at this width. aspect ratio will be
29
+ # maintained. Warning, if it's larger than video original, ffmpeg will
30
+ # upscale! If set to nil, thumb will be output at original video
31
+ # resolution. [Default nil]
32
+ def initialize(start_seconds: 0, frame_sample_size: false, width_pixels: nil)
33
+ @start_seconds = start_seconds
34
+ @frame_sample_size = frame_sample_size
35
+ @width_pixels = width_pixels
36
+ end
37
+
38
+
39
+
40
+ # @param input_arg [String,File,Shrine::UploadedFile] local File; String that
41
+ # can be URL or local file path; or Shrine::UploadedFile. If Shrine::UploadedFile,
42
+ # we'll try to get a URL from it if we can, otherwise use or make a local tempfile.
43
+ # Most efficient is if we have a remote URL to give ffmpeg, one way or another!
44
+ #
45
+ # @returns [Tempfile] jpg extracted from movie
46
+ def call(input_arg)
47
+ if input_arg.kind_of?(Shrine::UploadedFile)
48
+ if input_arg.respond_to?(:url) && input_arg.url&.start_with?(/https?\:/)
49
+ _call(input_arg.url)
50
+ else
51
+ Shrine.with_file(input_arg) do |local_file|
52
+ _call(local_file.path)
53
+ end
54
+ end
55
+ elsif input_arg.respond_to?(:path)
56
+ _call(input_arg.path)
57
+ else
58
+ _call(input_arg.to_s)
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ # Internal implementation, after input has already been normalized to an
65
+ # string that can be an ffmpeg arg.
66
+ #
67
+ # @param ffmpeg_source_arg [String] filepath or URL. ffmpeg can take urls, which
68
+ # can be very efficient.
69
+ #
70
+ # @returns Tempfile pointing to a thumbnail
71
+ def _call(ffmpeg_source_arg)
72
+ tempfile = Tempfile.new(['temp_deriv', ".jpg"])
73
+
74
+ ffmpeg_args = produce_ffmpeg_args(input_arg: ffmpeg_source_arg, output_path: tempfile.path)
75
+
76
+ TTY::Command.new(printer: :null).run(*ffmpeg_args)
77
+
78
+ return tempfile
79
+ rescue StandardError => e
80
+ tempfile.unlink if tempfile
81
+ raise e
82
+ end
83
+
84
+ def produce_ffmpeg_args(input_arg:, output_path:)
85
+ ffmpeg_args = [ffmpeg_command, "-y"]
86
+
87
+ if start_seconds && start_seconds > 0
88
+ ffmpeg_args.concat ["-ss", start_seconds.to_i]
89
+ end
90
+
91
+ ffmpeg_args.concat ["-i", input_arg]
92
+
93
+ video_filter_parts = []
94
+ video_filter_parts << "thumbnail=#{frame_sample_size}" if (frame_sample_size || 0) > 1
95
+ video_filter_parts << "scale=#{width_pixels}:-1" if width_pixels
96
+
97
+ if video_filter_parts.present?
98
+ ffmpeg_args.concat ["-vf", video_filter_parts.join(',')]
99
+ end
100
+
101
+ ffmpeg_args.concat ["-frames:v", "1"]
102
+
103
+ ffmpeg_args << output_path
104
+ end
105
+ end
106
+ end
@@ -24,6 +24,9 @@ module Kithe
24
24
  # it's nil, meaning we'll find the indexer to use from current thread settings,
25
25
  # or global settings.
26
26
  #
27
+ # (note: this design means we can't currently use traject processing_thread_pool for
28
+ # concurrency, sorry.)
29
+ #
27
30
  # @param writer [Traject::Writer] Can pass i a custom Traject::Writer which the
28
31
  # index representation will be sent to. By default it's nil, meaning we'll find
29
32
  # the writer to use from current thread settings or global settings.
@@ -43,48 +43,24 @@ class Kithe::Asset < Kithe::Model
43
43
  before_promotion :refresh_metadata_before_promotion
44
44
  after_promotion :schedule_derivatives
45
45
 
46
- # A convenience to call file_attacher.create_persisted_derivatives (from :kithe_derivatives)
47
- # to create derivatives with conncurrent access safety, with the :kithe_derivatives
48
- # processor argument, to create derivatives defined using kithe_derivative_definitions.
46
+ # Proxies to file_attacher.kithe_create_derivatives to create kithe managed derivatives.
49
47
  #
50
- # This is designed for use with kithe_derivatives processor, and has options only relevant
51
- # to it, although could be expanded to take a processor argument in the future if needed.
48
+ # This will include any derivatives you created with in the uploader with kithe
49
+ # Attacher.define_derivative, as well as any derivative processors you opted into
50
+ # kithe management with `kithe_include_derivatives_processors` in uploader.
52
51
  #
53
- # Create derivatives for every definition added to uploader/attacher with kithe_derivatives
54
- # `define_derivative`. Ordinarily will create a definition for every definition
55
- # that has not been marked `default_create: false`.
52
+ # This is safe for concurrent updates to the underlying model, the derivatives will
53
+ # be consistent and not left orphaned.
56
54
  #
57
- # But you can also pass `only` and/or `except` to customize the list of definitions to be created,
58
- # possibly including some that are `default_create: false`.
59
- #
60
- # create_derivatives should be idempotent. If it has failed having only created some derivatives,
61
- # you can always just run it again.
62
- #
63
- # Will normally re-create derivatives (per existing definitions) even if they already exist,
64
- # but pass `lazy: false` to skip creating if a derivative with a given key already exists.
65
- # This will use the asset `derivatives` association, so if you are doing this in bulk for several
66
- # assets, you should eager-load the derivatives association for efficiency.
67
- #
68
- # ## Avoiding eager-download
69
- #
70
- # Additionally, this has a feature to 'trick' shrine into not eager downloading
71
- # the original before our kithe_derivatives processor has a chance to decide if it
72
- # needs to create any derivatives (based on `lazy` arg), making no-op
73
- # create_derivatives so much faster and more efficient, which matters when doing
74
- # bulk operations say with our rake task.
75
- #
76
- # kithe_derivatives processor must then do a Shrine.with_file to make sure
77
- # it's a local file, when needed.
78
- #
79
- # See https://github.com/shrinerb/shrine/issues/470
55
+ # By default it will create all derivatives configured for default generation,
56
+ # but you can customize by listing derivative keys with :only and :except options,
57
+ # and with :lazy option which, if true, only executes derivative creation if
58
+ # a given derivative doesn't already exist.
80
59
  #
60
+ # See more in docs for kithe at
61
+ # Shrine::Plugins::KitheDerivativeDefinitions::AttacherMethods#kithe_create_derivatives
81
62
  def create_derivatives(only: nil, except: nil, lazy: false)
82
- source = file
83
- return false unless source
84
-
85
- local_files = file_attacher.process_derivatives(:kithe_derivatives, only: only, except: except, lazy: lazy)
86
-
87
- file_attacher.add_persisted_derivatives(local_files)
63
+ file_attacher.kithe_create_derivatives(only: only, except: except, lazy: lazy)
88
64
  end
89
65
 
90
66
 
@@ -186,7 +162,8 @@ class Kithe::Asset < Kithe::Model
186
162
 
187
163
  # called by after_promotion hook
188
164
  def schedule_derivatives
189
- return unless self.file_attacher.kithe_derivative_definitions.present? # no need to schedule if we don't have any
165
+ # no need to schedule if we don't have any
166
+ return unless self.file_attacher.kithe_derivative_definitions.present? || self.file_attacher.kithe_include_derivatives_processors.present?
190
167
 
191
168
  Kithe::TimingPromotionDirective.new(key: :create_derivatives, directives: file_attacher.promotion_directives) do |directive|
192
169
  if directive.inline?
@@ -33,7 +33,9 @@ class Kithe::Model < ActiveRecord::Base
33
33
  # when fetching all Kithe::Model. And it's to Kithe::Model so it can include
34
34
  # both Works and Assets. We do some app-level validation to try and make it used
35
35
  # as intended.
36
- has_many :members, class_name: "Kithe::Model", foreign_key: :parent_id, inverse_of: :parent, dependent: :destroy
36
+ has_many :members, class_name: "Kithe::Model", foreign_key: :parent_id,
37
+ inverse_of: :parent, dependent: :destroy
38
+
37
39
  belongs_to :parent, class_name: "Kithe::Model", inverse_of: :members, optional: true
38
40
 
39
41
  # a self-referential many-to-many is a bit confusing, but our "contains" relation
@@ -130,11 +132,15 @@ class Kithe::Model < ActiveRecord::Base
130
132
  LIMIT 1;
131
133
  EOS
132
134
 
133
- # trying to use a prepared statement, hoping it means performance advantage
135
+ # trying to use a prepared statement, hoping it means performance advantage,
136
+ # this is super undocumented
137
+
138
+ bind = ActiveRecord::Relation::QueryAttribute.new("m.id", self.representative_id, ActiveRecord::Type::Value.new)
139
+
134
140
  result = self.class.connection.select_all(
135
141
  recursive_cte,
136
142
  "set_leaf_representative",
137
- [[nil, self.representative_id]],
143
+ [bind],
138
144
  preparable: true
139
145
  ).first.try(:dig, "id")
140
146
 
@@ -63,11 +63,24 @@
63
63
  class Kithe::Parameters < ActionController::Parameters
64
64
  attr_reader :auto_allowed_keys
65
65
 
66
- def initialize(hash = {})
67
- if hash.respond_to?(:to_unsafe_h)
68
- hash = hash.to_unsafe_h
66
+
67
+ # Rails 7 adds another initializer method, annoyingly
68
+ if Rails.version.split.first.to_i >= 7
69
+ def initialize(hash = {}, logging_context = {})
70
+ if hash.respond_to?(:to_unsafe_h)
71
+ hash = hash.to_unsafe_h
72
+ end
73
+
74
+ super(hash, logging_context)
75
+ end
76
+ else
77
+ def initialize(hash = {})
78
+ if hash.respond_to?(:to_unsafe_h)
79
+ hash = hash.to_unsafe_h
80
+ end
81
+
82
+ super(hash)
69
83
  end
70
- super(hash)
71
84
  end
72
85
 
73
86
  def permit_attr_json(klass, except:nil)
@@ -6,22 +6,21 @@ module Kithe
6
6
  def initialize(solr_url:, writer_class_name:, writer_settings:,
7
7
  model_name_solr_field:, solr_id_value_attribute:, disable_callbacks: false,
8
8
  batching_mode_batch_size: 100)
9
- @solr_url = solr_url
10
9
  @writer_class_name = writer_class_name
11
10
  @writer_settings = writer_settings
12
11
  @model_name_solr_field = model_name_solr_field
13
12
  @solr_id_value_attribute = solr_id_value_attribute || 'id'
14
13
  @batching_mode_batch_size = batching_mode_batch_size
14
+
15
+ # use our local setter to set solr_url also in writer_settings
16
+ solr_url = solr_url
15
17
  end
16
18
 
17
- # Use configured solr_url, and merge together with configured
18
- # writer_settings
19
- def writer_settings
20
- if solr_url
21
- { "solr.url" => solr_url }.merge(@writer_settings)
22
- else
23
- @writer_settings
24
- end
19
+
20
+ # set solr_url also in writer_settings, cause it's expected there.
21
+ def solr_url=(v)
22
+ @solr_url = v
23
+ writer_settings["solr.url"] = v if writer_settings
25
24
  end
26
25
 
27
26
  # Turn writer_class_name into an actual Class object.
data/lib/kithe/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Kithe
2
- VERSION = '2.4.0'
2
+ VERSION = '2.6.1'
3
3
  end
@@ -5,9 +5,25 @@ class Shrine
5
5
  # use Rails class_attribute to conveniently have a class-level place
6
6
  # to store our derivative definitions that are inheritable and overrideable.
7
7
  # We store it on the Attacher class, because that's where shrine
8
- # puts derivative processor definitions, so seems appropriate.
8
+ # puts derivative processor definitions, so seems appropriate. Normally
9
+ # not touched directly by non-kithe code.
9
10
  uploader::Attacher.class_attribute :kithe_derivative_definitions, instance_writer: false, default: []
10
11
 
12
+ # Kithe exersizes lifecycle control over derivatives, normally just the
13
+ # shrine processor labelled :kithe_derivatives. But you can opt additional shrine
14
+ # derivative processors into kithe control by listing their labels in this attribute.
15
+ #
16
+ # @example
17
+ #
18
+ # class AssetUploader < Kithe::AssetUploader
19
+ # Attacher.kithe_include_derivatives_processors += [:my_processor]
20
+ # Attacher.derivatives(:my_processor) do |original|
21
+ # derivatives
22
+ # end
23
+ # end
24
+ #
25
+ uploader::Attacher.class_attribute :kithe_include_derivatives_processors, instance_writer: false, default: []
26
+
11
27
  # Register our derivative processor, that will create our registered derivatives,
12
28
  # with our custom options.
13
29
  #
@@ -31,11 +47,12 @@ class Shrine
31
47
  #
32
48
  # The most basic definition consists of a derivative key, and a ruby block that
33
49
  # takes the original file, transforms it, and returns a ruby File or other
34
- # (shrine-compatible) IO-like object. It will usually be done inside a custom Asset
35
- # class definition.
50
+ # (shrine-compatible) IO-like object. It will usually be done inside your custom
51
+ # AssetUploader class definition.
36
52
  #
37
- # class Asset < Kithe::Asset
38
- # define_derivative :thumbnail do |original_file|
53
+ # class AssetUploader < Kithe::AssetUploader
54
+ # Attacher.define_derivative :thumbnail do |original_file|
55
+ # someTempFileOrOtherIo
39
56
  # end
40
57
  # end
41
58
  #
@@ -52,7 +69,7 @@ class Shrine
52
69
  # will be passed in. You can then get the model object from `attacher.record`, or the
53
70
  # original file as a `Shrine::UploadedFile` object with `attacher.file`.
54
71
  #
55
- # define_derivative :thumbnail do |original_file, attacher:|
72
+ # Attacher.define_derivative :thumbnail do |original_file, attacher:|
56
73
  # attacher.record.title, attacher.file.width, attacher.file.content_type # etc
57
74
  # end
58
75
  #
@@ -62,7 +79,7 @@ class Shrine
62
79
  # remain, and be accessible, where they were created; there is no built-in solution at present
63
80
  # for moving them).
64
81
  #
65
- # define_derivative :thumbnail, storage_key: :my_thumb_storage do |original| # ...
82
+ # Attacher.define_derivative :thumbnail, storage_key: :my_thumb_storage do |original| # ...
66
83
  #
67
84
  # You can also set `default_create: false` if you want a particular definition not to be
68
85
  # included in a no-arg `asset.create_derivatives` that is normally triggered on asset creation.
@@ -101,6 +118,86 @@ class Shrine
101
118
  end.freeze
102
119
  end
103
120
  end
121
+
122
+ module AttacherMethods
123
+
124
+
125
+ # Similar to shrine create_derivatives, but with kithe standards:
126
+ #
127
+ # * Will call the :kithe_derivatives processor (that handles any define_derivative definitions),
128
+ # plus any processors you've configured with kithe_include_derivatives_processors
129
+ #
130
+ # * Uses the methods added by :kithe_persisted_derivatives to add derivatives completely
131
+ # concurrency-safely, if the model had it's attachment changed concurrently, you
132
+ # won't get derivatives attached that belong to old version of original attachment,
133
+ # and won't get any leftover "orphaned" derivatives either.
134
+ #
135
+ # The :kithe_derivatives processor has additional logic and options for determining
136
+ # *which* derivative definitions -- created with `define_deriative` will be executed:
137
+ #
138
+ # * Ordinarily will create a definition for every definition that has not been marked
139
+ # `default_create: false`.
140
+ #
141
+ # * But you can also pass `only` and/or `except` to customize the list of definitions to be created,
142
+ # possibly including some that are `default_create: false`.
143
+ #
144
+ # * Will normally re-create derivatives (per existing definitions) even if they already exist,
145
+ # but pass `lazy: false` to skip creating if a derivative with a given key already exists.
146
+ # This will use the asset `derivatives` association, so if you are doing this in bulk for several
147
+ # assets, you should eager-load the derivatives association for efficiency.
148
+ #
149
+ # If you've added any custom processors with `kithe_include_derivatives_processors`, it's
150
+ # your responsibility to make them respect those options. See #process_kithe_derivative?
151
+ # helper method.
152
+ #
153
+ # create_derivatives should be idempotent. If it has failed having only created some derivatives,
154
+ # you can always just run it again.
155
+ #
156
+ def kithe_create_derivatives(only: nil, except: nil, lazy: false)
157
+ return false unless file
158
+
159
+ local_files = self.process_derivatives(:kithe_derivatives, only: only, except: except, lazy: lazy)
160
+
161
+ # include any other configured processors
162
+ self.kithe_include_derivatives_processors.each do |processor|
163
+ local_files.merge!(
164
+ self.process_derivatives(processor.to_sym, only: only, except: except, lazy: lazy)
165
+ )
166
+ end
167
+
168
+ self.add_persisted_derivatives(local_files)
169
+ end
170
+
171
+ # a helper method that you can use in your own shrine processors to
172
+ # handle only/except/lazy guarding logic.
173
+ #
174
+ # @return [Boolean] should the `key` be processed based on only/except/lazy conditions?
175
+ #
176
+ # @param key [Symbol] derivative key to check for guarded processing
177
+ # @param only [Array<Symbol>] If present, method will only return true if `key` is included in `only`
178
+ # @param except [Array<Symbol] If present, method will only return true if `key` is NOT included in `except`
179
+ # @param lazy [Boolean] If true, method will only return true if derivative key is not already present
180
+ # in attacher with a value.
181
+ #
182
+ def process_kithe_derivative?(key, **options)
183
+ key = key.to_sym
184
+ only = options[:only] && Array(options[:only]).map(&:to_sym)
185
+ except = options[:except] && Array(options[:except]).map(&:to_sym)
186
+ lazy = !! options[:lazy]
187
+
188
+ (only.nil? ? true : only.include?(key)) &&
189
+ (except.nil? || ! except.include?(key)) &&
190
+ (!lazy || !derivatives.keys.include?(key))
191
+ end
192
+
193
+ # Convenience to check #process_kithe_derivative? for multiple keys at once,
194
+ # @return true if any key returns true
195
+ #
196
+ # @example process_any_kithe_derivative?([:thumb_mini, :thumb_large], only: [:thumb_large, :thumb_mega], lazy: true)
197
+ def process_any_kithe_derivative?(keys, **options)
198
+ keys.any? { |k| process_kithe_derivative?(k, **options) }
199
+ end
200
+ end
104
201
  end
105
202
  register_plugin(:kithe_derivative_definitions, KitheDerivativeDefinitions)
106
203
  end
@@ -134,6 +134,10 @@ class Shrine
134
134
  def remove_persisted_derivatives(*paths, **options)
135
135
  return if paths.empty?
136
136
 
137
+ # Shrine does weird things if we pass in Strings, let's save ourselves
138
+ # the terrible debugging on that mistake, and noramlize to symbols
139
+ paths = paths.collect(&:to_sym)
140
+
137
141
  other_changes_allowed = !!options.delete(:allow_other_changes)
138
142
  if record && !other_changes_allowed && record.changed?
139
143
  raise TypeError.new("Can't safely add_persisted_derivatives on model with unsaved changes. Pass `allow_other_changes: true` to force.")
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kithe
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 2.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Rochkind
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-14 00:00:00.000000000 Z
11
+ date: 2022-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: 5.2.1
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '6.2'
22
+ version: '7.1'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: 5.2.1
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '6.2'
32
+ version: '7.1'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: attr_json
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -346,6 +346,7 @@ files:
346
346
  - Rakefile
347
347
  - app/assets/config/kithe_manifest.js
348
348
  - app/characterization/kithe/ffprobe_characterization.rb
349
+ - app/derivative_transformers/kithe/ffmpeg_extract_jpg.rb
349
350
  - app/derivative_transformers/kithe/ffmpeg_transformer.rb
350
351
  - app/derivative_transformers/kithe/vips_cli_image_to_jpeg.rb
351
352
  - app/helpers/kithe/form_helper.rb
@@ -425,7 +426,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
425
426
  - !ruby/object:Gem::Version
426
427
  version: '0'
427
428
  requirements: []
428
- rubygems_version: 3.2.32
429
+ rubygems_version: 3.3.20
429
430
  signing_key:
430
431
  specification_version: 4
431
432
  summary: Shareable tools/components for building a digital collections app in Rails.