Simple yet powerful wrapper around the ffmpeg command for reading metadata and transcoding media.
Only guaranteed to work with Ruby 3.1 or later.
The current gem is tested against ffmpeg 4, 5, 6 and 7. So no guarantees with earlier (or much later) versions. Output and input standards have inconveniently changed rather a lot between versions of ffmpeg. Our goal is to keep this library in sync with new versions of ffmpeg as they come along.
On macOS: brew install ffmpeg
.
require 'ffmpeg', git: 'https://github.com/instructure/ruby-ffmpeg'
NOTE: For advanced usage, don't be afraid to dive into the source code. The gem is designed with care and documented all over the place, so dig in.
media = FFMPEG::Media.new('path/to/media.mp4')
media.valid? # true (would be false if ffmpeg fails to read the media metadata)
media.local? # true (would be false if the file is remote)
media.remote? # false
media.streams # [FFMPEG::Stream, FFMPEG::Stream]
media.video_streams # [FFMPEG::Stream]
media.video_streams? # true (tells you if the media has video streams or not)
media.default_video_stream # FFMPEG::Stream
media.audio_streams # [FFMPEG::Stream]
media.audio_streams? # true (tells you if the media has audio streams or not)
media.default_audio_stream # FFMPEG::Stream
media.video? # true (tells you if the media has a movie stream – non-attached-picture)
media.audio? # false (tells you if the media is an audio file – based on the streams)
media.rotation # 90 (rotation of the default video stream in degrees)
media.raw_width # 480 (the reported width of the default video stream in pixels)
media.raw_height # 640 (the reported height of the default video stream in pixels)
media.width # 640 (width of the default video stream in pixels)
media.height # 480 (height of the default video stream in pixels)
For the full list of attributes available on the FFMPEG::Media
object,
see the source code.
The two core components of the transcoding process are the FFMPEG::Preset
and FFMPEG::Transcoder
classes.
preset = FFMPEG::Preset.new(
name: 'My Preset',
filename: '%<basename>s.mp4',
metadata: {
some_important_metadata_for_you: 'foo'
}
) do
# This block sets up the output arguments of the ffmpeg command.
# It uses a DSL to define the arguments in a more human-readable way.
# The methods for the DSL are defined in the FFMPEG::RawCommandArgs and
# FFMPEG::CommandArgs classes.
video_codec_name 'libx264' # -c:v libx264
audio_codec_name 'aac' # -c:a aac
# media is the FFMPEG::Media object used as input for the transcoding process
map media.video_mapping_id do # -map v:0
filter FFMPEG::Filters.scale(width: -2, height: 360) # -vf scale=w=-2:h=360
constant_rate_factor 22 # -crf 22
frame_rate 30 # -r 30
end
# media is the FFMPEG::Media object used as input for the transcoding process
map media.audio_mapping_id do # -map a:0
audio_bit_rate 128000 # -b:a 128k
end
end
transcoder = FFMPEG::Transcoder.new(
name: 'My Transcoder',
metadata: {
some_important_metadata_for_you: 'bar'
},
# You can pass multiple presets to the transcoder
# and they will be processed in a single ffmpeg command
# to optimize the transcoding process.
presets: [preset],
# The reporters are used to generate reports during the transcoding process.
reporters: [FFMPEG::Reporters::Progress, FFMPEG::Reporters::Silence]
) do
# This block sets up the input arguments of the ffmpeg command.
# It uses the same DSL to define the arguments as the preset does for the output arguments.
# The methods for the DSL are defined in the FFMPEG::RawCommandArgs and
# FFMPEG::CommandArgs classes.
# media is the FFMPEG::Media object used as input for the transcoding process
if media.rotated?
raw_arg '-noautorotate' # -noautorotate
end
end
status = transcoder.process(media, '/path/to/output') do |report|
# This block is called for each report generated by the reporters
case report.class
when FFMPEG::Reporters::Progress
puts "Progress: #{report.time}"
when FFMPEG::Reporters::Silence
puts "Silence: #{report.duration}"
else
# FFMPEG::Reporters::Output
end
end
status.success? # true (would be false if ffmpeg fails to transcode the media)
status.exitstatus # 0 (the exit status of the ffmpeg command)
status.paths # ['/path/to/output.mp4'] (the paths of the output files)
status.media # [FFMPEG::Media] (the media objects of the output files)
The gem comes with a few presets out of the box.
You can find them in the lib/ffmpeg/presets
directory.
By default, the gem assumes that the ffmpeg and ffprobe binaries are available in the execution path (named ffmpeg and ffprobe)
and so will run commands that look something like ffmpeg -i /path/to/input.file ...
.
Use the below setters to specify the full path to the binaries if necessary:
FFMPEG.ffmpeg_binary = '/usr/local/bin/ffmpeg'
FFMPEG.ffprobe_binary = '/usr/local/bin/ffprobe'
By default, the gem will wait for 30 seconds between IO feedback from the FFMPEG process. After which an error is raised and the process killed. It is possible to modify this behaviour by setting a new default:
# Change the IO timeout
FFMPEG.io_timeout = 10
# Disable the IO timeout altogether
FFMPEG.io_timeout = nil
The gem uses a logger to output debug information. By default, it will log to STDOUT, but you can change this behaviour by setting a new logger:
FFMPEG.logger = Logger.new('path/to/logfile.log')
Copyright (c) Instructure, Inc. See LICENSE for details.