mirror of
https://github.com/onyx-and-iris/gobs-cli.git
synced 2026-01-11 16:37:49 +00:00
Merge branch 'input'
This commit is contained in:
commit
6cdc12790a
@ -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
|
||||
|
||||
48
README.md
48
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.
|
||||
|
||||
373
input.go
373
input.go
@ -3,6 +3,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@ -13,10 +14,63 @@ import (
|
||||
|
||||
// InputCmd provides commands to manage inputs in OBS Studio.
|
||||
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"`
|
||||
Toggle InputToggleCmd `cmd:"" help:"Toggle input." aliases:"tg"`
|
||||
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.
|
||||
type InputCreateCmd struct {
|
||||
Name string `arg:"" help:"Name for the input." required:""`
|
||||
Kind string `arg:"" help:"Input kind (e.g., coreaudio_input_capture, macos-avcapture)." required:""`
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete input: %w", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(ctx.Out, "Deleted %s\n", ctx.Style.Highlight(cmd.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
// InputListCmd provides a command to list all inputs.
|
||||
@ -122,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."`
|
||||
@ -188,3 +283,273 @@ func (cmd *InputToggleCmd) 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 ` help:"List all available input devices." flag:""`
|
||||
}
|
||||
|
||||
// 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
|
||||
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 := device(ctx, cmd.Name)
|
||||
if prop == "" {
|
||||
return fmt.Errorf("no device property found for input '%s'", cmd.Name)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
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), name)
|
||||
|
||||
fmt.Fprintln(ctx.Out, t.Render())
|
||||
|
||||
if cmd.Verbose {
|
||||
deviceListResp, err := ctx.Client.Inputs.GetInputPropertiesListPropertyItems(
|
||||
inputs.NewGetInputPropertiesListPropertyItemsParams().
|
||||
WithInputName(cmd.Name).
|
||||
WithPropertyName(prop),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get device list: %w", 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 deviceListResp.PropertyItems {
|
||||
if item.ItemName != "" {
|
||||
t.Row(item.ItemName)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(ctx.Out, t.Render())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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 []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.ItemValue == settings.InputSettings[propName] {
|
||||
return propName, item.ItemName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// 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, _ := device(ctx, cmd.InputName)
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user