Scripting Tutorial Source Shake Listing

Final listing of the scripts described in the Source Shake tutorial.

Python version - source-shake.py

import obspython as obs
import math, time

<a id='description-displayed-in-the-scripts-dialog-window' class='anchor' aria-hidden='true'></a>
# Description displayed in the Scripts dialog window
def script_description():
  return """Source Shake!!
            Shake a source in the current scene when a hotkey is pressed. Go to Settings
             then Hotkeys to select the key combination.Check the 
            Source Shake Scripting Tutorial on the OBS Wiki for more information."""

<a id='global-variables-to-restore-the-scene-item-after-shake' class='anchor' aria-hidden='true'></a>
# Global variables to restore the scene item after shake
shaken_sceneitem = None     # Reference to the modified scene item
shaken_sceneitem_angle = 0  # Initial rotation angle, used as well for oscillations
shaken_scene_handler = None # Signal handler of the scene kept to restore

<a id='callback-for-itemremove-signal' class='anchor' aria-hidden='true'></a>
# Callback for item_remove signal
def on_shaken_sceneitem_removed(calldata):
  restore_sceneitem_after_shake()

<a id='saves-the-original-rotation-angle-of-the-given-sceneitem-and-connects-itemremove-signal' class='anchor' aria-hidden='true'></a>
# Saves the original rotation angle of the given sceneitem and connects item_remove signal
def save_sceneitem_for_shake(sceneitem):
  global shaken_sceneitem, shaken_sceneitem_angle
  shaken_sceneitem = sceneitem
  shaken_sceneitem_angle = obs.obs_sceneitem_get_rot(sceneitem)

  # Handles scene item deletion
  global shaken_scene_handler
  scene_as_source = obs.obs_scene_get_source(obs.obs_sceneitem_get_scene(sceneitem))
  shaken_scene_handler = obs.obs_source_get_signal_handler(scene_as_source)
  obs.signal_handler_connect(shaken_scene_handler, "item_remove", on_shaken_sceneitem_removed)

<a id='restores-the-original-rotation-angle-on-the-scene-item-and-disconnects-itemremove-signal' class='anchor' aria-hidden='true'></a>
# Restores the original rotation angle on the scene item and disconnects item_remove signal
def restore_sceneitem_after_shake():
  global shaken_sceneitem, shaken_sceneitem_angle
  if shaken_sceneitem:
    obs.obs_sceneitem_set_rot(shaken_sceneitem, shaken_sceneitem_angle)

    obs.signal_handler_disconnect(shaken_scene_handler, "item_remove", on_shaken_sceneitem_removed)

    shaken_sceneitem = None

<a id='retrieves-the-scene-item-of-the-given-source-name-in-the-current-scene-or-none-if-not-found' class='anchor' aria-hidden='true'></a>
# Retrieves the scene item of the given source name in the current scene or None if not found
def get_sceneitem_from_source_name_in_current_scene(name):
  result_sceneitem = None
  current_scene_as_source = obs.obs_frontend_get_current_scene()
  if current_scene_as_source:
    current_scene = obs.obs_scene_from_source(current_scene_as_source)
    result_sceneitem = obs.obs_scene_find_source_recursive(current_scene, name)
    obs.obs_source_release(current_scene_as_source)
  return result_sceneitem

<a id='global-variables-holding-the-values-of-data-settings--properties' class='anchor' aria-hidden='true'></a>
# Global variables holding the values of data settings / properties
source_name = "Spaceship"  # Name of the source to shake
frequency = 2              # Frequency of oscillations in Hertz
amplitude = 10             # Angular amplitude of oscillations in degrees

<a id='animates-the-scene-item-corresponding-to-sourcename-in-the-current-scene' class='anchor' aria-hidden='true'></a>
# Animates the scene item corresponding to source_name in the current scene
def shake_source():
  sceneitem = get_sceneitem_from_source_name_in_current_scene(source_name)
  if sceneitem:
    id = obs.obs_sceneitem_get_id(sceneitem)
    if shaken_sceneitem and obs.obs_sceneitem_get_id(shaken_sceneitem) != id:
      restore_sceneitem_after_shake()
    if not shaken_sceneitem:
      save_sceneitem_for_shake(sceneitem)
    angle = shaken_sceneitem_angle + amplitude*math.sin(time.time()*frequency*2*math.pi)
    obs.obs_sceneitem_set_rot(sceneitem, angle)
  else:
    restore_sceneitem_after_shake()

<a id='called-at-script-unload' class='anchor' aria-hidden='true'></a>
# Called at script unload
def script_unload():
  restore_sceneitem_after_shake()

<a id='called-to-set-default-values-of-data-settings' class='anchor' aria-hidden='true'></a>
# Called to set default values of data settings
def script_defaults(settings):
  obs.obs_data_set_default_string(settings, "source_name", "")
  obs.obs_data_set_default_double(settings, "frequency", 2)
  obs.obs_data_set_default_int(settings, "amplitude", 10)

<a id='fills-the-given-list-property-object-with-the-names-of-all-sources-plus-an-empty-one' class='anchor' aria-hidden='true'></a>
# Fills the given list property object with the names of all sources plus an empty one
def populate_list_property_with_source_names(list_property):
  sources = obs.obs_enum_sources()
  obs.obs_property_list_clear(list_property)
  obs.obs_property_list_add_string(list_property, "", "")
  for source in sources:
    name = obs.obs_source_get_name(source)
    obs.obs_property_list_add_string(list_property, name, name)
  obs.source_list_release(sources)

<a id='called-to-display-the-properties-gui' class='anchor' aria-hidden='true'></a>
# Called to display the properties GUI
def script_properties():
  props = obs.obs_properties_create()

  # Drop-down list of sources
  list_property = obs.obs_properties_add_list(props, "source_name", "Source name",
                    obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING)
  populate_list_property_with_source_names(list_property)
  # obs.obs_properties_add_text(props, "source_name", "Source name", obs.OBS_TEXT_DEFAULT)

  # Button to refresh the drop-down list
  obs.obs_properties_add_button(props, "button", "Refresh list of sources",
    lambda props,prop: True if populate_list_property_with_source_names(list_property) else True)

  obs.obs_properties_add_float_slider(props, "frequency", "Shake frequency", 0.1, 20, 0.1)
  obs.obs_properties_add_int_slider(props, "amplitude", "Shake amplitude", 0, 90, 1)
  return props

<a id='called-after-change-of-settings-including-once-after-script-load' class='anchor' aria-hidden='true'></a>
# Called after change of settings including once after script load
def script_update(settings):
  global source_name, frequency, amplitude
  restore_sceneitem_after_shake()
  source_name = obs.obs_data_get_string(settings, "source_name")
  frequency = obs.obs_data_get_double(settings, "frequency")
  amplitude = obs.obs_data_get_int(settings, "amplitude")

<a id='global-animation-activity-flag' class='anchor' aria-hidden='true'></a>
# Global animation activity flag
is_active = False

<a id='called-every-frame' class='anchor' aria-hidden='true'></a>
# Called every frame
def script_tick(seconds):
  if is_active:
    shake_source()
  else:
    restore_sceneitem_after_shake()

<a id='callback-for-the-hotkey' class='anchor' aria-hidden='true'></a>
# Callback for the hotkey
def on_shake_hotkey(pressed):
  global is_active
  is_active = pressed

<a id='identifier-of-the-hotkey-set-by-obs' class='anchor' aria-hidden='true'></a>
# Identifier of the hotkey set by OBS
hotkey_id = obs.OBS_INVALID_HOTKEY_ID

<a id='called-at-script-load' class='anchor' aria-hidden='true'></a>
# Called at script load
def script_load(settings):
  global hotkey_id
  hotkey_id = obs.obs_hotkey_register_frontend(script_path(), "Source Shake", on_shake_hotkey)
  hotkey_save_array = obs.obs_data_get_array(settings, "shake_hotkey")
  obs.obs_hotkey_load(hotkey_id, hotkey_save_array)
  obs.obs_data_array_release(hotkey_save_array)

<a id='called-before-data-settings-are-saved' class='anchor' aria-hidden='true'></a>
# Called before data settings are saved
def script_save(settings):
  restore_sceneitem_after_shake()
  obs.obs_save_sources()

  # Hotkey save
  hotkey_save_array = obs.obs_hotkey_save(hotkey_id)
  obs.obs_data_set_array(settings, "shake_hotkey", hotkey_save_array)
  obs.obs_data_array_release(hotkey_save_array)

Lua version - source-shake.lua


obs = obslua

-- Description displayed in the Scripts dialog window
function script_description()
  return [[Source Shake!!
           Shake a source in the current scene when a hotkey is pressed. Go to Settings
            then Hotkeys to select the key combination.Check the 
           Source Shake Scripting Tutorial on the OBS Wiki for more information.]]
end

-- Global variables to restore the scene item after shake
shaken_sceneitem = nil     -- Reference to the modified scene item
shaken_sceneitem_angle = 0 -- Initial rotation angle, used as well for oscillations
shaken_scene_handler = nil -- Signal handler of the scene kept to restore

-- Callback for item_remove signal
function on_shaken_sceneitem_removed(calldata)
  restore_sceneitem_after_shake()
end

-- Saves the original rotation angle of the given sceneitem and connects item_remove signal
function save_sceneitem_for_shake(sceneitem)
  shaken_sceneitem = sceneitem
  shaken_sceneitem_angle = obs.obs_sceneitem_get_rot(sceneitem)

  -- Handles scene item deletion
  local scene_as_source = obs.obs_scene_get_source(obs.obs_sceneitem_get_scene(sceneitem))
  shaken_scene_handler = obs.obs_source_get_signal_handler(scene_as_source)
  obs.signal_handler_connect(shaken_scene_handler, "item_remove", on_shaken_sceneitem_removed)
end

-- Restores the original rotation angle on the scene item and disconnects item_remove signal
function restore_sceneitem_after_shake()
  if shaken_sceneitem then
    --print("Restoring scene item with ID=" .. tostring(obs.obs_sceneitem_get_id(shaken_sceneitem)))
    obs.obs_sceneitem_set_rot(shaken_sceneitem, shaken_sceneitem_angle)

    obs.signal_handler_disconnect(shaken_scene_handler, "item_remove", shaken_sceneitem_remove_callback)

    shaken_sceneitem = nil
  end
end

-- Retrieves the scene item of the given source name in the current scene or nil if not found
function get_sceneitem_from_source_name_in_current_scene(name)
  local result_sceneitem = nil
  local current_scene_as_source = obs.obs_frontend_get_current_scene()
  if current_scene_as_source then
    local current_scene = obs.obs_scene_from_source(current_scene_as_source)
    result_sceneitem = obs.obs_scene_find_source_recursive(current_scene, name)
    obs.obs_source_release(current_scene_as_source)
  end
  return result_sceneitem
end

-- Global variables holding the values of data settings / properties
source_name = ""           -- Name of the source to shake
frequency = 2              -- Frequency of oscillations in Hertz
amplitude = 10             -- Angular amplitude of oscillations in degrees

-- Animates the scene item corresponding to source_name in the current scene
function shake_source()
  local sceneitem = get_sceneitem_from_source_name_in_current_scene(source_name)
  if sceneitem then
    local id = obs.obs_sceneitem_get_id(sceneitem)
    if shaken_sceneitem and obs.obs_sceneitem_get_id(shaken_sceneitem) ~= id then
      restore_sceneitem_after_shake()
    end
    if not shaken_sceneitem then
      save_sceneitem_for_shake(sceneitem)
    end
    local angle = shaken_sceneitem_angle + amplitude*math.sin(os.clock()*frequency*2*math.pi)
    obs.obs_sceneitem_set_rot(sceneitem, angle)
  else
    restore_sceneitem_after_shake()
  end
end

-- Called at script unload
function script_unload()
  restore_sceneitem_after_shake()
end

-- Called to set default values of data settings
function script_defaults(settings)
  obs.obs_data_set_default_string(settings, "source_name", "")
  obs.obs_data_set_default_double(settings, "frequency", 2)
  obs.obs_data_set_default_int(settings, "amplitude", 10)
end

-- Fills the given list property object with the names of all sources plus an empty one
function populate_list_property_with_source_names(list_property)
  local sources = obs.obs_enum_sources()
  obs.obs_property_list_clear(list_property)
  obs.obs_property_list_add_string(list_property, "", "")
  for _,source in pairs(sources) do
    local name = obs.obs_source_get_name(source)
    obs.obs_property_list_add_string(list_property, name, name)
  end
  obs.source_list_release(sources)
end

-- Called to display the properties GUI
function script_properties()
  props = obs.obs_properties_create()

  -- Drop-down list of sources
  local list_property = obs.obs_properties_add_list(props, "source_name", "Source name",
              obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING)
  populate_list_property_with_source_names(list_property)
  -- obs.obs_properties_add_text(props, "source_name", "Source name", obs.OBS_TEXT_DEFAULT)

  -- Button to refresh the drop-down list
  obs.obs_properties_add_button(props, "button", "Refresh list of sources",
    function() populate_list_property_with_source_names(list_property) return true end)

  obs.obs_properties_add_float_slider(props, "frequency", "Shake frequency", 0.1, 20, 0.1)
  obs.obs_properties_add_int_slider(props, "amplitude", "Shake amplitude", 0, 90, 1)
  return props
end

-- Called after change of settings including once after script load
function script_update(settings)
  restore_sceneitem_after_shake()
  source_name = obs.obs_data_get_string(settings, "source_name")
  frequency = obs.obs_data_get_double(settings, "frequency")
  amplitude = obs.obs_data_get_int(settings, "amplitude")
end


-- Global animation activity flag
is_active = false

-- Called every frame
function script_tick(seconds)
  if is_active then
    shake_source()
  else
    restore_sceneitem_after_shake()
  end
end

-- Callback for the hotkey
function on_shake_hotkey(pressed)
  is_active = pressed
end

-- Identifier of the hotkey set by OBS
hotkey_id = obs.OBS_INVALID_HOTKEY_ID

-- Called at script load
function script_load(settings)
    hotkey_id = obs.obs_hotkey_register_frontend(script_path(), "Source Shake", on_shake_hotkey)
    local hotkey_save_array = obs.obs_data_get_array(settings, "shake_hotkey")
    obs.obs_hotkey_load(hotkey_id, hotkey_save_array)
  obs.obs_data_array_release(hotkey_save_array)
end

-- Called before data settings are saved
function script_save(settings)
  restore_sceneitem_after_shake()
  obs.obs_save_sources()

  -- Hotkey save
  local hotkey_save_array = obs.obs_hotkey_save(hotkey_id)
    obs.obs_data_set_array(settings, "shake_hotkey", hotkey_save_array)
    obs.obs_data_array_release(hotkey_save_array)
end