Add subtitles to videos in Ruby with open-source tools

Adding subtitles to videos significantly enhances accessibility, viewer engagement, and SEO. Ruby developers have access to several powerful open-source tools that simplify subtitle integration and editing. Let's explore how you can leverage these tools effectively.
Why subtitles matter
Subtitles aren't just beneficial for viewers with hearing impairments—they also improve comprehension for non-native speakers, enhance SEO by making content searchable, and can increase viewer retention. Providing subtitles is becoming a standard practice for inclusive and accessible content.
Open-source Ruby tools for subtitles
Ruby offers several robust libraries for subtitle management, primarily by interfacing with the powerful FFmpeg multimedia framework or by directly manipulating subtitle files:
- streamio-ffmpeg: A comprehensive Ruby wrapper for FFmpeg that allows various video processing tasks, including burning subtitles into videos or converting subtitle formats.
- srt: A dedicated gem for parsing, manipulating, and generating SRT (SubRip Text) subtitle files, the most common subtitle format.
Let's dive into practical examples using these tools.
Adding subtitles with streamio-ffmpeg
The streamio-ffmpeg
gem provides a convenient Ruby interface to FFmpeg, making it easy to embed
subtitles into videos. This process is often called "burning" subtitles, meaning they become part of
the video frames.
First, ensure you have FFmpeg installed on your system. Then, install the required gems:
gem install streamio-ffmpeg srt
Next, use the following Ruby script to add subtitles:
require 'streamio-ffmpeg'
require 'shellwords'
video_path = 'input.mp4'
subtitle_path = 'subtitles.srt'
output_path = 'output_with_subs.mp4'
begin
movie = FFMPEG::Movie.new(video_path)
# Basic validation
raise "Input video file not found or invalid." unless movie.valid?
raise "Subtitle file not found." unless File.exist?(subtitle_path)
# Escape the subtitle path for use in the FFmpeg filter string
# This handles spaces, special characters, and quotes safely.
escaped_subtitle_path = subtitle_path.gsub(/\ /, '\\\ ').gsub("'", "'\\\''")
# Use FFmpeg's 'subtitles' video filter
# Customize styling options as needed (FontName, FontSize, PrimaryColour, etc.)
# PrimaryColour uses BGR format with Alpha (AABBGGRR) in hexadecimal
options = {
custom: [
'-vf', "subtitles=#{escaped_subtitle_path}:force_style='FontName=Arial,FontSize=24,PrimaryColour=&H00FFFFFF&'"
# &H00FFFFFF& represents White with 00 Alpha (fully opaque)
]
}
puts "Starting transcoding to burn subtitles..."
movie.transcode(output_path, options)
puts "Successfully added subtitles to #{output_path}"
rescue FFMPEG::Error => e
puts "FFmpeg error during transcoding: #{e.message}"
rescue => e
puts "An error occurred: #{e.message}"
end
This script uses FFmpeg's subtitles
filter via streamio-ffmpeg
to burn the subtitles directly
into the video frames. Note the careful escaping of the subtitle path.
Manipulating srt files with the srt gem
The srt
gem allows you to programmatically parse, modify, and create SRT subtitle files. This is
useful for tasks like adjusting timing, correcting text, or generating subtitles from scratch.
require 'srt'
subtitle_path = 'subtitles.srt'
modified_subtitle_path = 'modified.srt'
begin
# Parse an existing SRT file
srt_file = SRT::File.parse(File.new(subtitle_path))
puts "Parsed #{srt_file.lines.count} subtitle lines."
# Example: Adjust timing (shift all subtitles forward by 2.5 seconds)
srt_file.timeshift(2.5)
puts "Shifted all subtitles forward by 2.5 seconds."
# Example: Add a new subtitle entry
new_line = SRT::Line.new(
sequence: srt_file.lines.size + 1, # Sequence number usually starts from 1
start_time: 120.0, # Start at 2 minutes (120 seconds)
end_time: 125.5, # End at 2 minutes 5.5 seconds
text: "This is a newly added subtitle line."
)
srt_file.lines << new_line
puts "Added a new subtitle line."
# Save the modified SRT file
File.open(modified_subtitle_path, 'w') do |f|
f.write(srt_file.to_s)
end
puts "Saved modified subtitles to #{modified_subtitle_path}"
rescue Errno::ENOENT
puts "Error: Subtitle file not found at #{subtitle_path}"
rescue SRT::Error => e
puts "SRT parsing error: #{e.message}"
rescue => e
puts "An error occurred: #{e.message}"
end
This example demonstrates parsing an SRT file, adjusting timing globally, adding a new subtitle line, and saving the changes.
Converting subtitle formats
While the srt
gem focuses specifically on the SRT format, you can leverage FFmpeg through the
streamio-ffmpeg
gem to convert between various subtitle formats. This is particularly useful for
web-based video players that often prefer the WebVTT format.
require 'shellwords'
input_subtitle = 'subtitles.srt'
output_subtitle = 'subtitles.vtt'
begin
# Use FFmpeg directly for subtitle conversion (streamio-ffmpeg doesn't have a dedicated method)
# Ensure the input file exists
raise "Input subtitle file not found: #{input_subtitle}" unless File.exist?(input_subtitle)
# Construct the FFmpeg command using Shellwords.escape for safety
# -i: input file
# -c:s webvtt: codec for subtitles set to webvtt
# output_subtitle: output file path
command = "ffmpeg -i #{Shellwords.escape(input_subtitle)} -c:s webvtt #{Shellwords.escape(output_subtitle)}"
puts "Executing FFmpeg command: #{command}"
system(command)
if $?.success?
puts "Successfully converted #{input_subtitle} to #{output_subtitle}"
else
puts "FFmpeg command failed."
end
rescue => e
puts "An error occurred during conversion: #{e.message}"
end
This snippet executes an FFmpeg command directly to convert an SRT file to WebVTT. You can adapt the
-c:s
codec and output filename extension for other formats supported by FFmpeg.
Error handling for subtitle integration
When working with external processes like FFmpeg and file I/O, robust error handling is crucial. Catching specific exceptions allows for more informative feedback.
require 'streamio-ffmpeg'
require 'srt'
require 'shellwords'
def add_subtitles_safely(video_path, subtitle_path, output_path)
begin
# Verify video file using streamio-ffmpeg
movie = FFMPEG::Movie.new(video_path)
raise "Invalid or non-existent video file: #{video_path}" unless movie.valid?
# Verify subtitle file exists and is parsable by the srt gem
begin
SRT::File.parse(File.new(subtitle_path))
rescue Errno::ENOENT
raise "Subtitle file not found: #{subtitle_path}"
rescue SRT::Error => e
raise "Invalid SRT file format: #{e.message}"
end
# Escape the subtitle path for the FFmpeg filter
escaped_subtitle_path = subtitle_path.gsub(/\ /, '\\\ ').gsub("'", "'\\\''")
# Define FFmpeg options for burning subtitles
options = {
custom: [
'-vf', "subtitles=#{escaped_subtitle_path}:force_style='FontName=Arial,FontSize=24,PrimaryColour=&H00FFFFFF&'"
]
}
puts "Starting transcoding for #{File.basename(video_path)}..."
movie.transcode(output_path, options)
puts "Successfully added subtitles to #{output_path}"
rescue FFMPEG::Error => e
# Specific error from streamio-ffmpeg/FFmpeg
puts "FFmpeg error processing '#{File.basename(video_path)}': #{e.message}"
rescue => e
# Catch other potential errors (file access, validation errors, etc.)
puts "Error processing '#{File.basename(video_path)}': #{e.message}"
end
end
# Example usage:
# Add_subtitles_safely('input.mp4', 'subtitles.srt', 'output_with_subs.mp4')
# Add_subtitles_safely('non_existent.mp4', 'subtitles.srt', 'output.mp4')
# Add_subtitles_safely('input.mp4', 'invalid_format.srt', 'output.mp4')
This function includes checks for file existence and validity before attempting the transcoding
process, catching specific errors from streamio-ffmpeg
and the srt
gem.
Supported subtitle formats
When working with subtitles, especially with FFmpeg, you might encounter various formats. Key formats include:
- SRT (SubRip Text):
.srt
- The most widely supported plain-text format. Handled directly by thesrt
gem and FFmpeg. - WebVTT (Web Video Text Tracks):
.vtt
- The standard for HTML5 video subtitles, supporting styling and positioning. FFmpeg can convert to/from this format. - ASS/SSA (Advanced SubStation Alpha / SubStation Alpha):
.ass
/.ssa
- More advanced formats supporting complex styling, positioning, and effects, often used in anime fansubs. FFmpeg has good support. - SBV (SubViewer):
.sbv
- A simple format used by YouTube. FFmpeg can handle conversion.
FFmpeg provides broad support for converting between these and other formats. The srt
gem
specifically focuses on parsing and manipulating the SRT format.
Practical example: batch processing videos with subtitles
Here's a script demonstrating how to process multiple videos in a directory, adding corresponding SRT subtitles if found.
require 'streamio-ffmpeg'
require 'srt'
require 'fileutils'
require 'shellwords' # For safe filename handling
def batch_process_videos(video_dir, subtitle_dir, output_dir)
# Create output directory if it doesn't exist
FileUtils.mkdir_p(output_dir)
# Find all video files (e.g., MP4)
video_files = Dir.glob(File.join(video_dir, '*.mp4'))
if video_files.empty?
puts "No MP4 video files found in #{video_dir}"
return
end
video_files.each do |video_path|
basename = File.basename(video_path, '.*')
subtitle_path = File.join(subtitle_dir, "#{basename}.srt")
output_path = File.join(output_dir, "#{basename}_subtitled.mp4")
if File.exist?(subtitle_path)
puts "Processing '#{basename}'..."
add_subtitles_safely(video_path, subtitle_path, output_path) # Use the helper function
else
puts "Skipping '#{basename}': No corresponding subtitle file found at #{subtitle_path}"
end
end
puts "\nBatch processing complete."
end
# --- helper function (add_subtitles_safely defined above) ---
def add_subtitles_safely(video_path, subtitle_path, output_path)
begin
movie = FFMPEG::Movie.new(video_path)
raise "Invalid or non-existent video file: #{video_path}" unless movie.valid?
begin
SRT::File.parse(File.new(subtitle_path))
rescue Errno::ENOENT
raise "Subtitle file not found: #{subtitle_path}"
rescue SRT::Error => e
raise "Invalid SRT file format: #{e.message}"
end
# Escape the subtitle path for the FFmpeg filter
escaped_subtitle_path = subtitle_path.gsub(/\ /, '\\\ ').gsub("'", "'\\\''")
options = {
custom: [
'-vf', "subtitles=#{escaped_subtitle_path}:force_style='FontName=Arial,FontSize=24,PrimaryColour=&H00FFFFFF&'"
]
}
movie.transcode(output_path, options)
puts " -> Successfully created #{File.basename(output_path)}"
rescue FFMPEG::Error => e
puts " -> FFmpeg error processing '#{File.basename(video_path)}': #{e.message}"
rescue => e
puts " -> Error processing '#{File.basename(video_path)}': #{e.message}"
end
end
# --- end helper function ---
# Example usage: process videos in 'input_videos/' with subtitles from 'input_subtitles/'
# And save results to 'output_videos/'
# Batch_process_videos('input_videos', 'input_subtitles', 'output_videos')
This script iterates through MP4 files in a specified directory, looks for a matching SRT file in
another directory, and uses the add_subtitles_safely
function to process each pair, placing the
output in a designated folder.
Conclusion
Adding subtitles to videos using Ruby is straightforward with open-source tools like
streamio-ffmpeg
(interfacing with FFmpeg) and the srt
gem. These libraries simplify burning
subtitles into videos, manipulating SRT files, and converting between formats, significantly
enhancing your video's accessibility and viewer engagement. Remember to handle potential errors
gracefully for robust applications.
For more complex subtitle workflows, large-scale video processing, or cloud-based solutions, consider a dedicated service. Transloadit's 🤖 /video/subtitle Robot offers advanced subtitle integration capabilities as part of its Video Encoding service, handling various formats and styles efficiently.