Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More examples please and how do you select a source in a scene correctly? #52

Closed
SonnyWalkman opened this issue Sep 4, 2023 · 6 comments

Comments

@SonnyWalkman
Copy link

I'm having difficulty using the library.

//SetScene
	myscene := "TowerCam"
	n, err := client.Scenes.SetCurrentProgramScene(&scenes.SetCurrentProgramSceneParams{
		SceneName: myscene,
	})
	if err != nil {
		logger.Fatalf("🛑 %s", err)
		fmt.Println(n)
		return
	} else {
		logger.Infof("Scene %s selected\n", myscene)
	}

Works fine for scene

How do you properly select a source within a scene?
Below pulls no errors however, the source isn't selected. What is the need for SceneItemIndex: 3, SceneItemId: 3, SceneName: "MyScene"? Any combination breaks.

g, err := client.SceneItems.GetSceneItemId(&sceneitems.GetSceneItemIdParams{SourceName: "SMPTE-GS", SceneName: "TestCharts"})
	if err != nil {
		logger.Fatalf("🛑 %s", err)
		fmt.Println(g)
		return
	} else {
		fmt.Println(g)
	}
	//SetSource 1234
	mysource := "1234"
	d, err := client.SceneItems.SetSceneItemIndex(&sceneitems.SetSceneItemIndexParams{
		SceneItemIndex: 3,
		SceneItemId:    3,
		SceneName:      "MyScene",
	})
	if err != nil {
		logger.Fatalf("🛑 %s", err)
		fmt.Println(d)
		return
	} else {
		logger.Infof("Source %s selected\n", mysource)
		fmt.Println(d)
	}

Is there a method to shut OBS down? Apparently OBS can be closed via websockets?

@andreykaipov
Copy link
Owner

Hi thanks for your questions!

How do you properly select a source within a scene?

Not sure I entirely understand. Since we're programmatically interacting with OBS, there's no need to "select" a scene. Instead we specify the scene and how we want to interact with it.

Below pulls no errors however, the source isn't selected. What is the need for SceneItemIndex: 3, SceneItemId: 3, SceneName: "MyScene"? Any combination breaks.

I added another example that creates some sources in a scene and then reverses them at https://github.com/andreykaipov/goobs/blob/master/examples/sceneitems/main.go. It uses the SetSceneItemIndex request you tried. The idea is to first iterate over all the scene items to figure out what IDs and indexes to use.

sceneitems


Below is some general advice.

The way I usually use this library is by first looking through the obs-websocket protocol docs to see all the available requests and if any involve what I wanna do. This library is generated from those docs so all requests, events, parameters, documentation are practically 1-1. https://github.com/obsproject/obs-websocket/blob/5.1.0/docs/generated/protocol.md

Any time I don't know what parameters to use for some request, I listen for the client events and inspect the logs. For example in the above example - dshow_input corresponds to the Video Capture Device source, and the way I found this was by running go run examples/sources/main.go, creating a Video Capture Device source manually, and finding the following log:

2023/09/05 19:36:05 Unhandled: &events.InputCreated{DefaultInputSettings:map[string]interface {}{"active":true, "audio_output_mode":0, "autorotation":true, "color_range":"default", "color_space":"default", "frame_interval":-1, "hw_decode":false, "res_type":0, "video_format":0}, InputKind:"dshow_input", InputName:"Video Capture Device", InputSettings:map[string]interface {}{}, UnversionedInputKind:"dshow_input"}
2023/09/05 19:36:05 Unhandled: &events.SceneItemSelected{SceneItemId:39, SceneName:"Scene"}
2023/09/05 19:36:05 Unhandled: &events.SceneItemCreated{SceneItemId:39, SceneItemIndex:0, SceneName:"Scene", SourceName:"Video Capture Device"}

Specifically - InputKind:"dshow_input". Hope this helps!

@andreykaipov
Copy link
Owner

Is there a method to shut OBS down? Apparently OBS can be closed via websockets?

I don't think so. There's this open feature request https://ideas.obsproject.com/posts/2225/allow-clean-shutdown-via-api which links to the currently open PR obsproject/obs-studio#8889. So the functionality is not even in OBS itself.

Depending on what you wanna do, maybe you could kill the program externally and listen for the ExitStarted event like in https://github.com/andreykaipov/goobs/blob/v0.12.0/examples/sources/main.go#L28-L31?

@SonnyWalkman
Copy link
Author

SonnyWalkman commented Sep 6, 2023

@andreykaipov thanks for your prompt reply.

I'm using obs studio as a remote switcher (minimised no need to see UI) I switch scenes and sources in a scenes.
My go app starts obs studio by executing a external script. Running a internal call directly to start obs64.exe (windows) and also for linux seems to not start clean and correctly load configuration. The script method works properly as a work around. I believe it to do with the path and how the run path relates to where the ini and json files are called?

Below pulls no errors however, the source isn't selected. What is the need for SceneItemIndex: 3, SceneItemId: 3, SceneName: "MyScene"? Any combination breaks.

After spending a few hours, I managed to get source switching working using a 'for range' and iterating through the source list matching the source name then enabling the source I wanted to show with disabling the other sources in that scene.

I'll post the code here shortly to let others know how to do so for additional help.

As far as shutting down obs, I've tried killing its pid however doesn't shut down cleanly since obs spawns children threads which need to kill in a specific sequence. Far to dirty IMHO. The Exit button on obs UI is the best approach to shutdown obs however, seems there is yet to be a WebSocket method to do such.

I've request to add the shutdown method in the next release on the obs forums however, I'm not sure if it be a priority for the obs team? Time will tell.

Sorry for my incorrect statement?

Is there a method to shut OBS down? Apparently OBS can be closed via websockets?

There is a plugin to shut down obs which interacts with the obs api directly.
https://github.com/norihiro/obs-shutdown-plugin/
not sure if the shutdown can be added to register a callback in your library ?

if (!obs_websocket_vendor_register_request(vendor, "shutdown", shutdown_callback, NULL)) {
		blog(LOG_ERROR, "Failed to register 'shutdown' request with obs-websocket.");
		return;
	}

static void shutdown_callback(obs_data_t *request_data, obs_data_t *response_data, void *priv_data)
{
	UNUSED_PARAMETER(priv_data);
	UNUSED_PARAMETER(response_data); // TDOO: Implement
	blog(LOG_INFO, "shutdown is called...");

	const char *reason = obs_data_get_string(request_data, "reason");
	if (!reason || strlen(reason) < MIN_REASON) {
		blog(LOG_ERROR, "shutdown requires reason with at least 8 characters");
		return;
	}

	const char *support_url = obs_data_get_string(request_data, "support_url");
	if (!support_url) {
		blog(LOG_ERROR, "shutdown requires support_url pointing a valid URL.");
		return;
	}

	const auto main_window = static_cast<QMainWindow *>(obs_frontend_get_main_window());
	if (!main_window) {
		blog(LOG_ERROR, "main_window is %p", main_window);
		return;
	}

	if (obs_data_get_bool(request_data, "force")) {
		bool confirmOnExit = config_get_bool(obs_frontend_get_global_config(), "General", "ConfirmOnExit");
		if (confirmOnExit) {
			blog(LOG_INFO, "Temporarily setting General/ConfirmOnExit to false");
			obs_frontend_add_event_callback(revert_confirmOnExit_at_exit, NULL);
			config_set_bool(obs_frontend_get_global_config(), "General", "ConfirmOnExit", false);
		}
		// TODO: Remux
	}

	blog(LOG_INFO, "Shutdown obs-studio with reason: %s", reason);
	blog(LOG_INFO, "If you need support, visit %s", support_url);

	QMetaObject::invokeMethod(main_window, "close", Qt::QueuedConnection);
}

Be nice to have the Shutdown properly added to the Websocket and add it to your library. I see there is already a method stub ready to go.

@andreykaipov
Copy link
Owner

There is a plugin to shut down obs which interacts with the obs api directly.
https://github.com/norihiro/obs-shutdown-plugin/

Ah I see, in the forum thread, that plugin was created as a workaround until obsproject/obs-studio#8889 and obsproject/obs-websocket#1138 are both in.

It has an example which uses CallVendorRequest. We can do the same thing!

	_, err := client.General.CallVendorRequest(&general.CallVendorRequestParams{
		VendorName:  "obs-shutdown-plugin",
		RequestType: "shutdown",
		RequestData: map[string]interface{}{
			"reason":      "cleaning up",
			"support_url": "https://github.com/norihiro/obs-shutdown-plugin/issues",
			"force":       true,
		},
	})

You'd have to first install that plugin by copying its release into your OBS directory though. https://github.com/norihiro/obs-shutdown-plugin/releases

@SonnyWalkman
Copy link
Author

SonnyWalkman commented Sep 7, 2023

Hello @andreykaipov,
Thanks for the valuable information..
I'll use the above code to buy time until the Shutdown method is added to the obs websockets API.

Below is my snippet of test code which I've put together to select a specific source within a Scene. Thought it could help others using the library. I do a getSceneList query and map to use for a local copy than having to websockets returning all the Scenes. Same principle can be extended to take a snapshot of all Sources per Scene.

// map obs scenes and TestChart sources
	sceneMap := make(map[int]string)
	sourceMap := make(map[int]string)

	resp, _ := client.Scenes.GetSceneList()
	for _, v := range resp.Scenes {
		sceneMap[v.SceneIndex] = v.SceneName
		//fmt.Printf(" Scene -  %2d %s\n", v.SceneIndex, v.SceneName)
	}

	//SetScene MyScene
	myscene := "MyScene"
	n, err := client.Scenes.SetCurrentProgramScene(&scenes.SetCurrentProgramSceneParams{
		SceneName: myscene,
	})
	if err != nil {
		log.Fatalf("🛑 %s", err)
		fmt.Println(n)
		return
	} else {
		log.Infof("🖵  Scene %s selected", myscene)
	}

	time.Sleep(2 * time.Second)

	// First, get the scene items (sources) in your scene

	// Fetch sources for the Scene name "MyScene"
	sceneItems, _ := client.SceneItems.GetSceneItemList(&sceneitems.GetSceneItemListParams{SceneName: "MyScene"})
	if err != nil {
		log.Fatalf("🛑 Could not get scene items: %s", err)
		return
	}
	for idx, v := range sceneItems.SceneItems {
		sourceMap[idx] = v.SourceName
		//fmt.Printf("Scene sources  - %2d %s\n", idx, v.SourceName)
	}

	// Next, loop through the scene items and set visibility
	enabled := true
	disabled := false
	for _, item := range sceneItems.SceneItems {
		if item.SourceName == "MySource" {
			log.Infof("🖵  Source %s selected", item.SourceName)
			// Set this source to visible
			_, err := client.SceneItems.SetSceneItemEnabled(&sceneitems.SetSceneItemEnabledParams{
				SceneItemEnabled: &enabled, // Set to true to enable
				SceneItemId:      float64(item.SceneItemID),
				SceneName:        "TMyScene",
			})
			if err != nil {
				log.Errorf("🛑 Error setting %s to visible: %s", item.SourceName, err)
			}
		} else {
			// Set all other sources to invisible
			_, err := client.SceneItems.SetSceneItemEnabled(&sceneitems.SetSceneItemEnabledParams{
				SceneItemEnabled: &disabled, // Set to false to disable
				SceneItemId:      float64(item.SceneItemID),
				SceneName:        "MyScene",
			})
			if err != nil {
				log.Errorf("🛑 Error setting %s to non-visible: %s", item.SourceName, err)
			}
		}
	}

@andreykaipov
Copy link
Owner

Since this issue I've added a few more examples to the repo and added a "dev walkthrough" of sorts to https://github.com/andreykaipov/goobs/tree/main/docs#making-requests that hopefully provides some techniques for figuring out things like this for any folks from the future :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants