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.
- HLS CMAF output with
master.m3u8, variant playlists, init segments, and fMP4 media segments. - DASH CMAF output with
manifest.mpd, init segments, and media segments. - Aspect-preserving ABR ladders. Square inputs stay square, portrait inputs stay portrait, and non-standard dimensions keep their display ratio.
- Orientation-aware probing using FFprobe display matrix and rotate metadata.
- Optional orientation normalization that physically rotates frames and clears rotation metadata.
- Automatic output directory creation for HLS and DASH jobs.
- Audio stream detection with conditional audio mapping.
- Progress callbacks from FFmpeg
-progressoutput. - Functional options for threads, hardware encoder backend, FFmpeg log level, and logger.
- Hardware encoder selection for NVENC, VAAPI, and VideoToolbox.
- Dependency-injected command execution for fast unit tests and controlled integrations.
- Go
1.25+ - FFmpeg
4.4+ - FFprobe, usually installed with FFmpeg
- FFmpeg builds must include H.264 and AAC support for the default outputs
Hardware acceleration requires the corresponding FFmpeg encoder to be present:
h264_nvencfor NVIDIA NVENCh264_vaapifor VAAPIh264_videotoolboxfor Apple VideoToolbox
go get github.com/farshidrezaei/mosaicpackage 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.
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
...
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.
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.
| 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.
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:
- The ladder is based on effective display dimensions after rotation metadata is applied.
- Output dimensions are forced to even numbers for broad H.264 and
yuv420pcompatibility. - Inputs below 360p are not upscaled. The fallback rung keeps the source display height.
- HLS no longer pads video into a fixed 16:9 frame. The output frame itself carries the preserved aspect ratio.
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:
probe.VideoInfoexposesDisplayWidth,DisplayHeight, andIsPortrait, so ladders are built from display dimensions instead of raw stored dimensions.WithNormalizeOrientation()physically rotates frames for90,180, and270degree 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.
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:
CurrentTime: FFmpegout_timeBitrate: FFmpegbitrateSpeed: FFmpegspeedPercentage: currently reserved and reported as0
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.
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) OptionSee docs/API.md for detailed API notes.
Runnable examples live under examples/.
cd examples/simple_hls
cp /path/to/input.mp4 input.mp4
GOCACHE=/tmp/go-build go run .Available examples:
examples/simple_hls: basic HLS encodeexamples/advanced_dash: DASH encode with optionsexamples/multi_gpu: tries several hardware encoder backends
# 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 ./... -coverOptional lint:
golangci-lint runSee docs/TESTING.md for integration and smoke-test guidance.
- docs/API.md: public API reference and usage notes
- docs/ARCHITECTURE.md: package boundaries and runtime flow
- docs/ENCODING.md: ladder, orientation, HLS, DASH, and FFmpeg behavior
- docs/TESTING.md: test strategy, commands, and smoke tests
- docs/TROUBLESHOOTING.md: common failures and debugging steps
- STRUCTURE.md: quick repository layout
- CONTRIBUTING.md: contribution workflow
- ROADMAP.md: planned work
- CHANGELOG.md: release notes
- SUPPORT.md: support and bug-report guidance
- SECURITY.md: security reporting policy
- AGENTS.md: instructions for AI coding agents
mosaic/
├── encode.go
├── job.go
├── orientation.go
├── config/
├── probe/
├── ladder/
├── optimize/
├── encoder/
├── internal/executor/
├── examples/
├── docs/
└── README.md
- H.264/AAC are the default codecs.
- HEVC and AV1 are planned but not implemented as public options yet.
ProgressInfo.Percentageis reserved and not computed yet.- Cloud uploads, DRM packaging, thumbnails, and sprites are planned but not implemented.
- Hardware encoder options assume FFmpeg and the host system are already configured.
MIT. See LICENSE.