milosev.com
  • Home
    • List all categories
    • Sitemap
  • Downloads
    • WebSphere
    • Hitachi902
    • Hospital
    • Kryptonite
    • OCR
    • APK
  • About me
    • Gallery
      • Italy2022
      • Côte d'Azur 2024
    • Curriculum vitae
      • Resume
      • Lebenslauf
    • Social networks
      • Facebook
      • Twitter
      • LinkedIn
      • Xing
      • GitHub
      • Google Maps
      • Sports tracker
    • Adventures planning
  1. You are here:  
  2. Home

Automating OBS Studio Using C#

Details
Written by: Stanko Milosev
Category: C#
Published: 15 February 2026
Last Updated: 15 February 2026
Hits: 9

This article demonstrates how to automate OBS Studio programmatically using C#.

The prerequisite is that OBS Studio is already running and properly configured.

The first step is to establish a connection to OBS Studio via the WebSocket API.

public async Task<bool> ConnectAsync(CancellationToken ct = default)
{
	if (IsConnected) return true;

	await _connectLock.WaitAsync(ct);
	try
	{
		if (IsConnected) return true;

		await DisposeSocketAsync();

		_ws = new ClientWebSocket();
		_ws.Options.AddSubProtocol("obswebsocket.json");
		await _ws.ConnectAsync(opt.Uri, ct);

		// Hello (op=0)
		var hello = await ReceiveJsonAsync(_ws, ct);
		if (hello.GetProperty("op").GetInt32() != 0)
			throw new Exception("Expected OBS Hello (op=0).");

		var helloD = hello.GetProperty("d");
		int rpcVersion = helloD.GetProperty("rpcVersion").GetInt32();

		string? auth = null;
		if (helloD.TryGetProperty("authentication", out var authObj))
		{
			var challenge = authObj.GetProperty("challenge").GetString()!;
			var salt = authObj.GetProperty("salt").GetString()!;
			auth = CreateObsAuthString(opt.Password, salt, challenge);
		}

		// Identify (op=1)
		var identify = new
		{
			op = 1,
			d = new
			{
				rpcVersion,
				authentication = auth,
				eventSubscriptions = opt.EventSubscriptions
			}
		};
		await SendJsonAsync(_ws, identify, ct);

		var identified = await ReceiveJsonAsync(_ws, ct);
		if (identified.GetProperty("op").GetInt32() != 2)
			throw new Exception("OBS Identify failed (expected op=2).");

		_isConnected = true;

		var ws = _ws;
		_receiveLoop = Task.Run(() => ReceiveLoopAsync(ws, CancellationToken.None));

		await loggerAsync.Log($"OBS connected: {opt.Uri}");
		return true;
	}
	catch (OperationCanceledException)
	{
		await loggerAsync.Log("OBS connect canceled.");
		_isConnected = false;
		await DisposeSocketAsync();
		return false;
	}
	catch (Exception ex)
	{
		_isConnected = false;
		await loggerAsync.Log(ex);

		await DisposeSocketAsync();
		return false;
	}
	finally
	{
		_connectLock.Release();
	}
}

Sending a command to OBS Studio is implemented as follows:

public async Task<bool> SendAsync(string requestType, object? requestData = null, CancellationToken ct = default)
{
	await loggerAsync.Log($"requestType: {requestType}");
	if (!await ConnectAsync(ct))
	{
		return false;
	}

	var ws = _ws;
	if (ws is null || ws.State != WebSocketState.Open || !_isConnected)
		return false;

	var requestId = Guid.NewGuid().ToString("N");
	var tcs = new TaskCompletionSource<JsonElement>(TaskCreationOptions.RunContinuationsAsynchronously);
	_pending[requestId] = tcs;

	try
	{
		var payload = new
		{
			op = 6,
			d = new { requestType, requestId, requestData = requestData ?? new { } }
		};

		await _sendLock.WaitAsync(ct);
		try
		{
			await SendJsonAsync(ws, payload, ct);
		}
		finally
		{
			_sendLock.Release();
		}

		using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
		timeoutCts.CancelAfter(TimeSpan.FromSeconds(15));

		var msg = await tcs.Task.WaitAsync(timeoutCts.Token);

		var d = msg.GetProperty("d");
		var status = d.GetProperty("requestStatus");
		if (!status.GetProperty("result").GetBoolean())
		{
			var code = status.TryGetProperty("code", out var c) ? c.GetInt32() : -1;
			var comment = status.TryGetProperty("comment", out var cm) ? cm.GetString() : "(no comment)";
			throw new Exception($"OBS request '{requestType}' failed. code={code}, comment={comment}");
		}

		return true;
	}
	catch (Exception ex)
	{
		await loggerAsync.Log(ex);
		return false;
	}
	finally
	{
		_pending.TryRemove(requestId, out _);
	}
}

Starting a recording looks like this:

public async Task StartAsync(StartStopRecordingCommand command, CancellationToken ct = default)
{
	var format = BuildFilenameFormat(command, opt.RecordDirectory) ?? opt.FilenameFormatting;
	await loggerAsync.Log($"Starting recording with filename format: {format}");

	if (!string.IsNullOrWhiteSpace(opt.RecordDirectory))
	{
		try
		{
			await obs.SendAsync("SetRecordDirectory", new { recordDirectory = opt.RecordDirectory }, ct);
			await loggerAsync.Log($"Set recording directory to: {opt.RecordDirectory}");
		}
		catch
		{
			await obs.SendAsync("SetProfileParameter", new
			{
				parameterCategory = "SimpleOutput",
				parameterName = "FilePath",
				parameterValue = opt.RecordDirectory
			}, ct);
		}
	}

	try
	{
		await obs.SendAsync("SetProfileParameter", new
		{
			parameterCategory = "Output",
			parameterName = "FilenameFormatting",
			parameterValue = format
		}, ct);
	}
	catch
	{
	}

	await obs.SendAsync("StartRecord", null, ct);
}
Full example download from here

How to Build a Chat Agent for Ollama Using C#

Details
Written by: Stanko Milosev
Category: C#
Published: 17 January 2026
Last Updated: 18 January 2026
Hits: 109
This example is based on an implementation taken from this website.

To begin, I create a new WinForms project in C# targeting .NET 10, and then install the OllamaSharp NuGet package.

In this example, txtOllamaUri, txtModel, and txtMessage are WinForms controls (for example, TextBox instances). Replace them with the corresponding controls or values from your own UI:

    Chat _chat;
    OllamaApiClient? _ollamaApiClient;
	
    uri = new Uri(txtOllamaUri.Text);
    _ollamaApiClient = new OllamaApiClient(uri);
    _ollamaApiClient.SelectedModel = txtModel.Text;

    _chat = new Chat(_ollamaApiClient);
	
    await foreach (var answerToken in _chat.SendAsync(txtMessage.Text))
        Console.Write(answerToken);
Example download from here.

My first OpenWebUI tool

Details
Written by: Stanko Milosev
Category: C#
Published: 09 January 2026
Last Updated: 10 January 2026
Hits: 147
First, about my setup, the host system runs Windows 11, and all tools are installed in a Windows 11 virtual machine using VMware.

On the host system, to fully utilize the available resources, I installed Ollama with the qwen2.5-coder model, as well as OpenWebUI via Docker.

Inside the VMware environment, I created an ASP.NET Core Web API project in Visual Studio, which automatically generated the WeatherForecastController.

Since Docker is running on the host system and the WeatherForecastController is running inside VMware, I changed the settings of my ASP.NET Core Web API to allow access from outside the virtual machine.

My \Properties\launchSettings.json file looks like this:

{
  "$schema": "https://json.schemastore.org/launchsettings.json",
  "profiles": {
    "http": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": false,
      "applicationUrl": "http://localhost:5259",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "https": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": false,
      "applicationUrl": "http://0.0.0.0:7216;https://0.0.0.0:7217",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

This means that the controller was accessible via the following endpoint:

https://192.168.2.50:7217/WeatherForecast
In OpenWebUI tools, I wrote:
import requests
import urllib3

class Tools:
    def today_from_csharp_http(self) -> str:
        """
        Get the current weather.
        """
        url = "https://192.168.2.50:7217/WeatherForecast"
        try:
            r = requests.get(url, timeout=5, verify=False)
            r.raise_for_status()

            data = r.json()
            if not isinstance(data, list) or len(data) == 0:
                return f"Unexpected JSON: {type(data).__name__}: {data}"

            first = data[0]
            date = first.get("date")
            summary = first.get("summary")
            temp = first.get("temperatureC")

            return f"OK: {date} | {summary} | {temp}°C"
        except Exception as e:
            return f"HTTP request failed: {e}"

The tool must be enabled for this model by checking the corresponding tool checkbox.

Post Joomla! article

Details
Written by: Stanko Milosev
Category: C#
Published: 06 July 2025
Last Updated: 06 July 2025
Hits: 1427

Here is my Example how to post Joomla! article from .NET.

Four steps are needed:

  1. Open Joomla! admin page
  2. Login to Joomla!
  3. Open add article page
  4. Save and close

First we will create HttpClient and same instance I will use for every step:

HttpClientHandler httpClientHandler = new HttpClientHandler
{
    CookieContainer = new CookieContainer(),
    UseCookies = true,
    AllowAutoRedirect = true
};
HttpClient client = new HttpClient(httpClientHandler);

For every step we will need token

static string ExtractTokenName(string html)
{
    var regex = new Regex(@"""csrf\.token"":""(?<token>[a-f0-9]{32})""");
    var match = regex.Match(html);

    if (match.Success)
    {
        return match.Groups["token"].Value;
    }

    throw new Exception("CSRF-Token not found.");
}
  1. Open Joomla! admin page:
    async Task<string> OpenJoomlaAdminPage(HttpClient httpClient, string url) 
    {
        HttpResponseMessage getResponse = await httpClient.GetAsync(url);
        string html = await getResponse.Content.ReadAsStringAsync();
        return html;
    }
    
  2. Login to Joomla!:
    async Task<bool> LoginToJoomla(HttpClient httpClient, string url, string username, string password, string joomlaAdminPagehtml)
    {
        var tokenName = ExtractTokenName(joomlaAdminPagehtml);
        var tokenValue = "1";
        var formContent = new FormUrlEncodedContent([
            new KeyValuePair<string, string>("username", username),
            new KeyValuePair<string, string>("passwd", password),
            new KeyValuePair<string, string>("option", "com_login"),
            new KeyValuePair<string, string>("task", "login"),
            new KeyValuePair<string, string>(tokenName, tokenValue)
        ]);
        HttpResponseMessage postResponse = await httpClient.PostAsync(url, formContent);
        string postResult = await postResponse.Content.ReadAsStringAsync();
        return postResult.Contains("mod_quickicon") || postResult.Contains("cpanel");
    }
    
  3. Open add article page:
    async Task<string> OpenAddArticle(HttpClient httpClient, string addArticleUrl)
    {
        HttpResponseMessage createResponse = await httpClient.GetAsync(addArticleUrl);
        string createHtml = await createResponse.Content.ReadAsStringAsync();
        return ExtractTokenName(createHtml);
    }
    
  4. Save and close:
     
    async Task<bool> PostArticleToJoomla(HttpClient httpClient, string url, string articleToken, string title, string catid, string articletext)
    {
        var formData = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("jform[title]", title),
            new KeyValuePair<string, string>("jform[catid]", catid),
            new KeyValuePair<string, string>("jform[language]", "*"), 
            new KeyValuePair<string, string>("jform[state]", "1"),
            new KeyValuePair<string, string>("jform[articletext]", articletext),
            new KeyValuePair<string, string>("task", "article.save"),
            new KeyValuePair<string, string>(articleToken, "1")
        });
    
        HttpResponseMessage postResponse = await httpClient.PostAsync(url, formData);
        string postResultHtml = await postResponse.Content.ReadAsStringAsync();
    
        return postResultHtml.Contains("Article saved.");
    }
    
Example download from here.
  1. Proper way to cancel task
  2. How to Load a Large File from MS SQL
  3. One consumer, multiple tasks
  4. Get types and size from database

Subcategories

C#

Azure

ASP.NET

JavaScript

Software Development Philosophy

MS SQL

IBM WebSphere MQ

MySQL

Joomla

Delphi

PHP

Windows

Life

Lazarus

Downloads

Android

CSS

Chrome

HTML

Linux

Eclipse

Page 1 of 168

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10