OBS Studio Lua Скриптинг. Часть 2

от автора

Всем привет, в этой части руководства рассмотрим:
фильтры, сцены, предметы сцен, Frontend API, создание функциональных фильтров и прочее…
С первой частью можно ознакомиться по этой ссылке.

КДПВ сделана в OBS 26.0.0

Краткая справка для этой части

  • Источник — Источники используются для рендера аудио/видео, например: захват камеры, игры, звука. С помощью источников можно создавать фильтры, переходы

  • Фильтр — Источник который дополняет другие источники

  • Сцены — Коллекция источников, сцена является источником ( сцену можно добавить как источник в другой сцене)

  • Предмет сцены — конкретный источник в сцене, его можно: перемещать, увеличивать/уменьшать, переворачивать, менять состояние выкл/вкл и.т.д

  • Frontend API — Набор функций который предоставляет OBS Studio, например:

    • подписка на событие о переключении сцен
    • запрос статуса о том, идёт ли стрим/запись
    • вкл/выкл стрим/запись
    • переключение сцен

Фильтры

Типы фильтров так же как и источников, можно узнать через функцию obs_source_get_unversioned_id

Название Внутреннее представление типа
Компрессор compressor_filter
Экспандер expander_filter
Усиление gain_filter
Инвертировать полярность invert_polarity_filter
Лимитер limiter_filter
Пропускной уровень шума noise_gate_filter
Шумоподавление noise_suppress_filter
VST 2.x плагин vst_filter
Задержка видео (асинхронность) async_delay_filter
Хромакей chroma_key_filter
Коррекция цвета color_filter
Цветовой ключ color_key_filter
Кадрирование crop_filter
Маска изображения/Смешивание mask_filter
Яркостный ключ luma_key_filter
Задержка отображения gpu_delay
Масштабирование/Соотношение сторон scale_filter
Прокрутка scroll_filter
Увеличить резкость sharpness_filter

В английском варианте: ссылка

Скрипт: изменение параметра прозрачности у фильтра на случайную величину от 1 до 100.

Чтобы узнать название параметра "прозрачность" необходимо добавить фильтр с прозрачностью на какой-нибудь источник, изменить этот параметр. Далее открыть файл коллекции сцен, путь к директории можно узнать через меню OBS:
Справка > Файлы журнала > Показать файлы журнала
далее с этой директории поднимаемся выше, и получаем путь ~/basic>scenes>название_сцены.json
В этом файле ищем color_filter или color_key_filter (оба фильтра могут изменить прозрачность источника).
В строке settings видим что прозрачность записана как opacity.
Ещё один способ узнать название параметра, прочитать исходный код фильтра — ссылка

Находим источник по имени

function add_filter_to_source(random_n)   source = obs.obs_get_source_by_name(source_name)

Создаём настройки с изменением параметра opacity на случайное число

settings = obs.obs_data_create() obs.obs_data_set_int(settings, "opacity",random_n)

Проверяем существует ли уже фильтр на источнике, если нет добавляем

_color_filter = obs.obs_source_get_filter_by_name(source,"opacity_random") if _color_filter == nil then -- if not exists   _color_filter = obs.obs_source_create_private( "color_filter", "opacity_random", settings)   obs.obs_source_filter_add(source, _color_filter) end

Обновляем и освобождаем память

  obs.obs_source_update(_color_filter,settings)    obs.obs_source_release(source)   obs.obs_data_release(settings)   obs.obs_source_release(_color_filter) end

Привязка к горячей клавише

function htk_1_cb(pressed)    if pressed then     n = math.random(1,100)     add_filter_to_source(n)   end end

Гифка

Исходный код

local obs = obslua source_name = ''  function htk_1_cb(pressed)    if pressed then     n = math.random(1,100)     add_filter_to_source(n)   end end  function add_filter_to_source(random_n)   source = obs.obs_get_source_by_name(source_name)   settings = obs.obs_data_create()    obs.obs_data_set_int(settings, "opacity",random_n)    _color_filter = obs.obs_source_get_filter_by_name(source,"opacity_random")   if _color_filter == nil then -- if not exists     _color_filter = obs.obs_source_create_private( "color_filter", "opacity_random", settings)     obs.obs_source_filter_add(source, _color_filter)   end    obs.obs_source_update(_color_filter,settings)    obs.obs_source_release(source)   obs.obs_data_release(settings)   obs.obs_source_release(_color_filter) end  function script_properties()   -- source https://raw.githubusercontent.com/insin/obs-bounce/master/bounce.lua   local props = obs.obs_properties_create()   local source = obs.obs_properties_add_list(     props,     'source',     'Source:',     obs.OBS_COMBO_TYPE_EDITABLE,     obs.OBS_COMBO_FORMAT_STRING)   for _, name in ipairs(get_source_names()) do     obs.obs_property_list_add_string(source, name, name)   end   return props end  function script_update(settings)   source_name = obs.obs_data_get_string(settings, 'source') end  --- get a list of source names, sorted alphabetically function get_source_names()   local sources = obs.obs_enum_sources()   local source_names = {}   if sources then     for _, source in ipairs(sources) do       -- exclude Desktop Audio and Mic/Aux by their capabilities       local capability_flags = obs.obs_source_get_output_flags(source)       if bit.band(capability_flags, obs.OBS_SOURCE_DO_NOT_SELF_MONITOR) == 0 and         capability_flags ~= bit.bor(obs.OBS_SOURCE_AUDIO, obs.OBS_SOURCE_DO_NOT_DUPLICATE) then         table.insert(source_names, obs.obs_source_get_name(source))       end     end   end   obs.source_list_release(sources)   table.sort(source_names, function(a, b)     return string.lower(a) < string.lower(b)   end)   return source_names end  key_1 = '{"htk_1": [ { "key": "OBS_KEY_1" } ]}' json_s = key_1 default_hotkeys = {   {id='htk_1',des='Кнопка 1 ',callback=htk_1_cb}, }  function script_load(settings)    s = obs.obs_data_create_from_json(json_s)   for _,v in pairs(default_hotkeys) do      a = obs.obs_data_get_array(s,v.id)     h = obs.obs_hotkey_register_frontend(v.id,v.des,v.callback)     obs.obs_hotkey_load(h,a)     obs.obs_data_array_release(a)   end   obs.obs_data_release(s) end

Стоит упомянуть также о функции obs_source_enum_filters с её помощью можно получить
список всех фильтров у конкретного источника, кстати эта функция не работает в obspython,
но об этом чуть позже.

function check()   source = obs.obs_get_source_by_name(source_name)    result = obs.obs_source_enum_filters(source)   for k,v in pairs(result) do      name = obs.obs_source_get_name(v)     print('name'.. name)   end    obs.source_list_release(result)   obs.obs_source_release(source) end

Эвенты и состояние

Скрипт: звуковое оповещение о том что сцена изменена, с использованием .mp3 файла.
На основе этого скрипта

Создадим функцию для проигрывания звука при смене сцен.

function on_event(event)    if event == obs.OBS_FRONTEND_EVENT_SCENE_CHANGED     then obs_play_sound_release_source()   end  end

Добавим источник медиа, установим настройки: файл alert.mp3 относителен директории нахождения
скрипта, obs_source_set_monitoring_type выставляет прослушивание аудио.

function play_sound()   mediaSource = obs.obs_source_create_private("ffmpeg_source", "Global Media Source", nil)   local s = obs.obs_data_create()   obs.obs_data_set_string(s, "local_file",script_path() .. "alert.mp3")   obs.obs_source_update(mediaSource,s)   obs.obs_source_set_monitoring_type(mediaSource,obs.OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT)   obs.obs_data_release(s)    obs.obs_set_output_source(outputIndex, mediaSource)   return mediaSource end  function obs_play_sound_release_source()   r = play_sound()   obs.obs_source_release(r) end

Исходный код

local obs = obslua mediaSource = nil -- Null pointer outputIndex = 63 -- Last index  function play_sound()   mediaSource = obs.obs_source_create_private("ffmpeg_source", "Global Media Source", nil)   local s = obs.obs_data_create()   obs.obs_data_set_string(s, "local_file",script_path() .. "alert.mp3")   obs.obs_source_update(mediaSource,s)   obs.obs_source_set_monitoring_type(mediaSource,obs.OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT)   obs.obs_data_release(s)    obs.obs_set_output_source(outputIndex, mediaSource)   return mediaSource end  function obs_play_sound_release_source()   r = play_sound()   obs.obs_source_release(r) end  function on_event(event)    if event == obs.OBS_FRONTEND_EVENT_SCENE_CHANGED     then obs_play_sound_release_source()   end  end  function script_load(settings)   obs.obs_frontend_add_event_callback(on_event) end  function script_unload()   obs.obs_set_output_source(outputIndex, nil) end

Время и файлы

Запись в файл, "a" — создаст(если нет) файл и добавит "content", а "w" — перезапишет .

io.output(io.open(script_path() .. "out.txt","a")) io.write("content") io.close()

print(os.date("%c")) -- День недели Месяц Время Год

Сцены и предметы сцен

  • obs_sceneitem_get_source — предметы сцен в источник
  • obs_scene_from_source — преобразование сцены в источник
  • obs_scene_find_source — преобразование источника в предмет сцены
  • obs_frontend_get_scenes — получение всех сцен, освобождать с source_list_release
  • obs_frontend_get_current_scene — получение текущей сцены
  • obs_scene_enum_items — список всех предметов в сцене, освобождать с sceneitem_list_release

Скрипт: включение и выключение предмета сцены(источника на сцене).

Получение всех сцен и предметов в них

function toggle_source()   scenes = obs.obs_frontend_get_scenes()   for _,scene in pairs(scenes) do     scene_source = obs.obs_scene_from_source(scene)     items = obs.obs_scene_enum_items(scene_source) ...

Поиск конкретного источника и его включение или выключение, source_name и boolean определены глобально.

... for _,scene_item in pairs(items) do   _source = obs.obs_sceneitem_get_source(scene_item)   _name = obs.obs_source_get_name(_source)   if _name == source_name then     boolean = not boolean      obs.obs_sceneitem_set_visible(scene_item, boolean)   end end ...

Гифка

Исходный код

local obs = obslua source_name = '' boolean = true  function htk_1_cb(pressed)    if pressed then     toggle_source()   end end  function toggle_source()   scenes = obs.obs_frontend_get_scenes()   for _,scene in pairs(scenes) do     scene_source = obs.obs_scene_from_source(scene)     items = obs.obs_scene_enum_items(scene_source)      for _,scene_item in pairs(items) do       _source = obs.obs_sceneitem_get_source(scene_item)       _name = obs.obs_source_get_name(_source)       if _name == source_name then         boolean = not boolean          obs.obs_sceneitem_set_visible(scene_item, boolean)       end     end     obs.sceneitem_list_release(items)   end   obs.source_list_release(scenes) end  function script_properties()   -- source https://raw.githubusercontent.com/insin/obs-bounce/master/bounce.lua   local props = obs.obs_properties_create()   local source = obs.obs_properties_add_list(     props,     'source',     'Source:',     obs.OBS_COMBO_TYPE_EDITABLE,     obs.OBS_COMBO_FORMAT_STRING)   for _, name in ipairs(get_source_names()) do     obs.obs_property_list_add_string(source, name, name)   end   obs.obs_property_set_long_description(source,"?" )   return props end  function script_update(settings)   source_name = obs.obs_data_get_string(settings, 'source') end  --- get a list of source names, sorted alphabetically function get_source_names()   local sources = obs.obs_enum_sources()   local source_names = {}   if sources then     for _, source in ipairs(sources) do       -- exclude Desktop Audio and Mic/Aux by their capabilities       local capability_flags = obs.obs_source_get_output_flags(source)       if bit.band(capability_flags, obs.OBS_SOURCE_DO_NOT_SELF_MONITOR) == 0 and         capability_flags ~= bit.bor(obs.OBS_SOURCE_AUDIO, obs.OBS_SOURCE_DO_NOT_DUPLICATE) then         table.insert(source_names, obs.obs_source_get_name(source))       end     end   end   obs.source_list_release(sources)   table.sort(source_names, function(a, b)     return string.lower(a) < string.lower(b)   end)   return source_names end  key_1 = '{"htk_1": [ { "key": "OBS_KEY_1" } ]}' json_s = key_1 default_hotkeys = {   {id='htk_1',des='Кнопка 1 ',callback=htk_1_cb}, }  function script_load(settings)    s = obs.obs_data_create_from_json(json_s)   for _,v in pairs(default_hotkeys) do      a = obs.obs_data_get_array(s,v.id)     h = obs.obs_hotkey_register_frontend(v.id,v.des,v.callback)     obs.obs_hotkey_load(h,a)     obs.obs_data_array_release(a)   end   obs.obs_data_release(s) end

Регистрация фильтров

В obslua доступна функция obs_register_source, с её помощью можно зарегистрировать
источник( переходы и фильтры это источники). Для этого понадобится создать таблицу источника
Регистрация фильтров удобна тем, что позволяет закрепить функциональность скрипта за
определенным источником. Поддерживает горячие клавиши, интерфейс, таймеры.

Скрипт: закрепление горячих клавиш на фильтре, и полный доступ к источнику.

Импорт библиотеки, и определение типа как источник-фильтр.

local obs = obslua local bit = require("bit")  local info = {} -- obs_source_info https://obsproject.com/docs/reference-sources.html info.id = "uniq_filter_id" info.type = obs.OBS_SOURCE_TYPE_FILTER info.output_flags = bit.bor(obs.OBS_SOURCE_VIDEO)  info.get_name = function() return 'default filter name' end

Инициализация фильтра, будет вызываться при запуске программы или при добавлении к источнику

info.create = function(settings,source)    local filter = {}   filter.context = source

Начальная регистрация горячих клавиш, принадлежащих конкретному фильтру.

filter.hotkeys = {   htk_stop = "[stop] ",   htk_restart = "[start] ", } filter.hotkey_mapping = function(hotkey,data)   if hotkey == "htk_stop" then     print('stop '.. data.srsn .. " : " .. data.filn)   elseif hotkey == "htk_restart" then     print('restart ' .. data.srsn .. " : " .. data.filn)   end end  filter.hk = {} for k,v in pairs(filter.hotkeys) do    filter.hk[k] = obs.OBS_INVALID_HOTKEY_ID end

Создание функции которая запустится не сразу ( это необходимо т.к фильтр ещё не создан)
Он будет создан после return

filter._reg_htk = function()     info.reg_htk(filter,settings)   end   obs.timer_add(filter._reg_htk,100) -- callback to register hotkeys, one time only

Завершающая регистрация горячих клавиш,obs_filter_get_parent источник который фильтруется
к которому прикреплён фильтр этого типа. Удаление таймера.

info.reg_htk = function(filter,settings) -- register hotkeys after 100 ms since filter was created   local target = obs.obs_filter_get_parent(filter.context)   local srsn = obs.obs_source_get_name(target)    local filn =  obs.obs_source_get_name(filter.context)   local data = {srsn = srsn, filn = filn}     for k, v in pairs(filter.hotkeys) do      filter.hk[k] = obs.obs_hotkey_register_frontend(k, v .. srsn .. " : " .. filn, function(pressed)     if pressed then filter.hotkey_mapping(k,data) end end)     local a = obs.obs_data_get_array(settings, k)     obs.obs_hotkey_load(filter.hk[k], a)     obs.obs_data_array_release(a)   end    obs.remove_current_callback() end

Необходимый код для пропуска фильтрации, без него при добавлении фильтра источник "выключится"

info.video_render = function(filter, effect)    -- called every frame   local target = obs.obs_filter_get_parent(filter.context)   if target ~= nil then     filter.width = obs.obs_source_get_base_width(target)     filter.height = obs.obs_source_get_base_height(target)   end   obs.obs_source_skip_video_filter(filter.context)  end  info.get_width = function(filter)   return filter.width end  info.get_height = function(filter)   return filter.height end

Функция .save вызывается при сохранении настроек, т.е можно переназначить горячие клавиши.
obs.obs_register_source(info) — регистрация фильтра, теперь его видно при нажатии ПКМ

info.save = function(filter,settings)   for k, v in pairs(filter.hotkeys) do     local a = obs.obs_hotkey_save(filter.hk[k])     obs.obs_data_set_array(settings, k, a)     obs.obs_data_array_release(a)   end end obs.obs_register_source(info)

info.load — также как и script_load, вызывается при запуске программы, но в данном
случае дублирует функциональность и требует перезапуска. .update, .get_properties
функции аналогичные script_update, script_properties.

Гифка

Исходный код

local obs = obslua local bit = require("bit")  local info = {} -- obs_source_info https://obsproject.com/docs/reference-sources.html info.id = "uniq_filter_id" info.type = obs.OBS_SOURCE_TYPE_FILTER info.output_flags = bit.bor(obs.OBS_SOURCE_VIDEO)  info.get_name = function() return 'default filter name' end  info.create = function(settings,source)    local filter = {}   filter.context = source    filter.hotkeys = {     htk_stop = "[stop] ",     htk_restart = "[start] ",   }   filter.hotkey_mapping = function(hotkey,data)     if hotkey == "htk_stop" then       print('stop '.. data.srsn .. " : " .. data.filn)     elseif hotkey == "htk_restart" then       print('restart ' .. data.srsn .. " : " .. data.filn)     end   end    filter.hk = {}   for k,v in pairs(filter.hotkeys) do      filter.hk[k] = obs.OBS_INVALID_HOTKEY_ID   end    filter._reg_htk = function()     info.reg_htk(filter,settings)   end   obs.timer_add(filter._reg_htk,100) -- callback to register hotkeys, one time only    return filter end  info.reg_htk = function(filter,settings) -- register hotkeys after 100 ms since filter was created   local target = obs.obs_filter_get_parent(filter.context)   local srsn = obs.obs_source_get_name(target)    local filn =  obs.obs_source_get_name(filter.context)   local data = {srsn = srsn, filn = filn}     for k, v in pairs(filter.hotkeys) do      filter.hk[k] = obs.obs_hotkey_register_frontend(k, v .. srsn .. " : " .. filn, function(pressed)     if pressed then filter.hotkey_mapping(k,data) end end)     local a = obs.obs_data_get_array(settings, k)     obs.obs_hotkey_load(filter.hk[k], a)     obs.obs_data_array_release(a)   end    obs.remove_current_callback() end  info.video_render = function(filter, effect)    -- called every frame   local target = obs.obs_filter_get_parent(filter.context)   if target ~= nil then     filter.width = obs.obs_source_get_base_width(target)     filter.height = obs.obs_source_get_base_height(target)   end   obs.obs_source_skip_video_filter(filter.context)  end  info.get_width = function(filter)   return filter.width end  info.get_height = function(filter)   return filter.height end  --info.load = function(filter,settings) -- restart required --... same code as in info.reg_htk, but filters will be created from scratch every time --obs restarts, there is no reason to define it here again becuase hotkeys will be duplicated --end  info.save = function(filter,settings)   for k, v in pairs(filter.hotkeys) do     local a = obs.obs_hotkey_save(filter.hk[k])     obs.obs_data_set_array(settings, k, a)     obs.obs_data_array_release(a)   end end  obs.obs_register_source(info)

obspython

В OBS также доступен скриптинг через Python, для Windows только 3.6 версия, для Linux встроенная (т.к в настройках нельзя указать путь),
для MacOS Python не доступен для текущей (26.0.0) версии.
В отличии от Lua тут нельзя регистрировать источники, перебор фильтров не работает,
т.к не написан wrapper на функции с аргументом типа указатель-указатель.
Но в контексте скриптинга имеет место быть т.к:

Задачи

Перед тем как начать делать задачи, рекомендую сделать бэкап коллекции сцен,
с осторожностью использовать script_tick(вызывается раз в каждый кадр)
Проверять утечки памяти в папке logs, последняя строка последнего файла
пример — время: Number of memory leaks: 0, если скрипт написан неправильно то
этой строчки там не окажется т.к OBS вылетит с ошибкой при закрытии.

3)[фильтры] "Динамическая прокрутка"
Создать программно или выбрать через интерфейс источник который будет фильтроваться,
к этом источнику добавить(если нет) фильтр Прокрутка (scroll_filter),
добавить интерфейс и/или горячие клавиши которые меняют значение вертикальной скорости
на случайную величину от 0 до 1000 при этом включать/выключать повторение с 50% шансом.

Гифка

4)[эвенты] "Проверка"
При переключении сцен проверять идёт ли запись.
Если нет — вывести оповещение ( например через error())

5)[время и файлы] "Пост-продакшен"
Создать скрипт который при нажатии горячей клавиши записывает текущее время,
относительное время от старта записи, добавляет текст "МЕТКА",
а через интерфейс UI кнопку записать текст, и место для набора текста.
изображение

6)[предметы сцены] "Сумма"
Посчитать количество сцен и предметов сцен, записать ответ в названии первой сцены.
Не учитывать группы, т.к перебор предметов груп не работает.
Гифка

7) [фильтры и источники] "Нэйтив скриптинг"
Создать фильтр который будет с интервалом в 2 секунды включать и выключать источник за которым он закреплён.
Гифка

Ответы на задачи и код скриптов включая первую часть на Github

Ссылки

ссылка на оригинал статьи https://habr.com/ru/post/522278/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *