TwitchDownloader/modules/utils.py

272 lines
8.4 KiB
Python
Raw Normal View History

"""
Utility functions and helpers for Twitch Archive.
"""
import os
import sys
import pathlib
import subprocess
from typing import Optional
from colorama import Fore, Style
def detect_operating_system() -> str:
"""
Detect the current operating system.
Returns:
str: 'windows' or 'linux'
Raises:
SystemExit: If OS is not supported
"""
if sys.platform.startswith('win32'):
return 'windows'
elif sys.platform.startswith('linux'):
return 'linux'
else:
print(f'{Fore.RED}✗ ERROR: Unsupported operating system: {sys.platform}{Style.RESET_ALL}')
print(f'{Fore.YELLOW} This script only supports Windows and Linux{Style.RESET_ALL}')
sys.exit(1)
def get_bin_path() -> str:
"""Get the path to the bin directory containing external tools."""
return str(pathlib.Path(__file__).parent.parent.resolve() / "bin")
def get_ffmpeg_executable(os_type: str) -> str:
"""
Get the platform-specific ffmpeg executable path.
Args:
os_type: Operating system type ('windows' or 'linux')
Returns:
str: Path to ffmpeg executable
"""
bin_path = get_bin_path()
if os_type == 'windows':
return os.path.join(bin_path, 'ffmpeg.exe')
return os.path.join(bin_path, 'ffmpeg')
def get_twitch_downloader_executable(os_type: str) -> str:
"""
Get the platform-specific TwitchDownloaderCLI executable path.
Args:
os_type: Operating system type ('windows' or 'linux')
Returns:
str: Path to TwitchDownloaderCLI executable
"""
bin_path = get_bin_path()
if os_type == 'windows':
return os.path.join(bin_path, 'TwitchDownloaderCLI.exe')
return os.path.join(bin_path, 'TwitchDownloaderCLI')
def get_unique_filename(filepath: str) -> str:
"""
Generate a unique filename by appending a counter if file already exists.
Args:
filepath: The desired file path
Returns:
str: A unique file path (original or with _N suffix)
Example:
If 'video.mp4' exists, returns 'video_1.mp4'
If 'video_1.mp4' also exists, returns 'video_2.mp4'
"""
if not os.path.exists(filepath):
return filepath
# Split into components
directory = os.path.dirname(filepath)
filename = os.path.basename(filepath)
name, ext = os.path.splitext(filename)
# Find next available counter
counter = 1
while True:
new_filepath = os.path.join(directory, f"{name}_{counter}{ext}")
if not os.path.exists(new_filepath):
return new_filepath
counter += 1
def get_video_duration(video_path: str, ffmpeg_path: str) -> Optional[float]:
"""
Get the duration of a video file in seconds using FFmpeg.
Args:
video_path: Path to the video file
ffmpeg_path: Path to FFmpeg executable
Returns:
float: Duration in seconds, or None if failed
"""
if not os.path.exists(video_path):
return None
try:
# Use ffprobe (comes with ffmpeg) to get duration
ffprobe_path = ffmpeg_path.replace('ffmpeg', 'ffprobe')
cmd = [
ffprobe_path,
'-v', 'error',
'-show_entries', 'format=duration',
'-of', 'default=noprint_wrappers=1:nokey=1',
video_path
]
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=30
)
if result.returncode == 0 and result.stdout.strip():
return float(result.stdout.strip())
except Exception:
pass
return None
def verify_streamlink() -> bool:
"""
Verify that streamlink is available.
Returns:
bool: True if streamlink is available, False otherwise
"""
try:
result = subprocess.run(['streamlink', '--version'],
capture_output=True,
text=True,
timeout=5)
if result.returncode == 0:
version = result.stdout.strip().split()[1] if len(result.stdout.split()) > 1 else 'unknown'
print(f'{Fore.GREEN}✓ Streamlink v{version} found{Style.RESET_ALL}')
return True
else:
raise FileNotFoundError()
except (FileNotFoundError, subprocess.TimeoutExpired, IndexError):
print(f'{Fore.RED}✗ ERROR: Streamlink not found{Style.RESET_ALL}')
print(f'{Fore.CYAN} → Install streamlink: pip install streamlink{Style.RESET_ALL}')
print(f'{Fore.CYAN} → Or download from: https://streamlink.github.io/{Style.RESET_ALL}')
return False
def verify_ffmpeg(os_type: str) -> bool:
"""
Verify that ffmpeg is available.
Args:
os_type: Operating system type ('windows' or 'linux')
Returns:
bool: True if ffmpeg is available, False otherwise
"""
try:
ffmpeg_path = get_ffmpeg_executable(os_type)
if os.path.exists(ffmpeg_path):
print(f'{Fore.GREEN}✓ FFmpeg found at {ffmpeg_path}{Style.RESET_ALL}')
return True
else:
print(f'{Fore.YELLOW}⚠ Warning: FFmpeg not found at {ffmpeg_path}{Style.RESET_ALL}')
print(f'{Fore.YELLOW} → Download FFmpeg and place it in the bin/ folder{Style.RESET_ALL}')
return False
except Exception as e:
print(f'{Fore.YELLOW}⚠ Warning: Could not verify FFmpeg: {e}{Style.RESET_ALL}')
return False
def verify_twitch_downloader(os_type: str) -> bool:
"""
Verify that TwitchDownloaderCLI is available.
Args:
os_type: Operating system type ('windows' or 'linux')
Returns:
bool: True if TwitchDownloaderCLI is available, False otherwise
"""
try:
downloader_path = get_twitch_downloader_executable(os_type)
if os.path.exists(downloader_path):
print(f'{Fore.GREEN}✓ TwitchDownloaderCLI found{Style.RESET_ALL}')
return True
else:
print(f'{Fore.YELLOW}⚠ Warning: TwitchDownloaderCLI not found at {downloader_path}{Style.RESET_ALL}')
print(f'{Fore.YELLOW} → Download from: https://github.com/lay295/TwitchDownloader/releases{Style.RESET_ALL}')
return False
except Exception as e:
print(f'{Fore.YELLOW}⚠ Warning: Could not verify TwitchDownloaderCLI: {e}{Style.RESET_ALL}')
return False
def detect_hardware_acceleration(hwaccel_config: str, os_type: str) -> Optional[str]:
"""
Detect available hardware acceleration based on config and system.
Args:
hwaccel_config: Hardware acceleration configuration ('auto', 'nvenc', 'qsv', 'amf', 'vaapi', 'none')
os_type: Operating system type ('windows' or 'linux')
Returns:
str: Hardware acceleration type or None
"""
# If user explicitly set to 'none', disable hardware acceleration
if hwaccel_config == 'none':
return 'none'
# If user specified a particular type, use it
if hwaccel_config in ['nvenc', 'qsv', 'amf', 'vaapi']:
return hwaccel_config
# Auto-detect: try to determine available hardware
if hwaccel_config == 'auto':
# On Windows, NVIDIA is most common
if os_type == 'windows':
# Could check for nvidia-smi, but just return 'auto' for ffmpeg to decide
return 'auto'
else:
# On Linux, VAAPI is common for Intel/AMD, or NVENC for NVIDIA
# Let ffmpeg auto-detect
return 'auto'
return None
def get_hwaccel_encoder(hwaccel_type: str) -> str:
"""
Get the appropriate hardware-accelerated encoder for the given acceleration type.
Args:
hwaccel_type: Type of hardware acceleration ('nvenc', 'qsv', 'amf', 'vaapi', 'auto', 'none')
Returns:
str: FFmpeg encoder name (e.g., 'h264_nvenc', 'libx264')
"""
encoder_map = {
'nvenc': 'h264_nvenc', # NVIDIA
'qsv': 'h264_qsv', # Intel Quick Sync
'amf': 'h264_amf', # AMD
'vaapi': 'h264_vaapi', # Linux VA-API
}
if hwaccel_type in encoder_map:
return encoder_map[hwaccel_type]
elif hwaccel_type == 'auto':
# Try NVENC first (most common), fall back to libx264
# In real usage, auto will attempt to use what's available
return 'h264_nvenc'
else:
return 'libx264' # Software encoding fallback