You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

354 lines
7.0 KiB
Go

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
}