123456789_123456789_123456789_123456789_123456789_

Class: ActiveStorage::Analyzer::VideoAnalyzer

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, Analyzer
Instance Chain:
self, Analyzer
Inherits: Analyzer
  • ::Object
Defined in: activestorage/lib/active_storage/analyzer/video_analyzer.rb

Overview

Active Storage Video Analyzer

Extracts the following from a video blob:

  • Width (pixels)
  • Height (pixels)
  • Duration (seconds)
  • Angle (degrees)
  • Display aspect ratio
  • Audio (true if file has an audio channel, false if not)
  • Video (true if file has an video channel, false if not)

Example:

ActiveStorage::Analyzer::VideoAnalyzer.new(blob).
# => { 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.

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Class Method Details

.accept?(blob) ⇒ Boolean

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 25

def self.accept?(blob)
  blob.video?
end

Instance Attribute Details

#audio?Boolean (readonly, private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 82

def audio?
  audio_stream.present?
end

#rotated?Boolean (readonly, private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 78

def rotated?
  angle == 90 || angle == 270 || angle == -90 || angle == -270
end

#video?Boolean (readonly, private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 86

def video?
  video_stream.present?
end

Instance Method Details

#angle (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 55

def angle
  if tags["rotate"]
    Integer(tags["rotate"])
  elsif display_matrix && display_matrix["rotation"]
    Integer(display_matrix["rotation"])
  end
end

#audio_stream (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 120

def audio_stream
  @audio_stream ||= streams.detect { |stream| stream["codec_type"] == "audio" } || {}
end

#computed_height (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 90

def computed_height
  if encoded_width && display_height_scale
    encoded_width * display_height_scale
  end
end

#container (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 128

def container
  probe["format"] || {}
end

#display_aspect_ratio (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 67

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 (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 104

def display_height_scale
  @display_height_scale ||= Float(display_aspect_ratio.last) / display_aspect_ratio.first if display_aspect_ratio
end

#display_matrix (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 63

def display_matrix
  side_data.detect { |data| data["side_data_type"] == "Display Matrix" }
end

#duration (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 50

def duration
  duration = video_stream["duration"] || container["duration"]
  Float(duration) if duration
end

#encoded_height (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 100

def encoded_height
  @encoded_height ||= Float(video_stream["height"]) if video_stream["height"]
end

#encoded_width (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 96

def encoded_width
  @encoded_width ||= Float(video_stream["width"]) if video_stream["width"]
end

#ffprobe_path (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 153

def ffprobe_path
  ActiveStorage.paths[:ffprobe] || "ffprobe"
end

#height (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 42

def height
  if rotated?
    encoded_width
  else
    computed_height || encoded_height
  end
end

#metadata

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 29

def 
  { width: width, height: height, duration: duration, angle: angle, display_aspect_ratio: display_aspect_ratio, audio: audio?, video: video? }.compact
end

#probe (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 132

def probe
  @probe ||= download_blob_to_tempfile { |file| probe_from(file) }
end

#probe_from(file) (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 136

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

#side_data (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 112

def side_data
  @side_data ||= video_stream["side_data_list"] || {}
end

#streams (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 124

def streams
  probe["streams"] || []
end

#tags (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 108

def tags
  @tags ||= video_stream["tags"] || {}
end

#video_stream (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 116

def video_stream
  @video_stream ||= streams.detect { |stream| stream["codec_type"] == "video" } || {}
end

#width (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/analyzer/video_analyzer.rb', line 34

def width
  if rotated?
    computed_height || encoded_height
  else
    encoded_width
  end
end