Converting videos to HLS and MPEG-DASH with Rust
Adaptive streaming is crucial for delivering high-quality video content to various devices under different network conditions. This DevTip demonstrates how to convert videos to HLS (HTTP Live Streaming) and MPEG-DASH (Dynamic Adaptive Streaming over HTTP) formats using Rust and FFmpeg.
Introduction to HLS and MPEG-DASH
HLS and MPEG-DASH are popular adaptive streaming protocols that split video files into small segments and offer multiple bitrate options. This segmentation enables clients to seamlessly switch to the optimal quality based on current network conditions and device capabilities.
Understanding adaptive streaming
Adaptive streaming typically involves:
- Encoding the video at several bitrates and resolutions.
- Splitting each encoded version into short segments.
- Generating manifest files that detail the available streams and segments.
- Allowing clients to automatically select the best-suited quality during playback.
Setting up a Rust project
Begin by installing the necessary system dependencies:
sudo apt-get update
sudo apt-get install ffmpeg
Create a new Rust project:
cargo new video_converter
cd video_converter
Add these dependencies to your Cargo.toml file:
[dependencies]
anyhow = "1.0.75"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
warp = "0.3.7"
Integrating FFmpeg with Rust
These examples run the FFmpeg executable, so no FFmpeg development headers or Rust bindings are
needed. Start src/main.rs with a check that FFmpeg is available:
use anyhow::Result;
use std::process::Command;
fn check_ffmpeg() -> Result<()> {
let status = Command::new("ffmpeg").arg("-version").status()?;
anyhow::ensure!(status.success(), "FFmpeg version check failed");
Ok(())
}
fn main() -> Result<()> {
check_ffmpeg()?;
Ok(())
}
Implementing video conversion to HLS and MPEG-DASH
To convert videos to HLS and MPEG-DASH, we invoke FFmpeg’s command-line tools through Rust’s
Command module. This approach leverages FFmpeg’s powerful CLI while keeping the Rust code
straightforward. Use an input with a video stream and an audio stream. The DASH example assumes a
16:9 video at least 1280×720. Each conversion requires a new output directory to avoid overwriting
an earlier conversion; its parent directory must already exist.
Converting to HLS
Use the following function to convert a video file to HLS format. It creates the necessary output
directory and invokes FFmpeg with parameters tailored for HLS generation. Add this function below
the imports in src/main.rs. This HLS example creates one rendition; its master playlist points to
a separate media playlist:
fn convert_to_hls(input: &str, output_dir: &str) -> Result<()> {
std::fs::create_dir(output_dir)?;
let output_path = format!("{}/index.m3u8", output_dir);
let status = Command::new("ffmpeg")
.args(&[
"-nostdin", "-n",
"-i", input,
"-map", "0:v:0", "-map", "0:a:0",
"-c:v", "libx264",
"-c:a", "aac",
"-profile:v", "main",
"-preset", "medium",
"-crf", "23",
"-sc_threshold", "0",
"-force_key_frames", "expr:gte(t,n_forced*4)",
"-hls_time", "4",
"-hls_playlist_type", "vod",
"-hls_segment_filename", &format!("{}/segment_%03d.ts", output_dir),
"-master_pl_name", "master.m3u8",
"-var_stream_map", "v:0,a:0",
"-f", "hls",
&output_path,
])
.status()?;
if !status.success() {
return Err(anyhow::anyhow!("FFmpeg process failed"));
}
Ok(())
}
Converting to MPEG-DASH
Similarly, the following function converts a video file to MPEG-DASH format. It sets up segment naming and manifest generation appropriate for DASH streaming. Add it to the same file. Mapping the first video stream twice creates two video representations, with aligned four-second keyframes:
fn convert_to_mpeg_dash(input: &str, output_dir: &str) -> Result<()> {
std::fs::create_dir(output_dir)?;
let output_path = format!("{}/manifest.mpd", output_dir);
let status = Command::new("ffmpeg")
.args(&[
"-nostdin", "-n",
"-i", input,
"-map", "0:v:0",
"-map", "0:v:0",
"-map", "0:a:0",
"-c:v", "libx264",
"-c:a", "aac",
"-b:v:0", "2M",
"-b:v:1", "1M",
"-s:v:1", "1280x720",
"-profile:v", "main",
"-sc_threshold", "0",
"-force_key_frames", "expr:gte(t,n_forced*4)",
"-seg_duration", "4",
"-use_template", "1",
"-use_timeline", "1",
"-init_seg_name", "init-$RepresentationID$.m4s",
"-media_seg_name", "chunk-$RepresentationID$-$Number%05d$.m4s",
"-adaptation_sets", "id=0,streams=v id=1,streams=a",
"-f", "dash",
&output_path,
])
.status()?;
if !status.success() {
return Err(anyhow::anyhow!("FFmpeg process failed"));
}
Ok(())
}
A failed conversion may leave partial files in its new output directory. Retry with a fresh directory, and serve the files only after both conversions finish successfully.
Building a simple streaming server in Rust
After converting your videos, you can serve the HLS or MPEG-DASH content using a basic HTTP server
built with the warp crate. Save this separate executable as src/bin/server.rs (create src/bin
first). The server assigns standard streaming content types to playlists and MPEG-TS segments, while
retaining Warp’s inferred content type for other files:
use warp::{Filter, Reply};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let cors = warp::cors()
.allow_any_origin()
.allow_methods(vec!["GET", "HEAD", "OPTIONS"]);
let routes = warp::fs::dir("./output")
.map(|file: warp::fs::File| {
let content_type = match file.path().extension().and_then(|ext| ext.to_str()) {
Some("m3u8") => "application/vnd.apple.mpegurl",
Some("mpd") => "application/dash+xml",
Some("ts") => "video/mp2t",
_ => return file.into_response(),
};
let mut response = file.into_response();
response.headers_mut().insert(
warp::http::header::CONTENT_TYPE,
warp::http::HeaderValue::from_static(content_type),
);
response
})
.with(cors);
println!("Starting server at http://127.0.0.1:3030/");
warp::serve(routes)
.run(([127, 0, 0, 1], 3030))
.await;
Ok(())
}
Testing the streaming setup
To test your streaming solution:
-
Replace
maininsrc/main.rswith the following, keeping the functions above:fn main() -> Result<()> { check_ffmpeg()?; std::fs::create_dir_all("./output")?; convert_to_hls("input.mp4", "./output/hls")?; convert_to_mpeg_dash("input.mp4", "./output/dash")?; println!("Conversion complete."); Ok(()) } -
Run the converter once, then start the streaming server from the project directory:
cargo run --bin video_converter && cargo run --bin server -
Use a compatible player—such as VLC, or web libraries like hls.js or dash.js—to play the stream from:
Conclusion
This DevTip demonstrated how to convert videos to HLS and MPEG-DASH formats using Rust and FFmpeg. We covered generating playlists and manifest files and setting up a basic streaming server for adaptive streaming. For a more robust solution, consider the following enhancements:
- Extend the single-rendition HLS example with multiple aligned renditions for adaptive playback.
- Experiment with advanced FFmpeg settings to optimize video quality.
- Add support for additional features such as multiple audio tracks and subtitles.
- Secure your streaming setup with HTTPS and token-based authentication.
If you are looking for a managed solution for video encoding and adaptive streaming, Transloadit offers powerful video processing capabilities that scale to meet your needs.
