Last updated: April 29, 2025

<span aria-hidden="true" id="add-subtitles-to-videos-in-ruby-with-open-source-tools"></span>

# Add subtitles to videos in Ruby with open-source tools

![Kevin van Zonneveld](/assets/images/teammates/avatar-kvz-4.jpg?dpl=dpl_C6YH6XrtnwbHLJcm1LDKywQn4CB3)

#### Kevin van Zonneveld

Co-founder · Amsterdam, The Netherlands · Show bio

[](https://x.com/kvz)[](https://github.com/kvz)

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.

<span aria-hidden="true" id="why-subtitles-matter"></span>

## 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.

<span aria-hidden="true" id="open-source-ruby-tools-for-subtitles"></span>

## 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.

<span aria-hidden="true" id="adding-subtitles-with-streamio-ffmpeg"></span>

## 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:

```bash
gem install streamio-ffmpeg srt

```

Next, use the following Ruby script to add subtitles:

```ruby
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.

<span aria-hidden="true" id="manipulating-srt-files-with-the-srt-gem"></span>

## 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.

```ruby
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.

<span aria-hidden="true" id="converting-subtitle-formats"></span>

## 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.

```ruby
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.

<span aria-hidden="true" id="error-handling-for-subtitle-integration"></span>

## 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.

```ruby
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.

<span aria-hidden="true" id="supported-subtitle-formats"></span>

## 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.

<span aria-hidden="true" id="practical-example-batch-processing-videos-with-subtitles"></span>

## 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.

```ruby
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.

<span aria-hidden="true" id="conclusion"></span>

## 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](/docs/robots/video-subtitle.md) Robot offers advanced subtitle integration capabilities as part of its[Video Encoding service](/services/video-encoding.md), handling various formats and styles efficiently.

\#ruby#subtitles#video-accessibility#open-source#video-encoding-service

### 👩‍💻 Join 20k+ developers

Sign up for our [monthly newsletter](/newsletters.md) to receive direct links to 3 exclusive tech — and 2 product updates. No less, no more.

Your email:

Get access

## File uploading and encoding. Made simple.

Transloadit streamlines file handling for developers, trusted by brands like Coursera and The New York Times. We’re known for a reliable API, top-notch support, and a strong commitment to open source, with projects like [Uppy⁠](https://uppy.io) and [Tus⁠](https://tus.io) setting standards in file processing.

[Sign up](/c/)[Book a Demo](https://survey.typeform.com/to/kRg47Xi5)

No credit card needed · 5 GB included in the free plan

Cancel anytime
