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:

  1. SRT (SubRip Text): .srt - The most widely supported plain-text format. Handled directly by the srt gem and FFmpeg.
  2. WebVTT (Web Video Text Tracks): .vtt - The standard for HTML5 video subtitles, supporting styling and positioning. FFmpeg can convert to/from this format.
  3. 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.
  4. 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.