如何编写可在 OBS 中运行的 Python 脚本
关注 1260
前提
阅读本节的前提是已经掌握了如何在 OBS 中运行 Python 脚本,你可以查看如何配置 OBS 运行 Python 脚本一节来了解相关信息。
将 Python 脚本函数导出至 OBS
当一个 Python 脚本拥有特定名称和参数的函数时,OBS 将发现并使用这些函数来实现某些效果,比如,在 OBS 中显示一段说明脚本作用的文字。Python 脚本与 OBS 之间的这种联系,被官方称为脚本函数导出(Script Function Exports)。
将脚本函数导出至 OBS 是可选行为
将脚本函数导出至 OBS 是一种可选行为,你所编写的 Python 脚本可以没有任何函数导出,这不会影响 OBS 对脚本的执行。
在 OBS 中展示 Python 脚本说明
每一个成功载入 OBS 的 Python 脚本,都会在脚本窗口中拥有一段说明信息。通过在脚本中定义一个名为script_description
的函数,你可以设置该脚本的相关说明文字,当然,script_description
应返回一个字符串。
OBS 支持在脚本说明中包含空白字符
script_description
函数返回的字符串中可以包含某些空白字符,比如,制表符(\t
),他们可以正常显示在脚本窗口中。
# 导入模块 obspython
import obspython as obs
def script_description():
return '这是一个简单但没有任何效果的脚本\n作者:\t哎呦喂\n版本:\t0.1\n联系:\txxx'
在 OBS 中展示 Python 脚本属性
函数script_properties
是非常重要的,当你希望 Python 脚本与用户可以交互时,该函数需要返回一个包含脚本相关属性信息的属性集对象。OBS 将根据script_properties
返回的属性集对象,在脚本窗口中创建一系列的控件,比如文本框,按钮,复选框等。通过这些控件,用户可以修改调整脚本的属性,从而改变脚本所实现的功能。
OBS 中的脚本属性是什么?
OBS 中的脚本属性(Script Properties)可以指表现在脚本窗口中的控件,也可以指用于创建这些控件的对象(脚本属性对象)。每一个脚本属性都对应了一个脚本设置(Script Settings)项,用于存储属性对应的值,不过,按钮控件除外。
OBS 中的脚本设置是什么?
脚本设置被存储在一个数据设置对象中,该对象采用类似于键值对的方式来读写一些数据。在 OBS 官方文档中,并没有类似于“脚本设置”这样的正式称呼,我们采用他只是为了方便讲述。脚本设置中的一个项未必会对应一个脚本属性,虽然你可以依据脚本设置来影响 Python 脚本所实现的功能,但他们对于用户是不可见的。
针对不同的场景集合,OBS 会在合适的时机,将脚本设置以某种形式保存至本地,或反转此过程,这样,脚本设置便具有了永久化效果。通过脚本窗口移除某个 Python 脚本文件后,其对应的脚本设置也将从本地删除。
交互控件
想要详细了解属性集对象以及如何在 OBS 中使用控件,你可以查看如何使用控件与 OBS 用户交互?属性集对象介绍一节。
在函数script_properties
中,我们使用obs_properties_create
创建了一个属性集对象,并通过obs_properties_add_text
为属性集添加了一个对应数字显示框的属性对象。
def script_properties():
# 创建一个属性集对象
props = obs.obs_properties_create()
# 添加一个对应数字显示框的脚本属性对象,用于表示小时
obs.obs_properties_add_int(props, 'hours', '小时:', 2, 5, 1)
return props
在 OBS 加载或卸载 Python 脚本时执行任务
如果希望在 Python 脚本被 OBS 加载或卸载时执行某些任务,比如从文件读取或写入数据,那么你可以在脚本中定义函数script_load
,script_unload
。
script_load(settings)
- settings 参数
settings
参数为 Python 脚本对应的脚本设置对象。
什么情况下 OBS 会加载或卸载 Python 脚本?
当你在脚本窗口添加脚本,重新载入脚本,恢复默认时,或 OBS 启动后,脚本就会被加载。当你在脚本窗口移除脚本,重新载入脚本时,恢复默认之前,或 OBS 关闭时,脚本就会被卸载。
在下面的代码中,我们通过函数script_load
和script_unload
,记录并显示了上一次脚本停止的时间。事实上,这种做法在直接关闭 OBS 时是无效的,原因会在稍后给予说明。
# 变量 data 表示脚本设置
data = None
def script_load(settings):
global data
data = settings
# 读取脚本设置项 closed_time,他是脚本的停止时间
closed_time = obs.obs_data_get_string(data, 'closed_time')
if closed_time:
obs.script_log(obs.LOG_INFO, f'上次脚本停止的时间为 {closed_time}')
def script_unload():
# 将当前时间写入脚本设置项 closed_time,作为脚本的停止时间
from datetime import datetime
obs.obs_data_set_string(data, 'closed_time', datetime.now().ctime())
[exports.py] 上次脚本停止的时间为 Mon Mar 4 07:12:37 2024
obspython 模块的 obs_data_get_string,obs_data_set_string 函数
obspython
模块的obs_data_get_string
和obs_data_set_string
函数,用于对数据设置对象中字符串类型的数据项进行读写操作。
与之类似的函数还有obs_data_get_int
,obs_data_set_int
等。
在 OBS 用户修改 Python 脚本属性后执行任务
如果希望在用户修改脚本属性(通过脚本窗口)后执行某些任务,比如,根据修改后的脚本属性调整实现的功能,那么你可以在 Python 脚本中定义函数script_update
,该函数的执行时间点在script_load
函数与script_unload
函数之间。
需要指出的是,任何脚本属性的修改都会导致script_update
函数的调用。如果仅监视某一个脚本属性的变化,那么可以使用obspython
模块的obs_property_set_modified_callback
函数。
script_update(settings)
- settings 参数
settings
参数为 Python 脚本对应的脚本设置对象。
什么情况下 OBS 会调用 script_update 函数?
除了在用户修改脚本属性后,script_update
函数还会在添加脚本,重新载入脚本,恢复默认或 OBS 启动时被调用。
脚本窗口中控件显示的值与脚本设置项对应的值可能不相同
在用户对脚本窗口中的控件进行有效编辑之前,控件所显示的值不会同步至对应的脚本设置项。这会产生困扰,因为用户从该控件看到的值,可能与你从脚本设置中读取到的值并不相同。一旦脚本属性对应的窗口控件被有效编辑,那么可从脚本设置安全的读取对应控件中的值。
要避免以上不相同的情况,可以定义script_defaults
函数,并在该函数中为控件对应的脚本设置项设置默认值。
之前,在函数script_properties
中,我们添加了最小值为2
的数字显示框,但读取其对应的脚本设置项后,其结果可能显示为0
,如果该数字显示框未被有效编辑的话。
def script_update(settings):
# 读取脚本设置项 hours 并显示
hours = obs.obs_data_get_int(settings, 'hours')
obs.script_log(obs.LOG_INFO, f'当前小时为 {hours}')
[exports.py] 当前小时为 0
在 OBS 关闭时将数据写入 Python 脚本设置
在 OBS 关闭时,Python 脚本中的script_save
函数会被调用,此时可将一些重要的数据写入脚本设置,以便下次启动时读取他们。
script_save(settings)
- settings 参数
settings
参数为 Python 脚本对应的脚本设置对象。
在关闭 OBS 时通过 script_unload 函数写入的脚本设置项不会被存储至本地
虽然 OBS 在关闭时,会调用script_unload
函数,但对脚本设置项的写入操作可能会“无效”,因为他们不会被 OBS 存储至本地,这不同于点击脚本窗口的重新载入脚本按钮。
保存重要数据的最佳方式是定义函数script_save
而非script_unload
,虽然之前的示例中演示了相反的做法。
我们使用script_save
函数代替script_unload
函数,这将使得 OBS 的关闭时间被视为脚本停止时间,而其他用户操作(比如,点击重新载入脚本按钮)的时间,不再被视为脚本的停止时间。
def script_save(settings):
# 将当前时间写入脚本设置项 closed_time,作为脚本的停止时间
from datetime import datetime
obs.obs_data_set_string(settings, 'closed_time', datetime.now().ctime())
为 Python 脚本属性指定默认值
在 OBS 官方提供的obspython
模块中,你无法直接通过参数为脚本属性指定默认值,虽然一些添加脚本属性的函数可以达到近似的效果。要为脚本窗口中的控件设置默认值,应该在 Python 脚本中定义函数script_defaults
,并在该函数中为脚本属性对应的脚本设置项指定默认值。
script_defaults(settings)
- settings 参数
settings
参数为 Python 脚本对应的脚本设置对象。
脚本设置项的默认值仅在其没有具体值时被使用
需要指出,一旦脚本设置项拥有了具体的值,其默认值将不再被使用,这会发生在用户编辑了某个控件后,或你通过函数为脚本设置项指定了一个值后。
当你点击脚本窗口的默认按钮后,脚本设置项的默认值将重新发挥作用,因为脚本设置项的具体值已经被清除。
在函数script_defaults
中,我们将脚本设置项hours
的默认值指定为3
,这会使脚本窗口中的数字显示框默认显示3
。如果之前编辑过该数字显示框,那么需要点击默认按钮,才能看到该效果。
def script_defaults(settings):
# 将设置项 hours 的默认值设置为 3
obs.obs_data_set_default_int(settings, 'hours', 3)
obspython 模块的 obs_data_set_default_int 函数
obspython
模块的obs_data_set_default_int
函数,用于为数据设置对象中整数类型的数据项设置默认值。
在 OBS 绘制每一帧时执行任务
如果你希望在 OBS 绘制每一帧时执行某些任务,那么可以在 Python 脚本中定义函数script_tick
。
script_tick(seconds)
- seconds 参数
seconds
参数是上一帧到本帧所经历的秒数。
使用 script_tick 函数可能为 OBS 带来性能问题
OBS 对script_tick
函数的调用频率较高,因此,使用该函数完成复杂任务并不是一个好主意,如果你通过script_log
函数不断向脚本日志窗口输出信息,那么 OBS 可能会无法响应用户,在长时间运行之后。
obspython
模块提供了另外一些关于计时器的函数,使用他们可以有效避免script_tick
函数所产生的问题。
def script_tick(seconds):
obs.script_log(obs.LOG_INFO, f'{seconds} OBS 就要无法响应了!!!')
获取 Python 脚本所在的目录
对于添加的 Python 脚本,OBS 会为其定义一个名为script_path
的函数(不属于obspython
模块),该函数可用于获取当前脚本所在的目录。
script_path 函数与 Python 模块的 __file__ 特性之间的区别
__file__
特性表示了 Python 模块对应的文件路径,而script_path
函数返回的是脚本文件所在文件夹的路径。
我们调整之前的代码,在script_load
中调用script_path
函数,并显示返回的目录。
def script_load(settings):
# …
obs.script_log(obs.LOG_INFO, script_path())
# Windows 中的输出结果
[exports.py] …/scripts/
为 OBS 添加或移除脚本计时器
通过obspython
模块的timer_add
和timer_remove
函数,你可以为 OBS 添加或移除脚本计时器(Script Timers)。脚本计时器将在指定时间间隔下,反复调用给出的回调函数或方法,这不同于script_tick
函数。
一旦使用了timer_add
函数,被添加的脚本计时器就会开始工作。如果希望脚本计时器停止,可以通过timer_remove
函数将其移除,或在回调函数和方法中调用obspython
模块的remove_current_callback
函数。
timer_add(callback, milliseconds)
timer_remove(callback)
- callback 参数
callback
参数是被脚本计时器回调的函数或方法,在timer_remove
函数中,callback
对应的脚本计时器将被移除。- milliseconds 参数
milliseconds
参数是计时器触发的时间间隔,以毫秒为单位,接受整数类型的值。
timer_remove 函数无法移除实例方法对应的脚本计时器
如果将模块中的 Python 函数作为回调目标,那么timer_add
和timer_remove
可以正确运行,如果将 Python 类的实例方法作为回调目标,那么timer_remove
可能失效,其对应的脚本计时器不会被移除。
在下面的代码中,welcome
是脚本计时器的回调函数,我们通过remove_current_callback
使该函数只被调用一次。
# 脚本计时器的回调函数 welcome
def welcome():
obs.script_log(obs.LOG_INFO, '这是只被调用一次的回调函数')
# 移除 welcome 对应的脚本计时器
obs.remove_current_callback()
# 添加脚本计时器,触发时间间隔为 3 秒
obs.timer_add(welcome, 3000)
[exports.py] 这是只被调用一次的回调函数