--no-video flag
This commit is contained in:
+30
-9
@@ -47,6 +47,7 @@ type appArgs struct {
|
||||
videoHeight uint
|
||||
videoFPS string
|
||||
videoUUID string
|
||||
noVideo bool
|
||||
|
||||
audioChannels uint8
|
||||
audioSamplingFreq string
|
||||
@@ -84,6 +85,7 @@ func printHelp(w io.Writer, fs *pflag.FlagSet) {
|
||||
fmt.Fprintln(w, "Usage: mxl-gen -d <domain> [-v <flowDef.json>] [-a <flowDef.json>] [options]")
|
||||
fmt.Fprintln(w, " or: mxl-gen -d <domain> [--width <width px>] [--height <height px>] [--fps <framerate>] \\")
|
||||
fmt.Fprintln(w, " [-c <channels amount>] [-f <sample rate>]")
|
||||
fmt.Fprintln(w, " or: mxl-gen -d <domain> --no-video (-c <channels amount> | -a <flowDef.json>)")
|
||||
fmt.Fprintln(w, " or: mxl-gen -d <domain> with default params")
|
||||
fmt.Fprintln(w, "Video and audio feed parameters are ignored when a flow definition file is provided.")
|
||||
fmt.Fprintln(w)
|
||||
@@ -92,7 +94,7 @@ func printHelp(w io.Writer, fs *pflag.FlagSet) {
|
||||
}
|
||||
|
||||
func printUsage(w io.Writer) {
|
||||
fmt.Fprintln(w, "Usage: mxl-gen -d <domain> [-v <flowDef.json>] [-a <flowDef.json>] [options]")
|
||||
fmt.Fprintln(w, "Usage: mxl-gen -d <domain> [--no-video] [-v <flowDef.json>] [-a <flowDef.json>] [options]")
|
||||
fmt.Fprintln(w, "Try 'mxl-gen -h' for more information.")
|
||||
}
|
||||
|
||||
@@ -131,6 +133,9 @@ func validateFlowDefPath(label, path string) error {
|
||||
}
|
||||
|
||||
func validateVideoArgs(args *appArgs) error {
|
||||
if args.noVideo {
|
||||
return nil
|
||||
}
|
||||
if !video.HasPattern(args.pattern) {
|
||||
return fmt.Errorf("unknown video pattern %q (use --list-patterns to see available patterns)", args.pattern)
|
||||
}
|
||||
@@ -158,6 +163,16 @@ func validateVideoArgs(args *appArgs) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateMediaSelection(args appArgs) error {
|
||||
if args.noVideo && args.videoFlowDefFile != "" {
|
||||
return fmt.Errorf("--no-video cannot be used with --video")
|
||||
}
|
||||
if args.noVideo && args.audioFlowDefFile == "" && args.audioChannels == 0 {
|
||||
return fmt.Errorf("--no-video requires audio enabled with --channel or --audio")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAudioArgs(args *appArgs) error {
|
||||
if args.audioFlowDefFile == "" && args.audioChannels == 0 {
|
||||
return nil
|
||||
@@ -189,6 +204,7 @@ func validateAudioArgs(args *appArgs) error {
|
||||
func validateArgs(args *appArgs) error {
|
||||
checks := []func() error{
|
||||
func() error { return validateDomain(args.domain) },
|
||||
func() error { return validateMediaSelection(*args) },
|
||||
func() error { return validateFlowDefPath("video", args.videoFlowDefFile) },
|
||||
func() error { return validateFlowDefPath("audio", args.audioFlowDefFile) },
|
||||
func() error { return validateVideoArgs(args) },
|
||||
@@ -263,10 +279,11 @@ func addFlags(fs *pflag.FlagSet, args *appArgs) {
|
||||
"br - bottom-right corner",
|
||||
)
|
||||
|
||||
fs.UintVar(&args.videoWidth, "width", 1920, "Video pattern width. Zero = no video [TODO: 0 width case]")
|
||||
fs.UintVar(&args.videoWidth, "width", 1920, "Video pattern width")
|
||||
fs.UintVar(&args.videoHeight, "height", 1080, "Video pattern height")
|
||||
fs.StringVar(&args.videoFPS, "fps", "25", "Video pattern FPS")
|
||||
fs.StringVar(&args.videoUUID, "video-id", "", "Video UUID. Will be created, if not provided")
|
||||
fs.BoolVar(&args.noVideo, "no-video", false, "Disable video generation; audio must be enabled")
|
||||
// Audio pattern flags
|
||||
fs.Uint8VarP(&args.audioChannels, "channel", "c", 0, "Amount of audio channels. Each channel: num * 1kHz")
|
||||
fs.StringVarP(&args.audioSamplingFreq, "freq", "f", "48", "Sampling frequency of test audio feed in kHz")
|
||||
@@ -302,12 +319,16 @@ func parseArgs(argv []string, stdout, stderr io.Writer) (parseResult, error) {
|
||||
return parseResult{args: args, shouldRun: true}, nil
|
||||
}
|
||||
|
||||
func buildVideoConfig(args appArgs) (video.Config, error) {
|
||||
func buildVideoConfig(args appArgs) (*video.Config, error) {
|
||||
if args.noVideo {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var definition flowdef.Video
|
||||
if args.videoFlowDefFile != "" {
|
||||
data, err := os.ReadFile(args.videoFlowDefFile)
|
||||
if err != nil {
|
||||
return video.Config{}, fmt.Errorf(
|
||||
return nil, fmt.Errorf(
|
||||
"read video flow definition %q: %w",
|
||||
args.videoFlowDefFile,
|
||||
err,
|
||||
@@ -316,7 +337,7 @@ func buildVideoConfig(args appArgs) (video.Config, error) {
|
||||
|
||||
definition, err = flowdef.ParseV210Video(data)
|
||||
if err != nil {
|
||||
return video.Config{}, fmt.Errorf(
|
||||
return nil, fmt.Errorf(
|
||||
"parse video flow definition %q: %w",
|
||||
args.videoFlowDefFile,
|
||||
err,
|
||||
@@ -325,7 +346,7 @@ func buildVideoConfig(args appArgs) (video.Config, error) {
|
||||
} else {
|
||||
rate, ok := frameRates[args.videoFPS]
|
||||
if !ok {
|
||||
return video.Config{}, fmt.Errorf("unsupported video FPS %q", args.videoFPS)
|
||||
return nil, fmt.Errorf("unsupported video FPS %q", args.videoFPS)
|
||||
}
|
||||
|
||||
var err error
|
||||
@@ -339,17 +360,17 @@ func buildVideoConfig(args appArgs) (video.Config, error) {
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return video.Config{}, fmt.Errorf(
|
||||
return nil, fmt.Errorf(
|
||||
"build video flow definition: %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
}
|
||||
if !video.HasPattern(args.pattern) {
|
||||
return video.Config{}, fmt.Errorf("unknown video pattern %q", args.pattern)
|
||||
return nil, fmt.Errorf("unknown video pattern %q", args.pattern)
|
||||
}
|
||||
|
||||
return video.Config{
|
||||
return &video.Config{
|
||||
Definition: definition,
|
||||
Pattern: args.pattern,
|
||||
Overlay: video.OverlayConfig{
|
||||
|
||||
@@ -95,6 +95,69 @@ func TestValidateAudioArgsSkipsDisabledAudio(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateVideoArgsSkipsDisabledVideo(t *testing.T) {
|
||||
args := appArgs{
|
||||
noVideo: true,
|
||||
pattern: "not-a-pattern",
|
||||
videoWidth: 1,
|
||||
videoFPS: "unsupported",
|
||||
videoUUID: "not-a-uuid",
|
||||
}
|
||||
|
||||
if err := validateVideoArgs(&args); err != nil {
|
||||
t.Fatalf("validateVideoArgs: %v", err)
|
||||
}
|
||||
if args.videoUUID != "not-a-uuid" {
|
||||
t.Fatalf("video UUID changed while video is disabled: %q", args.videoUUID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMediaSelection(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args appArgs
|
||||
wantErrSub string
|
||||
}{
|
||||
{
|
||||
name: "no video with video definition",
|
||||
args: appArgs{noVideo: true, videoFlowDefFile: "video.json", audioChannels: 2},
|
||||
wantErrSub: "cannot be used with --video",
|
||||
},
|
||||
{
|
||||
name: "no flows",
|
||||
args: appArgs{noVideo: true},
|
||||
wantErrSub: "requires audio enabled",
|
||||
},
|
||||
{
|
||||
name: "generated audio only",
|
||||
args: appArgs{noVideo: true, audioChannels: 2},
|
||||
},
|
||||
{
|
||||
name: "external audio only",
|
||||
args: appArgs{noVideo: true, audioFlowDefFile: "audio.json"},
|
||||
},
|
||||
{
|
||||
name: "video enabled by default",
|
||||
args: appArgs{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := validateMediaSelection(tc.args)
|
||||
if tc.wantErrSub == "" {
|
||||
if err != nil {
|
||||
t.Fatalf("validateMediaSelection: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err == nil || !strings.Contains(err.Error(), tc.wantErrSub) {
|
||||
t.Fatalf("error = %v, want substring %q", err, tc.wantErrSub)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPatternsIsSorted(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
listPatterns(&output)
|
||||
@@ -126,6 +189,16 @@ func TestBuildVideoConfigFromArgs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildVideoConfigDisabled(t *testing.T) {
|
||||
cfg, err := buildVideoConfig(appArgs{noVideo: true})
|
||||
if err != nil {
|
||||
t.Fatalf("buildVideoConfig: %v", err)
|
||||
}
|
||||
if cfg != nil {
|
||||
t.Fatalf("config = %+v, want nil for disabled video", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildVideoConfigFromFile(t *testing.T) {
|
||||
definition, err := flowdef.NewV210Video(
|
||||
"5fbec3b1-1b0f-417d-9059-8b94a47197ed",
|
||||
|
||||
+22
-9
@@ -19,22 +19,27 @@ const (
|
||||
|
||||
type Config struct {
|
||||
Domain string
|
||||
Video video.Config
|
||||
Video *video.Config
|
||||
Audio *audio.Config
|
||||
}
|
||||
|
||||
func Run(ctx context.Context, cfg Config) (runErr error) {
|
||||
if err := validateConfig(cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("%s %s", Name, Version)
|
||||
log.Printf("Domain: %s", cfg.Domain)
|
||||
log.Printf("Video: %dx%d %d/%d",
|
||||
cfg.Video.Width(), cfg.Video.Height(), cfg.Video.Rate().Num, cfg.Video.Rate().Den)
|
||||
log.Printf("Video ID: %s", cfg.Video.ID())
|
||||
if cfg.Video != nil {
|
||||
log.Printf("Video: %dx%d %d/%d",
|
||||
cfg.Video.Width(), cfg.Video.Height(), cfg.Video.Rate().Num, cfg.Video.Rate().Den)
|
||||
log.Printf("Video ID: %s", cfg.Video.ID())
|
||||
}
|
||||
if cfg.Audio != nil {
|
||||
log.Printf("Audio: %d channels %d/%d Hz %.0f dBFS",
|
||||
cfg.Audio.Channels(), cfg.Audio.Rate().Num, cfg.Audio.Rate().Den, cfg.Audio.LevelDBFS)
|
||||
log.Printf("Audio ID: %s", cfg.Audio.ID())
|
||||
}
|
||||
|
||||
inst, err := mxl.NewInstance(cfg.Domain, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("initialize MXL domain %q: %w", cfg.Domain, err)
|
||||
@@ -45,13 +50,14 @@ func Run(ctx context.Context, cfg Config) (runErr error) {
|
||||
}
|
||||
}()
|
||||
|
||||
runners := []Runner{
|
||||
{
|
||||
runners := make([]Runner, 0, 2)
|
||||
if cfg.Video != nil {
|
||||
runners = append(runners, Runner{
|
||||
Name: "video",
|
||||
Run: func(ctx context.Context) error {
|
||||
return video.Run(ctx, inst, cfg.Video)
|
||||
return video.Run(ctx, inst, *cfg.Video)
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
if cfg.Audio != nil {
|
||||
runners = append(runners, Runner{
|
||||
@@ -64,3 +70,10 @@ func Run(ctx context.Context, cfg Config) (runErr error) {
|
||||
|
||||
return RunConcurrent(ctx, runners...)
|
||||
}
|
||||
|
||||
func validateConfig(cfg Config) error {
|
||||
if cfg.Video == nil && cfg.Audio == nil {
|
||||
return fmt.Errorf("at least one media flow must be enabled")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateConfigRejectsNoFlows(t *testing.T) {
|
||||
err := validateConfig(Config{
|
||||
Domain: "/unused",
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "at least one media flow") {
|
||||
t.Fatalf("error = %v, want no-flow validation error", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user