feat: enhance chat downloading with stream monitoring and improved file paths

This commit is contained in:
MaddoScientisto 2026-02-18 18:11:53 +01:00
commit b47641feaa
3 changed files with 87 additions and 30 deletions

View file

@ -505,6 +505,16 @@ class ContentDownloader:
if not self.download_live_chat: if not self.download_live_chat:
print(f'{Fore.YELLOW}⚠ downloadLiveCHAT is disabled in config{Style.RESET_ALL}') print(f'{Fore.YELLOW}⚠ downloadLiveCHAT is disabled in config{Style.RESET_ALL}')
return False return False
# If a stream monitor was provided, check that the user is currently live
if stream_monitor is not None:
try:
if not stream_monitor.is_user_live():
print(f'{Fore.YELLOW}⚠ Stream is not live; skipping chat download{Style.RESET_ALL}')
return False
except Exception as e:
# If we couldn't determine live status, continue and let chat_downloader handle it
print(f'{Fore.YELLOW}⚠ Could not determine live status: {e} - proceeding with chat download{Style.RESET_ALL}')
print(f'\n{Fore.CYAN}Starting live chat download (chat_downloader)...{Style.RESET_ALL}') print(f'\n{Fore.CYAN}Starting live chat download (chat_downloader)...{Style.RESET_ALL}')
print(f'{Fore.MAGENTA}[VERBOSE] chat_downloader library version: {ChatDownloader.__module__}{Style.RESET_ALL}') print(f'{Fore.MAGENTA}[VERBOSE] chat_downloader library version: {ChatDownloader.__module__}{Style.RESET_ALL}')

View file

@ -129,51 +129,60 @@ class FileManager:
os.makedirs(os.path.dirname(upload_list_path), exist_ok=True) os.makedirs(os.path.dirname(upload_list_path), exist_ok=True)
files_to_upload = [] files_to_upload = []
# Always include metadata and chat JSON # Build files list relative to root_path so rclone can read them with --files-from
files_to_upload.append(f"{PREFIX_METADATA}{filename_base}.json") # Metadata and chat JSON
files_to_upload.append(f"{PREFIX_CHAT}{filename_base}.json") files_to_upload.append(os.path.join(self.username, 'metadata', f"{PREFIX_METADATA}{filename_base}.json"))
files_to_upload.append(os.path.join(self.username, 'chat', 'json', f"{PREFIX_CHAT}{filename_base}.json"))
# Add pre-merge videos (original LIVE and VOD files)
# Pre-merge videos (raw .ts in video/raw, mp4/mp3 in video)
if self.upload_pre_merge_video: if self.upload_pre_merge_video:
files_to_upload.extend([ files_to_upload.extend([
f"{PREFIX_LIVE}{filename_base}.ts", os.path.join(self.username, 'video', 'raw', f"{PREFIX_LIVE}{filename_base}.ts"),
f"{PREFIX_LIVE}{filename_base}.mp4", os.path.join(self.username, 'video', f"{PREFIX_LIVE}{filename_base}.mp4"),
f"{PREFIX_LIVE}{filename_base}.mp3", os.path.join(self.username, 'video', f"{PREFIX_LIVE}{filename_base}.mp3"),
f"{PREFIX_VOD}{filename_base}.ts", os.path.join(self.username, 'video', 'raw', f"{PREFIX_VOD}{filename_base}.ts"),
f"{PREFIX_VOD}{filename_base}.mp4", os.path.join(self.username, 'video', f"{PREFIX_VOD}{filename_base}.mp4"),
f"{PREFIX_VOD}{filename_base}.mp3" os.path.join(self.username, 'video', f"{PREFIX_VOD}{filename_base}.mp3")
]) ])
# Add merged videos # Merged videos (in video folder)
if self.upload_merged_video: if self.upload_merged_video:
files_to_upload.extend([ files_to_upload.extend([
f"{PREFIX_MERGED}{filename_base}.mp4", os.path.join(self.username, 'video', f"{PREFIX_MERGED}{filename_base}.mp4"),
f"{PREFIX_MERGED}{filename_base}.mp3", os.path.join(self.username, 'video', f"{PREFIX_MERGED}{filename_base}.mp3"),
f"{PREFIX_MERGED}{PREFIX_VOD}{filename_base}.mp4", os.path.join(self.username, 'video', f"{PREFIX_MERGED}{PREFIX_VOD}{filename_base}.mp4"),
f"{PREFIX_MERGED}{PREFIX_VOD}{filename_base}.mp3" os.path.join(self.username, 'video', f"{PREFIX_MERGED}{PREFIX_VOD}{filename_base}.mp3")
]) ])
# Add standalone chat video # Standalone chat video (in chat folder)
if self.upload_chat_video: if self.upload_chat_video:
files_to_upload.append(f"{PREFIX_CHAT}{filename_base}.mp4") files_to_upload.append(os.path.join(self.username, 'chat', f"{PREFIX_CHAT}{filename_base}.mp4"))
with open(upload_list_path, 'w') as f: with open(upload_list_path, 'w') as f:
f.write('\n'.join(files_to_upload)) f.write('\n'.join(files_to_upload))
# Run rclone # Run rclone using --files-from so the listed paths (relative to root_path) are uploaded.
try: try:
result = subprocess.call([ cmd = [
'rclone', 'copy', 'rclone', 'copy',
str(self.root_path.resolve()), str(self.root_path.resolve()),
self.rclone_path, self.rclone_path,
'--include-from', upload_list_path '--files-from', upload_list_path
]) ]
# Stream rclone output to console so user can see progress/errors
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
if proc.stdout:
for line in proc.stdout:
print(line, end='')
proc.wait()
result = proc.returncode
# Clean up upload list # Clean up upload list
if os.path.exists(upload_list_path): if os.path.exists(upload_list_path):
os.remove(upload_list_path) os.remove(upload_list_path)
if result == 0: if result == 0:
print(f'{Fore.GREEN}✓ Upload complete{Style.RESET_ALL}') print(f'{Fore.GREEN}✓ Upload complete{Style.RESET_ALL}')
if notification_callback: if notification_callback:
@ -186,7 +195,7 @@ class FileManager:
notification_callback(f'✗ Upload Failed - {filename_base}', notification_callback(f'✗ Upload Failed - {filename_base}',
f'Upload failed with code {result}. Files preserved locally.') f'Upload failed with code {result}. Files preserved locally.')
return False return False
except Exception as e: except Exception as e:
print(f'{Fore.RED}✗ Upload error: {str(e)}{Style.RESET_ALL}') print(f'{Fore.RED}✗ Upload error: {str(e)}{Style.RESET_ALL}')
return False return False

View file

@ -20,6 +20,7 @@ from modules.config import ConfigManager
from modules.file_manager import FileManager from modules.file_manager import FileManager
from modules.utils import get_ffmpeg_executable, get_twitch_downloader_executable, detect_operating_system from modules.utils import get_ffmpeg_executable, get_twitch_downloader_executable, detect_operating_system
from modules.downloader import ContentDownloader from modules.downloader import ContentDownloader
from modules.stream_monitor import StreamMonitor
def main(): def main():
@ -72,12 +73,49 @@ def main():
def shutdown_check(): def shutdown_check():
return stop_requested['stop'] return stop_requested['stop']
# Start thread # Prepare stream monitor
stream_monitor = StreamMonitor(args.username)
# If chat downloads are disabled in config, enter monitoring mode instead
if not downloader.download_live_chat:
print(f"{Fore.YELLOW}⚠ downloadLiveCHAT is disabled in config - entering monitoring mode for {args.username}{Style.RESET_ALL}")
try:
while True:
try:
is_live = stream_monitor.is_user_live()
if is_live:
print(f"{Fore.GREEN}{args.username} is live! Exiting monitor. Run the archiver to record video.{Style.RESET_ALL}")
break
else:
print(f"{Fore.CYAN}{args.username} is offline - checking again in 30s...{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}⚠ Could not check stream status: {e}{Style.RESET_ALL}")
time.sleep(30)
except KeyboardInterrupt:
print('\nKeyboard interrupt received; stopping monitor...')
return
# If chat download is enabled, but the stream is currently offline, wait until it goes live
try:
try:
if not stream_monitor.is_user_live():
print(f"{Fore.CYAN}{args.username} is currently offline - waiting for live stream to start...{Style.RESET_ALL}")
while not stream_monitor.is_user_live():
time.sleep(10)
except Exception:
# If we cannot determine live status, proceed to start chat downloader anyway
pass
except KeyboardInterrupt:
print('\nKeyboard interrupt received; exiting...')
return
# Start thread (stream_monitor passed so downloader can stop when stream ends)
thread = downloader.start_chat_downloader_thread( thread = downloader.start_chat_downloader_thread(
args.username, args.username,
json_path, json_path,
shutdown_check=shutdown_check, shutdown_check=shutdown_check,
stream_monitor=None, stream_monitor=stream_monitor,
verbose=args.verbose verbose=args.verbose
) )