i am building a live translator for Twitch that uses OBS. i am trying to change the size of a text element in OBS using python depending on the size of the text itself, so that the text never goes off the 16x9 border, but i cant seem to figure it out. I don't get any particular error, it just does not change the size of the element or font size.
import time
import speech_recognition as sr
from googletrans import Translator
from obsws_python import ReqClient # Use ReqClient for OBS WebSocket 5.x
from twitchio.ext import commands # For Twitch chat integration
import threading
import tkinter as tk
from tkinter import messagebox
# Global variable to control translation state
translation_enabled = False
src_lang = 'fr'
dest_lang = 'en'
# Function to recognize speech from microphone input
def recognize_speech_from_mic(recognizer, microphone):
with microphone as source:
recognizer.adjust_for_ambient_noise(source)
audio = recognizer.listen(source)
response = {
"success": True,
"error": None,
"transcription": None
}
try:
response["transcription"] = recognizer.recognize_google(audio, language=src_lang)
except sr.RequestError:
response["success"] = False
response["error"] = "API unavailable"
except sr.UnknownValueError:
response["error"] = "Unable to recognize speech"
return response
# Function to translate text using Google Translate
def translate_text(text, src_lang='fr', dest_lang='en'):
translator = Translator()
translation = translator.translate(text, src=src_lang, dest=dest_lang)
return translation.text
# Function to get the transform properties of a source in OBS
def get_source_transform(client, scene_name, source_name):
# Get the scene item ID
response = client.get_scene_item_id(scene_name=scene_name, source_name=source_name)
myItemID = response.scene_item_id
# Get the transform properties of the scene item
response = client.get_scene_item_transform(scene_name=scene_name, item_id=myItemID)
transform = {
"positionX": response.scene_item_transform.positionX,
"positionY": response.scene_item_transform.positionY,
"scaleX": response.scene_item_transform.scaleX,
"scaleY": response.scene_item_transform.scaleY,
"rotation": response.scene_item_transform.rotation,
"width": response.scene_item_transform.width,
"height": response.scene_item_transform.height,
"sourceWidth": response.scene_item_transform.sourceWidth,
"sourceHeight": response.scene_item_transform.sourceHeight,
"cropLeft": response.scene_item_transform.cropLeft,
"cropRight": response.scene_item_transform.cropRight,
"cropTop": response.scene_item_transform.cropTop,
"cropBottom": response.scene_item_transform.cropBottom,
}
return transform
# Function to set the transform properties of a source in OBS
def set_source_transform(client, scene_name, source_name, new_transform):
# Get the scene item ID
response = client.get_scene_item_id(scene_name=scene_name, source_name=source_name)
myItemID = response.scene_item_id
# Set the transform properties of the scene item
client.set_scene_item_transform(scene_name=scene_name, item_id=myItemID, scene_item_transform=new_transform)
# Function to calculate the required scale for the text to fit within the screen
def calculate_scale(text, max_width=1920, max_height=1080):
"""
Calculate the required scale for the text to fit within the screen.
"""
base_font_size = 50 # Starting font size
avg_char_width = 20 # Average width of a character in pixels for font size 50
# Calculate the required width based on text length
required_width = len(text) * avg_char_width
# Calculate the required scale to fit within the screen width
if required_width > max_width:
scale = max_width / required_width
else:
scale = 1.0
# Ensure the scale doesn't go too small
scale = max(scale, 0.5)
return scale
# Function to display text one word at a time with dynamic scaling
def display_text_word_by_word(client, text, scene_name="Scene", source_name="TranslatedText", delay=0.5):
words = text.split() # Split the text into words
for word in words:
if not translation_enabled:
break # Stop if translation is disabled
# Calculate the required scale for the current word
scale = calculate_scale(word)
# Update the text in OBS
client.set_input_settings(
source_name,
{"text": word},
overlay=True
)
# Adjust the scale of the text source
transform = get_source_transform(client, scene_name, source_name)
transform["scaleX"] = scale
transform["scaleY"] = scale
set_source_transform(client, scene_name, source_name, transform)
time.sleep(delay) # Delay between words
# Function to connect to OBS WebSocket
def connect_to_obs(host='localhost', port=4455, password='TwitchChat9'):
try:
print(f"Attempting to connect to OBS WebSocket at {host}:{port} with password: {password}")
client = ReqClient(host=host, port=port, password=password)
print("Connected to OBS WebSocket server!")
return client
except Exception as e:
print(f"Failed to connect to OBS WebSocket: {e}")
return None
# Retry logic for OBS WebSocket connection
def connect_to_obs_with_retry(host='localhost', port=4455, password='TwitchChat9', retries=5, delay=5):
for attempt in range(retries):
client = connect_to_obs(host, port, password)
if client:
return client
else:
print(f"Retry {attempt + 1}/{retries} failed, retrying in {delay} seconds...")
time.sleep(delay)
return None
# Function to handle live translation
def live_translation(obs_client):
recognizer = sr.Recognizer()
microphone = sr.Microphone()
while True:
if translation_enabled:
print("Listening...")
# Recognize speech from the microphone
speech_result = recognize_speech_from_mic(recognizer, microphone)
if speech_result["success"] and speech_result["transcription"]:
# Get the recognized text
text = speech_result["transcription"]
print(f"Recognized: {text}")
# Translate the text
translated_text = translate_text(text, src_lang, dest_lang)
print(f"Translated: {translated_text}")
# Display the translated text one word at a time
display_text_word_by_word(obs_client, translated_text)
# Non-blocking loop
time.sleep(0.5)
# Twitch bot class
class TwitchBot(commands.Bot):
def __init__(self, oauth_token, twitch_username):
super().__init__(
token=oauth_token,
prefix="!",
initial_channels=[twitch_username]
)
self.oauth_token = oauth_token
self.twitch_username = twitch_username
async def event_ready(self):
print(f"Logged in as {self.nick}")
@commandsmand(name="translate")
async def translate_command(self, ctx):
global translation_enabled
translation_enabled = not translation_enabled # Toggle translation state
status = "enabled" if translation_enabled else "disabled"
await ctx.send(f"Live translation is now {status}.")
# Function to create the GUI for user input
def create_gui():
def start_program():
oauth_token = oauth_token_entry.get()
twitch_username = twitch_username_entry.get()
obs_password = obs_password_entry.get()
if not oauth_token or not twitch_username or not obs_password:
messagebox.showerror("Error", "All fields are required!")
return
# Try connecting to OBS
obs_client = connect_to_obs_with_retry(password=obs_password)
if not obs_client:
messagebox.showerror("Error", "Failed to connect to OBS WebSocket after multiple attempts.")
return
# Start translation thread
translation_thread = threading.Thread(target=live_translation, args=(obs_client,))
translation_thread.daemon = True
translation_thread.start()
# Start the Twitch bot
bot = TwitchBot(oauth_token, twitch_username)
bot.run() # This should be called after everything else is ready
# GUI Setup
window = tk.Tk()
window.title("Translation Settings")
# Create GUI components
tk.Label(window, text="OAuth Token:").grid(row=0, column=0)
oauth_token_entry = tk.Entry(window, width=30)
oauth_token_entry.grid(row=0, column=1)
tk.Label(window, text="Twitch Username:").grid(row=1, column=0)
twitch_username_entry = tk.Entry(window, width=30)
twitch_username_entry.grid(row=1, column=1)
tk.Label(window, text="OBS WebSocket Password:").grid(row=2, column=0)
obs_password_entry = tk.Entry(window, width=30)
obs_password_entry.grid(row=2, column=1)
# Start button
start_button = tk.Button(window, text="Start Program", command=start_program)
start_button.grid(row=3, columnspan=2)
# Language selection (for future feature)
def update_languages():
global src_lang, dest_lang
src_lang = src_lang_var.get()
dest_lang = dest_lang_var.get()
tk.Label(window, text="Source Language:").grid(row=4, column=0)
src_lang_var = tk.StringVar(window)
src_lang_var.set("fr") # Default source language
src_lang_menu = tk.OptionMenu(window, src_lang_var, "fr", "en", "es", "de", "ja", "ko", "zh")
src_lang_menu.grid(row=4, column=1)
tk.Label(window, text="Destination Language:").grid(row=5, column=0)
dest_lang_var = tk.StringVar(window)
dest_lang_var.set("en") # Default destination language
dest_lang_menu = tk.OptionMenu(window, dest_lang_var, "en", "fr", "es", "de", "ja", "ko","zh")
dest_lang_menu.grid(row=5, column=1)
update_button = tk.Button(window, text="Update Languages", command=update_languages)
update_button.grid(row=6, columnspan=2)
window.mainloop()
# Main function to run the program
def main():
create_gui()
# Run the program
if __name__ == "__main__":
main()