Advertisement
fckerfckersht

Render YouTube Livechat as an HTML Webpage MkII

Apr 3rd, 2025
39
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.15 KB | None | 0 0
  1. import json
  2. import os
  3. import html
  4. import re
  5.  
  6. """
  7. This script renders YouTube live chat data from '.live_chat.json' files, generated by yt-dlp, into an interactive HTML webpage.
  8. It extracts chat messages, author details, and timestamps from the JSON files and presents them in a visually structured format,
  9. including a header with a "FakeTube" logo, video URL, and a link to the Python script. The script processes all '.live_chat.json'
  10. files in a specified directory, generating a corresponding HTML file for each to replay the live chat in a browser-friendly format.
  11.  
  12. **Requirements:**
  13. - Python libraries: 'os', 'json', 'html', and 're'. (json must be installed manually).
  14. - '.live_chat.json' files created by yt-dlp, containing live chat data from YouTube videos, using these arguments: --skip-download --write-subs live_chat --match-filter is_live
  15.  
  16. **Process:**
  17. 1. Prompts the user to input the full directory path containing the '.live_chat.json' files.
  18. 2. Scans the directory for all files ending in '.live_chat.json'.
  19. 3. For each JSON file:
  20. - Extracts the YouTube video ID from the filename using a regular expression.
  21. - Reads the file line by line, parsing JSON objects to extract chat messages, author names, profile pictures, and timestamps.
  22. - Formats messages, supporting both text and emoji rendering.
  23. - Sorts messages by timestamp for chronological display.
  24. - Generates an HTML file with:
  25. - A header featuring the "FakeTube" logo, video URL (clickable and copyable), and a script link.
  26. - A scrollable chat container displaying messages with profile pictures, authors, timestamps, and content.
  27. - A footer box showing the original JSON filename.
  28. 4. Saves each HTML file in the same directory with the base filename and a '.html' extension.
  29.  
  30. **Usage:**
  31. Run the script and enter the full path to the directory containing '.live_chat.json' files. The script will process each file
  32. and generate an HTML file for every valid JSON file found. Open the resulting HTML files in a web browser to view the
  33. rendered live chat replay.
  34.  
  35. **Customization:**
  36. - Modify the HTML and CSS in the `generate_html` function to alter the webpage's appearance (e.g., change colors, adjust layout and chatbox height/width,
  37. or modify the scrollbar style).
  38. - Update the `extract_video_id` function to handle different filename conventions if needed.
  39. - Replace the pastebin script link in the header (in `generate_html`) with a custom URL pointing to your script's source.
  40. """
  41.  
  42.  
  43. def ms_to_time(ms):
  44. """Convert milliseconds to HH:MM:SS format."""
  45. try:
  46. ms = int(ms)
  47. except ValueError:
  48. return "00:00:00"
  49. seconds = ms // 1000
  50. hours = seconds // 3600
  51. minutes = (seconds % 3600) // 60
  52. seconds = seconds % 60
  53. return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
  54.  
  55. def get_directory_path():
  56. """Prompt user for the full directory path containing 'live_chat.json' files and validate it."""
  57. while True:
  58. dir_path = input("Please enter the full directory path containing 'live_chat.json' files: ").strip()
  59. if os.path.isdir(dir_path):
  60. return dir_path
  61. else:
  62. print(f"Error: '{dir_path}' is not a directory. Please try again.")
  63.  
  64. def extract_video_id(filename):
  65. """Extract the YouTube video ID from the filename."""
  66. try:
  67. end_index = filename.rfind('.live_chat.json')
  68. if end_index == -1:
  69. return None
  70. start_index = filename.rfind('[', 0, end_index)
  71. end_bracket_index = filename.find(']', start_index, end_index)
  72. if start_index != -1 and end_bracket_index != -1:
  73. video_id = filename[start_index + 1:end_bracket_index]
  74. if len(video_id) == 11 and re.match(r'^[a-zA-Z0-9_-]{11}$', video_id):
  75. return video_id
  76. except Exception:
  77. return None
  78. return None
  79.  
  80. def process_json_file(json_file_path):
  81. """Process a single JSON file and extract chat messages."""
  82. messages = []
  83. try:
  84. with open(json_file_path, 'r', encoding='utf-8') as f:
  85. for line in f:
  86. try:
  87. event = json.loads(line)
  88. actions = event.get('replayChatItemAction', {}).get('actions', [])
  89. video_offset_time_msec = event.get('videoOffsetTimeMsec', '0')
  90.  
  91. for action in actions:
  92. item = action.get('addChatItemAction', {}).get('item', {})
  93. renderer = item.get('liveChatTextMessageRenderer')
  94. if renderer:
  95. message_runs = renderer.get('message', {}).get('runs', [])
  96. message_parts = []
  97. for run in message_runs:
  98. if 'text' in run:
  99. message_parts.append(run['text'])
  100. elif 'emoji' in run:
  101. emoji_url = run['emoji']['image']['thumbnails'][0]['url']
  102. message_parts.append(f'<img src="{emoji_url}" alt="emoji" style="width: 20px; height: 20px; vertical-align: middle;">')
  103. message_html = ''.join(message_parts)
  104.  
  105. author_name = renderer.get('authorName', {}).get('simpleText', 'Unknown')
  106. thumbnails = renderer.get('authorPhoto', {}).get('thumbnails', [])
  107. author_photo = ""
  108. if thumbnails:
  109. # Sort thumbnails by width to prefer 64x64 over 32x32
  110. sorted_thumbnails = sorted(thumbnails, key=lambda x: x.get('width', 0), reverse=True)
  111. author_photo = sorted_thumbnails[0].get('url', '')
  112. if not author_photo:
  113. author_photo = "https://via.placeholder.com/32?text="
  114. timestamp_formatted = ms_to_time(video_offset_time_msec)
  115.  
  116. messages.append({
  117. 'message_html': message_html,
  118. 'author': author_name,
  119. 'photo': author_photo,
  120. 'timestamp': video_offset_time_msec,
  121. 'timestamp_formatted': timestamp_formatted
  122. })
  123. except json.JSONDecodeError:
  124. continue # Skip invalid lines
  125. except Exception as e:
  126. print(f"Error processing '{json_file_path}': {e}")
  127. return None
  128. return messages
  129.  
  130. def generate_html(messages, base_filename, video_url, output_dir):
  131. """Generate HTML content for the chat messages and save it to a file."""
  132. escaped_filename = html.escape(base_filename)
  133. html_content = f'''
  134. <!DOCTYPE html>
  135. <html lang="en">
  136. <head>
  137. <meta charset="UTF-8">
  138. <title>YouTube Live Chat Replay - {escaped_filename}</title>
  139. <style>
  140. body {{
  141. font-family: Arial, sans-serif;
  142. background-color: #f0f0f0;
  143. margin: 0;
  144. padding: 0;
  145. }}
  146. .header {{
  147. width: 100%;
  148. display: flex;
  149. justify-content: space-between;
  150. align-items: center;
  151. padding: 10px;
  152. background: #fff;
  153. border-bottom: 1px solid #ddd;
  154. box-sizing: border-box;
  155. }}
  156. .logo {{
  157. font-size: 24px;
  158. }}
  159. .logo span.black {{
  160. color: black;
  161. }}
  162. .logo span.red {{
  163. color: white;
  164. background: #cc0000;
  165. padding: 0 5px;
  166. border-radius: 5px;
  167. }}
  168. .subtitle {{
  169. font-size: 12px;
  170. }}
  171. .url-bar {{
  172. text-align: center;
  173. border: 1px solid #ddd;
  174. border-radius: 20px;
  175. padding: 5px 10px;
  176. background: #fff;
  177. }}
  178. .url-bar button {{
  179. border-radius: 10px;
  180. border: 1px solid;
  181. }}
  182. .url-bar button:hover {{
  183. background: #e0e0e0;
  184. transition: background 0.2s ease;
  185. }}
  186. .script-link {{
  187. border: 1px solid;
  188. border-radius: 20px;
  189. padding: 5px 10px;
  190. text-decoration: none;
  191. color: black;
  192. background: #f0f0f0;
  193. transition: background 0.2s ease;
  194. font-size: 14px
  195. }}
  196. .script-link:hover {{
  197. background: #e0e0e0;
  198. }}
  199. #chat-container {{
  200. max-width: 800px;
  201. height: 730px;
  202. overflow-y: auto;
  203. background-color: #fff;
  204. border: 1px solid #aaa;
  205. border-radius: 5px;
  206. padding: 10px;
  207. margin: 20px auto;
  208. box-shadow: 0 2px 5px rgba(0,0,0,0.1);
  209. }}
  210. #chat-container::-webkit-scrollbar {{
  211. width: 20px;
  212. }}
  213. #chat-container::-webkit-scrollbar-track {{
  214. background: #f1f1f1;
  215. border: 1px solid #ccc;
  216. }}
  217. #chat-container::-webkit-scrollbar-thumb {{
  218. background: #888;
  219. border-radius: 5px;
  220. }}
  221. #chat-container::-webkit-scrollbar-thumb:hover {{
  222. background: #555;
  223. }}
  224. .chat-message {{
  225. display: flex;
  226. align-items: flex-start;
  227. margin-bottom: 15px;
  228. }}
  229. .chat-message img.profile-pic {{
  230. width: 32px;
  231. height: 32px;
  232. border-radius: 50%;
  233. margin-right: 10px;
  234. }}
  235. .message-content {{
  236. flex: 1;
  237. }}
  238. .author {{
  239. font-weight: bold;
  240. color: #333;
  241. margin-right: 10px;
  242. }}
  243. .timestamp {{
  244. color: #888;
  245. font-size: 0.9em;
  246. margin-right: 10px;
  247. }}
  248. .message-text {{
  249. margin: 5px 0 0 0;
  250. color: #555;
  251. word-wrap: break-word;
  252. }}
  253. .filename-box {{
  254. margin: 20px auto;
  255. padding: 10px;
  256. border: 1px solid #ccc;
  257. border-radius: 5px;
  258. background-color: #f9f9f9;
  259. text-align: center;
  260. max-width: 800px;
  261. }}
  262. </style>
  263. </head>
  264. <body>
  265. <div class="header">
  266. <div>
  267. <div class="logo"><span class="black">Fake</span><span class="red">Tube</span></div>
  268. <div class="subtitle">Rendered with <a href="https://github.com/yt-dlp/yt-dlp" target="_blank">YT-DLP</a> live_chat.json files</div>
  269. </div>
  270. <div class="url-bar">
  271. <span onclick="navigator.clipboard.writeText('{video_url}')">{video_url}</span>
  272. <button onclick="window.open('{video_url}', '_blank')">Open in New Tab</button>
  273. </div>
  274. <a href="https://pastebin.com/4mLyLHN8" target="_blank" class="script-link">Open Python Script in New Tab</a>
  275. </div>
  276. <div id="chat-container">
  277. '''
  278.  
  279. # Add each message to the HTML
  280. for msg in messages:
  281. html_content += f'''
  282. <div class="chat-message">
  283. <a href="{msg['photo']}" target="_blank"><img src="{msg['photo']}" alt="N/A" class="profile-pic"></a>
  284. <div class="message-content">
  285. <span class="author">{msg['author']}</span>
  286. <span class="timestamp">{msg['timestamp_formatted']}</span>
  287. <p class="message-text">{msg['message_html']}</p>
  288. </div>
  289. </div>
  290. '''
  291.  
  292. # Add the filename box
  293. html_content += f'''
  294. </div>
  295. <div class="filename-box">
  296. <p>{escaped_filename}</p>
  297. </div>
  298. </body>
  299. </html>
  300. '''
  301.  
  302. # Write to HTML file in the output directory
  303. html_filename = base_filename + '.html'
  304. output_file = os.path.join(output_dir, html_filename)
  305. with open(output_file, 'w', encoding='utf-8') as f:
  306. f.write(html_content)
  307. print(f"Chat has been rendered to '{os.path.abspath(output_file)}'.")
  308.  
  309. def main():
  310. dir_path = get_directory_path()
  311. json_files = [f for f in os.listdir(dir_path) if f.endswith('.live_chat.json') and os.path.isfile(os.path.join(dir_path, f))]
  312. if not json_files:
  313. print("No '.live_chat.json' files found in the directory.")
  314. return
  315. print(f"Found {len(json_files)} '.live_chat.json' files to process.")
  316. for filename in json_files:
  317. json_file_path = os.path.join(dir_path, filename)
  318. print(f"Processing '{filename}'...")
  319. video_id = extract_video_id(filename)
  320. if video_id:
  321. video_url = f"https://www.youtube.com/watch?v={video_id}"
  322. else:
  323. video_url = "N/A"
  324. messages = process_json_file(json_file_path)
  325. if messages is not None:
  326. messages.sort(key=lambda x: int(x['timestamp']))
  327. base_filename = os.path.splitext(filename)[0]
  328. generate_html(messages, base_filename, video_url, dir_path)
  329. else:
  330. print(f"Skipping '{filename}' due to errors.")
  331.  
  332. if __name__ == "__main__":
  333. main()
  334.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement