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
+94 -68
View File
@@ -4,7 +4,7 @@
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
@@ -18,8 +18,9 @@ import (
"github.com/qvest-digital/go-mxl/mxl"
"github.com/spf13/pflag"
flowdef "mxl-pattern-generator/internal/flow-def"
"mxl-pattern-generator/internal/flowdef"
"mxl-pattern-generator/internal/generator"
"mxl-pattern-generator/internal/video"
)
const (
@@ -128,6 +129,9 @@ func validateFlowDefPath(label, path string) error {
}
func validateVideoArgs(args *appArgs) error {
if _, exists := patterns[args.pattern]; !exists {
return fmt.Errorf("unknown video pattern %q (use --list-patterns to see available patterns)", args.pattern)
}
if args.videoFlowDefFile != "" {
return nil
}
@@ -142,9 +146,6 @@ func validateVideoArgs(args *appArgs) error {
return fmt.Errorf("unsupported video FPS %q (supported: %s); use a flow definition for other rates",
args.videoFPS, sortedMapKeys(frameRates))
}
if _, exists := patterns[args.pattern]; !exists {
return fmt.Errorf("unknown video pattern %q (use --list-patterns to see available patterns)", args.pattern)
}
if args.videoUUID == "" {
args.videoUUID = uuid.NewString()
return nil
@@ -356,6 +357,59 @@ func parseArgs(argv []string, stdout, stderr io.Writer) (parseResult, error) {
return parseResult{args: args, shouldRun: true}, nil
}
func buildVideoConfig(args appArgs) (video.Config, error) {
var definition flowdef.Video
if args.videoFlowDefFile != "" {
data, err := os.ReadFile(args.videoFlowDefFile)
if err != nil {
return video.Config{}, fmt.Errorf(
"read video flow definition %q: %w",
args.videoFlowDefFile,
err,
)
}
definition, err = flowdef.ParseV210Video(data)
if err != nil {
return video.Config{}, fmt.Errorf(
"parse video flow definition %q: %w",
args.videoFlowDefFile,
err,
)
}
} else {
rate, ok := frameRates[args.videoFPS]
if !ok {
return video.Config{}, fmt.Errorf("unsupported video FPS %q", args.videoFPS)
}
var err error
definition, err = flowdef.NewV210Video(
args.videoUUID,
args.videoWidth,
args.videoHeight,
flowdef.Rational{
Numerator: uint(rate.Num),
Denominator: uint(rate.Den),
},
)
if err != nil {
return video.Config{}, fmt.Errorf(
"build video flow definition: %w",
err,
)
}
}
if _, ok := patterns[args.pattern]; !ok {
return video.Config{}, fmt.Errorf("unknown video pattern %q", args.pattern)
}
return video.Config{
Definition: definition,
Pattern: args.pattern,
}, nil
}
func main() {
parsed, err := parseArgs(os.Args[1:], os.Stdout, os.Stderr)
if err != nil {
@@ -368,50 +422,28 @@ func main() {
}
args := parsed.args
type videoInfo struct {
uuid string
width uint
height uint
fps mxl.Rational
}
var vi videoInfo
var videoFlowDef string
if args.videoFlowDefFile == "" {
args.videoUUID = "8f1d2a4b-6c3e-4f5a-9b2c-1d7e8a3f0b5d" // TODO: remove before public release
vi = videoInfo{
uuid: args.videoUUID,
width: args.videoWidth,
height: args.videoHeight,
fps: frameRates[args.videoFPS],
}
flowDef, err := flowdef.NewFlowDefJSON(
flowdef.TYPE_VIDEO,
vi.uuid,
vi.width,
vi.height,
uint(vi.fps.Num),
uint(vi.fps.Den),
)
videoFlowDef = flowDef
if err != nil {
log.Fatalf("Could not create Flow Definition: %v", err)
}
} else {
flowDef, err := flowdef.ReadFlowDefFile(args.videoFlowDefFile)
if err != nil {
log.Fatalf("Could not read video flow def .json: %s. Reason: %v", args.videoFlowDefFile, err)
}
videoFlowDef = flowDef
videoCfg, err := buildVideoConfig(args)
if err != nil {
log.Fatalf("video configuration: %v", err)
}
log.Printf("%s %s", APP_NAME, APP_VER)
log.Printf("Domain: %s", args.domain)
log.Printf("Video: %dx%d %d/%d", vi.width, vi.height, vi.fps.Num, vi.fps.Den)
log.Printf("Video UUID: %s", vi.uuid)
log.Printf("Video: %dx%d %d/%d",
videoCfg.Width(),
videoCfg.Height(),
videoCfg.Rate().Num,
videoCfg.Rate().Den,
)
log.Printf("Video ID: %s", videoCfg.ID())
// TODO: if init failed -> CPU generator
videoPattern := patterns[args.pattern]
gen, err := generator.NewWGPUGenerator(vi.width, vi.height, videoPattern.kernelPath)
videoPattern := patterns[videoCfg.Pattern]
gen, err := generator.NewWGPUGenerator(
videoCfg.Width(),
videoCfg.Height(),
videoPattern.kernelPath,
)
if err != nil {
log.Fatalf("wgpu init failed: %v", err)
}
@@ -427,15 +459,16 @@ func main() {
}
defer face.Close()
if args.overlayX < 0 ||
args.overlayX > int(vi.width) ||
args.overlayX > int(videoCfg.Width()) ||
args.overlayY < 0 ||
args.overlayY > int(vi.height) {
log.Fatalf("overlay X/Y pos can't be negative or greater, than video width/height")
args.overlayY > int(videoCfg.Height()) {
log.Fatalf("text overlay position (%d, %d) is outside the %dx%d video frame",
args.overlayX, args.overlayY, videoCfg.Width(), videoCfg.Height())
}
overlay, err = generator.NewTextOverlay(
args.textOverlay,
int(vi.width),
int(vi.height),
int(videoCfg.Width()),
int(videoCfg.Height()),
args.overlayX,
args.overlayY,
args.overlayPos,
@@ -448,16 +481,21 @@ func main() {
inst, err := mxl.NewInstance(args.domain, "")
if err != nil {
log.Fatalf("MXL Init Failed: %v", err)
log.Fatalf("initialize MXL domain %q: %v", args.domain, err)
}
defer inst.Close()
writer, isCreated, err := inst.NewWriter(videoFlowDef)
flowJSON, err := json.Marshal(videoCfg.Definition)
if err != nil {
log.Fatalf("Failed to create MXL writer: %v", err)
log.Fatalf("marshal video flow definition: %v", err)
}
writer, isCreated, err := inst.NewWriter(string(flowJSON))
if err != nil {
log.Fatalf("create video writer: %v", err)
}
if !isCreated {
log.Printf("reusing existing flow: %s, domain: %s", vi.uuid, args.domain)
log.Printf("reusing existing flow: %s, domain: %s", videoCfg.ID(), args.domain)
}
defer writer.Close()
@@ -484,18 +522,18 @@ func main() {
gwa, err := writer.OpenGrain(idx)
if err != nil {
log.Fatalf("OpenGrain(%d): %v", idx, err)
log.Fatalf("open video grain %d: %v", idx, err)
}
if err := gen.GenerateFrame(gwa.Payload, int(tick)); err != nil {
log.Fatalf("GenerateFrame(%d): %v", idx, err)
log.Fatalf("generate frame for grain %d: %v", idx, err)
}
if overlay != nil {
if err := overlay.ApplyV210(gwa.Payload); err != nil {
log.Fatalf("text overlay: %v", err)
log.Fatalf("apply text overlay to grain %d: %v", idx, err)
}
}
if err := gwa.Commit(gwa.TotalSlices, 0); err != nil {
log.Fatalf("Commit(%d): %v", idx, err)
log.Fatalf("commit video grain %d: %v", idx, err)
}
grainsWritten++
@@ -508,15 +546,3 @@ func main() {
mxl.SleepNs(mxl.NsUntilIndex(idx, rate))
}
}
func run(ctx context.Context, args []string) error {
return nil
}
func runVideo(ctx context.Context) error {
return nil
}
func runAudio(ctx context.Context) error {
return nil
}