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