kithe 2.4.0 → 2.5.0

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: 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