Add support for merging video and chat with configurable layout options

This commit is contained in:
MaddoScientisto 2026-02-10 00:06:49 +01:00
commit 832bf4cf36
6 changed files with 199 additions and 10 deletions

View file

@ -43,7 +43,7 @@ from pytz import timezone
from dotenv import load_dotenv, find_dotenv
# Local module imports
from modules.constants import DEFAULT_CONFIG, PREFIX_LIVE, PREFIX_VOD, PREFIX_CHAT, PREFIX_METADATA
from modules.constants import DEFAULT_CONFIG, PREFIX_LIVE, PREFIX_VOD, PREFIX_CHAT, PREFIX_MERGED, PREFIX_METADATA
from modules.config import ConfigManager
from modules.notifications import NotificationManager
from modules.utils import (
@ -215,6 +215,10 @@ class TwitchArchive:
self._print_toggle('Metadata download', self.downloadMETADATA)
self._print_toggle('VOD download', self.downloadVOD)
self._print_toggle('Chat download & render', self.downloadCHAT)
if self.downloadCHAT:
self._print_toggle(' ↳ Merge video + chat', self.mergeVideoChat)
if self.mergeVideoChat:
print(f' Layout: {Fore.GREEN}{self.mergeChatLayout}{Style.RESET_ALL}')
self._print_toggle('Cloud upload', self.uploadCloud)
# Warning messages
@ -401,10 +405,23 @@ class TwitchArchive:
live_chat_downloaded = self.downloader.wait_for_chat_download(live_chat_process, chat_json_path)
# Render live chat if downloaded successfully
chat_rendered_successfully = False
chat_video_path = None
if live_chat_downloaded:
chat_video_path = str(self.file_manager.chat_mp4_path / f"{PREFIX_CHAT}{filename_base}.mp4")
output_args = self.processor.build_chat_output_args()
self.downloader.render_chat(chat_json_path, chat_video_path, output_args)
chat_rendered_successfully = self.downloader.render_chat(chat_json_path, chat_video_path, output_args)
# Merge video and chat if configured
merged_video_path = None
if chat_rendered_successfully and self.mergeVideoChat and os.path.exists(live_proc_path) and os.path.exists(chat_video_path):
merged_video_path = str(self.file_manager.video_path / f"{PREFIX_MERGED}{filename_base}{live_proc_ext}")
merge_success = self.processor.merge_video_and_chat(
live_proc_path,
chat_video_path,
merged_video_path,
self.mergeChatLayout
)
# Skip VOD/chat download if shutdown was requested or vodTimeout is 0
vod_response = None
@ -471,9 +488,29 @@ class TwitchArchive:
if not live_chat_downloaded:
chat_video_path = str(self.file_manager.chat_mp4_path / f"{PREFIX_CHAT}{filename_base}.mp4")
output_args = self.processor.build_chat_output_args()
self.downloader.download_and_render_chat(current_vod, chat_json_path, chat_video_path, output_args)
chat_rendered_successfully = self.downloader.download_and_render_chat(current_vod, chat_json_path, chat_video_path, output_args)
# Merge VOD and chat if configured
if chat_rendered_successfully and self.mergeVideoChat and os.path.exists(vod_path) and os.path.exists(chat_video_path):
merged_vod_path = str(self.file_manager.video_path / f"{PREFIX_MERGED}{PREFIX_VOD}{filename_base}{vod_ext}")
self.processor.merge_video_and_chat(
vod_path,
chat_video_path,
merged_vod_path,
self.mergeChatLayout
)
else:
print(f'{Fore.CYAN}Chat already downloaded from live stream, skipping VOD chat download{Style.RESET_ALL}')
# But still merge VOD with existing chat if configured
if self.mergeVideoChat and os.path.exists(vod_path) and chat_video_path and os.path.exists(chat_video_path):
merged_vod_path = str(self.file_manager.video_path / f"{PREFIX_MERGED}{PREFIX_VOD}{filename_base}{vod_ext}")
self.processor.merge_video_and_chat(
vod_path,
chat_video_path,
merged_vod_path,
self.mergeChatLayout
)
else:
print(f'{Fore.YELLOW}⚠ No matching VOD found for this stream{Style.RESET_ALL}')
@ -954,6 +991,8 @@ class TwitchArchiveManager:
# Wait for live chat download if it was started
live_chat_downloaded = False
chat_rendered_successfully = False
chat_video_path = None
if live_chat_process is not None:
live_chat_downloaded = archiver.downloader.wait_for_chat_download(live_chat_process, chat_json_path)
@ -961,7 +1000,18 @@ class TwitchArchiveManager:
if live_chat_downloaded:
chat_video_path = str(archiver.file_manager.chat_mp4_path / f"{PREFIX_CHAT}{filename_base}.mp4")
output_args = archiver.processor.build_chat_output_args()
archiver.downloader.render_chat(chat_json_path, chat_video_path, output_args)
chat_rendered_successfully = archiver.downloader.render_chat(chat_json_path, chat_video_path, output_args)
# Merge video and chat if configured
merged_video_path = None
if chat_rendered_successfully and archiver.mergeVideoChat and os.path.exists(live_proc_path) and os.path.exists(chat_video_path):
merged_video_path = str(archiver.file_manager.video_path / f"{PREFIX_MERGED}{filename_base}{proc_extension}")
archiver.processor.merge_video_and_chat(
live_proc_path,
chat_video_path,
merged_video_path,
archiver.mergeChatLayout
)
# Wait for VOD and download it
vod_response = None
@ -1031,9 +1081,29 @@ class TwitchArchiveManager:
if archiver.downloadCHAT and not live_chat_downloaded:
chat_video_path = str(archiver.file_manager.chat_mp4_path / f"{PREFIX_CHAT}{filename_base}.mp4")
output_args = archiver.processor.build_chat_output_args()
archiver.downloader.download_and_render_chat(current_vod, chat_json_path, chat_video_path, output_args)
chat_rendered_successfully = archiver.downloader.download_and_render_chat(current_vod, chat_json_path, chat_video_path, output_args)
# Merge VOD and chat if configured
if chat_rendered_successfully and archiver.mergeVideoChat and os.path.exists(vod_path) and os.path.exists(chat_video_path):
merged_vod_path = str(archiver.file_manager.video_path / f"{PREFIX_MERGED}{PREFIX_VOD}{filename_base}{vod_ext}")
archiver.processor.merge_video_and_chat(
vod_path,
chat_video_path,
merged_vod_path,
archiver.mergeChatLayout
)
elif live_chat_downloaded:
print(f'{Fore.CYAN}Chat already downloaded from live stream, skipping VOD chat download{Style.RESET_ALL}')
# But still merge VOD with existing chat if configured
if archiver.mergeVideoChat and archiver.downloadVOD and os.path.exists(vod_path) and chat_video_path and os.path.exists(chat_video_path):
merged_vod_path = str(archiver.file_manager.video_path / f"{PREFIX_MERGED}{PREFIX_VOD}{filename_base}{vod_ext}")
archiver.processor.merge_video_and_chat(
vod_path,
chat_video_path,
merged_vod_path,
archiver.mergeChatLayout
)
else:
print(f'{Fore.YELLOW}⚠ No matching VOD found for this stream{Style.RESET_ALL}')
elif archiver.downloadMETADATA: