Delivering video content efficiently requires adaptive streaming formats like HLS (HTTP Live Streaming) and MPEG-DASH. In this guide, we'll explore how to convert videos to these formats using Ruby, FFmpeg, and the open-source Streamio FFMPEG gem. By automating video conversion tasks, you can enhance video delivery across various devices and network conditions.

Introduction to HLS and MPEG-DASH

Before diving into the code, let's understand what HLS and MPEG-DASH are and why they're essential for modern video streaming.

HLS is a streaming protocol developed by Apple that breaks down video content into small, HTTP-based file segments, allowing for adaptive bitrate streaming. MPEG-DASH (Dynamic Adaptive Streaming over HTTP) is a similar standard that enables high-quality streaming over the internet by dynamically adjusting the video quality in real-time based on the user's network conditions.

Implementing these formats improves the viewer's experience by reducing buffering and providing optimal video quality. Ensuring your videos can be streamed smoothly across different devices and network environments is crucial for modern applications.

Setting up your Ruby environment for video conversion

First, set up the Ruby environment needed for video conversion.

Ensure you have Ruby 3.0 or higher installed:

ruby -v

If you need to install or upgrade Ruby, use a version manager like RVM or rbenv.

Next, install FFmpeg 6.0 or higher. FFmpeg is required for processing video files.

For macOS users:

brew install ffmpeg

For Ubuntu/Debian users:

sudo apt-get update
sudo apt-get install ffmpeg

Create a new project directory and initialize it:

mkdir video_conversion
cd video_conversion
bundle init

Add the required gems to your Gemfile:

source 'https://rubygems.org'

gem 'streamio-ffmpeg'

Install the dependencies:

bundle install

Introduction to FFmpeg and streamio FFmpeg gem

FFmpeg is a powerful open-source tool for handling multimedia data. It allows you to record, convert, and stream audio and video files. The Streamio FFMPEG gem provides a Ruby interface to FFmpeg, making it easier to integrate video processing into your Ruby applications.

Converting videos to HLS format in Ruby

Let's create a Ruby class to handle the conversion of videos to HLS format.

Create a new file called video_converter.rb:

require 'streamio-ffmpeg'
require 'fileutils'

class VideoConverter
  def initialize(input_file)
    @movie = FFMPEG::Movie.new(input_file)
    @input_file = input_file
  end

  def to_hls(output_dir)
    FileUtils.mkdir_p(output_dir)

    # Define quality variants
    variants = [
      { resolution: '1280x720', bitrate: '2500k' },
      { resolution: '854x480', bitrate: '1500k' },
      { resolution: '640x360', bitrate: '800k' }
    ]

    variant_commands = variants.map do |variant|
      %W[
        -vf scale=#{variant[:resolution]}
        -b:v #{variant[:bitrate]}
        -hls_time 10
        -hls_playlist_type vod
        -hls_segment_filename #{output_dir}/#{variant[:resolution]}_%03d.ts
        #{output_dir}/#{variant[:resolution]}.m3u8
      ]
    end

    variant_commands.each do |command|
      @movie.transcode(nil, {}, { custom: command.flatten })
    end

    generate_master_playlist(output_dir, variants)
  end

  private

  def generate_master_playlist(output_dir, variants)
    master_playlist = "#EXTM3U\n"

    variants.each do |variant|
      master_playlist += <<~PLAYLIST
        #EXT-X-STREAM-INF:BANDWIDTH=#{variant[:bitrate].to_i * 1000},RESOLUTION=#{variant[:resolution]}
        #{variant[:resolution]}.m3u8
      PLAYLIST
    end

    File.write("#{output_dir}/master.m3u8", master_playlist)
  end
end

This class initializes with an input video file and includes a method to_hls that converts the video to HLS format. It creates multiple quality variants and generates a master playlist for adaptive streaming.

Converting videos to MPEG-DASH in Ruby

To convert videos to MPEG-DASH format, add a to_dash method to the VideoConverter class:

def to_dash(output_dir)
  FileUtils.mkdir_p(output_dir)

  options = %W[
    -vf scale=1280:720
    -b:v 2500k
    -vf scale=854:480
    -b:v 1500k
    -vf scale=640:360
    -b:v 800k
    -use_template 1
    -use_timeline 1
    -adaptation_sets "id=0,streams=v id=1,streams=a"
    -seg_duration 10
    -f dash
    #{output_dir}/manifest.mpd
  ]

  @movie.transcode(nil, {}, { custom: options.flatten })
end

This method converts the input video into MPEG-DASH format, creating a manifest file and necessary segments for streaming.

Handling playlists and manifests in Ruby

Generating correct playlists and manifests is crucial for streaming. In the generate_master_playlist method, we build a master playlist that references the individual variant playlists.

For MPEG-DASH, the manifest.mpd file is automatically generated by FFmpeg when using the appropriate flags.

Using the converter

Create a script to utilize the VideoConverter class:

# Convert.rb
require_relative 'video_converter'

converter = VideoConverter.new('input.mp4')

# Convert to HLS
converter.to_hls('output/hls')

# Convert to MPEG-DASH
converter.to_dash('output/dash')

Run the script:

ruby convert.rb

Ensure that input.mp4 is replaced with the path to your actual video file.

Testing the streams

To test the generated streams, you can use a video player that supports HLS and MPEG-DASH. Here's an example using Video.js:

<!DOCTYPE html>
<html>
  <head>
    <title>Video Player</title>
    <link href="https://vjs.zencdn.net/7.20.3/video-js.css" rel="stylesheet" />
    <script src="https://vjs.zencdn.net/7.20.3/video.min.js"></script>
  </head>
  <body>
    <video
      id="player"
      class="video-js vjs-default-skin"
      controls
      preload="auto"
      width="640"
      height="360"
    >
      <source src="output/hls/master.m3u8" type="application/x-mpegURL" />
      <source src="output/dash/manifest.mpd" type="application/dash+xml" />
    </video>

    <script>
      var player = videojs('player')
    </script>
  </body>
</html>

Open this HTML file in a browser to test the streaming video.

Challenges and solutions in video conversion

Handling large files

Processing large video files can be resource-intensive. Ensure your system has enough CPU and memory resources. For production environments, consider processing videos asynchronously or leveraging cloud services.

Format compatibility

Different browsers and devices have varying levels of support for HLS and MPEG-DASH. Always test your streams across multiple platforms.

Error handling

Add error handling to your VideoConverter methods:

def to_hls(output_dir)
  begin
    # HLS conversion code
  rescue StandardError => e
    puts "Error converting to HLS: #{e.message}"
  end
end

Conclusion

Automating video conversion tasks using Ruby, FFmpeg, and the Streamio FFMPEG gem enables you to deliver video content efficiently in adaptive streaming formats like HLS and MPEG-DASH. This enhances the viewer's experience by providing smooth playback across different devices and network conditions.

For a scalable and robust solution to handle video encoding and streaming, consider using Transloadit's video encoding service, which can automate these conversions and offer additional features to streamline your workflow.