aboutsummaryrefslogtreecommitdiffstats
path: root/activestorage/lib/active_storage/analyzer
diff options
context:
space:
mode:
authorGeorge Claghorn <george.claghorn@gmail.com>2017-10-22 13:16:59 -0400
committerGitHub <noreply@github.com>2017-10-22 13:16:59 -0400
commit605484079d297d1ba6835628465be81f03c052ee (patch)
treee4e32f5e47f4bf48cb9eb9fc8d80c7da268ae262 /activestorage/lib/active_storage/analyzer
parent6525e7fb2e534a6564438008f9c02cf29eb37483 (diff)
downloadrails-605484079d297d1ba6835628465be81f03c052ee.tar.gz
rails-605484079d297d1ba6835628465be81f03c052ee.tar.bz2
rails-605484079d297d1ba6835628465be81f03c052ee.zip
Extract metadata from images and videos
Diffstat (limited to 'activestorage/lib/active_storage/analyzer')
-rw-r--r--activestorage/lib/active_storage/analyzer/image_analyzer.rb36
-rw-r--r--activestorage/lib/active_storage/analyzer/null_analyzer.rb13
-rw-r--r--activestorage/lib/active_storage/analyzer/video_analyzer.rb79
3 files changed, 128 insertions, 0 deletions
diff --git a/activestorage/lib/active_storage/analyzer/image_analyzer.rb b/activestorage/lib/active_storage/analyzer/image_analyzer.rb
new file mode 100644
index 0000000000..b25d2092b9
--- /dev/null
+++ b/activestorage/lib/active_storage/analyzer/image_analyzer.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ # Extracts width and height in pixels from an image blob.
+ #
+ # Example:
+ #
+ # ActiveStorage::Analyzer::ImageAnalyzer.new(blob).metadata
+ # # => { width: 4104, height: 2736 }
+ #
+ # This analyzer relies on the third-party [MiniMagick]{https://github.com/minimagick/minimagick} gem. MiniMagick requires
+ # the [ImageMagick]{http://www.imagemagick.org} system library. These libraries are not provided by Rails; you must
+ # install them yourself to use this analyzer.
+ class Analyzer::ImageAnalyzer < Analyzer
+ def self.accept?(blob)
+ blob.image?
+ end
+
+ def metadata
+ read_image do |image|
+ { width: image.width, height: image.height }
+ end
+ rescue LoadError
+ logger.info "Skipping image analysis because the mini_magick gem isn't installed"
+ {}
+ end
+
+ private
+ def read_image
+ download_blob_to_tempfile do |file|
+ require "mini_magick"
+ yield MiniMagick::Image.new(file.path)
+ end
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/analyzer/null_analyzer.rb b/activestorage/lib/active_storage/analyzer/null_analyzer.rb
new file mode 100644
index 0000000000..8ff7ce48e5
--- /dev/null
+++ b/activestorage/lib/active_storage/analyzer/null_analyzer.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ class Analyzer::NullAnalyzer < Analyzer # :nodoc:
+ def self.accept?(blob)
+ true
+ end
+
+ def metadata
+ {}
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/analyzer/video_analyzer.rb b/activestorage/lib/active_storage/analyzer/video_analyzer.rb
new file mode 100644
index 0000000000..408b5e58e9
--- /dev/null
+++ b/activestorage/lib/active_storage/analyzer/video_analyzer.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require "active_support/core_ext/hash/compact"
+
+module ActiveStorage
+ # Extracts the following from a video blob:
+ #
+ # * Width (pixels)
+ # * Height (pixels)
+ # * Duration (seconds)
+ # * Angle (degrees)
+ # * Aspect ratio
+ #
+ # Example:
+ #
+ # ActiveStorage::VideoAnalyzer.new(blob).metadata
+ # # => { width: 640, height: 480, duration: 5.0, angle: 0, aspect_ratio: [4, 3] }
+ #
+ # This analyzer requires the {ffmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails. You must
+ # install ffmpeg yourself to use this analyzer.
+ class Analyzer::VideoAnalyzer < Analyzer
+ def self.accept?(blob)
+ blob.video?
+ end
+
+ def metadata
+ { width: width, height: height, duration: duration, angle: angle, aspect_ratio: aspect_ratio }.compact
+ end
+
+ private
+ def width
+ Integer(video_stream["width"]) if video_stream["width"]
+ end
+
+ def height
+ Integer(video_stream["height"]) if video_stream["height"]
+ end
+
+ def duration
+ Float(video_stream["duration"]) if video_stream["duration"]
+ end
+
+ def angle
+ Integer(tags["rotate"]) if tags["rotate"]
+ end
+
+ def aspect_ratio
+ if descriptor = video_stream["display_aspect_ratio"]
+ descriptor.split(":", 2).collect(&:to_i)
+ end
+ end
+
+
+ def tags
+ @tags ||= video_stream["tags"] || {}
+ end
+
+ def video_stream
+ @video_stream ||= streams.detect { |stream| stream["codec_type"] == "video" } || {}
+ end
+
+ def streams
+ probe["streams"] || []
+ end
+
+ def probe
+ download_blob_to_tempfile { |file| probe_from(file) }
+ end
+
+ def probe_from(file)
+ IO.popen([ "ffprobe", "-print_format", "json", "-show_streams", "-v", "error", file.path ]) do |output|
+ JSON.parse(output.read)
+ end
+ rescue Errno::ENOENT
+ logger.info "Skipping video analysis because ffmpeg isn't installed"
+ {}
+ end
+ end
+end