ruby-ffmpeg
A modern Ruby wrapper for FFmpeg with zero dependencies. Provides clean APIs for metadata extraction, transcoding, scene detection, and keyframe extraction.
Features
- Zero Dependencies - Pure Ruby, no gem dependencies
- Modern Ruby - Requires Ruby 3.1+, uses Data classes and modern patterns
- FFmpeg 4-7 Support - Tested against FFmpeg versions 4, 5, 6, and 7
- Scene Detection - Detect scene changes for video analysis
- Keyframe Extraction - Extract frames at intervals, timestamps, or I-frames
- Progress Reporting - Real-time transcoding progress callbacks
- Rails Integration - Active Storage analyzer and previewer included
Installation
Add to your Gemfile:
gem 'ruby-ffmpeg'
Or install directly:
gem install ruby-ffmpeg
Note: FFmpeg must be installed on your system. Install with:
- macOS:
brew install ffmpeg - Ubuntu:
sudo apt install ffmpeg - Windows:
choco install ffmpeg
Quick Start
require 'ffmpeg'
# Load a video
media = FFMPEG::Media.new("/path/to/video.mp4")
# Get metadata
media.duration # => 120.5
media.resolution # => "1920x1080"
media.video_codec # => "h264"
media.audio_codec # => "aac"
media.frame_rate # => 29.97
media.bit_rate # => 5000000
# Check properties
media.valid? # => true
media.video? # => true
media.audio? # => true
media.hd? # => true
media.portrait? # => false
Transcoding
# Basic transcoding
media.transcode("/output.mp4")
# With options
media.transcode("/output.webm",
video_codec: "libvpx-vp9",
audio_codec: "libopus",
resolution: "1280x720",
video_bitrate: "2M"
)
# With progress callback
media.transcode("/output.mp4") do |progress|
puts "Progress: #{progress.round(1)}%"
end
# Copy streams (no re-encoding)
media.transcode("/output.mp4",
copy_video: true,
copy_audio: true
)
# Trim video
media.transcode("/clip.mp4",
seek: 30, # Start at 30 seconds
duration: 10 # 10 second clip
)
Scene Detection
Detect scene changes for video analysis, chapter generation, or highlight extraction:
# Detect scenes with default threshold
scenes = media.detect_scenes
# => [
# { timestamp: 0.0, score: 1.0 },
# { timestamp: 5.23, score: 0.45 },
# { timestamp: 12.8, score: 0.38 }
# ]
# Custom threshold (lower = more sensitive)
scenes = media.detect_scenes(threshold: 0.2)
# Using SceneDetector directly for more options
detector = FFMPEG::SceneDetector.new(media)
scenes = detector.detect(
threshold: 0.3,
min_scene_length: 2.0, # Minimum 2 seconds between scenes
max_scenes: 20 # Maximum 20 scenes
)
# Detect scenes and extract keyframes
scenes = detector.detect_with_keyframes(
threshold: 0.3,
output_dir: "/tmp/scenes"
)
# Each scene now has :keyframe_path
Keyframe Extraction
Extract frames for thumbnails, video analysis, or sprite sheets:
# Extract at regular intervals
frames = media.extract_keyframes(
output_dir: "/tmp/frames",
interval: 5.0 # Every 5 seconds
)
# => ["/tmp/frames/frame_0000.jpg", "/tmp/frames/frame_0005.jpg", ...]
# Extract specific number of frames
frames = media.extract_keyframes(
output_dir: "/tmp/frames",
count: 10 # 10 evenly-distributed frames
)
# Extract at specific timestamps
frames = media.extract_keyframes(
output_dir: "/tmp/frames",
timestamps: [0, 30, 60, 90, 120]
)
# Using KeyframeExtractor directly for more options
extractor = FFMPEG::KeyframeExtractor.new(media)
# Extract actual I-frames (keyframes) from video
iframes = extractor.extract_iframes(
output_dir: "/tmp/iframes",
max_frames: 50
)
# Create a thumbnail sprite sheet (for video scrubbing)
sprite = extractor.create_sprite(
columns: 10,
rows: 10,
width: 160,
output_path: "/tmp/sprite.jpg"
)
# => { path: "/tmp/sprite.jpg", columns: 10, rows: 10, ... }
Stream Information
Access detailed stream information:
# Video stream
video = media.video
video.codec # => "h264"
video.width # => 1920
video.height # => 1080
video.frame_rate # => 29.97
video.bit_rate # => 4500000
video.pixel_format # => "yuv420p"
video.rotation # => 0
# Audio stream
audio = media.audio
audio.codec # => "aac"
audio.sample_rate # => 48000
audio.channels # => 2
audio.channel_layout # => "stereo"
# All streams
media.streams.each do |stream|
puts stream.to_s
end
Configuration
FFMPEG.configure do |config|
# Custom binary paths
config.ffmpeg_binary = "/usr/local/bin/ffmpeg"
config.ffprobe_binary = "/usr/local/bin/ffprobe"
# Default timeout (seconds)
config.timeout = 600
# Enable logging
config.logger = Logger.new(STDOUT)
# Default codecs
config.default_video_codec = "libx264"
config.default_audio_codec = "aac"
# Number of threads (0 = auto)
config.threads = 0
# Temporary directory
config.temp_dir = "/tmp/ffmpeg"
# Overwrite existing files
config.overwrite_output = true
end
Rails Integration
Active Storage Analyzer
Automatically extract video metadata for Active Storage:
# config/initializers/active_storage.rb
Rails.application.config.active_storage.analyzers.prepend(
FFMPEG::ActiveStorage::Analyzer
)
Now video blobs will have metadata like:
video.
# => { width: 1920, height: 1080, duration: 120.5, video_codec: "h264", ... }
Active Storage Previewer
Generate video previews:
# config/initializers/active_storage.rb
Rails.application.config.active_storage.previewers.prepend(
FFMPEG::ActiveStorage::Previewer
)
Rake Tasks
# Check FFmpeg installation
rake ffmpeg:check
# Analyze a video
rake ffmpeg:analyze[/path/to/video.mp4]
# Extract keyframes
rake ffmpeg:keyframes[/path/to/video.mp4,output_dir,5]
# Detect scenes
rake ffmpeg:scenes[/path/to/video.mp4,0.3]
Error Handling
begin
media = FFMPEG::Media.new("/path/to/video.mp4")
media.transcode("/output.mp4")
rescue FFMPEG::MediaNotFound => e
puts "File not found: #{e.message}"
rescue FFMPEG::InvalidMedia => e
puts "Invalid media: #{e.message}"
rescue FFMPEG::TranscodingError => e
puts "Transcoding failed: #{e.message}"
puts "Command: #{e.command}"
puts "Output: #{e.output}"
rescue FFMPEG::CommandTimeout => e
puts "Command timed out: #{e.message}"
end
Comparison with streamio-ffmpeg
| Feature | ruby-ffmpeg | streamio-ffmpeg |
|---|---|---|
| Ruby Version | 3.1+ | 2.0+ |
| FFmpeg Version | 4-7 | 2.8.4 |
| Dependencies | None | None |
| Scene Detection | Built-in | No |
| Keyframe Extraction | Built-in | No |
| Active Storage | Built-in | No |
| Progress Callbacks | Yes | Yes |
| Last Updated | 2026 | 2016 |
Development
# Clone the repo
git clone https://github.com/activeagents/ruby-ffmpeg.git
cd ruby-ffmpeg
# Install dependencies
bin/setup
# Run tests
rake test
# Run linter
rake rubocop
Contributing
- Fork it
- Create your feature branch (
git checkout -b feature/my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin feature/my-new-feature) - Create a new Pull Request
License
The gem is available as open source under the terms of the MIT License.
Credits
Developed and maintained by Active Agents.
Inspired by streamio-ffmpeg and instructure/ruby-ffmpeg.