class ActiveStorage::Analyzer::VideoAnalyzer

Extracts the following from a video blob:

Example:

ActiveStorage::Analyzer::VideoAnalyzer.new(blob).metadata
# => { width: 640.0, height: 480.0, duration: 5.0, angle: 0, display_aspect_ratio: [4, 3], audio: true, video: true }

When a video’s angle is 90, -90, 270 or -270 degrees, its width and height are automatically swapped for convenience.

This analyzer requires the FFmpeg system library, which is not provided by Rails.

Public Class Methods

accept?(blob) click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 23
def self.accept?(blob)
  blob.video?
end

Public Instance Methods

metadata() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 27
def metadata
  { width: width, height: height, duration: duration, angle: angle, display_aspect_ratio: display_aspect_ratio, audio: audio?, video: video? }.compact
end

Private Instance Methods

angle() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 53
def angle
  if tags["rotate"]
    Integer(tags["rotate"])
  elsif side_data && side_data[0] && side_data[0]["rotation"]
    Integer(side_data[0]["rotation"])
  end
end
audio?() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 76
def audio?
  audio_stream.present?
end
audio_stream() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 114
def audio_stream
  @audio_stream ||= streams.detect { |stream| stream["codec_type"] == "audio" } || {}
end
computed_height() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 84
def computed_height
  if encoded_width && display_height_scale
    encoded_width * display_height_scale
  end
end
container() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 122
def container
  probe["format"] || {}
end
display_aspect_ratio() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 61
def display_aspect_ratio
  if descriptor = video_stream["display_aspect_ratio"]
    if terms = descriptor.split(":", 2)
      numerator   = Integer(terms[0])
      denominator = Integer(terms[1])

      [numerator, denominator] unless numerator == 0
    end
  end
end
display_height_scale() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 98
def display_height_scale
  @display_height_scale ||= Float(display_aspect_ratio.last) / display_aspect_ratio.first if display_aspect_ratio
end
duration() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 48
def duration
  duration = video_stream["duration"] || container["duration"]
  Float(duration) if duration
end
encoded_height() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 94
def encoded_height
  @encoded_height ||= Float(video_stream["height"]) if video_stream["height"]
end
encoded_width() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 90
def encoded_width
  @encoded_width ||= Float(video_stream["width"]) if video_stream["width"]
end
ffprobe_path() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 147
def ffprobe_path
  ActiveStorage.paths[:ffprobe] || "ffprobe"
end
height() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 40
def height
  if rotated?
    encoded_width
  else
    computed_height || encoded_height
  end
end
probe() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 126
def probe
  @probe ||= download_blob_to_tempfile { |file| probe_from(file) }
end
probe_from(file) click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 130
def probe_from(file)
  instrument(File.basename(ffprobe_path)) do
    IO.popen([ ffprobe_path,
      "-print_format", "json",
      "-show_streams",
      "-show_format",
      "-v", "error",
      file.path
    ]) do |output|
      JSON.parse(output.read)
    end
  end
rescue Errno::ENOENT
  logger.info "Skipping video analysis because ffprobe isn't installed"
  {}
end
rotated?() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 72
def rotated?
  angle == 90 || angle == 270 || angle == -90 || angle == -270
end
side_data() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 106
def side_data
  @side_data ||= video_stream["side_data_list"] || {}
end
streams() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 118
def streams
  probe["streams"] || []
end
tags() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 102
def tags
  @tags ||= video_stream["tags"] || {}
end
video?() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 80
def video?
  video_stream.present?
end
video_stream() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 110
def video_stream
  @video_stream ||= streams.detect { |stream| stream["codec_type"] == "video" } || {}
end
width() click to toggle source
# File lib/active_storage/analyzer/video_analyzer.rb, line 32
def width
  if rotated?
    computed_height || encoded_height
  else
    encoded_width
  end
end