📚 manifestor - Awesome Go Library for Video

Go Gopher mascot for manifestor

Zero-dependency library for parsing, filtering, transforming, and building HLS and DASH manifests

🏷️ Video
📂 Video
0 stars
View on GitHub 🔗

Detailed Description of manifestor

manifestor

Go Reference CI Go Report Card Coverage License: MIT

Parse, filter, build, and transform HLS & DASH manifests in Go. Zero dependencies. Ships as a library, HTTP proxy server, and CLI tool.


Features

  • Parse HLS Master Playlists (.m3u8) and DASH MPDs (.mpd) from string, file, or URL
  • Filter variants/representations by codec, resolution, bandwidth, frame rate, audio language, MIME type
  • Transform URIs — CDN rewrites, absolute URI resolution, auth token injection (variants, audio tracks, I-frame streams, and DASH <BaseURL> elements)
  • Inject extra tracks — append subtitle tracks, alternate audio, or additional representations after filtering
  • Build complete HLS or DASH manifests from scratch with a fluent builder API
  • Serve as an HTTP proxy that filters manifests on the fly
  • CLI tool for scripting and local use
  • Zero non-stdlib dependencies
  • Thread-safe — Filter() is safe for concurrent use
  • HLS versions 3–7; DASH profiles isoff-on-demand and isoff-live

Installation

Library

go get github.com/alanzng/manifestor

CLI

go install github.com/alanzng/manifestor/cmd/manifestor@latest

Docker

docker pull ghcr.io/alanng/manifestor:latest

Quick Start

Filter an HLS manifest

import (
    manifestor "github.com/alanzng/manifestor"
    "github.com/alanzng/manifestor/manifest"
)

filtered, err := manifest.Filter(content,
    manifest.WithCodec(manifestor.H264),
    manifest.WithMaxResolution(manifestor.Res1080p),
    manifest.WithMaxBandwidth(5_000_000),
    manifest.WithCDNBaseURL("https://cdn.example.com"),
)

Filter from URL

filtered, err := manifest.FilterFromURL("https://example.com/master.m3u8",
    manifest.WithCodec(manifestor.H264),
    manifest.WithAuthToken("token=abc123"),
)

Real-world example — Vieon VOD pipeline

Take a Bento4-generated master playlist with mixed AVC1/HVC1 video and a single audio track, and produce a delivery manifest with H.265 only, max 720p, absolute CDN URLs, a dubbed audio track, and subtitles.

HLS:

import (
    manifestor "github.com/alanzng/manifestor"
    "github.com/alanzng/manifestor/hls"
    "github.com/alanzng/manifestor/manifest"
)

const cdnBase = "https://vod-bp.vieon.vn/abc123/.../vod/2026/03/12/uuid/"
const dubbedBase = "https://vod-bp.vieon.vn/def456/.../vod/2026/03/24/uuid2/"

out, err := manifest.Filter(content,
    manifest.WithCodec(manifestor.H265),
    manifest.WithMaxResolution(manifestor.Res720p),
    manifest.WithAbsoluteURIs(cdnBase),
    manifest.WithHLSVariantSubtitleGroup("subs"),
    manifest.WithHLSInjectSubtitle(hls.SubtitleTrackParams{
        GroupID:  "subs",
        Name:     "Tiếng Việt",
        Language: "vi",
        URI:      "https://static.vieon.vn/subtitle/vi.m3u8",
        Default:  true,
    }),
    manifest.WithHLSInjectAudioTrack(hls.AudioTrackParams{
        GroupID:  "audio/mp4a",
        Name:     "Thuyết Minh",
        Language: "tm",
        URI:      dubbedBase + "audio-tg-mp4a.m3u8",
    }),
)

DASH:

import (
    manifestor "github.com/alanzng/manifestor"
    "github.com/alanzng/manifestor/dash"
    "github.com/alanzng/manifestor/manifest"
)

out, err := manifest.Filter(content,
    manifest.WithCodec(manifestor.H265),
    manifest.WithMaxResolution(manifestor.Res720p),
    manifest.WithAbsoluteURIs(cdnBase),
    manifest.WithDASHInjectAdaptationSet(dash.AdaptationSetParams{
        MimeType: "audio/mp4",
        Lang:     "tm",
        Name:     "Thuyết Minh",
        Representations: []dash.RepresentationParams{
            {ID: "tm-audio", Bandwidth: 196728, Codecs: "mp4a.40.2",
                BaseURL: dubbedBase + "media-audio-tg-mp4a.mp4"},
        },
    }),
    manifest.WithDASHInjectAdaptationSet(dash.AdaptationSetParams{
        ContentType: "text",
        MimeType:    "text/vtt",
        Lang:        "vi",
        Roles:       []dash.Role{{SchemeIDURI: "urn:mpeg:dash:role:2011", Value: "subtitle"}},
        Representations: []dash.RepresentationParams{
            {ID: "subtitles/vi", Bandwidth: 16,
                BaseURL: "https://static.vieon.vn/subtitle/vi.vtt"},
        },
    }),
)

Build an HLS Master Playlist

import "github.com/alanzng/manifestor/hls"

b := hls.NewMasterBuilder()
b.SetVersion(6).
    AddAudioTrack(hls.AudioTrackParams{
        GroupID:    "audio-en",
        Name:       "English",
        Language:   "en",
        URI:        "https://cdn.example.com/audio/en/index.m3u8",
        Default:    true,
        AutoSelect: true,
    }).
    AddVariant(hls.VariantParams{
        URI:          "https://cdn.example.com/1080p/index.m3u8",
        Bandwidth:    5_000_000,
        Codecs:       "avc1.640028,mp4a.40.2",
        Width:        1920,
        Height:       1080,
        FrameRate:    29.97,
        AudioGroupID: "audio-en",
    }).
    AddVariant(hls.VariantParams{
        URI:          "https://cdn.example.com/720p/index.m3u8",
        Bandwidth:    2_800_000,
        Codecs:       "avc1.4d401f,mp4a.40.2",
        Width:        1280,
        Height:       720,
        FrameRate:    29.97,
        AudioGroupID: "audio-en",
    })

playlist, err := b.Build()

Build a DASH MPD

import "github.com/alanzng/manifestor/dash"

b := dash.NewMPDBuilder(dash.MPDConfig{
    Profile:       "isoff-on-demand",
    Duration:      "PT4M0.00S",
    MinBufferTime: "PT1.5S",
})
b.AddAdaptationSet(dash.AdaptationSetParams{
    MimeType: "video/mp4",
    SegmentTemplate: &dash.SegmentTemplateParams{
        Initialization: "$RepresentationID$/init.mp4",
        Media:          "$RepresentationID$/$Number$.m4s",
        Timescale:      90000,
        Duration:       270000,
    },
    Representations: []dash.RepresentationParams{
        {ID: "v1", Bandwidth: 5_000_000, Codecs: "avc1.640028", Width: 1920, Height: 1080},
        {ID: "v2", Bandwidth: 2_000_000, Codecs: "avc1.4d401f", Width: 1280, Height: 720},
    },
})
b.AddAdaptationSet(dash.AdaptationSetParams{
    MimeType: "audio/mp4",
    Lang:     "en",
    Name:     "English",
    Representations: []dash.RepresentationParams{
        {
            ID:        "a1",
            Bandwidth: 128000,
            Codecs:    "mp4a.40.2",
            BaseURL:   "https://cdn.example.com/audio-en.mp4",
            AudioChannelConfiguration: &dash.AudioChannelConfiguration{
                SchemeIDURI: "urn:mpeg:dash:23003:3:audio_channel_configuration:2011",
                Value:       "2",
            },
        },
    },
})

mpd, err := b.Build()

HTTP Server

# Start the proxy server
manifestor serve --port 8080

# Filter a live manifest via HTTP
curl "http://localhost:8080/filter?url=https://example.com/master.m3u8&codec=h264&max_res=1920x1080"

CLI

# Filter a local file
manifestor filter --input master.m3u8 --codec h264 --max-res 1920x1080

# Filter from URL and write to file
manifestor filter --url https://example.com/master.m3u8 --codec h264 --output filtered.m3u8

# Build from a JSON spec
manifestor build --format hls --variants spec.json --output master.m3u8

API Reference

Filter Options (unified — work on both HLS and DASH)

OptionDescription
WithCodec(manifestor.Codec)Keep only video variants matching codec: H264, H265, VP9, AV1. Audio tracks are always preserved. Use manifestor.ParseCodec(s) for string input.
WithMaxResolution(manifestor.Resolution)Exclude variants wider or taller than the resolution. Presets: Res720p, Res1080p, Res4K, etc.
WithMinResolution(manifestor.Resolution)Exclude variants smaller than the resolution
WithExactResolution(manifestor.Resolution)Keep only variants matching exactly
WithMaxBandwidth(bps)Exclude variants above bps bits/s
WithMinBandwidth(bps)Exclude variants below bps bits/s
WithMaxFrameRate(fps)Exclude variants with frame rate above fps
WithAudioLanguage(lang)Keep only audio tracks matching BCP-47 lang
WithMimeType(manifestor.MimeType)Keep only representations matching MIME type (DASH only). Constants: MimeVideoMP4, MimeAudioMP4, MimeTextVTT, etc.
WithCDNBaseURL(base)Rewrite all URIs to use base as CDN origin
WithAbsoluteURIs(origin)Resolve relative URIs to absolute using origin
WithAuthToken(token)Append token= query parameter to all URIs

URI rewriting covers: HLS variant URIs, audio track URIs, I-frame stream URIs; DASH <BaseURL> elements.

HLS-only filter options

OptionDescription
WithHLSInjectVariant(p)Append a variant stream after filtering
WithHLSInjectAudioTrack(p)Append an #EXT-X-MEDIA AUDIO track after filtering
WithHLSInjectSubtitle(p)Append an #EXT-X-MEDIA SUBTITLES track after filtering
WithHLSVariantSubtitleGroup(id)Set SUBTITLES="id" on all surviving variants

DASH-only filter options

OptionDescription
WithDASHInjectAdaptationSet(p)Append an <AdaptationSet> to every Period after filtering

Custom callbacks (package-level only)

OptionPackageDescription
WithCustomFilter(fn)hls, dashUser-defined filter: func(*Variant) bool / func(*Representation) bool
WithCustomTransformer(fn)hls, dashUser-defined transformer applied to each surviving variant/representation

Errors

ErrorCondition
ErrInvalidFormatContent is neither valid HLS nor DASH
ErrNotMasterPlaylistHLS content is a media playlist, not a master
ErrNoVariantsRemainAll variants were filtered out
ErrFetchFailedUpstream URL fetch failed
ErrParseFailureManifest could not be parsed
ErrEmptyVariantListBuild() called with no variants added
ErrInvalidVariantA variant is missing URI or Bandwidth
ErrOrphanedGroupIDAudioGroupID references a non-existent #EXT-X-MEDIA group
ErrInvalidLanguageTagDASH lang is not a valid BCP-47 tag

DASH Data Model

Key fields parsed and round-tripped through ParseFilterSerialize:

ElementFields
<MPD>Profile, Duration, MinBufferTime, MinUpdatePeriod
<AdaptationSet>ID, ContentType, MimeType, Lang, Name (label attr), Roles, SegmentTemplate, SegmentBase
<Representation>ID, Bandwidth, Codecs, Width, Height, FrameRate, MimeType, StartWithSAP, BaseURL, AudioChannelConfiguration
<Role>SchemeIDURI, Value
<AudioChannelConfiguration>SchemeIDURI, Value

HTTP API

GET /filter

Fetches and filters an upstream manifest.

ParameterRequiredDescription
urlyesUpstream manifest URL
codecnoh264 | h265 | vp9 | av1
max_resnoe.g. 1920x1080
min_resnoe.g. 854x480
max_bwnobits/s e.g. 5000000
min_bwnobits/s e.g. 500000
fpsnomax frame rate e.g. 30
cdnnoCDN base URL
tokennoAuth token string
langnoBCP-47 audio language

Responses: 200 OK, 400 Bad Request, 422 Unprocessable Entity, 502 Bad Gateway

POST /build

Builds a manifest from a JSON payload. See HTTP API docs for full schema.


Performance

OperationTargetTypical
Parse + filter + serialize 50 KB manifest< 5 ms~1–2 ms
Build 100-variant manifest< 2 ms~0.5 ms

Supported Manifest Sources

Tested against real-world output from:


Who is using manifestor

NameLogoWebsiteDescription
Vieon Vieon vieon.vnVietnam's leading OTT streaming platform. Uses manifestor to filter and transform HLS & DASH manifests for multi-codec VOD delivery (AVC1 + HVC1) with CDN rewriting and per-request auth tokens.

Contributing

Contributions are welcome! Please read CONTRIBUTING.md before opening a pull request.


License

MIT — see LICENSE.