Skip to content

Repository files navigation

ruby-ffmpeg

A modern Ruby wrapper for FFmpeg with zero dependencies. Provides clean APIs for metadata extraction, transcoding, scene detection, and keyframe extraction.

Gem VersionRuby

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 videomedia=FFMPEG::Media.new("/path/to/video.mp4")# Get metadatamedia.duration# => 120.5media.resolution# => "1920x1080"media.video_codec# => "h264"media.audio_codec# => "aac"media.frame_rate# => 29.97media.bit_rate# => 5000000# Check propertiesmedia.valid?# => truemedia.video?# => truemedia.audio?# => truemedia.hd?# => truemedia.portrait?# => false

Transcoding

# Basic transcodingmedia.transcode("/output.mp4")# With optionsmedia.transcode("/output.webm",video_codec: "libvpx-vp9",audio_codec: "libopus",resolution: "1280x720",video_bitrate: "2M")# With progress callbackmedia.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 videomedia.transcode("/clip.mp4",seek: 30,# Start at 30 secondsduration: 10# 10 second clip)

Scene Detection

Detect scene changes for video analysis, chapter generation, or highlight extraction:

# Detect scenes with default thresholdscenes=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 optionsdetector=FFMPEG::SceneDetector.new(media)scenes=detector.detect(threshold: 0.3,min_scene_length: 2.0,# Minimum 2 seconds between scenesmax_scenes: 20# Maximum 20 scenes)# Detect scenes and extract keyframesscenes=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 intervalsframes=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 framesframes=media.extract_keyframes(output_dir: "/tmp/frames",count: 10# 10 evenly-distributed frames)# Extract at specific timestampsframes=media.extract_keyframes(output_dir: "/tmp/frames",timestamps: [0,30,60,90,120])# Using KeyframeExtractor directly for more optionsextractor=FFMPEG::KeyframeExtractor.new(media)# Extract actual I-frames (keyframes) from videoiframes=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 streamvideo=media.videovideo.codec# => "h264"video.width# => 1920video.height# => 1080video.frame_rate# => 29.97video.bit_rate# => 4500000video.pixel_format# => "yuv420p"video.rotation# => 0# Audio streamaudio=media.audioaudio.codec# => "aac"audio.sample_rate# => 48000audio.channels# => 2audio.channel_layout# => "stereo"# All streamsmedia.streams.eachdo |stream|
putsstream.to_send

Configuration

FFMPEG.configuredo |config|
# Custom binary pathsconfig.ffmpeg_binary="/usr/local/bin/ffmpeg"config.ffprobe_binary="/usr/local/bin/ffprobe"# Default timeout (seconds)config.timeout=600# Enable loggingconfig.logger=Logger.new(STDOUT)# Default codecsconfig.default_video_codec="libx264"config.default_audio_codec="aac"# Number of threads (0 = auto)config.threads=0# Temporary directoryconfig.temp_dir="/tmp/ffmpeg"# Overwrite existing filesconfig.overwrite_output=trueend

Rails Integration

Active Storage Analyzer

Automatically extract video metadata for Active Storage:

# config/initializers/active_storage.rbRails.application.config.active_storage.analyzers.prepend(FFMPEG::ActiveStorage::Analyzer)

Now video blobs will have metadata like:

video.metadata# => { width: 1920, height: 1080, duration: 120.5, video_codec: "h264", ... }

Active Storage Previewer

Generate video previews:

# config/initializers/active_storage.rbRails.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

beginmedia=FFMPEG::Media.new("/path/to/video.mp4")media.transcode("/output.mp4")rescueFFMPEG::MediaNotFound=>eputs"File not found: #{e.message}"rescueFFMPEG::InvalidMedia=>eputs"Invalid media: #{e.message}"rescueFFMPEG::TranscodingError=>eputs"Transcoding failed: #{e.message}"puts"Command: #{e.command}"puts"Output: #{e.output}"rescueFFMPEG::CommandTimeout=>eputs"Command timed out: #{e.message}"end

Comparison with streamio-ffmpeg

Featureruby-ffmpegstreamio-ffmpeg
Ruby Version3.1+2.0+
FFmpeg Version4-72.8.4
DependenciesNoneNone
Scene DetectionBuilt-inNo
Keyframe ExtractionBuilt-inNo
Active StorageBuilt-inNo
Progress CallbacksYesYes
Last Updated20262016

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

  1. Fork it
  2. Create your feature branch (git checkout -b feature/my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin feature/my-new-feature)
  5. 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.

About

Modern Ruby wrapper for FFmpeg with scene detection and keyframe extraction

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages