# 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 """
<div style="font-weight:bold">Auto Translate:</div>
<div>Automatically translate captions based on Text(GDI+) input.
The script uses a <i>Cloud Translate API</i> (with translatepy 2.3).</div>
  """

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)
