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 +4 -4
- data/app/characterization/kithe/ffprobe_characterization.rb +7 -0
- data/app/derivative_transformers/kithe/ffmpeg_extract_jpg.rb +97 -0
- data/app/models/kithe/asset.rb +15 -38
- data/lib/kithe/version.rb +1 -1
- data/lib/shrine/plugins/kithe_derivative_definitions.rb +104 -7
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a51da44ba82df89dd84f14a9559de8032d2e026ee666d9dba4e9fb41d05b1b0
|
4
|
+
data.tar.gz: feb995557174e9ef7c2fb6689bb352ec2dd227dc4e98a282d1b639e1d8d11393
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/app/models/kithe/asset.rb
CHANGED
@@ -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
|
-
#
|
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
|
51
|
-
#
|
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
|
-
#
|
54
|
-
#
|
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
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
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
|
-
|
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
|
-
|
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
@@ -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
|
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
|
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
|
+
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
|
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
|