Skip to content

Instantly share code, notes, and snippets.

@Cqoicebordel
Last active October 29, 2025 21:23
Show Gist options
  • Select an option

  • Save Cqoicebordel/d9110b4b1191b9e9f6a8165438e00ea0 to your computer and use it in GitHub Desktop.

Select an option

Save Cqoicebordel/d9110b4b1191b9e9f6a8165438e00ea0 to your computer and use it in GitHub Desktop.
Twitch's json comments to Youtube subtitles converter
#!/usr/bin/python3
import sys
import json
import datetime
import secrets
import html
# Name file should be in command line
if len(sys.argv) < 2:
print("You need to add the input json as an argument.")
exit()
with open(sys.argv[1], 'r', encoding='UTF8') as jsonFile:
data = json.loads(jsonFile.read())
# Time delay in second for chat, to sync with video. For me, 7s looks like it's in sync with what the video display
delta = 7
# Duration of display of the line
duration = 10
# Size of the font. Supposed to be in percent of standart, but it looks like all sizes aren't available
size = 10
# Bacground color. The standart of YT is solid black
background_color = "#000000"
# Opacity of the background color. Between 0 and 254. 254 means that the color is not transparent
background_opacity = 200
header = '<?xml version="1.0" encoding="utf-8"?><timedtext format="3"><head><wp id="0" ap="7" ah="0" av="0" /><wp id="1" ap="6" ah="0" av="100" /><ws id="0" ju="2" pd="0" sd="0" /><ws id="1" ju="0" pd="0" sd="0" />'
middle = '</head><body>'
footer = '</body></timedtext>'
spacer = '<s p="1">​\n​</s>'
output = ''
texts = []
timestamps = []
colors = ["#000000", "#FEFEFE"]
users = ["yt-bug", "white-text"]
for i in data['comments']:
display_name = i['commenter']['display_name']
message = i['message']['body']
if display_name not in users:
users.append(display_name)
userIndex = users.index(display_name)
if i['message']['user_color'] is not None:
color = i['message']['user_color']
else:
color = "#"+secrets.token_hex(3)
#print(display_name+color)
colors.insert(userIndex, color)
else:
userIndex = users.index(display_name)
texts.append('<s p="'+str(userIndex)+'">'+display_name+':</s><s p="1"> '+html.escape(message)+'</s>')
timestamps.append([i['content_offset_seconds']+delta, True])
timestamps.append([i['content_offset_seconds']+delta+duration, False])
lengthColors = len(colors)
for i in range(lengthColors):
header += '<pen id="'+str(i)+'" sz="'+str(size)+'" fc="'+colors[i]+'" fo="254" bc="'+background_color+'" bo="'+str(background_opacity)+'" />'
timestamps.sort(key=lambda x: x[0])
start = 0
end = 1
length = len(timestamps)
for i in range(1,length):
combined_text = ""
for j in range(start,end):
combined_text += texts[j]
if j != end-1:
combined_text += spacer
timestamp_start = int(timestamps[i-1][0]*1000)
timestamp_end = int(timestamps[i][0]*1000)
if start != end:
output += '<p t="'+str(timestamp_start)+'" d="'+str(timestamp_end-timestamp_start)+'" wp="1" ws="1"><s p="1">​</s>'+combined_text+'<s p="1">​</s></p>'
if timestamps[i][1]:
end += 1
else:
start += 1
if len(sys.argv) == 3:
with open(sys.argv[2], 'w', encoding='UTF8') as outputFile:
outputFile.write(header+middle+output+footer)
else:
print(header+middle+output+footer)

Python script to convert the Json containing all the chat of Twitch, to the Youtube Timed Text subtitle format (YTT).

Features

  • Use the users' colors
  • Create a unique color for users who don't have one
  • Display the subtitles in chat order (new at bottom). Do this by compiling collisions
  • Display the chat at the bottom left of the screen
  • Display unicode emojis

Absent forever features

  • Doesn't show Twitch only emojis (show only their names)
  • Doesn't show users' badges

Usage

Either make it executable (chmod +x), or just run python3 convert-combined-ytt.py twitchChat.json. It will write the output to the console. You can save the file by redirecting the output with > :
python3 convert-combined-ytt.py twitchChat.json > twitchChat.ytt

You can also use a second command line argument to set an output file. It's probably needed if your console/cmd is not in UTF-8. Beware, it'll write to the file without any confirmation asked if the file exists, overwritting it by default.
python3 convert-combined-ytt.py twitchChat.json twitchChat.ytt

Notes

You can use Twitch Downloader to download the JSON of the Twitch Chat : TwitchDownloaderCLI -m ChatDownload -u videoID -o videoID.json

I also used YTSubConverter extensively to understand the YTT format. Note that they implemented at my request a feature to handle the reverse order of Collision in .ass format, allowing for chat order of subtitles (new at bottom).
This tool is great, so don't hesitate to use it if it fits your needs more.

Note that in order to do this script, I also made JSON to .ssa and JSON to .ass converters. If you want them, ask and I'll add them.

The script may have bugs. I didn't test it extensively. But looks good as far as I know. Don't hesitate to comment if you find bugs.

@saulm314
Copy link

Thanks for this script. Having taken some inspiration from this, I wrote a console application that does something very similar and also has many more features like being able to choose which corner you want the chat to be in, the maximum number of messages you want displayed at a time, trimming the chats if the chat file covers a longer time period than the video, etc.

You can find it here: https://github.com/saulm314/TwitchChatOffset/

Since I wrote my app in C# and since YTSubConverter is also written in C#, I was able to directly use YTSubConverter as a library, instead of having to do the whole thing from scratch (though I am using a fork with a few modifications to make it more tailored to displaying Twitch chats, e.g. I have added a window opacity feature).

My app also supports bulk conversions, so you can specify how you want to transform each of many JSON files in a CSV document (e.g. for one video you may want it in the bottom-right corner, for another in the top-centre with a semi-transparent window, etc.). So we were able to use this to add Twitch chats to over 140 videos at https://www.youtube.com/@popruns/ in a very short space of time. Click on any recent video and turn on subtitles to see how it works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment