mosaic

Mosaic

Go Report Card Go License: MIT

mosaic is a Go library for adaptive bitrate video packaging. It probes input media with FFprobe, builds an optimized ABR ladder, and generates HLS or DASH CMAF outputs with FFmpeg.

The library is designed for applications that need predictable server-side encoding without shelling out manually for every FFmpeg command.

Highlights

Requirements

Hardware acceleration requires the corresponding FFmpeg encoder to be present:

Installation

go get github.com/farshidrezaei/mosaic

Quick Start: HLS

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/farshidrezaei/mosaic"
)

func main() {
	job := mosaic.Job{
		Input:     "/path/to/input.mp4",
		OutputDir: "/tmp/mosaic-hls",
		Profile:   mosaic.ProfileVOD,
		ProgressHandler: func(info mosaic.ProgressInfo) {
			fmt.Printf("time=%s bitrate=%s speed=%s\n", info.CurrentTime, info.Bitrate, info.Speed)
		},
	}

	usage, err := mosaic.EncodeHls(
		context.Background(),
		job,
		mosaic.WithNormalizeOrientation(),
		mosaic.WithThreads(4),
		mosaic.WithLogLevel("warning"),
	)
	if err != nil {
		log.Fatal(err)
	}

	if usage != nil {
		fmt.Printf("user=%.2fs system=%.2fs maxrss=%d\n", usage.UserTime, usage.SystemTime, usage.MaxMemory)
	}
}

The generated HLS directory contains:

master.m3u8
stream_0.m3u8
stream_1.m3u8
stream_2.m3u8
init_0.mp4 or init.mp4
seg_0_0.m4s
...

The exact number of variants depends on the input height and optimizer decisions.

Quick Start: DASH

package main

import (
	"context"
	"log"

	"github.com/farshidrezaei/mosaic"
)

func main() {
	job := mosaic.Job{
		Input:     "/path/to/input.mp4",
		OutputDir: "/tmp/mosaic-dash",
		Profile:   mosaic.ProfileVOD,
	}

	_, err := mosaic.EncodeDash(
		context.Background(),
		job,
		mosaic.WithNormalizeOrientation(),
		mosaic.WithLogLevel("warning"),
	)
	if err != nil {
		log.Fatal(err)
	}
}

The generated DASH directory contains:

manifest.mpd
init-stream$RepresentationID$.m4s
chunk-stream$RepresentationID$-$Number$.m4s
...

Input Sources

Job.Input can be a local file path or a public URL accepted by FFmpeg and FFprobe.

job := mosaic.Job{
	Input:     "https://example.com/video.mp4",
	OutputDir: "/tmp/output",
	Profile:   mosaic.ProfileVOD,
}

For remote inputs, FFmpeg and FFprobe must be able to resolve and fetch the URL from the runtime environment.

Output Directories

mosaic creates Job.OutputDir automatically before invoking FFmpeg.

If the process cannot write to the target directory, encoding fails with an error such as:

create output dir: mkdir /path: permission denied

Use a writable output directory and ensure enough disk space for all generated variants and segments.

Encoding Profiles

Profile Segment Duration Low Latency Flags
ProfileVOD 5s No
ProfileLive 2s Yes

ProfileLive enables shorter segments and HLS low-latency related flags. It does not implement a full live ingest loop by itself.

Aspect Ratio and Ladder Behavior

Mosaic builds quality rungs using target heights of 1080, 720, and 360, but it computes each rung width from the input display aspect ratio.

Examples:

Input Display Size Output Rungs
1920x1080 1920x1080, 1280x720, 640x360
1080x1080 1080x1080, 720x720, 360x360
1080x1920 608x1080, 404x720, 202x360
1280x718 642x360
426x240 426x240

Notes:

Orientation Handling

Some phone videos store frames in one orientation and rely on container metadata such as a display matrix or rotate tag to tell players how to display them.

Mosaic handles this in two layers:

  1. probe.VideoInfo exposes DisplayWidth, DisplayHeight, and IsPortrait, so ladders are built from display dimensions instead of raw stored dimensions.
  2. WithNormalizeOrientation() physically rotates frames for 90, 180, and 270 degree metadata and clears rotation metadata from output streams.

Recommended default:

usage, err := mosaic.EncodeHls(
	ctx,
	job,
	mosaic.WithNormalizeOrientation(),
)

Use orientation normalization when targeting browsers, mobile players, or mixed playback environments where rotation metadata support may differ.

Progress Reporting

Set Job.ProgressHandler to receive parsed values from FFmpeg -progress.

job.ProgressHandler = func(info mosaic.ProgressInfo) {
	fmt.Printf("time=%s bitrate=%s speed=%s\n", info.CurrentTime, info.Bitrate, info.Speed)
}

Fields:

Hardware Encoding

Use a convenience option:

mosaic.WithNVENC()
mosaic.WithVAAPI()
mosaic.WithVideoToolbox()

Or select a backend explicitly:

mosaic.WithGPU(config.GPU_NVENC)

Hardware options only select FFmpeg encoder names. They do not validate driver availability, device permissions, pixel format constraints, or platform-specific FFmpeg requirements.

Public API

type Job struct {
	Input           string
	OutputDir       string
	ProgressHandler ProgressHandler
	Profile         Profile
}

type ProgressInfo struct {
	CurrentTime string
	Bitrate     string
	Speed       string
	Percentage  float64
}

type Profile string

const (
	ProfileVOD  Profile = "vod"
	ProfileLive Profile = "live"
)

func EncodeHls(ctx context.Context, job Job, opts ...Option) (*executor.Usage, error)
func EncodeHlsWithExecutor(ctx context.Context, job Job, exec executor.CommandExecutor, opts ...Option) (*executor.Usage, error)

func EncodeDash(ctx context.Context, job Job, opts ...Option) (*executor.Usage, error)
func EncodeDashWithExecutor(ctx context.Context, job Job, exec executor.CommandExecutor, opts ...Option) (*executor.Usage, error)

func NormalizeVideoOrientation(ctx context.Context, inputPath, outputPath string) error

func WithThreads(n int) Option
func WithGPU(t ...config.GPUType) Option
func WithNormalizeOrientation(enabled ...bool) Option
func WithNVENC() Option
func WithVAAPI() Option
func WithVideoToolbox() Option
func WithLogLevel(level string) Option
func WithLogger(logger *slog.Logger) Option

See docs/API.md for detailed API notes.

Examples

Runnable examples live under examples/.

cd examples/simple_hls
cp /path/to/input.mp4 input.mp4
GOCACHE=/tmp/go-build go run .

Available examples:

Testing

# Unit and package tests
go test ./...

# If the default Go cache is not writable in your environment
GOCACHE=/tmp/go-build go test ./...

# Vet
GOCACHE=/tmp/go-build go vet ./...

# Coverage
GOCACHE=/tmp/go-build go test ./... -cover

Optional lint:

golangci-lint run

See docs/TESTING.md for integration and smoke-test guidance.

Documentation Map

Repository Layout

mosaic/
├── encode.go
├── job.go
├── orientation.go
├── config/
├── probe/
├── ladder/
├── optimize/
├── encoder/
├── internal/executor/
├── examples/
├── docs/
└── README.md

Current Limitations

License

MIT. See LICENSE.