86 lines
3.4 KiB
Python
86 lines
3.4 KiB
Python
import os
|
|
|
|
import linux
|
|
|
|
def combine_twitch_vod_and_chat(vod_path: str, layout: str = "side-by-side") -> 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_chat.replace(".mp4", "_mask.mp4")
|
|
|
|
# Step 1: Pre-flight checks
|
|
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 25 "
|
|
f"--generate-mask "
|
|
f"-o {temp_chat}"
|
|
f'--output-args="-threads 8 "'
|
|
)
|
|
|
|
chat_success = linux.run_command(chat_cmd, progress_prefix="Chat Render")
|
|
if not chat_success:
|
|
print("❌ Error: TwitchDownloaderCLI failed to render chat video.")
|
|
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(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)
|
|
return True
|
|
else:
|
|
print("❌ Error: FFmpeg failed to merge the video streams.")
|
|
return False |