activestorage 5.2.4.4 → 6.1.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activestorage might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +180 -69
- data/MIT-LICENSE +1 -1
- data/README.md +43 -8
- data/app/assets/javascripts/activestorage.js +5 -2
- data/app/controllers/active_storage/base_controller.rb +13 -4
- data/app/controllers/active_storage/blobs/proxy_controller.rb +14 -0
- data/app/controllers/active_storage/{blobs_controller.rb → blobs/redirect_controller.rb} +3 -3
- data/app/controllers/active_storage/direct_uploads_controller.rb +2 -2
- data/app/controllers/active_storage/disk_controller.rb +13 -22
- data/app/controllers/active_storage/representations/proxy_controller.rb +19 -0
- data/app/controllers/active_storage/{representations_controller.rb → representations/redirect_controller.rb} +3 -3
- data/app/controllers/concerns/active_storage/file_server.rb +18 -0
- data/app/controllers/concerns/active_storage/set_blob.rb +1 -1
- data/app/controllers/concerns/active_storage/set_current.rb +15 -0
- data/app/controllers/concerns/active_storage/set_headers.rb +12 -0
- data/app/javascript/activestorage/blob_record.js +7 -2
- data/app/jobs/active_storage/analyze_job.rb +5 -0
- data/app/jobs/active_storage/base_job.rb +0 -1
- data/app/jobs/active_storage/mirror_job.rb +15 -0
- data/app/jobs/active_storage/purge_job.rb +3 -0
- data/app/models/active_storage/attachment.rb +35 -16
- data/app/models/active_storage/blob.rb +178 -68
- data/app/models/active_storage/blob/analyzable.rb +6 -2
- data/app/models/active_storage/blob/identifiable.rb +7 -6
- data/app/models/active_storage/blob/representable.rb +36 -6
- data/app/models/active_storage/filename.rb +0 -6
- data/app/models/active_storage/preview.rb +37 -12
- data/app/models/active_storage/record.rb +7 -0
- data/app/models/active_storage/variant.rb +53 -67
- data/app/models/active_storage/variant_record.rb +8 -0
- data/app/models/active_storage/variant_with_record.rb +54 -0
- data/app/models/active_storage/variation.rb +30 -34
- data/config/routes.rb +66 -15
- data/db/migrate/20170806125915_create_active_storage_tables.rb +14 -5
- data/db/update_migrate/20190112182829_add_service_name_to_active_storage_blobs.rb +17 -0
- data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +11 -0
- data/lib/active_storage.rb +29 -6
- data/lib/active_storage/analyzer.rb +15 -4
- data/lib/active_storage/analyzer/image_analyzer.rb +14 -4
- data/lib/active_storage/analyzer/null_analyzer.rb +4 -0
- data/lib/active_storage/analyzer/video_analyzer.rb +17 -8
- data/lib/active_storage/attached.rb +7 -22
- data/lib/active_storage/attached/changes.rb +16 -0
- data/lib/active_storage/attached/changes/create_many.rb +47 -0
- data/lib/active_storage/attached/changes/create_one.rb +82 -0
- data/lib/active_storage/attached/changes/create_one_of_many.rb +10 -0
- data/lib/active_storage/attached/changes/delete_many.rb +27 -0
- data/lib/active_storage/attached/changes/delete_one.rb +19 -0
- data/lib/active_storage/attached/many.rb +19 -12
- data/lib/active_storage/attached/model.rb +212 -0
- data/lib/active_storage/attached/one.rb +19 -21
- data/lib/active_storage/downloader.rb +43 -0
- data/lib/active_storage/engine.rb +58 -23
- data/lib/active_storage/errors.rb +22 -3
- data/lib/active_storage/gem_version.rb +4 -4
- data/lib/active_storage/log_subscriber.rb +6 -0
- data/lib/active_storage/previewer.rb +24 -13
- data/lib/active_storage/previewer/mupdf_previewer.rb +3 -3
- data/lib/active_storage/previewer/poppler_pdf_previewer.rb +5 -5
- data/lib/active_storage/previewer/video_previewer.rb +17 -10
- data/lib/active_storage/reflection.rb +64 -0
- data/lib/active_storage/service.rb +44 -12
- data/lib/active_storage/service/azure_storage_service.rb +65 -44
- data/lib/active_storage/service/configurator.rb +6 -2
- data/lib/active_storage/service/disk_service.rb +57 -44
- data/lib/active_storage/service/gcs_service.rb +68 -64
- data/lib/active_storage/service/mirror_service.rb +31 -7
- data/lib/active_storage/service/registry.rb +32 -0
- data/lib/active_storage/service/s3_service.rb +58 -24
- data/lib/active_storage/transformers/image_processing_transformer.rb +45 -0
- data/lib/active_storage/transformers/transformer.rb +39 -0
- data/lib/tasks/activestorage.rake +7 -0
- metadata +84 -19
- data/app/models/active_storage/filename/parameters.rb +0 -36
- data/lib/active_storage/attached/macros.rb +0 -110
- data/lib/active_storage/downloading.rb +0 -39
@@ -1,22 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "mimemagic"
|
4
|
+
|
3
5
|
# A set of transformations that can be applied to a blob to create a variant. This class is exposed via
|
4
6
|
# the ActiveStorage::Blob#variant method and should rarely be used directly.
|
5
7
|
#
|
6
8
|
# In case you do need to use this directly, it's instantiated using a hash of transformations where
|
7
9
|
# the key is the command and the value is the arguments. Example:
|
8
10
|
#
|
9
|
-
# ActiveStorage::Variation.new(
|
10
|
-
#
|
11
|
-
# You can also combine multiple transformations in one step, e.g. for center-weighted cropping:
|
11
|
+
# ActiveStorage::Variation.new(resize_to_limit: [100, 100], monochrome: true, trim: true, rotate: "-90")
|
12
12
|
#
|
13
|
-
#
|
14
|
-
# resize: "100x100^",
|
15
|
-
# gravity: "center",
|
16
|
-
# crop: "100x100+0+0",
|
17
|
-
# })
|
18
|
-
#
|
19
|
-
# A list of all possible transformations is available at https://www.imagemagick.org/script/mogrify.php.
|
13
|
+
# The options map directly to {ImageProcessing}[https://github.com/janko-m/image_processing] commands.
|
20
14
|
class ActiveStorage::Variation
|
21
15
|
attr_reader :transformations
|
22
16
|
|
@@ -48,42 +42,44 @@ class ActiveStorage::Variation
|
|
48
42
|
end
|
49
43
|
|
50
44
|
def initialize(transformations)
|
51
|
-
@transformations = transformations
|
45
|
+
@transformations = transformations.deep_symbolize_keys
|
46
|
+
end
|
47
|
+
|
48
|
+
def default_to(defaults)
|
49
|
+
self.class.new transformations.reverse_merge(defaults)
|
52
50
|
end
|
53
51
|
|
54
|
-
# Accepts
|
55
|
-
#
|
56
|
-
def transform(
|
52
|
+
# Accepts a File object, performs the +transformations+ against it, and
|
53
|
+
# saves the transformed image into a temporary file.
|
54
|
+
def transform(file, &block)
|
57
55
|
ActiveSupport::Notifications.instrument("transform.active_storage") do
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
67
|
-
end
|
56
|
+
transformer.transform(file, format: format, &block)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def format
|
61
|
+
transformations.fetch(:format, :png).tap do |format|
|
62
|
+
if MimeMagic.by_extension(format).nil?
|
63
|
+
raise ArgumentError, "Invalid variant format (#{format.inspect})"
|
68
64
|
end
|
69
65
|
end
|
70
66
|
end
|
71
67
|
|
68
|
+
def content_type
|
69
|
+
MimeMagic.by_extension(format).to_s
|
70
|
+
end
|
71
|
+
|
72
72
|
# Returns a signed key for all the +transformations+ that this variation was instantiated with.
|
73
73
|
def key
|
74
74
|
self.class.encode(transformations)
|
75
75
|
end
|
76
76
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
command.public_send(method, argument)
|
81
|
-
else
|
82
|
-
command.public_send(method)
|
83
|
-
end
|
84
|
-
end
|
77
|
+
def digest
|
78
|
+
Digest::SHA1.base64digest Marshal.dump(transformations)
|
79
|
+
end
|
85
80
|
|
86
|
-
|
87
|
-
|
81
|
+
private
|
82
|
+
def transformer
|
83
|
+
ActiveStorage::Transformers::ImageProcessingTransformer.new(transformations.except(:format))
|
88
84
|
end
|
89
85
|
end
|
data/config/routes.rb
CHANGED
@@ -1,17 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
Rails.application.routes.draw do
|
4
|
-
|
4
|
+
scope ActiveStorage.routes_prefix do
|
5
|
+
get "/blobs/redirect/:signed_id/*filename" => "active_storage/blobs/redirect#show", as: :rails_service_blob
|
6
|
+
get "/blobs/proxy/:signed_id/*filename" => "active_storage/blobs/proxy#show", as: :rails_service_blob_proxy
|
7
|
+
get "/blobs/:signed_id/*filename" => "active_storage/blobs/redirect#show"
|
5
8
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
resolve("ActiveStorage::Blob") { |blob, options| route_for(:rails_blob, blob, options) }
|
11
|
-
resolve("ActiveStorage::Attachment") { |attachment, options| route_for(:rails_blob, attachment.blob, options) }
|
9
|
+
get "/representations/redirect/:signed_blob_id/:variation_key/*filename" => "active_storage/representations/redirect#show", as: :rails_blob_representation
|
10
|
+
get "/representations/proxy/:signed_blob_id/:variation_key/*filename" => "active_storage/representations/proxy#show", as: :rails_blob_representation_proxy
|
11
|
+
get "/representations/:signed_blob_id/:variation_key/*filename" => "active_storage/representations/redirect#show"
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
get "/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service
|
14
|
+
put "/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service
|
15
|
+
post "/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads
|
16
|
+
end
|
15
17
|
|
16
18
|
direct :rails_representation do |representation, options|
|
17
19
|
signed_blob_id = representation.blob.signed_id
|
@@ -21,11 +23,60 @@ Rails.application.routes.draw do
|
|
21
23
|
route_for(:rails_blob_representation, signed_blob_id, variation_key, filename, options)
|
22
24
|
end
|
23
25
|
|
24
|
-
resolve("ActiveStorage::Variant") { |variant, options| route_for(
|
25
|
-
resolve("ActiveStorage::
|
26
|
+
resolve("ActiveStorage::Variant") { |variant, options| route_for(ActiveStorage.resolve_model_to_route, variant, options) }
|
27
|
+
resolve("ActiveStorage::VariantWithRecord") { |variant, options| route_for(ActiveStorage.resolve_model_to_route, variant, options) }
|
28
|
+
resolve("ActiveStorage::Preview") { |preview, options| route_for(ActiveStorage.resolve_model_to_route, preview, options) }
|
26
29
|
|
30
|
+
direct :rails_blob do |blob, options|
|
31
|
+
route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
|
32
|
+
end
|
33
|
+
|
34
|
+
resolve("ActiveStorage::Blob") { |blob, options| route_for(ActiveStorage.resolve_model_to_route, blob, options) }
|
35
|
+
resolve("ActiveStorage::Attachment") { |attachment, options| route_for(ActiveStorage.resolve_model_to_route, attachment.blob, options) }
|
27
36
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
37
|
+
direct :rails_storage_proxy do |model, options|
|
38
|
+
if model.respond_to?(:signed_id)
|
39
|
+
route_for(
|
40
|
+
:rails_service_blob_proxy,
|
41
|
+
model.signed_id,
|
42
|
+
model.filename,
|
43
|
+
options
|
44
|
+
)
|
45
|
+
else
|
46
|
+
signed_blob_id = model.blob.signed_id
|
47
|
+
variation_key = model.variation.key
|
48
|
+
filename = model.blob.filename
|
49
|
+
|
50
|
+
route_for(
|
51
|
+
:rails_blob_representation_proxy,
|
52
|
+
signed_blob_id,
|
53
|
+
variation_key,
|
54
|
+
filename,
|
55
|
+
options
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
direct :rails_storage_redirect do |model, options|
|
61
|
+
if model.respond_to?(:signed_id)
|
62
|
+
route_for(
|
63
|
+
:rails_service_blob,
|
64
|
+
model.signed_id,
|
65
|
+
model.filename,
|
66
|
+
options
|
67
|
+
)
|
68
|
+
else
|
69
|
+
signed_blob_id = model.blob.signed_id
|
70
|
+
variation_key = model.variation.key
|
71
|
+
filename = model.blob.filename
|
72
|
+
|
73
|
+
route_for(
|
74
|
+
:rails_blob_representation,
|
75
|
+
signed_blob_id,
|
76
|
+
variation_key,
|
77
|
+
filename,
|
78
|
+
options
|
79
|
+
)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end if ActiveStorage.draw_routes
|
@@ -1,13 +1,14 @@
|
|
1
1
|
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
|
2
2
|
def change
|
3
3
|
create_table :active_storage_blobs do |t|
|
4
|
-
t.string :key,
|
5
|
-
t.string :filename,
|
4
|
+
t.string :key, null: false
|
5
|
+
t.string :filename, null: false
|
6
6
|
t.string :content_type
|
7
7
|
t.text :metadata
|
8
|
-
t.
|
9
|
-
t.
|
10
|
-
t.
|
8
|
+
t.string :service_name, null: false
|
9
|
+
t.bigint :byte_size, null: false
|
10
|
+
t.string :checksum, null: false
|
11
|
+
t.datetime :created_at, null: false
|
11
12
|
|
12
13
|
t.index [ :key ], unique: true
|
13
14
|
end
|
@@ -22,5 +23,13 @@ class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
|
|
22
23
|
t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
|
23
24
|
t.foreign_key :active_storage_blobs, column: :blob_id
|
24
25
|
end
|
26
|
+
|
27
|
+
create_table :active_storage_variant_records do |t|
|
28
|
+
t.belongs_to :blob, null: false, index: false
|
29
|
+
t.string :variation_digest, null: false
|
30
|
+
|
31
|
+
t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
|
32
|
+
t.foreign_key :active_storage_blobs, column: :blob_id
|
33
|
+
end
|
25
34
|
end
|
26
35
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0]
|
2
|
+
def up
|
3
|
+
unless column_exists?(:active_storage_blobs, :service_name)
|
4
|
+
add_column :active_storage_blobs, :service_name, :string
|
5
|
+
|
6
|
+
if configured_service = ActiveStorage::Blob.service.name
|
7
|
+
ActiveStorage::Blob.unscoped.update_all(service_name: configured_service)
|
8
|
+
end
|
9
|
+
|
10
|
+
change_column :active_storage_blobs, :service_name, :string, null: false
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def down
|
15
|
+
remove_column :active_storage_blobs, :service_name
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0]
|
2
|
+
def change
|
3
|
+
create_table :active_storage_variant_records do |t|
|
4
|
+
t.belongs_to :blob, null: false, index: false
|
5
|
+
t.string :variation_digest, null: false
|
6
|
+
|
7
|
+
t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
|
8
|
+
t.foreign_key :active_storage_blobs, column: :blob_id
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/active_storage.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#--
|
4
|
-
# Copyright (c) 2017-
|
4
|
+
# Copyright (c) 2017-2020 David Heinemeier Hansson, Basecamp
|
5
5
|
#
|
6
6
|
# Permission is hereby granted, free of charge, to any person obtaining
|
7
7
|
# a copy of this software and associated documentation files (the
|
@@ -26,6 +26,7 @@
|
|
26
26
|
require "active_record"
|
27
27
|
require "active_support"
|
28
28
|
require "active_support/rails"
|
29
|
+
require "active_support/core_ext/numeric/time"
|
29
30
|
|
30
31
|
require "active_storage/version"
|
31
32
|
require "active_storage/errors"
|
@@ -42,12 +43,34 @@ module ActiveStorage
|
|
42
43
|
|
43
44
|
mattr_accessor :logger
|
44
45
|
mattr_accessor :verifier
|
45
|
-
mattr_accessor :
|
46
|
+
mattr_accessor :variant_processor, default: :mini_magick
|
47
|
+
|
48
|
+
mattr_accessor :queues, default: {}
|
49
|
+
|
46
50
|
mattr_accessor :previewers, default: []
|
47
|
-
mattr_accessor :analyzers,
|
51
|
+
mattr_accessor :analyzers, default: []
|
52
|
+
|
48
53
|
mattr_accessor :paths, default: {}
|
49
|
-
|
54
|
+
|
55
|
+
mattr_accessor :variable_content_types, default: []
|
56
|
+
mattr_accessor :web_image_content_types, default: []
|
57
|
+
mattr_accessor :binary_content_type, default: "application/octet-stream"
|
50
58
|
mattr_accessor :content_types_to_serve_as_binary, default: []
|
51
|
-
mattr_accessor :content_types_allowed_inline,
|
52
|
-
|
59
|
+
mattr_accessor :content_types_allowed_inline, default: []
|
60
|
+
|
61
|
+
mattr_accessor :service_urls_expire_in, default: 5.minutes
|
62
|
+
|
63
|
+
mattr_accessor :routes_prefix, default: "/rails/active_storage"
|
64
|
+
mattr_accessor :draw_routes, default: true
|
65
|
+
mattr_accessor :resolve_model_to_route, default: :rails_storage_redirect
|
66
|
+
|
67
|
+
mattr_accessor :replace_on_assign_to_many, default: false
|
68
|
+
mattr_accessor :track_variants, default: false
|
69
|
+
|
70
|
+
module Transformers
|
71
|
+
extend ActiveSupport::Autoload
|
72
|
+
|
73
|
+
autoload :Transformer
|
74
|
+
autoload :ImageProcessingTransformer
|
75
|
+
end
|
53
76
|
end
|
@@ -1,13 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_storage/downloading"
|
4
|
-
|
5
3
|
module ActiveStorage
|
6
4
|
# This is an abstract base class for analyzers, which extract metadata from blobs. See
|
7
5
|
# ActiveStorage::Analyzer::ImageAnalyzer for an example of a concrete subclass.
|
8
6
|
class Analyzer
|
9
|
-
include Downloading
|
10
|
-
|
11
7
|
attr_reader :blob
|
12
8
|
|
13
9
|
# Implement this method in a concrete subclass. Have it return true when given a blob from which
|
@@ -16,6 +12,12 @@ module ActiveStorage
|
|
16
12
|
false
|
17
13
|
end
|
18
14
|
|
15
|
+
# Implement this method in concrete subclasses. It will determine if blob analysis
|
16
|
+
# should be done in a job or performed inline. By default, analysis is enqueued in a job.
|
17
|
+
def self.analyze_later?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
19
21
|
def initialize(blob)
|
20
22
|
@blob = blob
|
21
23
|
end
|
@@ -26,8 +28,17 @@ module ActiveStorage
|
|
26
28
|
end
|
27
29
|
|
28
30
|
private
|
31
|
+
# Downloads the blob to a tempfile on disk. Yields the tempfile.
|
32
|
+
def download_blob_to_tempfile(&block) #:doc:
|
33
|
+
blob.open tmpdir: tmpdir, &block
|
34
|
+
end
|
35
|
+
|
29
36
|
def logger #:doc:
|
30
37
|
ActiveStorage.logger
|
31
38
|
end
|
39
|
+
|
40
|
+
def tmpdir #:doc:
|
41
|
+
Dir.tmpdir
|
42
|
+
end
|
32
43
|
end
|
33
44
|
end
|
@@ -25,17 +25,27 @@ module ActiveStorage
|
|
25
25
|
{ width: image.width, height: image.height }
|
26
26
|
end
|
27
27
|
end
|
28
|
-
rescue LoadError
|
29
|
-
logger.info "Skipping image analysis because the mini_magick gem isn't installed"
|
30
|
-
{}
|
31
28
|
end
|
32
29
|
|
33
30
|
private
|
34
31
|
def read_image
|
35
32
|
download_blob_to_tempfile do |file|
|
36
33
|
require "mini_magick"
|
37
|
-
|
34
|
+
image = MiniMagick::Image.new(file.path)
|
35
|
+
|
36
|
+
if image.valid?
|
37
|
+
yield image
|
38
|
+
else
|
39
|
+
logger.info "Skipping image analysis because ImageMagick doesn't support the file"
|
40
|
+
{}
|
41
|
+
end
|
38
42
|
end
|
43
|
+
rescue LoadError
|
44
|
+
logger.info "Skipping image analysis because the mini_magick gem isn't installed"
|
45
|
+
{}
|
46
|
+
rescue MiniMagick::Error => error
|
47
|
+
logger.error "Skipping image analysis due to an ImageMagick error: #{error.message}"
|
48
|
+
{}
|
39
49
|
end
|
40
50
|
|
41
51
|
def rotated_image?(image)
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/core_ext/hash/compact"
|
4
|
-
|
5
3
|
module ActiveStorage
|
6
4
|
# Extracts the following from a video blob:
|
7
5
|
#
|
@@ -13,12 +11,12 @@ module ActiveStorage
|
|
13
11
|
#
|
14
12
|
# Example:
|
15
13
|
#
|
16
|
-
# ActiveStorage::VideoAnalyzer.new(blob).metadata
|
14
|
+
# ActiveStorage::Analyzer::VideoAnalyzer.new(blob).metadata
|
17
15
|
# # => { width: 640.0, height: 480.0, duration: 5.0, angle: 0, display_aspect_ratio: [4, 3] }
|
18
16
|
#
|
19
17
|
# When a video's angle is 90 or 270 degrees, its width and height are automatically swapped for convenience.
|
20
18
|
#
|
21
|
-
# This analyzer requires the {
|
19
|
+
# This analyzer requires the {FFmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails.
|
22
20
|
class Analyzer::VideoAnalyzer < Analyzer
|
23
21
|
def self.accept?(blob)
|
24
22
|
blob.video?
|
@@ -46,7 +44,8 @@ module ActiveStorage
|
|
46
44
|
end
|
47
45
|
|
48
46
|
def duration
|
49
|
-
|
47
|
+
duration = video_stream["duration"] || container["duration"]
|
48
|
+
Float(duration) if duration
|
50
49
|
end
|
51
50
|
|
52
51
|
def angle
|
@@ -100,16 +99,26 @@ module ActiveStorage
|
|
100
99
|
probe["streams"] || []
|
101
100
|
end
|
102
101
|
|
102
|
+
def container
|
103
|
+
probe["format"] || {}
|
104
|
+
end
|
105
|
+
|
103
106
|
def probe
|
104
|
-
download_blob_to_tempfile { |file| probe_from(file) }
|
107
|
+
@probe ||= download_blob_to_tempfile { |file| probe_from(file) }
|
105
108
|
end
|
106
109
|
|
107
110
|
def probe_from(file)
|
108
|
-
IO.popen([ ffprobe_path,
|
111
|
+
IO.popen([ ffprobe_path,
|
112
|
+
"-print_format", "json",
|
113
|
+
"-show_streams",
|
114
|
+
"-show_format",
|
115
|
+
"-v", "error",
|
116
|
+
file.path
|
117
|
+
]) do |output|
|
109
118
|
JSON.parse(output.read)
|
110
119
|
end
|
111
120
|
rescue Errno::ENOENT
|
112
|
-
logger.info "Skipping video analysis because
|
121
|
+
logger.info "Skipping video analysis because FFmpeg isn't installed"
|
113
122
|
{}
|
114
123
|
end
|
115
124
|
|