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

View File

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

View File

@@ -7,6 +7,8 @@ import asyncio
import ffmpeg import ffmpeg
import yt_dlp import yt_dlp
import shutil import shutil
import subprocess
import json
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple
from pathlib import Path from pathlib import Path
@@ -152,21 +154,50 @@ class VideoDownloader:
def _verify_video_file(self, file_path: str) -> bool: def _verify_video_file(self, file_path: str) -> bool:
"""Verify video file integrity""" """Verify video file integrity"""
try: 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 # Check if file has video stream
video_streams = [s for s in probe["streams"] if s["codec_type"] == "video"] video_streams = [s for s in probe["streams"] if s["codec_type"] == "video"]
if not video_streams: if not video_streams:
raise VideoVerificationError("No video streams found") raise VideoVerificationError("No video streams found")
# Check if duration is valid # Check if duration is valid
duration = float(probe["format"].get("duration", 0)) duration = float(probe["format"].get("duration", 0))
if duration <= 0: if duration <= 0:
raise VideoVerificationError("Invalid video duration") raise VideoVerificationError("Invalid video duration")
# Check if file is readable # Check if file is readable
with open(file_path, "rb") as f: with open(file_path, "rb") as f:
f.seek(0, 2) # Seek to end f.seek(0, 2) # Seek to end
if f.tell() == 0: if f.tell() == 0:
raise VideoVerificationError("Empty file") raise VideoVerificationError("Empty file")
return True return True
except Exception as e: except Exception as e:
logger.error(f"Error verifying video file {file_path}: {e}") logger.error(f"Error verifying video file {file_path}: {e}")
return False return False