video config construction refactoring

This commit is contained in:
Dmitry Sergeev
2026-09-16 21:31:12 +03:00
parent 765aa8d3e1
commit c9bdfec3b0
7 changed files with 406 additions and 199 deletions
+143
View File
@@ -0,0 +1,143 @@
// Package flowdef models and validates MXL flow-definition JSON.
package flowdef
import (
"encoding/json"
"fmt"
"github.com/google/uuid"
)
const (
FormatVideo = "urn:x-nmos:format:video"
FormatAudio = "urn:x-nmos:format:audio"
MediaTypeV210 = "video/v210"
InterlaceProgressive = "progressive"
ColorSpaceBT709 = "BT709"
)
type Common struct {
Description string `json:"description"`
ID string `json:"id"`
Tags map[string][]string `json:"tags"`
Format string `json:"format"`
Label string `json:"label"`
Parents []string `json:"parents"`
MediaType string `json:"media_type"`
}
type Rational struct {
Numerator uint `json:"numerator"`
Denominator uint `json:"denominator"`
}
type Video struct {
Common
GrainRate Rational `json:"grain_rate"`
FrameWidth uint `json:"frame_width"`
FrameHeight uint `json:"frame_height"`
InterlaceMode string `json:"interlace_mode"`
ColorSpace string `json:"colorspace"`
Components []VideoComponent `json:"components"`
}
type Audio struct {
Common
SampleRate Rational `json:"sample_rate"`
ChannelCount uint `json:"channel_count"`
BitDepth uint `json:"bit_depth"`
}
type VideoComponent struct {
Name string `json:"name"`
Width uint `json:"width"`
Height uint `json:"height"`
BitDepth uint `json:"bit_depth"`
}
func NewV210Video(id string, width, height uint, rate Rational) (Video, error) {
definition := Video{
Common: Common{
Description: "go-mxl-pattern-gen generated video",
ID: id,
Tags: map[string][]string{
"urn:x-nmos:tag:grouphint/v1.0": {"go-mxl-pattern-gen:Video"},
},
Format: FormatVideo,
Label: "go-mxl-pattern-gen generated video",
Parents: []string{},
MediaType: MediaTypeV210,
},
GrainRate: rate,
FrameWidth: width,
FrameHeight: height,
InterlaceMode: InterlaceProgressive,
ColorSpace: ColorSpaceBT709,
Components: []VideoComponent{
{Name: "Y", Width: width, Height: height, BitDepth: 10},
{Name: "Cb", Width: width / 2, Height: height, BitDepth: 10},
{Name: "Cr", Width: width / 2, Height: height, BitDepth: 10},
},
}
if err := definition.Validate(); err != nil {
return Video{}, err
}
return definition, nil
}
func ParseV210Video(data []byte) (Video, error) {
var definition Video
if err := json.Unmarshal(data, &definition); err != nil {
return Video{}, fmt.Errorf("decode video flow definition: %w", err)
}
if err := definition.Validate(); err != nil {
return Video{}, fmt.Errorf("invalid video flow definition: %w", err)
}
return definition, nil
}
func (v Video) Validate() error {
if err := uuid.Validate(v.ID); err != nil {
return fmt.Errorf("invalid id %q: %w", v.ID, err)
}
if v.Format != FormatVideo {
return fmt.Errorf("format must be %q, got %q", FormatVideo, v.Format)
}
if v.MediaType != MediaTypeV210 {
return fmt.Errorf("media_type must be %q, got %q", MediaTypeV210, v.MediaType)
}
if v.InterlaceMode != InterlaceProgressive {
return fmt.Errorf("interlace_mode must be %q, got %q", InterlaceProgressive, v.InterlaceMode)
}
if v.ColorSpace != ColorSpaceBT709 {
return fmt.Errorf("colorspace must be %q, got %q", ColorSpaceBT709, v.ColorSpace)
}
if v.FrameWidth == 0 || v.FrameWidth%6 != 0 {
return fmt.Errorf("frame_width must be greater than zero and divisible by 6, got %d", v.FrameWidth)
}
if v.FrameHeight == 0 {
return fmt.Errorf("frame_height must be greater than zero")
}
if v.GrainRate.Numerator == 0 || v.GrainRate.Denominator == 0 {
return fmt.Errorf("grain_rate numerator and denominator must be greater than zero, got %d/%d",
v.GrainRate.Numerator, v.GrainRate.Denominator)
}
want := []VideoComponent{
{Name: "Y", Width: v.FrameWidth, Height: v.FrameHeight, BitDepth: 10},
{Name: "Cb", Width: v.FrameWidth / 2, Height: v.FrameHeight, BitDepth: 10},
{Name: "Cr", Width: v.FrameWidth / 2, Height: v.FrameHeight, BitDepth: 10},
}
if len(v.Components) != len(want) {
return fmt.Errorf("v210 requires %d components, got %d", len(want), len(v.Components))
}
for i := range want {
if v.Components[i] != want[i] {
return fmt.Errorf("component %d must be %+v, got %+v", i, want[i], v.Components[i])
}
}
return nil
}