feat: enhance chat downloading with stream monitoring and improved file paths
This commit is contained in:
parent
22a1f5b600
commit
b47641feaa
3 changed files with 87 additions and 30 deletions
|
|
@ -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}')
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue