package cli import ( "errors" "fmt" "log/slog" "time" "github.com/spf13/cobra" "galahad2lcd/internal/config" "galahad2lcd/internal/device" "galahad2lcd/internal/stream" "galahad2lcd/internal/transcoder" ) // openTimeout bounds how long the daemon waits for the USB device to appear. const openTimeout = 30 * time.Second func newDaemonCmd() *cobra.Command { var ( configPath string input string rotate int speed float64 fps float64 ) cmd := &cobra.Command{ Use: "daemon", Short: "Run the display streaming daemon", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { cfg, err := config.Load(configPath) if err != nil { return err } if err := applyOverrides(cmd, &cfg, input, rotate, speed, fps); err != nil { return err } if err := cfg.Validate(); err != nil { return fmt.Errorf("invalid configuration: %w", err) } return runDaemon(cfg) }, } cmd.Flags().StringVar(&configPath, "config", config.DefaultConfigPath, "path to the configuration file") cmd.Flags().StringVar(&input, "input", "", "override the input media file") cmd.Flags().IntVar(&rotate, "rotate", 0, "override the rotation in degrees (0, 90, 180, 270)") cmd.Flags().Float64Var(&speed, "speed", 0, "override the playback speed (>1 slower, <1 faster)") cmd.Flags().Float64Var(&fps, "fps", 0, "override the output framerate (0 = auto)") return cmd } // runDaemon transcodes the configured media, loads the resulting frames and // streams them to the display, reconnecting automatically if the USB link is // lost (e.g. after system suspend). func runDaemon(cfg config.Config) error { ctx, stop := newSignalContext() defer stop() tc, err := transcoder.New() if err != nil { return err } slog.Info("transcoding input to H.264", "input", cfg.Display.Input, "rotate", cfg.Display.Rotate) fps, err := tc.Transcode(ctx, cfg) if err != nil { return err } slog.Info("transcoding complete", "fps", fps, "cache", cfg.Stream.CachePath) packets, err := stream.LoadPackets(cfg.Stream.CachePath) if err != nil { return err } if len(packets) == 0 { return errors.New("no video frames found in input") } slog.Info("frames buffered", "count", len(packets)) dev, err := device.OpenWithRetry(ctx, cfg.USB.VendorID, cfg.USB.ProductID, openTimeout) if err != nil { return err } slog.Info("device connected") for { err := stream.New(dev, packets, fps, cfg.Display.Speed).Run(ctx) if err == nil || ctx.Err() != nil { _ = dev.Close() return nil // clean shutdown } slog.Error("stream interrupted; reconnecting", "error", err) _ = dev.Close() dev, err = device.OpenWithRetry(ctx, cfg.USB.VendorID, cfg.USB.ProductID, openTimeout) if err != nil { return err } slog.Info("device reconnected") } }