1

I want/need to make this code run inside OBS as a script. I took this OSC Sender for OBS that it does what the name says: it sends OSC messages. But, inside the code, the server part is commented, because it doesn't work... right out of the box. It is needed to run OBS with LD_PRELOAD=/path/libpython3.7.so.

I modified the code but (uncommented lines) but, when server is opened, OBS stays unusable, blocked. So, I tried the "Async Server" - "Concurrent Mode" of python-osc. I take the example from its wiki (that it works in a console) and I mixed with the other script. I made a nice code... but it makes something weird. This is the code:

"""

"""
# osc data

import argparse
import random
import time
import math
import asyncio

from pythonosc import osc_message_builder
from pythonosc import udp_client

from pythonosc.dispatcher import Dispatcher
from pythonosc.osc_server import AsyncIOOSCUDPServer

client = None
server = None

#obs !
import obspython as obs
pleaseLog = False

host = None
serverPort = None
# transport = None

async def loop():
    """Example main loop that only runs for 10 iterations before finishing"""
    for i in range(10):
        print(f"Loop {i}")
        await asyncio.sleep(.5)

async def init_main():
    # global transport
    
    server = AsyncIOOSCUDPServer((host, serverPort), dispatcher, asyncio.get_event_loop())
    transport, protocol = await server.create_serve_endpoint()  # Create datagram endpoint and start serving
    
    await loop()  # Enter main loop of program
    transport.close()  # Clean up serve endpoint

def handleOSC(*args):
    for i in args:
        print(i)

# defines script description 
def script_description():
    return '''Send OSC data when source is activated if source name begins with /'''

# defines user properties
def script_properties():
    global props 
    props = obs.obs_properties_create()
    obs.obs_properties_add_text(props, "host", "Host IP", obs.OBS_TEXT_DEFAULT)
    obs.obs_properties_add_int(props, "clientPort", "Host port", 1, 400000, 1)
    obs.obs_properties_add_bool(props, "logOscOutput", "Log OSC output")
    obs.obs_properties_add_int(props, "serverPort", "Listen port", 1, 400000, 1)
    return props

def script_defaults(settings):
    obs.obs_data_set_default_string(settings, "host", "127.0.0.1")
    obs.obs_data_set_default_int(settings, "clientPort", 10000)
    obs.obs_data_set_default_int(settings, "serverPort", 10001)

def source_activated(cd):
    global pleaseLog
    source = obs.calldata_source(cd, "source")
    if source is not None:
        name = obs.obs_source_get_name(source)
        if name[0] == "/":
            client.send_message(name, 1)
            if (pleaseLog):
                print("send " + name)

def script_load(settings):
    global dispatcher
    global host
    
    sh = obs.obs_get_signal_handler()
    obs.signal_handler_connect(sh, "source_activate", source_activated)

    dispatcher = Dispatcher()
    dispatcher.map("/*", handleOSC)

# def script_unload():
#   global transport
#   print(f'script_unload(settings)')
#   transport.close()  # Clean up serve endpoint

def script_update(settings):
    global host
    global client
    global server
    global clientPort
    global serverPort
    global pleaseLog

    pleaseLog = obs.obs_data_get_bool(settings, "logOscOutput")
    host = obs.obs_data_get_string(settings, "host")
    clientPort = obs.obs_data_get_int(settings, "clientPort")
    serverPort = obs.obs_data_get_int(settings, "serverPort")
    
    # Client
    client = udp_client.SimpleUDPClient(host, clientPort)
    print("target set to "+host+":"+str(clientPort)+"")
    
    # Server
    print("serving in "+host+":"+str(serverPort)+"")
    asyncio.run(init_main())

I'm not a programmer, so asyncio is a mess for me. I don't understand it. But I know what it does and what I'd like it to do.

When the script is loaded (in script_update(settings)), it opens the server and run loop() function. This function is a 5 seconds loop that prints some text. If I connect to the correct port from PureData and send some OSC messages, these messages arrives to OBS... but all of them drops together when the loop finishes. Meanwhile, there's nothing, OBS is blocked and nothing is in Script Log.

If I run the python-osc asyncio example code in a console, while loop is being executed, every Loop 0, Loop 1, etc, and every message arrives and all of them are printed at the right time.

How should I get this code to work? I need to open that server and run a much more code at the same time. I'm using OBS like a game engine.

Mario Mey
  • 1,582
  • 3
  • 13
  • 13

1 Answers1

0

Finally, I didn't use Asyncio. Instead of that, I use the Blocking Server method but inside a threading.Thread() function. For the moment, it is working right in this way.

It's important to close port when unload or reload script.

Oro, Treblig_Punisher (users from OBS Discor) helped me to achieve this.

"""

"""
# osc data

import argparse, random, time, math, threading

from pythonosc import osc_message_builder
from pythonosc import udp_client

from pythonosc import dispatcher
from pythonosc import osc_server

targetIp = "127.0.0.1"
targetPort = 10000
serverPort = 10008

client = None
server = None

#obs !

import obspython as obs
pleaseLog = False

def handleOSC(address, args, data):
    print (address)
    print (args)
    print (data)

# defines script description 
def script_description():
    return '''Send and receive OSC messages'''

# defines user properties
def script_properties():
    #global props 
    props = obs.obs_properties_create()
    obs.obs_properties_add_text(props, "host", "Host IP", obs.OBS_TEXT_DEFAULT)
    obs.obs_properties_add_int(props, "port", "Host port", 1, 400000, 1)
    obs.obs_properties_add_bool(props, "logOscOutput", "Log OSC output")
    obs.obs_properties_add_int(props, "serverPort", "Listen port", 1, 400000, 1)
    return props

def script_defaults(settings):
    obs.obs_data_set_default_string(settings, "host", targetIp)
    obs.obs_data_set_default_int(settings, "port", targetPort)
    obs.obs_data_set_default_int(settings, "serverPort", serverPort)

# Cuando se activa un source
def source_activated(cd):
    global pleaseLog
    source = obs.calldata_source(cd, "source")
    if source is not None:
        name = obs.obs_source_get_name(source)
        
        current_scene = obs.obs_scene_from_source(obs.obs_frontend_get_current_scene())
        scene_item = obs.obs_scene_find_source(current_scene, name)
        boolean = obs.obs_sceneitem_visible(scene_item)
        
        print(boolean)
        if name[0] == "/":
            client.send_message(name, boolean)
            if (pleaseLog):
                print("send " + name + boolean)

def script_load(settings):
    global despachante
    
    sh = obs.obs_get_signal_handler()
    obs.signal_handler_connect(sh, "source_activate", source_activated)

    despachante = dispatcher.Dispatcher()
    despachante.map("/*", handleOSC)

def script_unload():
    global server
    server.server_close()

def script_update(settings):
    global host
    global port
    global client
    global server
    global pleaseLog

    pleaseLog = obs.obs_data_get_bool(settings, "logOscOutput")
    host = obs.obs_data_get_string(settings, "host")
    port = obs.obs_data_get_int(settings, "port")

    # Client
    client = udp_client.SimpleUDPClient(host, port)
    print("target set to "+host+":"+str(port)+"")

    # Server
    serverPort = obs.obs_data_get_int(settings, "serverPort")
    try:
        server.server_close()
    except:
        print('*Server aun no creado')
        # raise
    server = osc_server.BlockingOSCUDPServer(("127.0.0.1", serverPort), despachante)
    t = threading.Thread(target=server_th, args=([settings]))
    t.daemon = True
    t.start()

    # Loop every 1000ms
    # obs.timer_remove(funcion_loop)
    # if and source_name != "":
        # obs.timer_add(funcion_loop, 1000)

# Threading function
def server_th(settings):
    print(f'Serving on {server.server_address}')
    server.serve_forever()  # Blocks forever
Mario Mey
  • 1,582
  • 3
  • 13
  • 13