#!/usr/bin/env python3 import os import linux def combine_twitch_vod_and_chat(vod_path: str, layout: str = "side-by-side", force: bool = False) -> bool: """ Renders Twitch chat JSON to video and combines it with the source VOD. Provides real-time terminal feedback for all processing steps. """ video_path = vod_path chat_path = video_path.replace(".mp4", "_chat.json") temp_chat = video_path.replace(".mp4", "_temp_chat.mp4") video_chat = video_path.replace(".mp4", "_with_chat.mp4") mask_path = video_path.replace(".mp4", "_temp_chat_mask.mp4") # Step 1: Pre-flight checks if os.path.exists(video_chat) and force is False: print(f"โŒ Error: Chat video allready exists: {video_chat}") return False if not os.path.exists(video_path): print(f"โŒ Error: Source video not found: {video_path}") return False if not os.path.exists(chat_path): print(f"โŒ Error: Chat file not found: {chat_path}") return False # Step 2: Render Chat to Video print("====================================================") print("๐Ÿš€ STEP 1: Rendering Chat JSON to Video Layer") print("====================================================") chat_cmd = ( f"TwitchDownloaderCLI chatrender " f"-i {chat_path} " f"-w 400 -h 1080 " f"--collision Overwrite " f"--temp-path download/temp " f"--font-size 20 " f"--generate-mask " f"--background-color #00000000 " f"-o {temp_chat} " ) if not os.path.exists(temp_chat): chat_success = linux.run_command(chat_cmd, look_for=["[STATUS]"]) if chat_success: print("โœ… Success: TwitchDownloaderCLI render chat video.") else: print(f"โŒ Error: TwitchDownloaderCLI failed to render chat video. {chat_success['error']} : {chat_success['stderr']}") os.remove(temp_chat) os.remove(mask_path) return False # Step 3: Combine Video and Chat using FFmpeg print("====================================================") print(f"๐Ÿš€ STEP 2: Merging VOD and Chat Layout ({layout})") print("====================================================") # -preset superfast speeds up the 3+ hour encoding process significantly # -map 0:a? safely includes audio if it exists, without breaking on silent VODs if layout == "side-by-side": ffmpeg_cmd = ( f"ffmpeg -y -i {video_path} -i {temp_chat} " f"-filter_complex '[0:v][1:v]hstack=inputs=2[v]' " f"-map '[v]' -map 0:a? -c:a copy -preset superfast {video_chat}" ) elif layout == "overlay": ffmpeg_cmd = ( f"ffmpeg -y -i {video_path} -i {temp_chat} -i {mask_path} " f"-filter_complex '[1:v][2:v]alphamerge[masked_chat];" f"[0:v][masked_chat]overlay=x=10:y=10[v]' " f"-map '[v]' -map 0:a? -c:a copy -preset superfast {video_chat}" ) else: raise ValueError("Invalid layout choice. Choose 'side-by-side' or 'overlay'.") ffmpeg_success = linux.run_command(ffmpeg_cmd, progress_prefix="FFmpeg Merge") # Step 4: Final verification and cleanup if ffmpeg_success and os.path.exists(video_chat): print("====================================================") print(f"๐ŸŽ‰ SUCCESS: Video processing complete!") print(f"๐Ÿ“ Output Saved: {video_chat}") print("====================================================") # Clean up the massive temporary chat video to save storage space if os.path.exists(temp_chat): print("๐Ÿงน Cleaning up temporary chat render video...") os.remove(temp_chat) os.remove(mask_path) return True else: print("โŒ Error: FFmpeg failed to merge the video streams.") os.remove(video_chat) return False if __name__ == "__main__": combine_twitch_vod_and_chat("download/videos/2813112936/2813112936.mp4", "overlay")