# Source Translation:
# Author: TomBoop (https://www.twitch.tv/tomboop)
# Version 0.2
# -----------------
# Needs the Cloud Captioning plugin as well as translatepy.
# Use open captioning to direct the captions to a Text(GDI+) source.
# Use that source as the "Source" of the plugin.
# Feel free to turn off the visibility on the "Source" as it is not needed.
# -----------------
# TODO: Handle all bugs and edge cases.
# TODO: Maybe move to printing to file and reading from file instead.
import obspython as obs
from translatepy import Translator
from translatepy.exceptions import TranslatepyException, UnknownLanguage
import math, time
import threading
# Global variables
src_name = None # source name
dst_name = None # dest name
lang_code = None # language code
up_interval = 0 # translation update interval
hold_interval = 0
prev_text = "" # will hold prev text for verification
activated = False
translator = Translator()
items_arr = [None, None, None, None] # used for deleting unreleased references
# Description displayed in the Scripts dialog window
def script_description():
return """
Auto Translate:
Automatically translate captions based on Text(GDI+) input.
The script uses a Cloud Translate API (with translatepy 2.3).
"""
def timer_callback():
global src_name, dst_name, prev_text, items_arr
s_src = obs.obs_get_source_by_name(src_name)
if not s_src:
return
items_arr[0] = s_src
s_stt = obs.obs_source_get_settings(s_src)
items_arr[1] = s_stt
new_text = obs.obs_data_get_string(s_stt, "text")
obs.obs_data_release(s_stt)
items_arr[1] = None
obs.obs_source_release(s_src)
items_arr[0] = None
if new_text != prev_text:
x = threading.Thread(target=translate_threaded, args=(new_text,prev_text,))
x.start()
def translate_threaded(new_text, prev_text):
global translator, dst_name, lang_code, hold_interval, items_arr
d_src = obs.obs_get_source_by_name(dst_name)
if not d_src:
return
items_arr[2] = d_src
d_stt = obs.obs_data_create()
items_arr[3] = d_stt
if new_text.strip() != '':
try:
trans = translator.translate(new_text, destination_language=lang_code)
except UnknownLanguage as err:
print("An error occured while searching for the language you passed in")
print("Similarity:", round(err.similarity), "%")
return
except TranslatepyException:
print("An error occured while translating with translatepy")
return
except Exception:
print("An unknown error occured")
return
obs.obs_data_set_string(d_stt, "text", trans.result)
else:
if prev_text.strip() != '':
time.sleep(hold_interval * math.pow(10,-3))
obs.obs_data_set_string(d_stt, "text", "")
obs.obs_source_update(d_src, d_stt)
obs.obs_data_release(d_stt)
items_arr[3] = None
obs.obs_source_release(d_src)
items_arr[2] = None
def activate(activating):
global activated
if activated == activating:
return
activated = activating
if activating:
obs.timer_add(timer_callback, up_interval)
else:
if items_arr[0]:
obs.obs_source_release(items_arr[0])
if items_arr[1]:
obs.obs_data_release(items_arr[1])
if items_arr[2]:
obs.obs_source_release(items_arr[2])
if items_arr[3]:
obs.obs_data_release(items_arr[3])
obs.timer_remove(timer_callback)
def activate_signal(cd, activating):
global src_name
source = obs.calldata_source(cd, "source")
if source:
name = obs.obs_source_get_name(source)
if name == dst_name:
activate(activating)
def source_activated(cd):
activate_signal(cd, True)
def source_deactivated(cd):
activate_signal(cd, False)
# Called to set default values of data settings
def script_defaults(settings):
obs.obs_data_set_default_string(settings, "dest_lang", "ja")
obs.obs_data_set_default_int(settings, "up_interval", 500)
obs.obs_data_set_default_int(settings, "hold_interval", 500)
# Called to display the properties GUI
def script_properties():
props = obs.obs_properties_create()
src_lst = obs.obs_properties_add_list(props, "source", "Text source", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING)
dst_lst = obs.obs_properties_add_list(props, "destination", "Text destination", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING)
sources = obs.obs_enum_sources()
if sources:
for _s in sources:
_s_type = obs.obs_source_get_unversioned_id(_s)
if _s_type == "text_gdiplus":
_s_name = obs.obs_source_get_name(_s)
obs.obs_property_list_add_string(src_lst, _s_name, _s_name)
obs.obs_property_list_add_string(dst_lst, _s_name, _s_name)
obs.source_list_release(sources)
obs.obs_properties_add_text(props, "dest_lang", "Destination language:", obs.OBS_TEXT_DEFAULT)
obs.obs_properties_add_int(props, "up_interval", "Update interval (ms):", 0,3000,50)
obs.obs_properties_add_int(props, "hold_interval", "Caption hold interval (ms):", 0,1000,50)
return props
# Called after change of settings including once after script load
def script_update(settings):
activate(False)
global src_name, dst_name, lang_code, up_interval, hold_interval
src_name = obs.obs_data_get_string(settings, "source")
dst_name = obs.obs_data_get_string(settings, "destination")
lang_code = obs.obs_data_get_string(settings, "dest_lang")
up_interval = obs.obs_data_get_int(settings, "up_interval")
hold_interval = obs.obs_data_get_int(settings, "hold_interval")
activate(True)
def script_load(settings):
sh = obs.obs_get_signal_handler()
obs.signal_handler_connect(sh, "source_activate", source_activated)
obs.signal_handler_connect(sh, "source_deactivate", source_deactivated)
# Called before data settings are saved
def script_save(settings):
activate(False)