Skip to content

keefetang/hls-r2

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hls-r2

Encode videos to adaptive HLS streams, upload to Cloudflare R2, and generate embeddable player snippets.

Features

  • Encode single videos or entire directories to multi-bitrate HLS (H.264/AAC)
  • Upload encoded files to Cloudflare R2 with proper MIME types and cache headers
  • Preview uploaded videos locally via a built-in server streaming from R2
  • Embed — generate copy-paste HTML snippets using Mux Player or HLS.js

Quality Ladder

The encoder automatically selects tiers at or below the source resolution:

Tier Resolution Video Bitrate Audio Bitrate
360p 640x360 800 kbps 96 kbps
480p 854x480 1,400 kbps 128 kbps
720p 1280x720 2,800 kbps 128 kbps
1080p 1920x1080 5,000 kbps 192 kbps
1440p 2560x1440 8,000 kbps 192 kbps
2160p 3840x2160 14,000 kbps 192 kbps

Tiers above the source resolution are skipped (no upscaling).

Prerequisites

  • Python 3.10+
  • ffmpeg — must be on PATH (or configured in config.toml)
  • Cloudflare R2 bucket with public access enabled

Quick Start

Using uv (recommended)

# 1. Clone and install
git clone <repo-url> && cd hls-r2
uv sync

# 2. Configure R2 credentials
cp config.example.toml config.toml
# Edit config.toml with your R2 account details

# 3. Encode a video
uv run hls-r2 encode my-video.mp4

# 4. Upload to R2
uv run hls-r2 upload my-video
# Or upload everything:
uv run hls-r2 upload --all

# 5. Preview in browser
uv run hls-r2 preview my-video

# 6. Get embed HTML
uv run hls-r2 embed my-video

Using pip

# 1. Clone and install
git clone <repo-url> && cd hls-r2
pip install .

# 2. Configure R2 credentials
cp config.example.toml config.toml
# Edit config.toml with your R2 account details

# 3. Use the tool (same commands, no "uv run" prefix)
hls-r2 encode my-video.mp4
hls-r2 upload my-video
hls-r2 preview my-video
hls-r2 embed my-video

Configuration

Copy config.example.toml to config.toml in your working directory:

[r2]
account_id = "your-account-id"
access_key_id = "your-access-key-id"
secret_access_key = "your-secret-access-key"
bucket_name = "my-video-bucket"
public_base_url = "https://cdn.example.com"
# prefix = "videos"  # optional, default: "videos"

[encoding]
# output_dir = "output"       # optional, default: "output"
# ffmpeg_path = "ffmpeg"      # optional, if not on PATH
# ffprobe_path = "ffprobe"    # optional, if not on PATH

Environment Variable Overrides

R2 credentials can also be set via environment variables, which take precedence over config.toml:

Variable Overrides
R2_ACCOUNT_ID [r2] account_id
R2_ACCESS_KEY_ID [r2] access_key_id
R2_SECRET_ACCESS_KEY [r2] secret_access_key

This is useful for CI/CD or when you don't want a config file on disk.

R2 Setup

  1. Create an R2 bucket in the Cloudflare Dashboard
  2. Enable public access (Settings > Public Access) via custom domain or r2.dev subdomain
  3. Create an API token at R2 > Manage R2 API Tokens with read/write access to the bucket
  4. Copy the Access Key ID and Secret Access Key into config.toml

Commands

Global Options

These flags apply to all commands:

Option Description
-v, --verbose Enable debug-level output (ffmpeg commands, detailed logging)
-q, --quiet Suppress informational output, only show errors
--version Show version and exit

hls-r2 encode <INPUT_PATH>

Encode a single video file or all videos in a directory to HLS.

# Single file
uv run hls-r2 encode video.mp4

# Directory (all supported formats)
uv run hls-r2 encode ./raw-videos/

# Custom output directory
uv run hls-r2 encode video.mp4 -o ./encoded/

# Overwrite existing encoded output
uv run hls-r2 encode video.mp4 --force
Option Description
-o, --output Output directory for encoded files (default: output)
-c, --config Path to config file (default: ./config.toml)
--force Overwrite existing encoded output for a slug

Supported input formats: .mp4, .mkv, .mov, .avi, .webm, .flv, .wmv

Output structure:

output/
  my-video/
    master.m3u8          # Master playlist (adaptive bitrate)
    360p/
      stream.m3u8        # Per-tier playlist
      segment_000.ts     # MPEG-TS segments
      segment_001.ts
    720p/
      stream.m3u8
      segment_000.ts
      ...
    1080p/
      ...

hls-r2 upload [SLUGS...] [--all]

Upload encoded video(s) to R2. Validates credentials before uploading.

# Upload specific videos by slug (directory name)
uv run hls-r2 upload my-video another-video

# Upload all encoded videos
uv run hls-r2 upload --all

# Control upload concurrency
uv run hls-r2 upload --all --workers 4
Option Description
--all Upload all encoded videos in the output directory
--workers Concurrent upload threads, 1–32 (default: 8)
-o, --output Output directory containing encoded files (default: output)
-c, --config Path to config file (default: ./config.toml)

hls-r2 preview [SLUG]

Start a local server to preview R2-hosted videos in the browser.

# List all videos and pick one
uv run hls-r2 preview

# Open directly to a specific video
uv run hls-r2 preview my-video

# Custom host/port
uv run hls-r2 preview --host 0.0.0.0 --port 3000

hls-r2 embed <SLUG>

Print HTML embed code to stdout. Pipe or copy-paste into your site.

# Both Mux Player and HLS.js snippets (default)
uv run hls-r2 embed my-video

# Mux Player only
uv run hls-r2 embed my-video --player mux

# HLS.js only
uv run hls-r2 embed my-video --player hlsjs

# Custom title
uv run hls-r2 embed my-video --title "My Great Video"

# Save to file
uv run hls-r2 embed my-video > embed.html

hls-r2 list

List available videos (remote or local).

# List videos in R2
uv run hls-r2 list

# List locally encoded videos
uv run hls-r2 list --local
Option Description
--local List locally encoded videos instead of R2
-o, --output Output directory for local listing (default: output)
-c, --config Path to config file (default: ./config.toml)

Architecture

src/hls_r2/
  __init__.py       # Package metadata
  cli.py            # Click CLI (entry point)
  config.py         # TOML config loading/validation
  constants.py      # Quality ladder, defaults, extensions
  encoder.py        # FFmpeg HLS encoding
  uploader.py       # R2 upload via boto3 (S3-compatible)
  preview.py        # Local HTTP preview server
  embed.py          # HTML snippet generation
  py.typed          # PEP 561 typed package marker

R2 Object Structure

Uploaded files follow this pattern:

{prefix}/{slug}/master.m3u8
{prefix}/{slug}/360p/stream.m3u8
{prefix}/{slug}/360p/segment_000.ts
{prefix}/{slug}/720p/stream.m3u8
{prefix}/{slug}/720p/segment_000.ts
...

Default prefix is videos. The slug is derived from the source filename.

Encoding Details

  • Codec: H.264/AVC (libx264) + AAC audio
  • Segments: MPEG-TS (.ts), 6 seconds each
  • Playlist: HLS VOD with independent segments
  • Scaling: Aspect ratio preserved with letterboxing/pillarboxing
  • Single-pass: All tiers encoded in one ffmpeg invocation for speed
  • No upscaling: Source resolution below a tier = that tier is skipped

License

MIT

About

CLI tool to encode videos to adaptive HLS streams, upload to Cloudflare R2, and generate embeddable player snippets

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages