Refactor downloader and file manager for improved rclone integration and add healthcheck and smoke test options

- Renamed download flags in ContentDownloader for clarity.
- Enhanced FileManager with methods to build upload paths and verify existing files for rclone uploads.
- Updated StreamProcessor to return success status for stream processing.
- Added rclone smoke test and healthcheck functions to validate configuration and tool availability.
- Improved environment variable handling with a utility function.
- Updated TwitchArchive to incorporate new rclone verification and processing logic.
- Added unit tests for new functionality and refactored existing tests for clarity and coverage.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
MaddoScientisto 2026-04-25 11:54:03 +02:00
commit f97e0200d6
23 changed files with 1013 additions and 289 deletions

View file

@ -37,37 +37,78 @@ class StreamProcessor:
os_type
)
def process_raw_stream(self, raw_path: str, output_path: str) -> None:
def process_raw_stream(self, raw_path: str, output_path: str) -> bool:
"""
Process raw .ts file into mp4/mp3 using ffmpeg.
Args:
raw_path: Path to the raw .ts file
output_path: Path for the processed output file
Returns:
bool: True when conversion succeeded, False otherwise
"""
if not os.path.exists(raw_path):
print(f'{Fore.YELLOW}⚠ Raw file not found, skipping processing{Style.RESET_ALL}')
return
return False
if self.only_raw:
print(f'{Fore.CYAN}Keeping raw .ts file (onlyRaw mode){Style.RESET_ALL}')
return
return False
print(f'{Fore.YELLOW}Processing raw stream file...{Style.RESET_ALL}')
# Build ffmpeg command based on quality
if self.quality == 'audio_only':
self._process_audio(raw_path, output_path)
result = self._process_audio(raw_path, output_path)
else:
self._process_video(raw_path, output_path)
result = self._process_video(raw_path, output_path)
print(f'{Fore.GREEN}✓ Stream processed successfully{Style.RESET_ALL}')
if result:
print(f'{Fore.GREEN}✓ Stream processed successfully{Style.RESET_ALL}')
else:
print(f'{Fore.RED}✗ Stream processing failed{Style.RESET_ALL}')
return result
def _process_audio(self, raw_path: str, output_path: str) -> None:
def _run_ffmpeg_command(self, cmd: list, output_path: str) -> bool:
"""Run FFmpeg while streaming its output to the terminal."""
print(f'{Fore.CYAN}Running FFmpeg: {' '.join(cmd)}{Style.RESET_ALL}')
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
encoding='utf-8',
errors='replace'
)
if process.stdout:
for line in process.stdout:
print(line, end='')
result = process.wait()
if result != 0:
print(f'{Fore.RED}✗ FFmpeg exited with code: {result}{Style.RESET_ALL}')
return False
if not os.path.exists(output_path):
print(f'{Fore.RED}✗ FFmpeg did not create output: {output_path}{Style.RESET_ALL}')
return False
if os.path.getsize(output_path) == 0:
print(f'{Fore.RED}✗ FFmpeg created an empty output file: {output_path}{Style.RESET_ALL}')
return False
return True
def _process_audio(self, raw_path: str, output_path: str) -> bool:
"""Process audio-only stream."""
# Audio-only conversion with modern AAC encoding
cmd = [
self.ffmpeg_path,
'-y',
'-i', raw_path,
'-vn', # No video
'-c:a', self.ffmpeg_audio_codec,
@ -85,14 +126,9 @@ class StreamProcessor:
cmd.extend(['-movflags', '+faststart'])
cmd.append(output_path)
# Run FFmpeg
if self.ffmpeg_progress:
subprocess.call(cmd)
else:
subprocess.call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
return self._run_ffmpeg_command(cmd, output_path)
def _process_video(self, raw_path: str, output_path: str) -> None:
def _process_video(self, raw_path: str, output_path: str) -> bool:
"""Process video stream."""
cmd = [
self.ffmpeg_path,
@ -135,12 +171,7 @@ class StreamProcessor:
cmd.extend(['-movflags', '+faststart'])
cmd.append(output_path)
# Run FFmpeg
if self.ffmpeg_progress:
subprocess.call(cmd)
else:
subprocess.call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
return self._run_ffmpeg_command(cmd, output_path)
def build_chat_output_args(self) -> str:
"""