import obspython as obs
import time
import datetime
import os
import urllib.request
import json
# Globals
log_file_path = ""
hotkey_id = obs.OBS_INVALID_HOTKEY_ID
start_time = None
user_stream_title = ""
# Credentials
twitch_user_login = ""
twitch_client_id = ""
twitch_oauth_token = ""
youtube_api_key = ""
youtube_channel_id = ""
title_preference = ""
def script_description():
return (
"Do not forget to set the 'Timestamp snap' hotkey.
"
"If you are streaming to both Twitch and YouTube simultaneously, select the corresponding checkbox. "
'Instructions'
)
def script_properties():
props = obs.obs_properties_create()
obs.obs_properties_add_path(props, "log_file", "Timestamp list location", obs.OBS_PATH_FILE_SAVE, "*.txt", None)
obs.obs_properties_add_text(props, "stream_name", "Forced broadcast title \n(will be displayed in the list)", obs.OBS_TEXT_DEFAULT)
obs.obs_properties_add_bool(props, "use_twitch", "Twitch Title Priority")
obs.obs_properties_add_text(props, "twitch_user_login", "Twitch Login", obs.OBS_TEXT_DEFAULT)
obs.obs_properties_add_text(props, "twitch_client_id", "Twitch Client ID", obs.OBS_TEXT_PASSWORD)
obs.obs_properties_add_text(props, "twitch_oauth_token", "OAuth token", obs.OBS_TEXT_PASSWORD)
obs.obs_properties_add_bool(props, "use_youtube", "YouTube Title Priority")
obs.obs_properties_add_text(props, "youtube_api_key", "YouTube API Key", obs.OBS_TEXT_PASSWORD)
obs.obs_properties_add_text(props, "youtube_channel_id", "YouTube Channel ID", obs.OBS_TEXT_PASSWORD)
obs.obs_property_set_modified_callback(obs.obs_properties_get(props, "use_twitch"), on_twitch_radio_changed)
obs.obs_property_set_modified_callback(obs.obs_properties_get(props, "use_youtube"), on_youtube_radio_changed)
obs.obs_properties_add_button(props, "reset_timer", " Reset the timer manually ", reset_timer_callback)
return props
def script_update(settings):
global log_file_path, user_stream_title
global twitch_user_login, twitch_client_id, twitch_oauth_token, youtube_api_key, youtube_channel_id
log_file_path = obs.obs_data_get_string(settings, "log_file")
user_stream_title = obs.obs_data_get_string(settings, "stream_name")
twitch_user_login = obs.obs_data_get_string(settings, "twitch_user_login")
twitch_client_id = obs.obs_data_get_string(settings, "twitch_client_id")
twitch_oauth_token = obs.obs_data_get_string(settings, "twitch_oauth_token")
youtube_api_key = obs.obs_data_get_string(settings, "youtube_api_key")
youtube_channel_id = obs.obs_data_get_string(settings, "youtube_channel_id")
def on_twitch_radio_changed(props, prop, settings):
global title_preference
if obs.obs_data_get_bool(settings, "use_twitch"):
obs.obs_data_set_bool(settings, "use_youtube", False)
title_preference = "twitch"
#obs.script_log(obs.LOG_INFO, f"preference {title_preference}")
return True
def on_youtube_radio_changed(props, prop, settings):
global title_preference
if obs.obs_data_get_bool(settings, "use_youtube"):
obs.obs_data_set_bool(settings, "use_twitch", False)
title_preference = "youtube"
#obs.script_log(obs.LOG_INFO, f"preference {title_preference}")
return True
def script_load(settings):
global hotkey_id, start_time
hotkey_id = obs.obs_hotkey_register_frontend(
"stream_timestamp_hotkey",
"Timestamp snap",
on_hotkey
)
saved = obs.obs_data_get_array(settings, "hotkey_array")
obs.obs_hotkey_load(hotkey_id, saved)
obs.obs_data_array_release(saved)
obs.obs_frontend_add_event_callback(frontend_event_callback)
start_time = None
def script_defaults(settings):
default = os.path.join(os.path.expanduser("~"), "stream_timestamps.txt")
obs.obs_data_set_default_string(settings, "log_file", default)
obs.obs_data_set_default_bool(settings, "use_youtube", True)
def script_save(settings):
arr = obs.obs_hotkey_save(hotkey_id)
obs.obs_data_set_array(settings, "hotkey_array", arr)
obs.obs_data_array_release(arr)
def frontend_event_callback(event):
global start_time
if event == obs.OBS_FRONTEND_EVENT_STREAMING_STARTED:
start_time = time.time()
with open(log_file_path, "a", encoding="utf-8") as f:
f.write("\n")
elif event == obs.OBS_FRONTEND_EVENT_STREAMING_STOPPED:
start_time = None
def on_hotkey(pressed):
if pressed and obs.obs_frontend_streaming_active():
record_timestamp()
elif pressed:
obs.script_log(obs.LOG_WARNING, "Stream is not live — timestamp not recorded")
def reset_timer_callback(props, prop):
global start_time, log_file_path
start_time = time.time()
try:
with open(log_file_path, "a", encoding="utf-8") as f:
f.write("Timer was manually reset\n")
except:
pass
return True
def fetch_youtube_title():
if not youtube_api_key or not youtube_channel_id:
return ""
try:
url = (
"https://www.googleapis.com/youtube/v3/search"
f"?part=snippet&channelId={youtube_channel_id}"
"&eventType=live&type=video"
f"&key={youtube_api_key}"
)
resp = urllib.request.urlopen(url)
data = json.load(resp)
items = data.get("items", [])
if items:
return "[Youtube] " + items[0]["snippet"]["title"]
except Exception as e:
obs.script_log(obs.LOG_WARNING, f"YouTube API error: {e}")
return ""
def fetch_twitch_title():
if not twitch_client_id or not twitch_oauth_token or not twitch_user_login:
#obs.script_log(obs.LOG_INFO, "no twitch credentials")
return ""
try:
url = f"https://api.twitch.tv/helix/streams?user_login={twitch_user_login}"
req = urllib.request.Request(url)
req.add_header("Client-ID", twitch_client_id)
req.add_header("Authorization", f"Bearer {twitch_oauth_token}")
with urllib.request.urlopen(req) as resp:
data = json.load(resp)
streams = data.get("data", [])
if streams:
#obs.script_log(obs.LOG_INFO, streams[0]["title"])
return streams[0]["title"]
except Exception as e:
obs.script_log(obs.LOG_WARNING, f"Twitch API error: {e}")
return ""
def record_timestamp():
global start_time, user_stream_title, title_preference
if start_time is None:
start_time = time.time()
elapsed = time.time() - start_time
hh = int(elapsed // 3600)
mm = int(elapsed // 60) % 60
ss = int(elapsed % 60)
ts = f"{hh:02d}:{mm:02d}:{ss:02d}"
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
title = ""
if user_stream_title and user_stream_title.strip():
title = user_stream_title.strip()
elif all([twitch_user_login, twitch_client_id, twitch_oauth_token, youtube_api_key, youtube_channel_id]):
if title_preference == "twitch":
#obs.script_log(obs.LOG_INFO, f"{title_preference} preference")
title = fetch_twitch_title()
elif title_preference == "youtube":
#obs.script_log(obs.LOG_INFO, f"{title_preference} preference")
title = fetch_youtube_title()
if not title:
title = fetch_twitch_title()
if not title:
title = fetch_youtube_title()
if not title:
#obs.script_log(obs.LOG_INFO, "Missing credentials")
title = "Unknown"
line = f"{title} | {now} | {ts}\n"
try:
with open(log_file_path, "a", encoding="utf-8") as f:
f.write(line)
except:
pass
def script_unload():
pass