From db70f8766da4023a32b8683c4a17accc1b55591a Mon Sep 17 00:00:00 2001 From: Noah Zoschke Date: Tue, 29 Jul 2025 14:30:49 -0700 Subject: [PATCH 01/11] create input --- input.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/input.go b/input.go index 9761068..c9d2a59 100644 --- a/input.go +++ b/input.go @@ -13,12 +13,19 @@ import ( // InputCmd provides commands to manage inputs in OBS Studio. type InputCmd struct { + Create InputCreateCmd `cmd:"" help:"Create input." aliases:"c"` List InputListCmd `cmd:"" help:"List all inputs." aliases:"ls"` Mute InputMuteCmd `cmd:"" help:"Mute input." aliases:"m"` Unmute InputUnmuteCmd `cmd:"" help:"Unmute input." aliases:"um"` Toggle InputToggleCmd `cmd:"" help:"Toggle input." aliases:"tg"` } +// InputCreateCmd provides a command to create an input. +type InputCreateCmd struct { + Kind string `arg:"" help:"Input kind (e.g., coreaudio_input_capture, macos-avcapture)." required:""` + Name string `arg:"" help:"Name for the input." required:""` +} + // InputListCmd provides a command to list all inputs. type InputListCmd struct { Input bool `flag:"" help:"List all inputs." aliases:"i"` @@ -29,6 +36,28 @@ type InputListCmd struct { UUID bool `flag:"" help:"Display UUIDs of inputs." aliases:"u"` } +// Run executes the command to create an input. +func (cmd *InputCreateCmd) Run(ctx *context) error { + currentScene, err := ctx.Client.Scenes.GetCurrentProgramScene() + if err != nil { + return err + } + + _, err = ctx.Client.Inputs.CreateInput( + inputs.NewCreateInputParams(). + WithInputKind(cmd.Kind). + WithInputName(cmd.Name). + WithSceneName(currentScene.CurrentProgramSceneName), + ) + if err != nil { + return err + } + + fmt.Fprintf(ctx.Out, "Created input: %s (%s) in scene %s\n", + ctx.Style.Highlight(cmd.Name), cmd.Kind, ctx.Style.Highlight(currentScene.CurrentProgramSceneName)) + return nil +} + // Run executes the command to list all inputs. func (cmd *InputListCmd) Run(ctx *context) error { resp, err := ctx.Client.Inputs.GetInputList(inputs.NewGetInputListParams()) From cb735cd666f93748163d791204a68f6dfc98c7d7 Mon Sep 17 00:00:00 2001 From: Noah Zoschke Date: Tue, 29 Jul 2025 15:33:22 -0700 Subject: [PATCH 02/11] show --- input.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/input.go b/input.go index c9d2a59..a29cf48 100644 --- a/input.go +++ b/input.go @@ -6,6 +6,7 @@ import ( "sort" "strings" + "github.com/andreykaipov/goobs" "github.com/andreykaipov/goobs/api/requests/inputs" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss/table" @@ -17,6 +18,7 @@ type InputCmd struct { List InputListCmd `cmd:"" help:"List all inputs." aliases:"ls"` Mute InputMuteCmd `cmd:"" help:"Mute input." aliases:"m"` Unmute InputUnmuteCmd `cmd:"" help:"Unmute input." aliases:"um"` + Show InputShowCmd `cmd:"" help:"Show input details." aliases:"s"` Toggle InputToggleCmd `cmd:"" help:"Toggle input." aliases:"tg"` } @@ -187,6 +189,86 @@ func (cmd *InputUnmuteCmd) Run(ctx *context) error { return nil } +// InputShowCmd provides a command to show input details. +type InputShowCmd struct { + Name string `arg:"" help:"Name of the input to show." required:""` +} + +// Run executes the command to show input details. +func (cmd *InputShowCmd) Run(ctx *context) error { + lresp, err := ctx.Client.Inputs.GetInputList(inputs.NewGetInputListParams()) + if err != nil { + return fmt.Errorf("failed to get input list: %w", err) + } + + var inputKind string + for _, input := range lresp.Inputs { + if input.InputName == cmd.Name { + inputKind = input.InputKind + break + } + } + + prop, name, _, err := device(ctx.Client, cmd.Name) + if err != nil { + return fmt.Errorf("failed to get device: %w", err) + } + + t := table.New().Border(lipgloss.RoundedBorder()). + BorderStyle(lipgloss.NewStyle().Foreground(ctx.Style.border)) + t.Headers("Input Name", "Kind", "Device") + t.StyleFunc(func(row, col int) lipgloss.Style { + style := lipgloss.NewStyle().Padding(0, 3) + switch col { + case 0: + style = style.Align(lipgloss.Left) + case 1: + style = style.Align(lipgloss.Left) + case 2: + style = style.Align(lipgloss.Center) + case 3: + style = style.Align(lipgloss.Left) + } + 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(cmd.Name, snakeCaseToTitleCase(inputKind), fmt.Sprintf("%s %s", prop, name)) + + fmt.Fprintln(ctx.Out, t.Render()) + + return nil +} + +func device(c *goobs.Client, inputName string) (string, string, string, error) { + propNames := []string{ + "device", "device_id", + } + + for _, propName := range propNames { + deviceListResp, err := c.Inputs.GetInputPropertiesListPropertyItems( + inputs.NewGetInputPropertiesListPropertyItemsParams(). + WithInputName(inputName). + WithPropertyName(propName), + ) + if err == nil && len(deviceListResp.PropertyItems) > 0 { + for _, item := range deviceListResp.PropertyItems { + if item.ItemName != "" { + return propName, item.ItemName, fmt.Sprint(item.ItemValue), nil + } + } + } + } + + return "", "", "", nil +} + // InputToggleCmd provides a command to toggle the mute state of an input. type InputToggleCmd struct { InputName string `arg:"" help:"Name of the input to toggle."` From 72fc7d4092356ad36ffa605d1fb72f4f5e2a77a3 Mon Sep 17 00:00:00 2001 From: Noah Zoschke Date: Tue, 29 Jul 2025 15:55:44 -0700 Subject: [PATCH 03/11] kinds --- input.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/input.go b/input.go index a29cf48..5f9cef6 100644 --- a/input.go +++ b/input.go @@ -15,6 +15,7 @@ import ( // InputCmd provides commands to manage inputs in OBS Studio. type InputCmd struct { Create InputCreateCmd `cmd:"" help:"Create input." aliases:"c"` + Kinds InputKindsCmd `cmd:"" help:"List input kinds." aliases:"k"` List InputListCmd `cmd:"" help:"List all inputs." aliases:"ls"` Mute InputMuteCmd `cmd:"" help:"Mute input." aliases:"m"` Unmute InputUnmuteCmd `cmd:"" help:"Unmute input." aliases:"um"` @@ -60,6 +61,47 @@ func (cmd *InputCreateCmd) Run(ctx *context) error { return nil } +// InputKindsCmd provides a command to list all input kinds. +type InputKindsCmd struct{} + +// Run executes the command to list all input kinds. +func (cmd *InputKindsCmd) Run(ctx *context) error { + resp, err := ctx.Client.Inputs.GetInputKindList( + inputs.NewGetInputKindListParams().WithUnversioned(false), + ) + if err != nil { + return fmt.Errorf("failed to get input kinds: %w", err) + } + + t := table.New().Border(lipgloss.RoundedBorder()). + BorderStyle(lipgloss.NewStyle().Foreground(ctx.Style.border)) + t.Headers("Kind") + t.StyleFunc(func(row, col int) lipgloss.Style { + style := lipgloss.NewStyle().Padding(0, 3) + switch col { + case 0: + style = style.Align(lipgloss.Left) + } + 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 + }) + + for _, kind := range resp.InputKinds { + t.Row(kind) + } + + fmt.Fprintln(ctx.Out, t.Render()) + + return nil +} + // Run executes the command to list all inputs. func (cmd *InputListCmd) Run(ctx *context) error { resp, err := ctx.Client.Inputs.GetInputList(inputs.NewGetInputListParams()) From bd4a6cad4b3bba6bbe6941e5e4d5ec874b5ef563 Mon Sep 17 00:00:00 2001 From: Noah Zoschke Date: Tue, 29 Jul 2025 16:18:56 -0700 Subject: [PATCH 04/11] show verbose --- input.go | 47 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/input.go b/input.go index 5f9cef6..8c4ea29 100644 --- a/input.go +++ b/input.go @@ -233,7 +233,8 @@ func (cmd *InputUnmuteCmd) Run(ctx *context) error { // InputShowCmd provides a command to show input details. type InputShowCmd struct { - Name string `arg:"" help:"Name of the input to show." required:""` + Name string `arg:"" help:"Name of the input to show." required:""` + Verbose bool `flag:"" help:"Show all properties and their keys and values."` } // Run executes the command to show input details. @@ -268,8 +269,6 @@ func (cmd *InputShowCmd) Run(ctx *context) error { style = style.Align(lipgloss.Left) case 2: style = style.Align(lipgloss.Center) - case 3: - style = style.Align(lipgloss.Left) } switch { case row == table.HeaderRow: @@ -281,10 +280,50 @@ func (cmd *InputShowCmd) Run(ctx *context) error { } return style }) - t.Row(cmd.Name, snakeCaseToTitleCase(inputKind), fmt.Sprintf("%s %s", prop, name)) + t.Row(cmd.Name, snakeCaseToTitleCase(inputKind), name) fmt.Fprintln(ctx.Out, t.Render()) + if cmd.Verbose { + resp, err := ctx.Client.Inputs.GetInputPropertiesListPropertyItems( + inputs.NewGetInputPropertiesListPropertyItemsParams(). + WithInputName(cmd.Name). + WithPropertyName(prop), + ) + if err != nil { + return err + } + + t := table.New().Border(lipgloss.RoundedBorder()). + BorderStyle(lipgloss.NewStyle().Foreground(ctx.Style.border)) + t.StyleFunc(func(row, col int) lipgloss.Style { + style := lipgloss.NewStyle().Padding(0, 3) + switch col { + case 0: + style = style.Align(lipgloss.Left) + } + 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.Headers("Devices") + + for _, item := range resp.PropertyItems { + if item.ItemName != "" { + t.Row(item.ItemName) + } + } + + fmt.Fprintln(ctx.Out, t.Render()) + } + return nil } From e087fdefe3e695fe39d47dc3c6d6fa1e0a3794fb Mon Sep 17 00:00:00 2001 From: Noah Zoschke Date: Tue, 29 Jul 2025 16:29:52 -0700 Subject: [PATCH 05/11] update --- input.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/input.go b/input.go index 8c4ea29..bb7c5e7 100644 --- a/input.go +++ b/input.go @@ -3,6 +3,7 @@ package main import ( "fmt" + "maps" "sort" "strings" @@ -21,6 +22,7 @@ type InputCmd struct { Unmute InputUnmuteCmd `cmd:"" help:"Unmute input." aliases:"um"` Show InputShowCmd `cmd:"" help:"Show input details." aliases:"s"` Toggle InputToggleCmd `cmd:"" help:"Toggle input." aliases:"tg"` + Update InputUpdateCmd `cmd:"" help:"Update input settings." aliases:"up"` } // InputCreateCmd provides a command to create an input. @@ -380,3 +382,69 @@ func (cmd *InputToggleCmd) Run(ctx *context) error { } return nil } + +// InputUpdateCmd provides a command to update input settings. +type InputUpdateCmd struct { + InputName string `arg:"" help:"Name of the input to update." required:""` + DeviceName string `arg:"" help:"Name of the device to set." required:""` +} + +// Run executes the command to update input settings. +func (cmd *InputUpdateCmd) Run(ctx *context) error { + // Use the device helper to find the correct device property name + prop, _, _, err := device(ctx.Client, cmd.InputName) + if err != nil { + return fmt.Errorf("failed to get device property: %w", err) + } + if prop == "" { + return fmt.Errorf("no device property found for input '%s'", cmd.InputName) + } + + resp, err := ctx.Client.Inputs.GetInputPropertiesListPropertyItems( + inputs.NewGetInputPropertiesListPropertyItemsParams(). + WithInputName(cmd.InputName). + WithPropertyName(prop), + ) + if err != nil { + return err + } + + var deviceValue any + var found bool + for _, item := range resp.PropertyItems { + if item.ItemName == cmd.DeviceName { + deviceValue = item.ItemValue + found = true + break + } + } + + if !found { + return fmt.Errorf("device '%s' not found for input '%s'", cmd.DeviceName, cmd.InputName) + } + + sresp, err := ctx.Client.Inputs.GetInputSettings( + inputs.NewGetInputSettingsParams().WithInputName(cmd.InputName), + ) + if err != nil { + return err + } + + settings := make(map[string]any) + maps.Copy(settings, sresp.InputSettings) + settings[prop] = deviceValue + + _, err = ctx.Client.Inputs.SetInputSettings( + inputs.NewSetInputSettingsParams(). + WithInputName(cmd.InputName). + WithInputSettings(settings), + ) + if err != nil { + return fmt.Errorf("failed to update input settings: %w", err) + } + + fmt.Fprintf(ctx.Out, "Input %s %s set to %s\n", + ctx.Style.Highlight(cmd.InputName), prop, ctx.Style.Highlight(cmd.DeviceName)) + + return nil +} From c5e7bb4e1a3b6d83cf2538bad6cabd264e4fe966 Mon Sep 17 00:00:00 2001 From: Noah Zoschke Date: Tue, 29 Jul 2025 16:35:10 -0700 Subject: [PATCH 06/11] delete --- input.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/input.go b/input.go index bb7c5e7..f7d80d8 100644 --- a/input.go +++ b/input.go @@ -16,6 +16,7 @@ import ( // InputCmd provides commands to manage inputs in OBS Studio. type InputCmd struct { Create InputCreateCmd `cmd:"" help:"Create input." aliases:"c"` + Delete InputDeleteCmd `cmd:"" help:"Delete input." aliases:"d"` Kinds InputKindsCmd `cmd:"" help:"List input kinds." aliases:"k"` List InputListCmd `cmd:"" help:"List all inputs." aliases:"ls"` Mute InputMuteCmd `cmd:"" help:"Mute input." aliases:"m"` @@ -31,6 +32,11 @@ type InputCreateCmd struct { Name string `arg:"" help:"Name for the input." required:""` } +// InputDeleteCmd provides a command to delete an input. +type InputDeleteCmd struct { + Name string `arg:"" help:"Name of the input to delete." required:""` +} + // InputListCmd provides a command to list all inputs. type InputListCmd struct { Input bool `flag:"" help:"List all inputs." aliases:"i"` @@ -63,6 +69,19 @@ func (cmd *InputCreateCmd) Run(ctx *context) error { return nil } +// Run executes the command to delete an input. +func (cmd *InputDeleteCmd) Run(ctx *context) error { + _, err := ctx.Client.Inputs.RemoveInput( + inputs.NewRemoveInputParams().WithInputName(cmd.Name), + ) + if err != nil { + return fmt.Errorf("failed to delete input: %w", err) + } + + fmt.Fprintf(ctx.Out, "Deleted input: %s\n", ctx.Style.Highlight(cmd.Name)) + return nil +} + // InputKindsCmd provides a command to list all input kinds. type InputKindsCmd struct{} @@ -247,13 +266,19 @@ func (cmd *InputShowCmd) Run(ctx *context) error { } var inputKind string + var found bool for _, input := range lresp.Inputs { if input.InputName == cmd.Name { inputKind = input.InputKind + found = true break } } + if !found { + return fmt.Errorf("input '%s' not found", cmd.Name) + } + prop, name, _, err := device(ctx.Client, cmd.Name) if err != nil { return fmt.Errorf("failed to get device: %w", err) From fba7c4ce206e68ea9529c0641e115f98404d816a Mon Sep 17 00:00:00 2001 From: Noah Zoschke Date: Wed, 30 Jul 2025 07:02:27 -0700 Subject: [PATCH 07/11] format --- input.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/input.go b/input.go index f7d80d8..61c96b3 100644 --- a/input.go +++ b/input.go @@ -28,8 +28,8 @@ type InputCmd struct { // InputCreateCmd provides a command to create an input. type InputCreateCmd struct { - Kind string `arg:"" help:"Input kind (e.g., coreaudio_input_capture, macos-avcapture)." required:""` Name string `arg:"" help:"Name for the input." required:""` + Kind string `arg:"" help:"Input kind (e.g., coreaudio_input_capture, macos-avcapture)." required:""` } // InputDeleteCmd provides a command to delete an input. @@ -78,7 +78,7 @@ func (cmd *InputDeleteCmd) Run(ctx *context) error { return fmt.Errorf("failed to delete input: %w", err) } - fmt.Fprintf(ctx.Out, "Deleted input: %s\n", ctx.Style.Highlight(cmd.Name)) + fmt.Fprintf(ctx.Out, "Deleted %s\n", ctx.Style.Highlight(cmd.Name)) return nil } From 8ce8727a0a8b0b7b326a486169d80ab00fdae723 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Thu, 8 Jan 2026 14:14:40 +0000 Subject: [PATCH 08/11] rename input kinds to input list-kinds rename input delete to input remove add input volume --- input.go | 131 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 79 insertions(+), 52 deletions(-) diff --git a/input.go b/input.go index 61c96b3..b7437df 100644 --- a/input.go +++ b/input.go @@ -7,7 +7,6 @@ import ( "sort" "strings" - "github.com/andreykaipov/goobs" "github.com/andreykaipov/goobs/api/requests/inputs" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss/table" @@ -15,38 +14,24 @@ import ( // InputCmd provides commands to manage inputs in OBS Studio. type InputCmd struct { - Create InputCreateCmd `cmd:"" help:"Create input." aliases:"c"` - Delete InputDeleteCmd `cmd:"" help:"Delete input." aliases:"d"` - Kinds InputKindsCmd `cmd:"" help:"List input kinds." aliases:"k"` - List InputListCmd `cmd:"" help:"List all inputs." aliases:"ls"` - Mute InputMuteCmd `cmd:"" help:"Mute input." aliases:"m"` - Unmute InputUnmuteCmd `cmd:"" help:"Unmute input." aliases:"um"` - Show InputShowCmd `cmd:"" help:"Show input details." aliases:"s"` - Toggle InputToggleCmd `cmd:"" help:"Toggle input." aliases:"tg"` - Update InputUpdateCmd `cmd:"" help:"Update input settings." aliases:"up"` + Create InputCreateCmd `cmd:"" help:"Create input." aliases:"c"` + Remove InputRemoveCmd `cmd:"" help:"Remove input." aliases:"d"` + ListKinds InputListKindsCmd `cmd:"" help:"List input kinds." aliases:"k"` + List InputListCmd `cmd:"" help:"List all inputs." aliases:"ls"` + Mute InputMuteCmd `cmd:"" help:"Mute input." aliases:"m"` + Unmute InputUnmuteCmd `cmd:"" help:"Unmute input." aliases:"um"` + Volume InputVolumeCmd `cmd:"" help:"Set input volume." aliases:"v"` + Show InputShowCmd `cmd:"" help:"Show input details." aliases:"s"` + Toggle InputToggleCmd `cmd:"" help:"Toggle input." aliases:"tg"` + Update InputUpdateCmd `cmd:"" help:"Update input settings." aliases:"up"` } // InputCreateCmd provides a command to create an input. type InputCreateCmd struct { - Name string `arg:"" help:"Name for the input." required:""` + Name string `arg:"" help:"Name for the input." required:""` Kind string `arg:"" help:"Input kind (e.g., coreaudio_input_capture, macos-avcapture)." required:""` } -// InputDeleteCmd provides a command to delete an input. -type InputDeleteCmd struct { - Name string `arg:"" help:"Name of the input to delete." required:""` -} - -// InputListCmd provides a command to list all inputs. -type InputListCmd struct { - Input bool `flag:"" help:"List all inputs." aliases:"i"` - Output bool `flag:"" help:"List all outputs." aliases:"o"` - Colour bool `flag:"" help:"List all colour sources." aliases:"c"` - Ffmpeg bool `flag:"" help:"List all ffmpeg sources." aliases:"f"` - Vlc bool `flag:"" help:"List all VLC sources." aliases:"v"` - UUID bool `flag:"" help:"Display UUIDs of inputs." aliases:"u"` -} - // Run executes the command to create an input. func (cmd *InputCreateCmd) Run(ctx *context) error { currentScene, err := ctx.Client.Scenes.GetCurrentProgramScene() @@ -69,8 +54,13 @@ func (cmd *InputCreateCmd) Run(ctx *context) error { return nil } -// Run executes the command to delete an input. -func (cmd *InputDeleteCmd) Run(ctx *context) error { +// InputRemoveCmd provides a command to remove an input. +type InputRemoveCmd struct { + Name string `arg:"" help:"Name of the input to remove." required:""` +} + +// Run executes the command to remove an input. +func (cmd *InputRemoveCmd) Run(ctx *context) error { _, err := ctx.Client.Inputs.RemoveInput( inputs.NewRemoveInputParams().WithInputName(cmd.Name), ) @@ -82,11 +72,11 @@ func (cmd *InputDeleteCmd) Run(ctx *context) error { return nil } -// InputKindsCmd provides a command to list all input kinds. -type InputKindsCmd struct{} +// InputListKindsCmd provides a command to list all input kinds. +type InputListKindsCmd struct{} // Run executes the command to list all input kinds. -func (cmd *InputKindsCmd) Run(ctx *context) error { +func (cmd *InputListKindsCmd) Run(ctx *context) error { resp, err := ctx.Client.Inputs.GetInputKindList( inputs.NewGetInputKindListParams().WithUnversioned(false), ) @@ -123,6 +113,16 @@ func (cmd *InputKindsCmd) Run(ctx *context) error { return nil } +// InputListCmd provides a command to list all inputs. +type InputListCmd struct { + Input bool `flag:"" help:"List all inputs." aliases:"i"` + Output bool `flag:"" help:"List all outputs." aliases:"o"` + Colour bool `flag:"" help:"List all colour sources." aliases:"c"` + Ffmpeg bool `flag:"" help:"List all ffmpeg sources." aliases:"f"` + Vlc bool `flag:"" help:"List all VLC sources." aliases:"v"` + UUID bool `flag:"" help:"Display UUIDs of inputs." aliases:"u"` +} + // Run executes the command to list all inputs. func (cmd *InputListCmd) Run(ctx *context) error { resp, err := ctx.Client.Inputs.GetInputList(inputs.NewGetInputListParams()) @@ -252,10 +252,37 @@ func (cmd *InputUnmuteCmd) Run(ctx *context) error { return nil } +// InputVolumeCmd provides a command to set the volume of an input. +type InputVolumeCmd struct { + InputName string `arg:"" help:"Name of the input to set volume for." required:""` + Volume float64 `arg:"" help:"Volume level (-90.0 to 0.0)." required:""` +} + +// Run executes the command to set the volume of an input. +// accepts values between -90.0 and 0.0 representing decibels (dB). +func (cmd *InputVolumeCmd) Run(ctx *context) error { + if cmd.Volume < -90.0 || cmd.Volume > 0.0 { + return fmt.Errorf("volume must be between -90.0 and 0.0 dB") + } + + _, err := ctx.Client.Inputs.SetInputVolume( + inputs.NewSetInputVolumeParams(). + WithInputName(cmd.InputName). + WithInputVolumeDb(cmd.Volume), + ) + if err != nil { + return fmt.Errorf("failed to set input volume: %w", err) + } + + fmt.Fprintf(ctx.Out, "Set volume of input %s to %.1f dB\n", + ctx.Style.Highlight(cmd.InputName), cmd.Volume) + return nil +} + // InputShowCmd provides a command to show input details. type InputShowCmd struct { - Name string `arg:"" help:"Name of the input to show." required:""` - Verbose bool `flag:"" help:"Show all properties and their keys and values."` + Name string `arg:"" help:"Name of the input to show." required:""` + Verbose bool ` help:"Show all properties and their keys and values." flag:""` } // Run executes the command to show input details. @@ -279,9 +306,9 @@ func (cmd *InputShowCmd) Run(ctx *context) error { return fmt.Errorf("input '%s' not found", cmd.Name) } - prop, name, _, err := device(ctx.Client, cmd.Name) - if err != nil { - return fmt.Errorf("failed to get device: %w", err) + prop, name := device(ctx, cmd.Name) + if prop == "" { + return fmt.Errorf("no device property found for input '%s'", cmd.Name) } t := table.New().Border(lipgloss.RoundedBorder()). @@ -312,13 +339,13 @@ func (cmd *InputShowCmd) Run(ctx *context) error { fmt.Fprintln(ctx.Out, t.Render()) if cmd.Verbose { - resp, err := ctx.Client.Inputs.GetInputPropertiesListPropertyItems( + deviceListResp, err := ctx.Client.Inputs.GetInputPropertiesListPropertyItems( inputs.NewGetInputPropertiesListPropertyItemsParams(). WithInputName(cmd.Name). WithPropertyName(prop), ) if err != nil { - return err + return fmt.Errorf("failed to get device list: %w", err) } t := table.New().Border(lipgloss.RoundedBorder()). @@ -342,7 +369,7 @@ func (cmd *InputShowCmd) Run(ctx *context) error { t.Headers("Devices") - for _, item := range resp.PropertyItems { + for _, item := range deviceListResp.PropertyItems { if item.ItemName != "" { t.Row(item.ItemName) } @@ -354,27 +381,30 @@ func (cmd *InputShowCmd) Run(ctx *context) error { return nil } -func device(c *goobs.Client, inputName string) (string, string, string, error) { - propNames := []string{ - "device", "device_id", +func device(ctx *context, inputName string) (string, string) { + settings, err := ctx.Client.Inputs.GetInputSettings( + inputs.NewGetInputSettingsParams().WithInputName(inputName), + ) + if err != nil { + return "", "" } - for _, propName := range propNames { - deviceListResp, err := c.Inputs.GetInputPropertiesListPropertyItems( + for _, propName := range []string{"device", "device_id"} { + deviceListResp, err := ctx.Client.Inputs.GetInputPropertiesListPropertyItems( inputs.NewGetInputPropertiesListPropertyItemsParams(). WithInputName(inputName). WithPropertyName(propName), ) if err == nil && len(deviceListResp.PropertyItems) > 0 { for _, item := range deviceListResp.PropertyItems { - if item.ItemName != "" { - return propName, item.ItemName, fmt.Sprint(item.ItemValue), nil + if item.ItemValue == settings.InputSettings[propName] { + return propName, item.ItemName } } } } - return "", "", "", nil + return "", "" } // InputToggleCmd provides a command to toggle the mute state of an input. @@ -411,16 +441,13 @@ func (cmd *InputToggleCmd) Run(ctx *context) error { // InputUpdateCmd provides a command to update input settings. type InputUpdateCmd struct { InputName string `arg:"" help:"Name of the input to update." required:""` - DeviceName string `arg:"" help:"Name of the device to set." required:""` + DeviceName string `arg:"" help:"Name of the device to set." required:""` } // Run executes the command to update input settings. func (cmd *InputUpdateCmd) Run(ctx *context) error { // Use the device helper to find the correct device property name - prop, _, _, err := device(ctx.Client, cmd.InputName) - if err != nil { - return fmt.Errorf("failed to get device property: %w", err) - } + prop, _ := device(ctx, cmd.InputName) if prop == "" { return fmt.Errorf("no device property found for input '%s'", cmd.InputName) } From a960c9ffa53031515b62dc5af85e6cb5eed6e6b0 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Thu, 8 Jan 2026 15:31:02 +0000 Subject: [PATCH 09/11] reorder commands add input kind-defaults --- input.go | 221 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 137 insertions(+), 84 deletions(-) diff --git a/input.go b/input.go index b7437df..977fc26 100644 --- a/input.go +++ b/input.go @@ -14,16 +14,17 @@ import ( // InputCmd provides commands to manage inputs in OBS Studio. type InputCmd struct { - Create InputCreateCmd `cmd:"" help:"Create input." aliases:"c"` - Remove InputRemoveCmd `cmd:"" help:"Remove input." aliases:"d"` - ListKinds InputListKindsCmd `cmd:"" help:"List input kinds." aliases:"k"` - List InputListCmd `cmd:"" help:"List all inputs." aliases:"ls"` - Mute InputMuteCmd `cmd:"" help:"Mute input." aliases:"m"` - Unmute InputUnmuteCmd `cmd:"" help:"Unmute input." aliases:"um"` - Volume InputVolumeCmd `cmd:"" help:"Set input volume." aliases:"v"` - Show InputShowCmd `cmd:"" help:"Show input details." aliases:"s"` - Toggle InputToggleCmd `cmd:"" help:"Toggle input." aliases:"tg"` - Update InputUpdateCmd `cmd:"" help:"Update input settings." aliases:"up"` + Create InputCreateCmd `cmd:"" help:"Create input." aliases:"c"` + Remove InputRemoveCmd `cmd:"" help:"Remove input." aliases:"d"` + List InputListCmd `cmd:"" help:"List all inputs." aliases:"ls"` + ListKinds InputListKindsCmd `cmd:"" help:"List input kinds." aliases:"k"` + Mute InputMuteCmd `cmd:"" help:"Mute input." aliases:"m"` + Unmute InputUnmuteCmd `cmd:"" help:"Unmute input." aliases:"um"` + Toggle InputToggleCmd `cmd:"" help:"Toggle input." aliases:"tg"` + Volume InputVolumeCmd `cmd:"" help:"Set input volume." aliases:"v"` + Show InputShowCmd `cmd:"" help:"Show input details." aliases:"s"` + Update InputUpdateCmd `cmd:"" help:"Update input settings." aliases:"up"` + KindDefaults InputKindDefaultsCmd `cmd:"" help:"Get default settings for an input kind." aliases:"df"` } // InputCreateCmd provides a command to create an input. @@ -72,47 +73,6 @@ func (cmd *InputRemoveCmd) Run(ctx *context) error { return nil } -// InputListKindsCmd provides a command to list all input kinds. -type InputListKindsCmd struct{} - -// Run executes the command to list all input kinds. -func (cmd *InputListKindsCmd) Run(ctx *context) error { - resp, err := ctx.Client.Inputs.GetInputKindList( - inputs.NewGetInputKindListParams().WithUnversioned(false), - ) - if err != nil { - return fmt.Errorf("failed to get input kinds: %w", err) - } - - t := table.New().Border(lipgloss.RoundedBorder()). - BorderStyle(lipgloss.NewStyle().Foreground(ctx.Style.border)) - t.Headers("Kind") - t.StyleFunc(func(row, col int) lipgloss.Style { - style := lipgloss.NewStyle().Padding(0, 3) - switch col { - case 0: - style = style.Align(lipgloss.Left) - } - 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 - }) - - for _, kind := range resp.InputKinds { - t.Row(kind) - } - - fmt.Fprintln(ctx.Out, t.Render()) - - return nil -} - // InputListCmd provides a command to list all inputs. type InputListCmd struct { Input bool `flag:"" help:"List all inputs." aliases:"i"` @@ -216,6 +176,47 @@ func (cmd *InputListCmd) Run(ctx *context) error { return nil } +// InputListKindsCmd provides a command to list all input kinds. +type InputListKindsCmd struct{} + +// Run executes the command to list all input kinds. +func (cmd *InputListKindsCmd) Run(ctx *context) error { + resp, err := ctx.Client.Inputs.GetInputKindList( + inputs.NewGetInputKindListParams().WithUnversioned(false), + ) + if err != nil { + return fmt.Errorf("failed to get input kinds: %w", err) + } + + t := table.New().Border(lipgloss.RoundedBorder()). + BorderStyle(lipgloss.NewStyle().Foreground(ctx.Style.border)) + t.Headers("Kind") + t.StyleFunc(func(row, col int) lipgloss.Style { + style := lipgloss.NewStyle().Padding(0, 3) + switch col { + case 0: + style = style.Align(lipgloss.Left) + } + 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 + }) + + for _, kind := range resp.InputKinds { + t.Row(kind) + } + + fmt.Fprintln(ctx.Out, t.Render()) + + return nil +} + // InputMuteCmd provides a command to mute an input. type InputMuteCmd struct { InputName string `arg:"" help:"Name of the input to mute."` @@ -252,6 +253,37 @@ func (cmd *InputUnmuteCmd) Run(ctx *context) error { return nil } +// InputToggleCmd provides a command to toggle the mute state of an input. +type InputToggleCmd struct { + InputName string `arg:"" help:"Name of the input to toggle."` +} + +// Run executes the command to toggle the mute state of an input. +func (cmd *InputToggleCmd) Run(ctx *context) error { + // Get the current mute state of the input + resp, err := ctx.Client.Inputs.GetInputMute( + inputs.NewGetInputMuteParams().WithInputName(cmd.InputName), + ) + if err != nil { + return fmt.Errorf("failed to get input mute state: %w", err) + } + // Toggle the mute state + newMuteState := !resp.InputMuted + _, err = ctx.Client.Inputs.SetInputMute( + inputs.NewSetInputMuteParams().WithInputName(cmd.InputName).WithInputMuted(newMuteState), + ) + if err != nil { + return fmt.Errorf("failed to toggle input mute state: %w", err) + } + + if newMuteState { + fmt.Fprintf(ctx.Out, "Muted input: %s\n", ctx.Style.Highlight(cmd.InputName)) + } else { + fmt.Fprintf(ctx.Out, "Unmuted input: %s\n", ctx.Style.Highlight(cmd.InputName)) + } + return nil +} + // InputVolumeCmd provides a command to set the volume of an input. type InputVolumeCmd struct { InputName string `arg:"" help:"Name of the input to set volume for." required:""` @@ -281,8 +313,8 @@ func (cmd *InputVolumeCmd) Run(ctx *context) error { // InputShowCmd provides a command to show input details. type InputShowCmd struct { - Name string `arg:"" help:"Name of the input to show." required:""` - Verbose bool ` help:"Show all properties and their keys and values." flag:""` + Name string `arg:"" help:"Name of the input to show." required:""` + Verbose bool ` help:"List all available input devices." flag:""` } // Run executes the command to show input details. @@ -407,37 +439,6 @@ func device(ctx *context, inputName string) (string, string) { return "", "" } -// InputToggleCmd provides a command to toggle the mute state of an input. -type InputToggleCmd struct { - InputName string `arg:"" help:"Name of the input to toggle."` -} - -// Run executes the command to toggle the mute state of an input. -func (cmd *InputToggleCmd) Run(ctx *context) error { - // Get the current mute state of the input - resp, err := ctx.Client.Inputs.GetInputMute( - inputs.NewGetInputMuteParams().WithInputName(cmd.InputName), - ) - if err != nil { - return fmt.Errorf("failed to get input mute state: %w", err) - } - // Toggle the mute state - newMuteState := !resp.InputMuted - _, err = ctx.Client.Inputs.SetInputMute( - inputs.NewSetInputMuteParams().WithInputName(cmd.InputName).WithInputMuted(newMuteState), - ) - if err != nil { - return fmt.Errorf("failed to toggle input mute state: %w", err) - } - - if newMuteState { - fmt.Fprintf(ctx.Out, "Muted input: %s\n", ctx.Style.Highlight(cmd.InputName)) - } else { - fmt.Fprintf(ctx.Out, "Unmuted input: %s\n", ctx.Style.Highlight(cmd.InputName)) - } - return nil -} - // InputUpdateCmd provides a command to update input settings. type InputUpdateCmd struct { InputName string `arg:"" help:"Name of the input to update." required:""` @@ -500,3 +501,55 @@ func (cmd *InputUpdateCmd) Run(ctx *context) error { return nil } + +// InputKindDefaultsCmd provides a command to get default settings for an input kind. +type InputKindDefaultsCmd struct { + Kind string `arg:"" help:"Input kind to get default settings for." required:""` +} + +// Run executes the command to get default settings for an input kind. +func (cmd *InputKindDefaultsCmd) Run(ctx *context) error { + resp, err := ctx.Client.Inputs.GetInputDefaultSettings( + inputs.NewGetInputDefaultSettingsParams(). + WithInputKind(cmd.Kind), + ) + if err != nil { + return fmt.Errorf("failed to get default settings for input kind '%s': %w", cmd.Kind, err) + } + + t := table.New().Border(lipgloss.RoundedBorder()). + BorderStyle(lipgloss.NewStyle().Foreground(ctx.Style.border)) + t.Headers("Setting", "Value") + t.StyleFunc(func(row, col int) lipgloss.Style { + style := lipgloss.NewStyle().Padding(0, 3) + switch col { + case 0: + style = style.Align(lipgloss.Left) + case 1: + style = style.Align(lipgloss.Center) + } + 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 + }) + + keys := make([]string, 0, len(resp.DefaultInputSettings)) + for k := range resp.DefaultInputSettings { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, key := range keys { + value := resp.DefaultInputSettings[key] + t.Row(key, fmt.Sprintf("%v", value)) + } + + fmt.Fprintln(ctx.Out, t.Render()) + return nil +} From 474693e0f7c19455e8270856786ebb34c7ac52e6 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Thu, 8 Jan 2026 15:33:56 +0000 Subject: [PATCH 10/11] add new input subcommands to README --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/README.md b/README.md index fce67de..4c8b9fa 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,20 @@ gobs-cli group status START "test_group" ### InputCmd +- create: Create input. + - args: Name Kind + +```console +gobs-cli input create 'stream mix' 'wasapi_input_capture' +``` + +- remove: Remove input. + - args: Name + +```console +gobs-cli input remove 'stream mix' +``` + - list: List all inputs. - flags: @@ -281,6 +295,12 @@ gobs-cli input list gobs-cli input list --input --colour ``` +- list-kinds: List input kinds. + +```console +gobs-cli input list-kinds +``` + - mute: Mute input. - args: InputName @@ -302,6 +322,34 @@ gobs-cli input unmute "Mic/Aux" gobs-cli input toggle "Mic/Aux" ``` +- volume: Set input volume. + - args: InputName Volume + +```console +gobs-cli input volume -- 'Mic/Aux' -30.6 +``` + +- show: Show input details. + - args: Name + - flags: + + *optional* + - --verbose: List all available input devices. + +- update: Update input settings. + - args: InputName DeviceName + +```console +gobs-cli input update 'Mic/Aux' 'Voicemeeter Out B1 (VB-Audio Voicemeeter VAIO)' +``` + +- kind-defaults: Get default settings for an input kind. + - args: Kind + +```console +gobs-cli input kind-defaults 'wasapi_input_capture' +``` + ### TextCmd - current: Display current text for a text input. From 8a5ce67ba066a3f6de70ed8563fb2dfdf85dcc2c Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Thu, 8 Jan 2026 15:36:12 +0000 Subject: [PATCH 11/11] add 0.15.0 to CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7da58f3..582ec99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +# [0.15.0] - 2026-01-26 + +### Added + +- new subcommands added to input, see [InputCmd](https://github.com/onyx-and-iris/gobs-cli?tab=readme-ov-file#inputcmd) + # [0.14.1] - 2025-07-14 ### Added