kithe 2.4.0 → 2.5.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: 6f3d3c39a3bdccd9fc97f8f5e16c94cf12d4926e7ca50cc5e3343406ad3044d3
4
- data.tar.gz: 2bd6fc9c175d4ec213e58460fbb95b5c40b49ca1ae5808e77df26932cf60474a
3
+ metadata.gz: 4a51da44ba82df89dd84f14a9559de8032d2e026ee666d9dba4e9fb41d05b1b0
4
+ data.tar.gz: feb995557174e9ef7c2fb6689bb352ec2dd227dc4e98a282d1b639e1d8d11393
5
5
  SHA512:
6
- metadata.gz: 55c9e6eb14e975a46927fa01b4c7a8a67ca1b804ae7662f2e34e7878ee0a3247b944395edf93e311f7c39b640392ddd5b74a3df77381ba5f761e78f883e48dfa
7
- data.tar.gz: 730f6f1d20d45d7973d2c340e15889d6f2b56295f70eec8703a1e1b666ba078357b1d1ec2e34fa14ca5575ff17032f52340ffba3070e2b16e3da35012c5e8d9e
6
+ metadata.gz: 6de9ece27273199672bc007b229b63707fb4470456091a5f166b1913d6affad22d16ecb2c1a32e5a053459b408a227149ff3b2947ed06e00e2e1408ca6cd9cce
7
+ data.tar.gz: 1004d59f35ce5a4abdd958fa87891b3e51c6da3e8e65f078a16e74cb99b601b471aaacf895fb5f4f2249cb8a728d78542b66fe656865ae235c8fbd7a8aa2ce27
@@ -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,97 @@
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] 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
+ # [Default 900, around 30 seconds at 30 fps]
24
+ #
25
+ # @width_pixels [Integer] output thumb at this width. aspect ratio will be
26
+ # maintained. Warning, if it's larger than video original, ffmpeg will
27
+ # upscale! If set to nil, thumb will be output at original video
28
+ # resolution. [Default nil]
29
+ def initialize(start_seconds: 0, frame_sample_size: 900, width_pixels: nil)
30
+ @start_seconds = start_seconds
31
+ @frame_sample_size = frame_sample_size
32
+ @width_pixels = width_pixels
33
+ end
34
+
35
+
36
+
37
+ # @param input_arg [String,File,Shrine::UploadedFile] local File; String that
38
+ # can be URL or local file path; or Shrine::UploadedFile. If Shrine::UploadedFile,
39
+ # we'll try to get a URL from it if we can, otherwise use or make a local tempfile.
40
+ # Most efficient is if we have a remote URL to give ffmpeg, one way or another!
41
+ #
42
+ # @returns [Tempfile] jpg extracted from movie
43
+ def call(input_arg)
44
+ if input_arg.kind_of?(Shrine::UploadedFile)
45
+ if input_arg.respond_to?(:url) && input_arg.url&.start_with?(/https?\:/)
46
+ _call(input_arg.url)
47
+ else
48
+ Shrine.with_file(input_arg) do |local_file|
49
+ _call(local_file.path)
50
+ end
51
+ end
52
+ elsif input_arg.respond_to?(:path)
53
+ _call(input_arg.path)
54
+ else
55
+ _call(input_arg.to_s)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ # Internal implementation, after input has already been normalized to an
62
+ # string that can be an ffmpeg arg.
63
+ #
64
+ # @param ffmpeg_source_arg [String] filepath or URL. ffmpeg can take urls, which
65
+ # can be very efficient.
66
+ #
67
+ # @returns Tempfile pointing to a thumbnail
68
+ def _call(ffmpeg_source_arg)
69
+ tempfile = Tempfile.new(['temp_deriv', ".jpg"])
70
+
71
+ ffmpeg_args = [ffmpeg_command, "-y"]
72
+ if start_seconds && start_seconds > 0
73
+ ffmpeg_args.concat ["-ss", start_seconds.to_i]
74
+ end
75
+
76
+ ffmpeg_args.concat ["-i", ffmpeg_source_arg]
77
+
78
+ video_filter_parts = []
79
+ video_filter_parts << "thumbnail=#{frame_sample_size}" if frame_sample_size
80
+ video_filter_parts << "scale=#{width_pixels}:-1" if width_pixels
81
+ if video_filter_parts
82
+ ffmpeg_args.concat ["-vf", video_filter_parts.join(',')]
83
+ end
84
+
85
+ ffmpeg_args.concat ["-frames:v", "1"]
86
+
87
+ ffmpeg_args << tempfile.path
88
+
89
+ TTY::Command.new(printer: :null).run(*ffmpeg_args)
90
+
91
+ return tempfile
92
+ rescue StandardError => e
93
+ tempfile.unlink if tempfile
94
+ raise e
95
+ end
96
+ end
97
+ end
@@ -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?
data/lib/kithe/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Kithe
2
- VERSION = '2.4.0'
2
+ VERSION = '2.5.0'
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
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.5.0
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-03-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -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