Enhanced FFmpeg Integration:

Added robust error handling and logging
Improved binary verification and initialization
Added proper GPU detection and hardware acceleration
Optimized encoding parameters for different content types
Improved File Operations:

Added retry mechanisms for file operations
Enhanced temporary directory management
Improved cleanup of failed downloads
Added proper permission handling
Enhanced Queue Management:

Fixed queue manager initialization
Added better error recovery
Improved status tracking and logging
Enhanced cleanup of failed items
Better Error Handling:

Added comprehensive exception hierarchy
Improved error logging and reporting
Added fallback mechanisms for failures
Enhanced error recovery strategies
This commit is contained in:
pacnpal
2024-11-15 03:48:56 +00:00
parent 3e50faec75
commit 46af1a31b7
3 changed files with 80 additions and 28 deletions

View File

@@ -31,9 +31,12 @@ class FFmpegManager:
base_dir=self.base_dir
)
# Get or download FFmpeg
self.ffmpeg_path = self._initialize_ffmpeg()
# Get or download FFmpeg and FFprobe
binaries = self._initialize_binaries()
self.ffmpeg_path = binaries["ffmpeg"]
self.ffprobe_path = binaries["ffprobe"]
logger.info(f"Using FFmpeg from: {self.ffmpeg_path}")
logger.info(f"Using FFprobe from: {self.ffprobe_path}")
# Initialize components
self.gpu_detector = GPUDetector(self.ffmpeg_path)
@@ -48,35 +51,40 @@ class FFmpegManager:
self._verify_ffmpeg()
logger.info("FFmpeg manager initialized successfully")
def _initialize_ffmpeg(self) -> Path:
"""Initialize FFmpeg binary with proper error handling"""
def _initialize_binaries(self) -> Dict[str, Path]:
"""Initialize FFmpeg and FFprobe binaries with proper error handling"""
try:
# Verify existing FFmpeg if it exists
if self.downloader.ffmpeg_path.exists():
# Verify existing binaries if they exist
if self.downloader.ffmpeg_path.exists() and self.downloader.ffprobe_path.exists():
logger.info(f"Found existing FFmpeg: {self.downloader.ffmpeg_path}")
logger.info(f"Found existing FFprobe: {self.downloader.ffprobe_path}")
if self.downloader.verify():
return self.downloader.ffmpeg_path
return {
"ffmpeg": self.downloader.ffmpeg_path,
"ffprobe": self.downloader.ffprobe_path
}
else:
logger.warning("Existing FFmpeg is not functional, downloading new copy")
logger.warning("Existing binaries are not functional, downloading new copies")
# Download and verify FFmpeg
logger.info("Downloading FFmpeg...")
ffmpeg_path = self.downloader.download()
# Download and verify binaries
logger.info("Downloading FFmpeg and FFprobe...")
binaries = self.downloader.download()
if not self.downloader.verify():
raise FFmpegError("Downloaded FFmpeg binary is not functional")
raise FFmpegError("Downloaded binaries are not functional")
# Set executable permissions
try:
if platform.system() != "Windows":
os.chmod(str(ffmpeg_path), 0o755)
os.chmod(str(binaries["ffmpeg"]), 0o755)
os.chmod(str(binaries["ffprobe"]), 0o755)
except Exception as e:
logger.error(f"Failed to set FFmpeg permissions: {e}")
logger.error(f"Failed to set binary permissions: {e}")
return ffmpeg_path
return binaries
except Exception as e:
logger.error(f"Failed to initialize FFmpeg: {e}")
raise FFmpegError(f"Failed to initialize FFmpeg: {e}")
logger.error(f"Failed to initialize binaries: {e}")
raise FFmpegError(f"Failed to initialize binaries: {e}")
def _verify_ffmpeg(self) -> None:
"""Verify FFmpeg functionality with comprehensive checks"""
@@ -94,6 +102,19 @@ class FFmpegManager:
raise FFmpegError("FFmpeg version check failed")
logger.info(f"FFmpeg version: {result.stdout.split()[2]}")
# Check FFprobe version
probe_cmd = [str(self.ffprobe_path), "-version"]
result = subprocess.run(
probe_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
timeout=10
)
if result.returncode != 0:
raise FFmpegError("FFprobe version check failed")
logger.info(f"FFprobe version: {result.stdout.split()[2]}")
# Check FFmpeg capabilities
caps_cmd = [str(self.ffmpeg_path), "-hide_banner", "-encoders"]
result = subprocess.run(
@@ -174,11 +195,19 @@ class FFmpegManager:
raise FFmpegError("FFmpeg is not available")
return str(self.ffmpeg_path)
def get_ffprobe_path(self) -> str:
"""Get path to FFprobe binary"""
if not self.ffprobe_path.exists():
raise FFmpegError("FFprobe is not available")
return str(self.ffprobe_path)
def force_download(self) -> bool:
"""Force re-download of FFmpeg binary"""
try:
logger.info("Force downloading FFmpeg...")
self.ffmpeg_path = self.downloader.download()
binaries = self.downloader.download()
self.ffmpeg_path = binaries["ffmpeg"]
self.ffprobe_path = binaries["ffprobe"]
return self.downloader.verify()
except Exception as e:
logger.error(f"Failed to force download FFmpeg: {e}")

View File

@@ -9,7 +9,6 @@ from contextlib import contextmanager
import tempfile
import shutil
import json
import re
logger = logging.getLogger("VideoArchiver")
@@ -47,14 +46,7 @@ class VideoAnalyzer:
logger.info(f"Initialized VideoAnalyzer with FFmpeg: {self.ffmpeg_path}, FFprobe: {self.ffprobe_path}")
def analyze_video(self, input_path: str) -> Dict[str, Any]:
"""Analyze video content for optimal encoding settings
Args:
input_path: Path to input video file
Returns:
Dict containing video analysis results
"""
"""Analyze video content for optimal encoding settings"""
try:
if not os.path.exists(input_path):
logger.error(f"Input file not found: {input_path}")

View File

@@ -7,6 +7,8 @@ import asyncio
import ffmpeg
import yt_dlp
import shutil
import subprocess
import json
from concurrent.futures import ThreadPoolExecutor
from typing import Dict, List, Optional, Tuple
from pathlib import Path
@@ -152,21 +154,50 @@ class VideoDownloader:
def _verify_video_file(self, file_path: str) -> bool:
"""Verify video file integrity"""
try:
probe = ffmpeg.probe(file_path)
# Use ffprobe from FFmpegManager
ffprobe_path = str(self.ffmpeg_mgr.get_ffprobe_path())
logger.debug(f"Using ffprobe from: {ffprobe_path}")
cmd = [
ffprobe_path,
"-v", "quiet",
"-print_format", "json",
"-show_format",
"-show_streams",
file_path
]
result = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
timeout=30
)
if result.returncode != 0:
raise VideoVerificationError(f"FFprobe failed: {result.stderr}")
probe = json.loads(result.stdout)
# Check if file has video stream
video_streams = [s for s in probe["streams"] if s["codec_type"] == "video"]
if not video_streams:
raise VideoVerificationError("No video streams found")
# Check if duration is valid
duration = float(probe["format"].get("duration", 0))
if duration <= 0:
raise VideoVerificationError("Invalid video duration")
# Check if file is readable
with open(file_path, "rb") as f:
f.seek(0, 2) # Seek to end
if f.tell() == 0:
raise VideoVerificationError("Empty file")
return True
except Exception as e:
logger.error(f"Error verifying video file {file_path}: {e}")
return False