Switching from regex patterns to yt-dlp simulation for URL detection:

Removed the regex-based URL detection
Added proper yt-dlp simulation to check if URLs are supported
Added better error handling for URL checking
Added detailed logging of URL detection results
Improving FFmpeg integration:

Added proper ffprobe binary download alongside FFmpeg
Added better verification of both binaries
Added retry mechanisms for binary downloads
Added proper cleanup of failed downloads
Enhancing error handling and logging:

Added detailed logging throughout the system
Added better error recovery mechanisms
Added proper cleanup of temporary files
Added better tracking of failed operations
This commit is contained in:
pacnpal
2024-11-15 03:40:53 +00:00
parent 8503fc6fdd
commit 3e50faec75
3 changed files with 128 additions and 111 deletions

View File

@@ -12,7 +12,7 @@ import platform
import hashlib
from pathlib import Path
from contextlib import contextmanager
from typing import Optional
from typing import Optional, Dict, List
import time
from .exceptions import DownloadError
@@ -37,31 +37,31 @@ class FFmpegDownloader:
"Windows": {
"x86_64": {
"url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip",
"bin_name": "ffmpeg.exe",
"bin_names": ["ffmpeg.exe", "ffprobe.exe"],
}
},
"Linux": {
"x86_64": {
"url": "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz",
"bin_name": "ffmpeg",
"bin_names": ["ffmpeg", "ffprobe"],
},
"aarch64": {
"url": "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-arm64-static.tar.xz",
"bin_name": "ffmpeg",
"bin_names": ["ffmpeg", "ffprobe"],
},
"armv7l": {
"url": "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-armhf-static.tar.xz",
"bin_name": "ffmpeg",
"bin_names": ["ffmpeg", "ffprobe"],
},
},
"Darwin": {
"x86_64": {
"url": "https://evermeet.cx/ffmpeg/getrelease/zip",
"bin_name": "ffmpeg",
"bin_names": ["ffmpeg", "ffprobe"],
},
"arm64": {
"url": "https://evermeet.cx/ffmpeg/getrelease/zip",
"bin_name": "ffmpeg",
"bin_names": ["ffmpeg", "ffprobe"],
},
},
}
@@ -73,15 +73,17 @@ class FFmpegDownloader:
if self.machine == "arm64":
self.machine = "aarch64" # Normalize ARM64 naming
self.base_dir = base_dir
self.ffmpeg_path = self.base_dir / self._get_binary_name()
self.ffmpeg_path = self.base_dir / self._get_binary_names()[0]
self.ffprobe_path = self.base_dir / self._get_binary_names()[1]
logger.info(f"Initialized FFmpeg downloader for {system}/{machine}")
logger.info(f"FFmpeg binary path: {self.ffmpeg_path}")
logger.info(f"FFprobe binary path: {self.ffprobe_path}")
def _get_binary_name(self) -> str:
"""Get the appropriate binary name for the current system"""
def _get_binary_names(self) -> List[str]:
"""Get the appropriate binary names for the current system"""
try:
return self.FFMPEG_URLS[self.system][self.machine]["bin_name"]
return self.FFMPEG_URLS[self.system][self.machine]["bin_names"]
except KeyError:
raise DownloadError(f"Unsupported system/architecture: {self.system}/{self.machine}")
@@ -92,8 +94,8 @@ class FFmpegDownloader:
except KeyError:
raise DownloadError(f"Unsupported system/architecture: {self.system}/{self.machine}")
def download(self) -> Path:
"""Download and set up FFmpeg binary with retries"""
def download(self) -> Dict[str, Path]:
"""Download and set up FFmpeg and FFprobe binaries with retries"""
max_retries = 3
retry_delay = 5
last_error = None
@@ -106,12 +108,13 @@ class FFmpegDownloader:
self.base_dir.mkdir(parents=True, exist_ok=True)
os.chmod(str(self.base_dir), 0o777)
# Clean up any existing file or directory
if self.ffmpeg_path.exists():
if self.ffmpeg_path.is_dir():
shutil.rmtree(str(self.ffmpeg_path))
else:
self.ffmpeg_path.unlink()
# Clean up any existing files
for binary_path in [self.ffmpeg_path, self.ffprobe_path]:
if binary_path.exists():
if binary_path.is_dir():
shutil.rmtree(str(binary_path))
else:
binary_path.unlink()
with temp_path_context() as temp_dir:
# Download archive
@@ -121,18 +124,23 @@ class FFmpegDownloader:
if not self._verify_download(archive_path):
raise DownloadError("Downloaded file verification failed")
# Extract FFmpeg binary
self._extract_binary(archive_path, temp_dir)
# Extract binaries
self._extract_binaries(archive_path, temp_dir)
# Set proper permissions
os.chmod(str(self.ffmpeg_path), 0o755)
for binary_path in [self.ffmpeg_path, self.ffprobe_path]:
os.chmod(str(binary_path), 0o755)
# Verify binary
# Verify binaries
if not self.verify():
raise DownloadError("FFmpeg binary verification failed")
raise DownloadError("Binary verification failed")
logger.info(f"Successfully downloaded FFmpeg to {self.ffmpeg_path}")
return self.ffmpeg_path
logger.info(f"Successfully downloaded FFprobe to {self.ffprobe_path}")
return {
"ffmpeg": self.ffmpeg_path,
"ffprobe": self.ffprobe_path
}
except Exception as e:
last_error = str(e)
@@ -193,9 +201,9 @@ class FFmpegDownloader:
logger.error(f"Download verification failed: {str(e)}")
return False
def _extract_binary(self, archive_path: Path, temp_dir: str):
"""Extract FFmpeg binary from archive"""
logger.info("Extracting FFmpeg binary")
def _extract_binaries(self, archive_path: Path, temp_dir: str):
"""Extract FFmpeg and FFprobe binaries from archive"""
logger.info("Extracting FFmpeg and FFprobe binaries")
if self.system == "Windows":
self._extract_zip(archive_path, temp_dir)
@@ -205,50 +213,70 @@ class FFmpegDownloader:
def _extract_zip(self, archive_path: Path, temp_dir: str):
"""Extract from zip archive (Windows)"""
with zipfile.ZipFile(archive_path, "r") as zip_ref:
ffmpeg_files = [f for f in zip_ref.namelist() if self._get_binary_name() in f]
if not ffmpeg_files:
raise DownloadError("FFmpeg binary not found in archive")
zip_ref.extract(ffmpeg_files[0], temp_dir)
extracted_path = Path(temp_dir) / ffmpeg_files[0]
shutil.copy2(extracted_path, self.ffmpeg_path)
binary_names = self._get_binary_names()
for binary_name in binary_names:
binary_files = [f for f in zip_ref.namelist() if binary_name in f]
if not binary_files:
raise DownloadError(f"{binary_name} not found in archive")
zip_ref.extract(binary_files[0], temp_dir)
extracted_path = Path(temp_dir) / binary_files[0]
target_path = self.base_dir / binary_name
shutil.copy2(extracted_path, target_path)
def _extract_tar(self, archive_path: Path, temp_dir: str):
"""Extract from tar archive (Linux/macOS)"""
with tarfile.open(archive_path, "r:xz") as tar_ref:
ffmpeg_files = [f for f in tar_ref.getnames() if f.endswith("/ffmpeg")]
if not ffmpeg_files:
raise DownloadError("FFmpeg binary not found in archive")
tar_ref.extract(ffmpeg_files[0], temp_dir)
extracted_path = Path(temp_dir) / ffmpeg_files[0]
shutil.copy2(extracted_path, self.ffmpeg_path)
binary_names = self._get_binary_names()
for binary_name in binary_names:
binary_files = [f for f in tar_ref.getnames() if f.endswith(f"/{binary_name}")]
if not binary_files:
raise DownloadError(f"{binary_name} not found in archive")
tar_ref.extract(binary_files[0], temp_dir)
extracted_path = Path(temp_dir) / binary_files[0]
target_path = self.base_dir / binary_name
shutil.copy2(extracted_path, target_path)
def verify(self) -> bool:
"""Verify FFmpeg binary works"""
"""Verify FFmpeg and FFprobe binaries work"""
try:
if not self.ffmpeg_path.exists():
if not self.ffmpeg_path.exists() or not self.ffprobe_path.exists():
return False
# Ensure proper permissions
os.chmod(str(self.ffmpeg_path), 0o755)
os.chmod(str(self.ffprobe_path), 0o755)
# Test FFmpeg functionality
result = subprocess.run(
ffmpeg_result = subprocess.run(
[str(self.ffmpeg_path), "-version"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=5
)
if result.returncode == 0:
version = result.stdout.decode().split('\n')[0]
logger.info(f"FFmpeg verification successful: {version}")
# Test FFprobe functionality
ffprobe_result = subprocess.run(
[str(self.ffprobe_path), "-version"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=5
)
if ffmpeg_result.returncode == 0 and ffprobe_result.returncode == 0:
ffmpeg_version = ffmpeg_result.stdout.decode().split('\n')[0]
ffprobe_version = ffprobe_result.stdout.decode().split('\n')[0]
logger.info(f"FFmpeg verification successful: {ffmpeg_version}")
logger.info(f"FFprobe verification successful: {ffprobe_version}")
return True
else:
logger.error(f"FFmpeg verification failed: {result.stderr.decode()}")
if ffmpeg_result.returncode != 0:
logger.error(f"FFmpeg verification failed: {ffmpeg_result.stderr.decode()}")
if ffprobe_result.returncode != 0:
logger.error(f"FFprobe verification failed: {ffprobe_result.stderr.decode()}")
return False
except Exception as e:
logger.error(f"FFmpeg verification failed: {e}")
logger.error(f"Binary verification failed: {e}")
return False