diff --git a/main.go b/main.go index ee7a7eb..e596406 100644 --- a/main.go +++ b/main.go @@ -71,6 +71,7 @@ type CLI struct { Filter FilterCmd `help:"Manage filters." cmd:"" aliases:"f" group:"Filter"` Projector ProjectorCmd `help:"Manage projectors." cmd:"" aliases:"prj" group:"Projector"` Screenshot ScreenshotCmd `help:"Take screenshots." cmd:"" aliases:"ss" group:"Screenshot"` + Settings SettingsCmd `help:"Manage video and profile settings." cmd:"" aliases:"set" group:"Settings"` } type context struct { diff --git a/settings.go b/settings.go new file mode 100644 index 0000000..1586301 --- /dev/null +++ b/settings.go @@ -0,0 +1,334 @@ +package main + +import ( + "fmt" + + "github.com/andreykaipov/goobs/api/requests/config" + "github.com/andreykaipov/goobs/api/typedefs" + "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/lipgloss/table" +) + +// SettingsCmd handles settings management. +type SettingsCmd struct { + Show SettingsShowCmd `help:"Show settings." cmd:"" aliases:"s"` + Profile SettingsProfileCmd `help:"Get/Set profile parameter setting." cmd:"" aliases:"p"` + StreamService SettingsStreamServiceCmd `help:"Get/Set stream service setting." cmd:"" aliases:"ss"` + Video SettingsVideoCmd `help:"Get/Set video setting." cmd:"" aliases:"v"` +} + +// SettingsShowCmd shows the video settings. +type SettingsShowCmd struct { + Video bool `flag:"" help:"Show video settings."` + Record bool `flag:"" help:"Show record directory."` + Profile bool `flag:"" help:"Show profile parameters."` +} + +// Run executes the show command. +// nolint: misspell +func (cmd *SettingsShowCmd) Run(ctx *context) error { + if !cmd.Video && !cmd.Record && !cmd.Profile { + cmd.Video = true + cmd.Record = true + cmd.Profile = true + } + + // Get video settings + videoResp, err := ctx.Client.Config.GetVideoSettings() + if err != nil { + return fmt.Errorf("failed to get video settings: %w", err) + } + + vt := table.New().Border(lipgloss.RoundedBorder()). + BorderStyle(lipgloss.NewStyle().Foreground(ctx.Style.border)). + Headers("Video Setting", "Value"). + StyleFunc(func(row, _ int) lipgloss.Style { + style := lipgloss.NewStyle().Padding(0, 3) + switch { + case row == table.HeaderRow: + style = style.Bold(true).Align(lipgloss.Center) + case row%2 == 0: + style = style.Foreground(ctx.Style.evenRows) + default: + style = style.Foreground(ctx.Style.oddRows) + } + return style + }) + + vt.Row("Base Width", fmt.Sprintf("%.0f", videoResp.BaseWidth)) + vt.Row("Base Height", fmt.Sprintf("%.0f", videoResp.BaseHeight)) + vt.Row("Output Width", fmt.Sprintf("%.0f", videoResp.OutputWidth)) + vt.Row("Output Height", fmt.Sprintf("%.0f", videoResp.OutputHeight)) + vt.Row("FPS Numerator", fmt.Sprintf("%.0f", videoResp.FpsNumerator)) + vt.Row("FPS Denominator", fmt.Sprintf("%.0f", videoResp.FpsDenominator)) + + // Get record directory + dirResp, err := ctx.Client.Config.GetRecordDirectory() + if err != nil { + return fmt.Errorf("failed to get record directory: %w", err) + } + + rt := table.New().Border(lipgloss.RoundedBorder()). + BorderStyle(lipgloss.NewStyle().Foreground(ctx.Style.border)). + Headers("Record Setting", "Value"). + StyleFunc(func(row, _ int) lipgloss.Style { + style := lipgloss.NewStyle().Padding(0, 3) + switch { + case row == table.HeaderRow: + style = style.Bold(true).Align(lipgloss.Center) + case row%2 == 0: + style = style.Foreground(ctx.Style.evenRows) + default: + style = style.Foreground(ctx.Style.oddRows) + } + return style + }) + + rt.Row("Directory", dirResp.RecordDirectory) + + // Get profile prameters + pt := table.New().Border(lipgloss.RoundedBorder()). + BorderStyle(lipgloss.NewStyle().Foreground(ctx.Style.border)). + Headers("Profile Parameter", "Value"). + StyleFunc(func(row, _ int) lipgloss.Style { + style := lipgloss.NewStyle().Padding(0, 3) + switch { + case row == table.HeaderRow: + style = style.Bold(true).Align(lipgloss.Center) + case row%2 == 0: + style = style.Foreground(ctx.Style.evenRows) + default: + style = style.Foreground(ctx.Style.oddRows) + } + return style + }) + + // Common profile parameters to display + params := []struct { + category string + name string + label string + }{ + {"Output", "Mode", "Output Mode"}, + + {"SimpleOutput", "StreamEncoder", "Simple Streaming Encoder"}, + {"SimpleOutput", "RecEncoder", "Simple Recording Encoder"}, + {"SimpleOutput", "RecFormat2", "Simple Recording Video Format"}, + {"SimpleOutput", "RecAudioEncoder", "Simple Recording Audio Format"}, + {"SimpleOutput", "RecQuality", "Simple Recording Quality"}, + + {"AdvOut", "Encoder", "Advanced Streaming Encoder"}, + {"AdvOut", "RecEncoder", "Advanced Recording Encoder"}, + {"AdvOut", "RecType", "Advanced Recording Type"}, + {"AdvOut", "RecFormat2", "Advanced Recording Video Format"}, + {"AdvOut", "RecAudioEncoder", "Advanced Recording Audio Format"}, + } + + for _, param := range params { + resp, err := ctx.Client.Config.GetProfileParameter( + config.NewGetProfileParameterParams(). + WithParameterCategory(param.category). + WithParameterName(param.name), + ) + if err == nil && resp.ParameterValue != "" { + pt.Row(param.label, resp.ParameterValue) + } + } + + if cmd.Video { + fmt.Fprintln(ctx.Out, vt.Render()) + } + + if cmd.Record { + fmt.Fprintln(ctx.Out, rt.Render()) + } + + if cmd.Profile { + fmt.Fprintln(ctx.Out, pt.Render()) + } + + return nil +} + +// SettingsProfileCmd gets/ sets a profile parameter. +type SettingsProfileCmd struct { + Category string `arg:"" help:"Parameter category (e.g., AdvOut, SimpleOutput, Output)." required:""` + Name string `arg:"" help:"Parameter name (e.g., RecFormat2, RecEncoder)." required:""` + Value string `arg:"" help:"Parameter value to set." optional:""` +} + +// Run executes the set command. +func (cmd *SettingsProfileCmd) Run(ctx *context) error { + if cmd.Value == "" { + resp, err := ctx.Client.Config.GetProfileParameter( + config.NewGetProfileParameterParams(). + WithParameterCategory(cmd.Category). + WithParameterName(cmd.Name), + ) + if err != nil { + return fmt.Errorf("failed to get parameter %s.%s: %w", cmd.Category, cmd.Name, err) + } + + fmt.Fprintf(ctx.Out, "%s.%s = %s\n", cmd.Category, cmd.Name, resp.ParameterValue) + return nil + } + + _, err := ctx.Client.Config.SetProfileParameter( + config.NewSetProfileParameterParams(). + WithParameterCategory(cmd.Category). + WithParameterName(cmd.Name). + WithParameterValue(cmd.Value), + ) + if err != nil { + return fmt.Errorf("failed to set parameter %s.%s: %w", cmd.Category, cmd.Name, err) + } + + fmt.Fprintf(ctx.Out, "Set %s.%s = %s\n", cmd.Category, cmd.Name, cmd.Value) + return nil +} + +// SettingsStreamServiceCmd gets/ sets stream service settings. +type SettingsStreamServiceCmd struct { + Type string `arg:"" help:"Stream type (e.g., rtmp_common, rtmp_custom)." required:""` + Key string ` help:"Stream key." flag:""` + Server string ` help:"Stream server URL." flag:""` +} + +// Run executes the set stream service command. +// nolint: misspell +func (cmd *SettingsStreamServiceCmd) Run(ctx *context) error { + resp, err := ctx.Client.Config.GetStreamServiceSettings() + if err != nil { + return fmt.Errorf("failed to get stream service settings: %w", err) + } + + if cmd.Key == "" && cmd.Server == "" { + t := table.New().Border(lipgloss.RoundedBorder()). + BorderStyle(lipgloss.NewStyle().Foreground(ctx.Style.border)). + Headers("Stream Service Setting", "Value"). + StyleFunc(func(row, _ int) lipgloss.Style { + style := lipgloss.NewStyle().Padding(0, 3) + switch { + case row == table.HeaderRow: + style = style.Bold(true).Align(lipgloss.Center) + case row%2 == 0: + style = style.Foreground(ctx.Style.evenRows) + default: + style = style.Foreground(ctx.Style.oddRows) + } + return style + }) + + t.Row("Type", cmd.Type) + t.Row("Key", resp.StreamServiceSettings.Key) + t.Row("Server", resp.StreamServiceSettings.Server) + + fmt.Fprintln(ctx.Out, t.Render()) + return nil + } + + if cmd.Key == "" { + cmd.Key = resp.StreamServiceSettings.Key + } + if cmd.Server == "" { + cmd.Server = resp.StreamServiceSettings.Server + } + + _, err = ctx.Client.Config.SetStreamServiceSettings( + config.NewSetStreamServiceSettingsParams(). + WithStreamServiceSettings(&typedefs.StreamServiceSettings{ + Key: cmd.Key, + Server: cmd.Server, + }). + WithStreamServiceType(cmd.Type), + ) + if err != nil { + return fmt.Errorf("failed to set stream service settings: %w", err) + } + + fmt.Fprintln(ctx.Out, "Stream service settings updated successfully.") + return nil +} + +// SettingsVideoCmd gets/ sets video settings. +type SettingsVideoCmd struct { + Show bool `flag:"" help:"Show video settings."` + BaseWidth int `flag:"" help:"Base (canvas) width." min:"8"` + BaseHeight int `flag:"" help:"Base (canvas) height." min:"8"` + OutputWidth int `flag:"" help:"Output (scaled) width." min:"8"` + OutputHeight int `flag:"" help:"Output (scaled) height." min:"8"` + FPSNum int `flag:"" help:"Frames per second numerator." min:"1"` + FPSDen int `flag:"" help:"Frames per second denominator." min:"1"` +} + +// Run executes the gets/ set video command. +// nolint: misspell +func (cmd *SettingsVideoCmd) Run(ctx *context) error { + resp, err := ctx.Client.Config.GetVideoSettings() + if err != nil { + return fmt.Errorf("failed to get video settings: %w", err) + } + + if cmd.Show { + t := table.New().Border(lipgloss.RoundedBorder()). + BorderStyle(lipgloss.NewStyle().Foreground(ctx.Style.border)). + Headers("Video Setting", "Value"). + StyleFunc(func(row, _ int) lipgloss.Style { + style := lipgloss.NewStyle().Padding(0, 3) + switch { + case row == table.HeaderRow: + style = style.Bold(true).Align(lipgloss.Center) + case row%2 == 0: + style = style.Foreground(ctx.Style.evenRows) + default: + style = style.Foreground(ctx.Style.oddRows) + } + return style + }) + + t.Row("Base Width", fmt.Sprintf("%.0f", resp.BaseWidth)) + t.Row("Base Height", fmt.Sprintf("%.0f", resp.BaseHeight)) + t.Row("Output Width", fmt.Sprintf("%.0f", resp.OutputWidth)) + t.Row("Output Height", fmt.Sprintf("%.0f", resp.OutputHeight)) + t.Row("FPS Numerator", fmt.Sprintf("%.0f", resp.FpsNumerator)) + t.Row("FPS Denominator", fmt.Sprintf("%.0f", resp.FpsDenominator)) + + fmt.Fprintln(ctx.Out, t.Render()) + return nil + } + + if cmd.BaseWidth == 0 { + cmd.BaseWidth = int(resp.BaseWidth) + } + if cmd.BaseHeight == 0 { + cmd.BaseHeight = int(resp.BaseHeight) + } + if cmd.OutputWidth == 0 { + cmd.OutputWidth = int(resp.OutputWidth) + } + if cmd.OutputHeight == 0 { + cmd.OutputHeight = int(resp.OutputHeight) + } + if cmd.FPSNum == 0 { + cmd.FPSNum = int(resp.FpsNumerator) + } + if cmd.FPSDen == 0 { + cmd.FPSDen = int(resp.FpsDenominator) + } + + _, err = ctx.Client.Config.SetVideoSettings( + config.NewSetVideoSettingsParams(). + WithBaseWidth(float64(cmd.BaseWidth)). + WithBaseHeight(float64(cmd.BaseHeight)). + WithOutputWidth(float64(cmd.OutputWidth)). + WithOutputHeight(float64(cmd.OutputHeight)). + WithFpsNumerator(float64(cmd.FPSNum)). + WithFpsDenominator(float64(cmd.FPSDen)), + ) + if err != nil { + return fmt.Errorf("failed to set video settings: %w", err) + } + + fmt.Fprintln(ctx.Out, "Video settings updated successfully.") + return nil +}