123456789_123456789_123456789_123456789_123456789_

Class: ActiveStorage::Analyzer::VideoAnalyzer

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
Instance Chain:
Inherits: ActiveStorage::Analyzer
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 Attribute Summary

::ActiveStorage::Analyzer - Inherited

.analyze_later?

Implement this method in concrete subclasses.

Class Method Summary

::ActiveStorage::Analyzer - Inherited

.accept?

Implement this method in a concrete subclass.

.new

Instance Attribute Summary

::ActiveStorage::Analyzer - Inherited

Instance Method Summary

::ActiveStorage::Analyzer - Inherited

#metadata

Override this method in a concrete subclass.

#download_blob_to_tempfile

Downloads the blob to a tempfile on disk.

#instrument, #logger, #tmpdir

Constructor Details

This class inherits a constructor from ActiveStorage::Analyzer

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 78

def audio?
  audio_stream.present?
end

#rotated?Boolean (readonly, private)

[ GitHub ]

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

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 82

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 side_data && side_data[0] && side_data[0]["rotation"]
    Integer(side_data[0]["rotation"])
  end
end

#audio_stream (private)

[ GitHub ]

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

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 86

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 124

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

#display_aspect_ratio (private)

[ GitHub ]

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

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 100

def display_height_scale
  @display_height_scale ||= Float(display_aspect_ratio.last) / display_aspect_ratio.first if display_aspect_ratio
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 96

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 92

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 149

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 128

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 132

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 108

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

#streams (private)

[ GitHub ]

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

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

#tags (private)

[ GitHub ]

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

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

#video_stream (private)

[ GitHub ]

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

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