dovi

package module
v0.0.0-...-701da24 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 15, 2026 License: MIT Imports: 17 Imported by: 0

README

go-dovi-extract

This repository is a Go port of quietvoid/dovi_tool, created primarily for use with upbrr.

A pure-Go, library-first Dolby Vision RPU reader, HEVC/Matroska extractor, structure reporter and L1 metadata plotter. Requires Go 1.27.0. Runtime use needs no Rust, libdovi, cgo, FFmpeg, dovi_tool, installed fonts or network access.

import (
    dovi "github.com/Audionut/go-dovi-extract"
    "github.com/Audionut/go-dovi-extract/extract"
)

// input is a caller-owned io.ReadSeeker. TrackID is an actual MKV TrackNumber;
// zero requires exactly one eligible HEVC track.
extraction, err := extract.Extract(ctx, input, extract.Options{KeepRaw: true})
if err != nil { return err }
analysis, err := extraction.Analysis(dovi.AnalysisOptions{})
if err != nil { return err }
report, err := dovi.Summarize(analysis, dovi.SummaryOptions{})
if err != nil { return err }
_ = report // JSON-compatible, schema_version: 1; inventories are sorted.
picture, err := dovi.Render(ctx, analysis, dovi.PlotOptions{Title: "Dolby Vision"})
// ErrNoL1 means the structural analysis still succeeded without a plot.

Call png.Encode on the returned image. Create a new temporary destination, encode, close successfully, then publish it; callers own destination selection, replacement policy and partial-output cleanup. extract.WriteRPU likewise writes to a caller-owned writer and may leave partial output on IO failure. See the executed examples in example_test.go and extract/example_test.go.

Inputs and ownership

Entry point Input and boundary
dovi.ParseRPU One unescaped payload, beginning 19, including CRC and 80 terminator
dovi.ParseNAL One escaped type-62 HEVC NAL including 7c 01; no start code
extract.ReadRPU Standalone escaped RPU records, delimited by three/four-byte start codes
extract.Extract Standalone RPU, Annex B HEVC, or one selected native MKV HEVC track; signatures or explicit format
extract.NewStream, Push, Finish Incremental Annex B or length-prefixed HEVC; the latter requires matching hvcC parameter sets and length size

Readers stay caller-owned. A Chunk and its markers are borrowed only until Push(ctx, chunk) returns. Offsets must be contiguous. Sample markers carry exact rational PTS/DTS and optional invisible-output suppression. Discontinuities fail. Call Abort(cause) after a producer error; only normal EOF calls Finish(ctx). Collectors are synchronous, single-owner and single-use. Cancellation is checked between bounded work units, but cannot interrupt arbitrary blocking caller IO. Use deadline-aware IO or close your own resource when that is required.

Results own their slices and are caller-mutable, without concurrent mutation. Analysis deep-copies metadata under its own finite budget. Summary and render validate caller-visible data; they do not trust counts from previously saved reports.

Supported metadata includes coarse profiles 5, 7 (MEL/FEL evidence), and 8; integer/float polynomial and MMR traversal; CM2.9 levels 1/2/4/5/6/255 and CM4 levels 3/8/9/10/11/253/254, including supported variable lengths. Unknown levels, profile 4, polynomial interpolation and unsupported grammar return explicit errors. Mapping reuse does not inherit DM. Compressed DM retains present extensions but its absent base/mastering fields stay unknown. Profile 8 is never labelled 8.1/8.4 from RPU evidence alone. L1 values are preserved independently in 0–4095.

Video collection validates base-layer Main/Main10 HEVC header syntax and orders pictures through POC/reference/IRAP rules. It accepts type-62 RPUs in an explicitly delimited prefix or picture suffix, including a suffix after EOS. It retains gaps and rejects duplicate/orphan RPUs. Dolby Vision type-63 enhancement envelopes are validated as transport and skipped; they are not decoded or counted as extra pictures. Nonzero HEVC layers and separate BL/EL tracks are unsupported. This does not validate entropy-coded image bodies or reconstruct profile-7 video.

MP4, TS/M2TS, Blu-ray playlists, AV1, encrypted/compressed tracks, video decoding, RPU editing/conversion/injection, a production CLI, and upbrr integration are outside this release. The library does not validate artistic grading or synchronization.

Timeline, reports and raw output

Standalone input uses rpu_sequence: records remain in stored order, the x axis is RPU index, picture count is zero, and video coverage is unknown. Video uses presentation_frames, including pictures with no metadata. Records are stored in presentation order and referenced once by containing pictures; HasRPU=false represents a gap. Suppressed-picture RPUs are parsed and counted as observed/suppressed, then excluded from retained records and inventories.

Coverage is complete, partial, absent, or unknown. A completed sparse or zero-RPU video is a successful structural analysis. Interrupted scans return no completed result. No L1 yields ErrNoL1 from rendering; zero refresh flags are valid. Refresh flags are metadata markers, not independently detected scene cuts.

KeepRaw retains escaped payload bytes without the HEVC header. WriteRPU validates retained metadata/raw consistency and writes four-byte delimiters. It refuses sparse video instead of hiding its gaps. Delimiter padding and trailing framing zeros are discarded; the escaped payload through its terminator is unchanged. Original raw bytes are optional for all analysis/plot operations.

Reports distinguish source PQ mastering values, L6 mastering/CLL/FALL, L1-derived statistics, display/content blocks, trims and profile evidence. The L1 mean is computed in PQ space before conversion to nits. Missing samples are excluded. L5 ranges preserve missing intervals. Geometry is unknown unless the caller supplies established applicable raster coordinates; display aspect ratio is not sufficient evidence. This library makes no decoded black-bar comparison.

Block.Values follows the pinned wire field order, with Length preserving optional-field presence: L2 target then slope/offset/power/chroma/saturation/signed MS weight; L8 target index, six trim fields, optional mid contrast/clip trim, six saturation and six hue bytes; L9 source primary index then optional RGB/white xy; L10 target index/max PQ/min PQ/primary index then optional RGB/white xy; L11 four encoded bytes; L254 mode/version. L253 validates filler bytes and records length. L1/L5/L6 convenience fields must agree with their blocks.

Plots follow dovi_tool's L1 graph: opaque 3000×1200 images, maximum/average/minimum filled areas, normalized-PQ coordinates with nit-labelled ticks, observed-peak scaling, and upstream statistics and metadata captions. Samples are drawn directly without downsampling. Crops select inclusive original indices (nil end differs from zero), then display frame numbers from zero as the original tool does. Statistics describe the selected range. These are metadata visualizations, not measured pixel luminance.

The embedded Go font, text spacing, antialiasing and integer alpha blending can differ from Rust; PNG bytes are not identical. Long captions are fitted to avoid overlap. Missing metadata remains a gap and is disclosed, rather than replaced by synthetic L1 values. Unknown compressed mastering fields remain unknown. Unlike the original Plotters tick generator, peaks below 100 nits do not hang; all-zero metadata uses a finite one-code PQ range. See plot provenance for original-source numerical and image comparisons.

Full-analysis persistence contract (version 1)

Persist the entire dovi.Analysis, not just Summary: schema version, timeline, completion, TrackNumber, every picture's AU/decode ordinal, POC and known flag, exact PTS value/timescale/valid flag, RPU presence/index, all retained RPU header, DM/extension fields and presence pointers, and observed/suppressed counts. Optional base fields must remain absent, not become zero-valued facts. Do not infer an initial scene marker or carry L1/L5 across gaps. A separate raw sidecar is required only if raw export must be restored. No private source paths belong in this schema.

Consumers own serialization and bounded decoding. Bound the encoded bytes before decoding, bound nested collections during decoding for untrusted large input, reject unknown versions, then call Analysis.Validate before reporting or rendering. Recompute summary counts, inventories, scene/L5 ranges and statistics with Summarize; never accept serialized derived counts as authority. Preserve completed partial coverage separately from interrupted work. Integration must test a full write/read round trip for exact rational timestamps (including valid zero), gaps, absent L1/base DM, zero refresh flags, reports and rendered pixels. The tests here exercise that contract with Go JSON; no application cache or serializer is included.

Limits and validation

Zero options choose finite defaults: 2,000,000 records/pictures, 512 MiB extractor accounting, 128 MiB container index, 64 MiB blocks, 64 KiB parameter/slice prefixes, 1 MiB RPUs, 64 reordered pictures (also constrained by syntax), depth 32 and 256 extension blocks per RPU. Storage sublimits cannot exceed the operation limit. These are allocation-accounting limits, not process RSS. MemoryBudget can measure or constrain active extraction work; returned results become caller-owned. Analysis/summary each have separate 512 MiB default budgets. Rendering has its own 48 MiB budget. Retaining multiple results/images and caller PNG buffers can exceed any one operation's budget.

go mod verify
gofmt -l .
go vet ./...
go test ./...
go test -race ./...
$env:CGO_ENABLED='0'
go test ./...
go test -run '^$' -bench . -benchmem ./...

Normal tests are offline and use authored syntax fixtures, not private media. Optional differential tests use the pinned development oracle documented in extract/testdata/PROVENANCE.md. See THIRD_PARTY_NOTICES.md for source mappings.

Documentation

Overview

Package dovi reads Dolby Vision metadata without decoding video pictures.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidBitstream = errors.New("invalid Dolby Vision bitstream")
	ErrUnsupported      = errors.New("unsupported syntax or input")
	ErrIncomplete       = errors.New("incomplete input")
	ErrLimit            = errors.New("resource limit exceeded")
	ErrNoL1             = errors.New("no L1 metadata")
	ErrInvalidMetadata  = errors.New("invalid caller metadata")
	ErrInvalidOptions   = errors.New("invalid options")
)

Functions

func NitsToPQ

func NitsToPQ(n float64) (float64, error)

NitsToPQ converts finite luminance in [0,10000] cd/m² to ST 2084.

func PQToNits

func PQToNits(p float64) (float64, error)

PQToNits converts finite normalized ST 2084 values in [0,1] to cd/m².

func Render

func Render(ctx context.Context, a *Analysis, o PlotOptions) (image.Image, error)

Render returns an opaque 3000×1200 dovi_tool-style L1 plot in PQ coordinates. Missing samples remain gaps rather than synthetic brightness measurements.

Example
package main

import (
	"bytes"
	"context"
	"fmt"

	dovi "github.com/Audionut/go-dovi-extract"
	"github.com/Audionut/go-dovi-extract/extract"

	f "github.com/Audionut/go-dovi-extract/internal/testfixture"
	"image/png"
)

func main() {
	ctx := context.Background()
	e, err := extract.ReadRPU(ctx, bytes.NewReader(f.File(f.Options{Max: 3000, Avg: 1500})), extract.Options{})
	if err != nil {
		panic(err)
	}
	a, err := e.Analysis(dovi.AnalysisOptions{})
	if err != nil {
		panic(err)
	}
	s, err := dovi.Summarize(a, dovi.SummaryOptions{})
	if err != nil {
		panic(err)
	}
	img, err := dovi.Render(ctx, a, dovi.PlotOptions{})
	if err != nil {
		panic(err)
	}
	var encoded bytes.Buffer
	if err := png.Encode(&encoded, img); err != nil {
		panic(err)
	}
	fmt.Println(s.Coverage, img.Bounds().Dx(), img.Bounds().Dy())
}
Output:
unknown 3000 1200

Types

type ActiveAreaRange

type ActiveAreaRange struct {
	Start, End                uint64
	Offsets                   *L5
	Geometry                  string
	ActiveWidth, ActiveHeight uint32
	CoordinateBasis           string
}

ActiveAreaRange is an inclusive interval of original timeline indices with equal L5 offsets. Nil Offsets means missing metadata; active dimensions require an applicable Raster.

type Analysis

type Analysis struct {
	SchemaVersion  int       `json:"schema_version"`
	Timeline       Timeline  `json:"timeline"`
	Complete       bool      `json:"complete"`
	TrackID        uint64    `json:"track_id"`
	Records        []RPU     `json:"records"`
	Pictures       []Picture `json:"pictures"`
	ObservedRPUs   uint64    `json:"observed_rpus"`
	SuppressedRPUs uint64    `json:"suppressed_rpus"`
}

Analysis is a complete, owned metadata timeline: schema 1 for files and RPU sequences, schema 2 for playlist frames. Counts and inventories are derived by Summarize, never trusted from a serialized report. Standalone sequences have no Pictures. Raw bytes are intentionally separate.

func (*Analysis) Clone

func (a *Analysis) Clone(opts AnalysisOptions) (*Analysis, error)

Clone returns independently owned data after a finite-budget preflight.

func (*Analysis) RetainedBytes

func (a *Analysis) RetainedBytes(opts AnalysisOptions) (int64, error)

RetainedBytes validates the complete model and reports its conservative owned storage bound. It excludes caller aliases and Go runtime overhead.

func (*Analysis) Validate

func (a *Analysis) Validate(opts AnalysisOptions) error

Validate preflights caller-visible sizes and validates the full timeline. Complete sparse scans are valid; interrupted scans never are.

type AnalysisOptions

type AnalysisOptions struct {
	MaxRecords int
	MaxBytes   int64
}

AnalysisOptions bounds newly owned storage per call, not process RSS.

type BaseDM

type BaseDM struct {
	YCCToRGB                                      [9]int16
	YCCOffsets                                    [3]uint32
	RGBToLMS                                      [9]int16
	EOTF, EOTFParam0, EOTFParam1                  uint16
	EOTFParam2                                    uint32
	BitDepth, ColorSpace, ChromaFormat, FullRange uint8
	SourceMinPQ, SourceMaxPQ, SourceDiagonal      uint16
}

BaseDM is absent for compressed DM, whose omitted values are unknown.

type Block

type Block struct {
	Level  uint8
	Length uint16
	Values []int64
}

Block retains validated extension fields in their documented wire order. Length distinguishes optional absent fields from encoded zero values. L2's final signed 13-bit field is sign extended into Values.

type Counts

type Counts struct {
	OutputPictures  uint64 `json:"output_pictures"`
	PicturesWithRPU uint64 `json:"pictures_with_rpu"`
	RetainedRPUs    uint64 `json:"retained_rpus"`
	L1Samples       uint64 `json:"l1_samples"`
	MissingPictures uint64 `json:"missing_rpu_pictures"`
	ObservedRPUs    uint64 `json:"observed_source_rpus"`
	SuppressedRPUs  uint64 `json:"suppressed_picture_rpus"`
}

Counts separates retained output metadata from observed and suppressed source RPUs.

type Coverage

type Coverage string

Coverage describes RPU presence on the output picture timeline; standalone sequences have unknown coverage.

const (
	CoverageComplete Coverage = "complete"
	CoveragePartial  Coverage = "partial"
	CoverageAbsent   Coverage = "absent"
	CoverageUnknown  Coverage = "unknown"
)

type DM

type DM struct {
	Compressed                          bool
	AffectedID, CurrentID, SceneRefresh uint64
	Base                                *BaseDM
	CM29, CM40                          bool
	Blocks                              []Block
	L1                                  *L1
	L5                                  *L5
	L6                                  *L6
}

DM owns display-management metadata for one RPU. Nil level pointers mean absent metadata; compressed records do not inherit missing fields from earlier records.

type EnhancementInventory

type EnhancementInventory struct {
	Type  string `json:"type"`
	Count uint64 `json:"count"`
}
type Header struct {
	Format                                          uint16
	Profile, Level                                  uint8
	SequenceInfo, ChromaFilter                      bool
	CoefficientType                                 uint8
	CoefficientDenominator                          uint8
	NormalizedIDC                                   uint8
	FullRange                                       bool
	BLBitDepth, ELBitDepth, VDRBitDepth             uint8
	ExtendedMapping                                 uint8
	SpatialFilter, ELSpatialFilter, DisableResidual bool
	Reserved                                        uint8
	DMPresent, ReuseMapping                         bool
	PreviousID                                      uint64
}

Header preserves the evidence used to infer the coarse Dolby Vision profile. Profile 8 does not establish a base-layer compatibility subprofile.

type Inventory

type Inventory struct {
	Value uint64 `json:"value"`
	Count uint64 `json:"count"`
}

type L1

type L1 struct{ MinPQ, MaxPQ, AvgPQ uint16 }

L1 contains metadata luminance codes on the 12-bit PQ scale, not decoded-pixel measurements.

type L1Statistics

type L1Statistics struct {
	Samples            uint64  `json:"samples"`
	MinimumNits        float64 `json:"minimum_nits"`
	PeakNits           float64 `json:"peak_nits"`
	MaximumAverageNits float64 `json:"maximum_average_nits"`
	PQMeanPeakNits     float64 `json:"pq_mean_peak_nits"`
	PQMeanAverageNits  float64 `json:"pq_mean_average_nits"`
}

L1Statistics summarizes native L1 metadata in nits. PQMean fields average PQ codes before conversion; they are not arithmetic means of linear luminance.

type L5

type L5 struct{ Left, Right, Top, Bottom uint16 }

L5 contains active-area offsets in the metadata's pixel coordinate basis. Applying them to a raster requires an independently established coordinate relationship.

type L6

type L6 struct{ MaxMastering, MinMastering, MaxCLL, MaxFALL uint16 }

L6 units are those encoded on the wire: maximum luminance/CLL/FALL in cd/m² and minimum mastering luminance in 0.0001 cd/m².

type Mastering

type Mastering struct {
	SourceMinPQ, SourceMaxPQ uint16
	Count                    uint64
}

type MasteringL6

type MasteringL6 struct {
	Values L6
	Count  uint64
}

type MetadataInventory

type MetadataInventory struct {
	Block Block  `json:"block"`
	Count uint64 `json:"count"`
}

type ParseError

type ParseError struct {
	Field       string
	BitOffset   int
	OffsetKnown bool
	Err         error
}

ParseError supplies a field and zero-based bit offset in the unescaped RPU. OffsetKnown is false for errors without a single syntactic location.

func (*ParseError) Error

func (e *ParseError) Error() string

func (*ParseError) Unwrap

func (e *ParseError) Unwrap() error

type ParseOptions

type ParseOptions struct{ MaxBytes, MaxBlocks int }

ParseOptions selects finite per-record limits. Zero selects the defaults.

type Picture

type Picture struct {
	Stream                   StreamKey
	OccurrenceOrdinal        uint64
	AUOrdinal, DecodeOrdinal uint64
	POC                      int64
	POCKnown                 bool
	PTS                      Timestamp
	RPUIndex                 uint32
	HasRPU                   bool
}

Picture maps one output presentation entry to source syntax and optional RPU metadata. RPUIndex is meaningful only when HasRPU is true. Playlist occurrences may repeat source pictures.

type PlotOptions

type PlotOptions struct {
	Title         string
	Start, End    *uint64
	MaxBytes      int64
	MaxInputBytes int64
	MaxRecords    int
}

PlotOptions uses inclusive original timeline indices; nil End differs from an explicit zero. The displayed crop begins at frame zero, like dovi_tool. MaxBytes bounds rendering allocations, excluding input and caller PNG encoding. MaxInputBytes bounds the input model's conservative accounting separately. Zero limits choose 48 MiB for rendering and 512 MiB for input.

type RPU

type RPU struct {
	Header           Header
	Profile          uint8
	EnhancementLayer string
	MappingID        uint64
	DM               *DM
	Remaining        []byte
	CRC              uint32
}

RPU owns all its metadata. Unknown enhancement-layer classification is ""; reuse does not inherit metadata or mapping state from an earlier record.

func ParseNAL

func ParseNAL(nal []byte, opts ParseOptions) (RPU, error)

ParseNAL reads an escaped type-62 NAL, including its two-byte HEVC header, without a start code. Caller memory is borrowed only for this call.

func ParseRPU

func ParseRPU(data []byte, opts ParseOptions) (RPU, error)

ParseRPU reads one unescaped payload beginning with 0x19 and ending with CRC-32/MPEG-2 and 0x80. Permitted trailing zero bytes are discarded.

Example
package main

import (
	"fmt"

	dovi "github.com/Audionut/go-dovi-extract"

	f "github.com/Audionut/go-dovi-extract/internal/testfixture"
)

func main() {
	r, err := dovi.ParseRPU(f.RPU(f.Options{Max: 3000, Avg: 1500}), dovi.ParseOptions{})
	if err != nil {
		panic(err)
	}
	fmt.Println(r.Profile, r.DM.L1.MaxPQ, r.DM.Base == nil)
}
Output:
8 3000 true

type Raster

type Raster struct {
	Width, Height   uint32
	CoordinateBasis string
	Applicable      bool
}

Raster is supplied only when the L5 coordinate relationship is established. Display aspect ratio or container display dimensions do not establish it.

type StreamKey

type StreamKey struct {
	SourceID, ClipID string
	TrackID          uint64
}

StreamKey distinguishes a source/clip stream using its container-owned track identity.

type Summary

type Summary struct {
	ProfileEvidence   string                 `json:"profile_evidence"`
	EnhancementLayers []EnhancementInventory `json:"enhancement_layers"`
	DisplayAndContent []MetadataInventory    `json:"display_and_content_metadata"`
	SchemaVersion     int                    `json:"schema_version"`
	Timeline          Timeline               `json:"timeline"`
	Complete          bool                   `json:"complete"`
	TrackID           uint64                 `json:"track_id"`
	Coverage          Coverage               `json:"coverage"`
	Counts            Counts                 `json:"counts"`
	Profiles          []Inventory            `json:"profiles"`
	CMVersions        []Inventory            `json:"cm_versions"`
	Levels            []Inventory            `json:"levels"`
	Trims             []TrimTarget           `json:"trim_targets"`
	SceneMarkers      []uint64               `json:"scene_refresh_indices"`
	SourceMastering   []Mastering            `json:"source_mastering_pq"`
	L6Mastering       []MasteringL6          `json:"l6_mastering"`
	ActiveAreas       []ActiveAreaRange      `json:"l5_ranges"`
	L1                *L1Statistics          `json:"l1,omitempty"`
	Warnings          []Warning              `json:"warnings"`
}

Summary contains derived observations from validated retained output metadata. Scene markers and luminance describe metadata, not independently decoded pictures.

func Summarize

func Summarize(a *Analysis, opts SummaryOptions) (Summary, error)

Summarize reports only retained output metadata. Metadata refresh markers are not independently detected scene cuts; luminance is not measured pixels.

type SummaryOptions

type SummaryOptions struct {
	MaxRecords int
	MaxBytes   int64
	Raster     Raster
}

SummaryOptions bounds input validation and derived-output accounting and optionally supplies an established L5 raster. Zero limits use the finite AnalysisOptions defaults.

type Timeline

type Timeline string

Timeline identifies whether indices address standalone RPUs, presentation pictures or playlist occurrences.

const (
	RPUSequence        Timeline = "rpu_sequence"
	PresentationFrames Timeline = "presentation_frames"
	PlaylistFrames     Timeline = "playlist_frames"
)

type Timestamp

type Timestamp struct {
	Value     int64
	Timescale uint64
	Valid     bool
}

Timestamp is an exact rational time Value/Timescale. Valid false means unknown; a known timestamp requires a nonzero timescale and may have a negative value.

type TrimTarget

type TrimTarget struct {
	Level  uint8  `json:"level"`
	Target uint16 `json:"target"`
	Count  uint64 `json:"count"`
}

TrimTarget counts encoded L2 target-PQ values or L8 target indices without converting their units.

type Warning

type Warning struct {
	Index uint64 `json:"index"`
	Code  string `json:"code"`
}

Directories

Path Synopsis
SPDX-License-Identifier: MIT Playlist assembly adapted from go-hdr10-plus, Copyright (c) 2026 Audionut.
SPDX-License-Identifier: MIT Playlist assembly adapted from go-hdr10-plus, Copyright (c) 2026 Audionut.
internal
testfixture
Package testfixture authors synthetic syntax, not encoded video pictures.
Package testfixture authors synthetic syntax, not encoded video pictures.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL