mirror of
https://github.com/pacnpal/Pac-cogs.git
synced 2025-12-20 02:41:06 -05:00
fix imports
This commit is contained in:
@@ -19,16 +19,30 @@ A Red-DiscordBot cog for automatically archiving videos from monitored Discord c
|
|||||||
|
|
||||||
The cog is organized into several modules for better maintainability:
|
The cog is organized into several modules for better maintainability:
|
||||||
|
|
||||||
|
### Core Files
|
||||||
- `video_archiver.py`: Main cog class and entry point
|
- `video_archiver.py`: Main cog class and entry point
|
||||||
- `commands.py`: Discord command handlers
|
- `commands.py`: Discord command handlers
|
||||||
- `config_manager.py`: Guild configuration management
|
- `config_manager.py`: Guild configuration management
|
||||||
- `processor.py`: Video processing logic
|
- `processor.py`: Video processing logic
|
||||||
- `enhanced_queue.py`: Advanced queue management system
|
- `enhanced_queue.py`: Advanced queue management system
|
||||||
- `update_checker.py`: yt-dlp update management
|
- `update_checker.py`: yt-dlp update management
|
||||||
- `utils.py`: Utility functions and classes
|
|
||||||
- `ffmpeg_manager.py`: FFmpeg configuration and hardware acceleration
|
|
||||||
- `exceptions.py`: Custom exception classes
|
- `exceptions.py`: Custom exception classes
|
||||||
|
|
||||||
|
### Utils Package
|
||||||
|
- `utils/video_downloader.py`: Video download and processing
|
||||||
|
- `utils/message_manager.py`: Message handling and cleanup
|
||||||
|
- `utils/file_ops.py`: File operations and secure deletion
|
||||||
|
- `utils/path_manager.py`: Path management utilities
|
||||||
|
- `utils/exceptions.py`: Utility-specific exceptions
|
||||||
|
|
||||||
|
### FFmpeg Package
|
||||||
|
- `ffmpeg/ffmpeg_manager.py`: FFmpeg configuration and management
|
||||||
|
- `ffmpeg/gpu_detector.py`: GPU capability detection
|
||||||
|
- `ffmpeg/video_analyzer.py`: Video analysis utilities
|
||||||
|
- `ffmpeg/encoder_params.py`: Encoding parameter optimization
|
||||||
|
- `ffmpeg/ffmpeg_downloader.py`: FFmpeg binary management
|
||||||
|
- `ffmpeg/exceptions.py`: FFmpeg-specific exceptions
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Install the cog using Red's cog manager:
|
1. Install the cog using Red's cog manager:
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
from redbot.core.utils import get_end_user_data_statement
|
from redbot.core.utils import get_end_user_data_statement
|
||||||
|
|
||||||
from .video_archiver import VideoArchiver
|
from videoarchiver.video_archiver import VideoArchiver
|
||||||
from .ffmpeg.ffmpeg_manager import FFmpegManager
|
from videoarchiver.ffmpeg.ffmpeg_manager import FFmpegManager
|
||||||
from .ffmpeg.exceptions import FFmpegError, GPUError, DownloadError
|
from videoarchiver.ffmpeg.exceptions import FFmpegError, GPUError, DownloadError
|
||||||
|
|
||||||
__red_end_user_data_statement__ = get_end_user_data_statement(__file__)
|
__red_end_user_data_statement__ = get_end_user_data_statement(__file__)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""FFmpeg management package"""
|
"""FFmpeg management package"""
|
||||||
|
|
||||||
from .exceptions import FFmpegError, GPUError, DownloadError
|
from videoarchiver.ffmpeg.exceptions import FFmpegError, GPUError, DownloadError
|
||||||
from .ffmpeg_manager import FFmpegManager
|
from videoarchiver.ffmpeg.ffmpeg_manager import FFmpegManager
|
||||||
|
|
||||||
__all__ = ['FFmpegManager', 'FFmpegError', 'GPUError', 'DownloadError']
|
__all__ = ['FFmpegManager', 'FFmpegError', 'GPUError', 'DownloadError']
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import logging
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
from .exceptions import FFmpegError
|
from videoarchiver.ffmpeg.exceptions import FFmpegError
|
||||||
from .gpu_detector import GPUDetector
|
from videoarchiver.ffmpeg.gpu_detector import GPUDetector
|
||||||
from .video_analyzer import VideoAnalyzer
|
from videoarchiver.ffmpeg.video_analyzer import VideoAnalyzer
|
||||||
from .encoder_params import EncoderParams
|
from videoarchiver.ffmpeg.encoder_params import EncoderParams
|
||||||
from .ffmpeg_downloader import FFmpegDownloader
|
from videoarchiver.ffmpeg.ffmpeg_downloader import FFmpegDownloader
|
||||||
|
|
||||||
logger = logging.getLogger("VideoArchiver")
|
logger = logging.getLogger("VideoArchiver")
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import asyncio
|
|||||||
import traceback
|
import traceback
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from .utils.video_downloader import VideoDownloader
|
from videoarchiver.utils.video_downloader import VideoDownloader
|
||||||
from .utils.file_ops import secure_delete_file, cleanup_downloads
|
from videoarchiver.utils.file_ops import secure_delete_file, cleanup_downloads
|
||||||
from .exceptions import ProcessingError, DiscordAPIError
|
from videoarchiver.exceptions import ProcessingError, DiscordAPIError
|
||||||
from .enhanced_queue import EnhancedVideoQueueManager
|
from videoarchiver.enhanced_queue import EnhancedVideoQueueManager
|
||||||
|
|
||||||
logger = logging.getLogger('VideoArchiver')
|
logger = logging.getLogger('VideoArchiver')
|
||||||
|
|
||||||
|
|||||||
@@ -1,247 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
from typing import Dict, Optional, Set, Tuple, Callable, Any
|
|
||||||
from datetime import datetime
|
|
||||||
import traceback
|
|
||||||
from dataclasses import dataclass
|
|
||||||
import weakref
|
|
||||||
|
|
||||||
# Configure logging
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.INFO,
|
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
||||||
)
|
|
||||||
logger = logging.getLogger('QueueManager')
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class QueueItem:
|
|
||||||
"""Represents a video processing task in the queue"""
|
|
||||||
url: str
|
|
||||||
message_id: int
|
|
||||||
channel_id: int
|
|
||||||
guild_id: int
|
|
||||||
author_id: int
|
|
||||||
added_at: datetime
|
|
||||||
callback: Callable[[str, bool, str], Any]
|
|
||||||
status: str = "pending" # pending, processing, completed, failed
|
|
||||||
error: Optional[str] = None
|
|
||||||
attempt: int = 0
|
|
||||||
|
|
||||||
class VideoQueueManager:
|
|
||||||
"""Manages a queue of videos to be processed, ensuring sequential processing"""
|
|
||||||
|
|
||||||
def __init__(self, max_retries: int = 3, retry_delay: int = 5):
|
|
||||||
self.max_retries = max_retries
|
|
||||||
self.retry_delay = retry_delay
|
|
||||||
|
|
||||||
# Queue storage
|
|
||||||
self._queue: asyncio.Queue[QueueItem] = asyncio.Queue()
|
|
||||||
self._processing: Dict[str, QueueItem] = {}
|
|
||||||
self._failed: Dict[str, QueueItem] = {}
|
|
||||||
self._completed: Dict[str, QueueItem] = {}
|
|
||||||
|
|
||||||
# Track active tasks
|
|
||||||
self._active_tasks: Set[asyncio.Task] = set()
|
|
||||||
self._processing_lock = asyncio.Lock()
|
|
||||||
|
|
||||||
# Status tracking
|
|
||||||
self._guild_queues: Dict[int, Set[str]] = {}
|
|
||||||
self._channel_queues: Dict[int, Set[str]] = {}
|
|
||||||
|
|
||||||
# Cleanup references
|
|
||||||
self._weak_refs: Set[weakref.ref] = set()
|
|
||||||
|
|
||||||
# Start queue processor
|
|
||||||
self._processor_task = asyncio.create_task(self._process_queue())
|
|
||||||
self._active_tasks.add(self._processor_task)
|
|
||||||
|
|
||||||
async def add_to_queue(
|
|
||||||
self,
|
|
||||||
url: str,
|
|
||||||
message_id: int,
|
|
||||||
channel_id: int,
|
|
||||||
guild_id: int,
|
|
||||||
author_id: int,
|
|
||||||
callback: Callable[[str, bool, str], Any]
|
|
||||||
) -> bool:
|
|
||||||
"""Add a video to the processing queue"""
|
|
||||||
try:
|
|
||||||
# Create queue item
|
|
||||||
item = QueueItem(
|
|
||||||
url=url,
|
|
||||||
message_id=message_id,
|
|
||||||
channel_id=channel_id,
|
|
||||||
guild_id=guild_id,
|
|
||||||
author_id=author_id,
|
|
||||||
added_at=datetime.utcnow(),
|
|
||||||
callback=callback
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add to tracking collections
|
|
||||||
if guild_id not in self._guild_queues:
|
|
||||||
self._guild_queues[guild_id] = set()
|
|
||||||
self._guild_queues[guild_id].add(url)
|
|
||||||
|
|
||||||
if channel_id not in self._channel_queues:
|
|
||||||
self._channel_queues[channel_id] = set()
|
|
||||||
self._channel_queues[channel_id].add(url)
|
|
||||||
|
|
||||||
# Add to queue
|
|
||||||
await self._queue.put(item)
|
|
||||||
|
|
||||||
# Create weak reference for cleanup
|
|
||||||
self._weak_refs.add(weakref.ref(item))
|
|
||||||
|
|
||||||
logger.info(f"Added video to queue: {url}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error adding video to queue: {str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def _process_queue(self):
|
|
||||||
"""Process videos in the queue sequentially"""
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
# Get next item from queue
|
|
||||||
item = await self._queue.get()
|
|
||||||
|
|
||||||
async with self._processing_lock:
|
|
||||||
self._processing[item.url] = item
|
|
||||||
item.status = "processing"
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Execute callback with the URL
|
|
||||||
success = await item.callback(item.url, True, "")
|
|
||||||
|
|
||||||
if success:
|
|
||||||
item.status = "completed"
|
|
||||||
self._completed[item.url] = item
|
|
||||||
logger.info(f"Successfully processed video: {item.url}")
|
|
||||||
else:
|
|
||||||
# Handle retry logic
|
|
||||||
item.attempt += 1
|
|
||||||
if item.attempt < self.max_retries:
|
|
||||||
# Re-queue with delay
|
|
||||||
await asyncio.sleep(self.retry_delay * item.attempt)
|
|
||||||
await self._queue.put(item)
|
|
||||||
logger.info(f"Retrying video processing: {item.url} (Attempt {item.attempt + 1})")
|
|
||||||
else:
|
|
||||||
item.status = "failed"
|
|
||||||
item.error = "Max retries exceeded"
|
|
||||||
self._failed[item.url] = item
|
|
||||||
logger.error(f"Failed to process video after {self.max_retries} attempts: {item.url}")
|
|
||||||
|
|
||||||
# Notify callback of failure
|
|
||||||
await item.callback(item.url, False, item.error)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error processing video: {str(e)}\n{traceback.format_exc()}")
|
|
||||||
item.status = "failed"
|
|
||||||
item.error = str(e)
|
|
||||||
self._failed[item.url] = item
|
|
||||||
|
|
||||||
# Notify callback of failure
|
|
||||||
await item.callback(item.url, False, str(e))
|
|
||||||
|
|
||||||
finally:
|
|
||||||
# Clean up tracking
|
|
||||||
self._processing.pop(item.url, None)
|
|
||||||
if item.guild_id in self._guild_queues:
|
|
||||||
self._guild_queues[item.guild_id].discard(item.url)
|
|
||||||
if item.channel_id in self._channel_queues:
|
|
||||||
self._channel_queues[item.channel_id].discard(item.url)
|
|
||||||
|
|
||||||
# Mark queue item as done
|
|
||||||
self._queue.task_done()
|
|
||||||
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Queue processor error: {str(e)}\n{traceback.format_exc()}")
|
|
||||||
await asyncio.sleep(1) # Prevent tight error loop
|
|
||||||
|
|
||||||
def get_queue_status(self, guild_id: Optional[int] = None) -> Dict[str, int]:
|
|
||||||
"""Get current queue status, optionally filtered by guild"""
|
|
||||||
if guild_id is not None:
|
|
||||||
guild_urls = self._guild_queues.get(guild_id, set())
|
|
||||||
return {
|
|
||||||
"pending": sum(1 for _ in self._queue._queue if _.url in guild_urls),
|
|
||||||
"processing": sum(1 for url in self._processing if url in guild_urls),
|
|
||||||
"completed": sum(1 for url in self._completed if url in guild_urls),
|
|
||||||
"failed": sum(1 for url in self._failed if url in guild_urls)
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
return {
|
|
||||||
"pending": self._queue.qsize(),
|
|
||||||
"processing": len(self._processing),
|
|
||||||
"completed": len(self._completed),
|
|
||||||
"failed": len(self._failed)
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_channel_queue_size(self, channel_id: int) -> int:
|
|
||||||
"""Get number of items queued for a specific channel"""
|
|
||||||
return len(self._channel_queues.get(channel_id, set()))
|
|
||||||
|
|
||||||
async def clear_guild_queue(self, guild_id: int) -> int:
|
|
||||||
"""Clear all queued items for a specific guild"""
|
|
||||||
if guild_id not in self._guild_queues:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
cleared = 0
|
|
||||||
guild_urls = self._guild_queues[guild_id].copy()
|
|
||||||
|
|
||||||
# Remove from main queue
|
|
||||||
new_queue = asyncio.Queue()
|
|
||||||
while not self._queue.empty():
|
|
||||||
item = await self._queue.get()
|
|
||||||
if item.guild_id != guild_id:
|
|
||||||
await new_queue.put(item)
|
|
||||||
else:
|
|
||||||
cleared += 1
|
|
||||||
|
|
||||||
self._queue = new_queue
|
|
||||||
|
|
||||||
# Clean up tracking
|
|
||||||
for url in guild_urls:
|
|
||||||
self._processing.pop(url, None)
|
|
||||||
self._completed.pop(url, None)
|
|
||||||
self._failed.pop(url, None)
|
|
||||||
|
|
||||||
self._guild_queues.pop(guild_id, None)
|
|
||||||
|
|
||||||
# Clean up channel queues
|
|
||||||
for channel_id, urls in list(self._channel_queues.items()):
|
|
||||||
urls.difference_update(guild_urls)
|
|
||||||
if not urls:
|
|
||||||
self._channel_queues.pop(channel_id, None)
|
|
||||||
|
|
||||||
return cleared
|
|
||||||
|
|
||||||
async def cleanup(self):
|
|
||||||
"""Clean up resources and stop queue processing"""
|
|
||||||
# Cancel processor task
|
|
||||||
if self._processor_task and not self._processor_task.done():
|
|
||||||
self._processor_task.cancel()
|
|
||||||
try:
|
|
||||||
await self._processor_task
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Cancel all active tasks
|
|
||||||
for task in self._active_tasks:
|
|
||||||
if not task.done():
|
|
||||||
task.cancel()
|
|
||||||
|
|
||||||
await asyncio.gather(*self._active_tasks, return_exceptions=True)
|
|
||||||
|
|
||||||
# Clear all collections
|
|
||||||
self._queue = asyncio.Queue()
|
|
||||||
self._processing.clear()
|
|
||||||
self._completed.clear()
|
|
||||||
self._failed.clear()
|
|
||||||
self._guild_queues.clear()
|
|
||||||
self._channel_queues.clear()
|
|
||||||
|
|
||||||
# Clear weak references
|
|
||||||
self._weak_refs.clear()
|
|
||||||
@@ -9,10 +9,10 @@ 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
|
||||||
|
|
||||||
from ..ffmpeg.ffmpeg_manager import FFmpegManager
|
from videoarchiver.ffmpeg.ffmpeg_manager import FFmpegManager
|
||||||
from .exceptions import VideoVerificationError
|
from videoarchiver.utils.exceptions import VideoVerificationError
|
||||||
from .file_ops import secure_delete_file
|
from videoarchiver.utils.file_ops import secure_delete_file
|
||||||
from .path_manager import temp_path_context
|
from videoarchiver.utils.path_manager import temp_path_context
|
||||||
|
|
||||||
logger = logging.getLogger("VideoArchiver")
|
logger = logging.getLogger("VideoArchiver")
|
||||||
|
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ from datetime import datetime
|
|||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from .config_manager import ConfigManager
|
from videoarchiver.config_manager import ConfigManager
|
||||||
from .update_checker import UpdateChecker
|
from videoarchiver.update_checker import UpdateChecker
|
||||||
from .processor import VideoProcessor
|
from videoarchiver.processor import VideoProcessor
|
||||||
from .commands import VideoArchiverCommands
|
from videoarchiver.commands import VideoArchiverCommands
|
||||||
from .utils.video_downloader import VideoDownloader
|
from videoarchiver.utils.video_downloader import VideoDownloader
|
||||||
from .utils.message_manager import MessageManager
|
from videoarchiver.utils.message_manager import MessageManager
|
||||||
from .utils.file_ops import cleanup_downloads
|
from videoarchiver.utils.file_ops import cleanup_downloads
|
||||||
from .enhanced_queue import EnhancedVideoQueueManager
|
from videoarchiver.enhanced_queue import EnhancedVideoQueueManager
|
||||||
from .exceptions import (
|
from videoarchiver.exceptions import (
|
||||||
ProcessingError,
|
ProcessingError,
|
||||||
ConfigError,
|
ConfigError,
|
||||||
UpdateError,
|
UpdateError,
|
||||||
|
|||||||
Reference in New Issue
Block a user