package main import ( "flag" "fmt" "log" "os" "github.com/andreykaipov/goobs" "github.com/andreykaipov/goobs/api/requests/sceneitems" "github.com/andreykaipov/goobs/api/typedefs" // "github.com/andreykaipov/goobs/api/requests/scenes" // toml library "github.com/BurntSushi/toml" pretty "github.com/kr/pretty" ) var ( Client *goobs.Client // OBS websocket client Commands = []string{"source", "scene", "list-scenes", "list-sources"} ) const ( Help = `Usage: obs [options] [command] COMMANDS: source scene list-scenes list-sources FLAGS:` ) type Config struct { Host string `toml:"obs_host"` Pass string `toml:"obs_pass"` } type Source struct { enabled bool SceneItem *typedefs.SceneItem Scene } type Scene struct { id int name string } type CliFlags struct { show *bool hide *bool toggle *bool sourceName *string sceneName *string } // Return true if elm in list func InArray[T comparable](list []T, elm T) bool { for _, v := range list { if elm == v { return true } } return false } func (s *Source) sync() error { resp, err := Client.SceneItems.SetSceneItemEnabled(&sceneitems.SetSceneItemEnabledParams{ SceneItemEnabled: &s.enabled, SceneItemId: float64(s.SceneItem.SceneItemID), SceneName: s.Scene.name, }) if err != nil { return err } pretty.Println(resp) return nil } func (s *Source) Show() { s.enabled = true err := s.sync() if err != nil { log.Fatal(err) } } func (s *Source) Hide() { s.enabled = false err := s.sync() if err != nil { log.Fatal(err) } } func (s *Source) Toggle() { s.enabled = !s.SceneItem.SceneItemEnabled err := s.sync() if err != nil { log.Fatal(err) } } func getSourceByName(sources []*Source, name string) *Source { for _, v := range sources { if v.SceneItem.SourceName == name { return v } } return nil } // let the user choose a source from active scene func selectSource(scene Scene) *Source { sources := getSources(scene) for i, v := range sources { // Print index, name, type left aligned fmt.Printf("%2d %-20s %-20s\n", i, v.SceneItem.SourceName, v.SceneItem.SourceType) } fmt.Print("Select a source by number> ") var input int fmt.Scanln(&input) return sources[input] } // return all scenes func getScenes() []Scene { var scenes []Scene if Client == nil { log.Fatal("Client is nil") } resp, err := Client.Scenes.GetSceneList() if err != nil { log.Fatal(err) } for _, v := range resp.Scenes { scenes = append(scenes, Scene{id: v.SceneIndex, name: v.SceneName}) } return scenes } // returns all sources in a scene func getSources(scene Scene) []*Source { var sources []*Source resp, err := Client.SceneItems.GetSceneItemList(&sceneitems.GetSceneItemListParams{ SceneName: scene.name, }) if err != nil { log.Fatal(err) } for _, v := range resp.SceneItems { sources = append(sources, &Source{SceneItem: v, Scene: scene}) } return sources } func getCurrentScene() Scene { if Client == nil { log.Fatal("Client is nil") } resp, err := Client.Scenes.GetCurrentProgramScene() if err != nil { log.Fatal(err) } return Scene{name: resp.CurrentProgramSceneName} } // Display list of scenes and let the user select a scene by number // Returns a pointer to the selected scene func promptScene() Scene { var scene Scene resp, err := Client.Scenes.GetSceneList() if err != nil { log.Fatal(err) } // Print list of scenes for i, v := range resp.Scenes { fmt.Printf("%2d %s\n", i, v.SceneName) } // Ask user to select a scene fmt.Print("Select a scene by number> ") var input int fmt.Scanln(&input) scene.id = resp.Scenes[input].SceneIndex scene.name = resp.Scenes[input].SceneName return scene } func handleSourceCommand(source *Source, flags CliFlags) { if *flags.show { source.Show() } else if *flags.hide { source.Hide() } else if *flags.toggle { source.Toggle() } } func main() { var err error // load host and pass from config config := loadConfig() Client, err = goobs.New(config.Host, goobs.WithPassword(config.Pass)) if err != nil { log.Fatal(err) } defer Client.Disconnect() version, _ := Client.General.GetVersion() fmt.Printf("OBS Studio version: %s\n", version.ObsVersion) fmt.Printf("Websocket server version: %s\n", version.ObsWebSocketVersion) // Handle CLI // Commands: // - source // - scene // - list-scenes // - list-sources // Flags: // -s = show // -d = hide // -t = toggle // -scn = scene name // -srn = source name // Examples: // obsctl source -s will show the selected source // Parse CLI flags help := flag.Bool("h", false, "Show help") flags := CliFlags{ show: flag.Bool("s", false, "Show source"), hide: flag.Bool("d", false, "Hide source"), toggle: flag.Bool("t", false, "Toggle source"), sourceName: flag.String("srn", "", "Source name"), sceneName: flag.String("scn", "", "Source name"), } flag.Parse() if *flags.show && *flags.hide { log.Fatal("Cannot show and hide at the same time") } if *help { fmt.Println(Help) flag.PrintDefaults() } // parse commands using os.Args if len(os.Args) < 2 { log.Fatal("No command given") } // find command in args var cmd string for _, v := range os.Args { // test if v contains a command if InArray(Commands, v) { cmd = v } } switch cmd { case "source": // assume current active scene var source *Source if *flags.sourceName == "" { source = selectSource(getCurrentScene()) } else { fmt.Println("handling source", *flags.sourceName) source = getSourceByName(getSources(getCurrentScene()), *flags.sourceName) } handleSourceCommand(source, flags) case "scene": // prompt user to select a scene scene := promptScene() source := selectSource(scene) handleSourceCommand(source, flags) case "list-scenes": scenes := getScenes() for _, v := range scenes { fmt.Printf("%s\n", v.name) } case "list-sources": // If no scene selected assume current scene if flags.sceneName == nil || *flags.sceneName == "" { sources := getSources(getCurrentScene()) for _, v := range sources { fmt.Printf("%s\n", v.SceneItem.SourceName) } } } // select a scene // selected := promptScene(client) // fmt.Println(selected) // select source from currently active scene // scene := getCurrentScene() // source := selectSource(scene) // source.Hide() // pretty.Println(source) // Get current scene // scene := getCurrentScene(client) // fmt.Println(scene) // resp, _ := client.Scenes.GetSceneList() // for _, v := range resp.Scenes { // fmt.Printf("%2d %s\n", v.SceneIndex, v.SceneName) // } // // newScene := &scenes.SetCurrentProgramSceneParams{ // SceneName: "Coding", // } // _, err = client.Scenes.SetCurrentProgramScene(newScene) // if err != nil { // log.Fatal(err) // } } func loadConfig() Config { var config Config if _, err := toml.DecodeFile("config.toml", &config); err != nil { log.Fatal(err) } return config }