mirror of
https://github.com/pacnpal/Pac-cogs.git
synced 2025-12-20 10:51:05 -05:00
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:
@@ -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}")
|
||||||
|
|||||||
@@ -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}")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user