video config construction refactoring
This commit is contained in:
+94
-68
@@ -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
|
||||
}
|
||||
|
||||
@@ -2,8 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"mxl-pattern-generator/internal/flowdef"
|
||||
)
|
||||
|
||||
func TestParseArgsHelpStopsBeforeValidation(t *testing.T) {
|
||||
@@ -99,3 +103,62 @@ func TestListPatternsIsSorted(t *testing.T) {
|
||||
t.Fatalf("patterns are not sorted: %q", text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildVideoConfigFromArgs(t *testing.T) {
|
||||
args := appArgs{
|
||||
videoUUID: "5fbec3b1-1b0f-417d-9059-8b94a47197ed",
|
||||
videoWidth: 1920,
|
||||
videoHeight: 1080,
|
||||
videoFPS: "29.97",
|
||||
pattern: "ebu75",
|
||||
}
|
||||
|
||||
cfg, err := buildVideoConfig(args)
|
||||
if err != nil {
|
||||
t.Fatalf("buildVideoConfig: %v", err)
|
||||
}
|
||||
if cfg.Width() != 1920 || cfg.Height() != 1080 {
|
||||
t.Fatalf("dimensions = %dx%d, want 1920x1080", cfg.Width(), cfg.Height())
|
||||
}
|
||||
if cfg.Rate().Num != 30000 || cfg.Rate().Den != 1001 {
|
||||
t.Fatalf("rate = %d/%d, want 30000/1001", cfg.Rate().Num, cfg.Rate().Den)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildVideoConfigFromFile(t *testing.T) {
|
||||
definition, err := flowdef.NewV210Video(
|
||||
"5fbec3b1-1b0f-417d-9059-8b94a47197ed",
|
||||
3840,
|
||||
2160,
|
||||
flowdef.Rational{Numerator: 60000, Denominator: 1001},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewV210Video: %v", err)
|
||||
}
|
||||
data, err := json.Marshal(definition)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Marshal: %v", err)
|
||||
}
|
||||
path := t.TempDir() + "/video.json"
|
||||
if err := os.WriteFile(path, data, 0o600); err != nil {
|
||||
t.Fatalf("os.WriteFile: %v", err)
|
||||
}
|
||||
|
||||
cfg, err := buildVideoConfig(appArgs{
|
||||
videoFlowDefFile: path,
|
||||
pattern: "smpte",
|
||||
// These values must be ignored when a definition file is supplied.
|
||||
videoWidth: 1920,
|
||||
videoHeight: 1080,
|
||||
videoFPS: "25",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("buildVideoConfig: %v", err)
|
||||
}
|
||||
if cfg.Width() != 3840 || cfg.Height() != 2160 {
|
||||
t.Fatalf("dimensions = %dx%d, want file values 3840x2160", cfg.Width(), cfg.Height())
|
||||
}
|
||||
if cfg.Rate().Num != 60000 || cfg.Rate().Den != 1001 {
|
||||
t.Fatalf("rate = %d/%d, want file value 60000/1001", cfg.Rate().Num, cfg.Rate().Den)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user