This commit is contained in:
pacnpal
2024-11-16 22:32:08 +00:00
parent b7d99490cf
commit dac21f2fcd
30 changed files with 5854 additions and 2279 deletions

View File

@@ -1,109 +1,205 @@
"""Module for tracking download and compression progress"""
"""Progress tracking module."""
import logging
from typing import Dict, Any, Optional
from datetime import datetime
logger = logging.getLogger("ProgressTracker")
logger = logging.getLogger(__name__)
class ProgressTracker:
"""Tracks progress of downloads and compression operations"""
"""Progress tracker singleton."""
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
self._download_progress: Dict[str, Dict[str, Any]] = {}
self._compression_progress: Dict[str, Dict[str, Any]] = {}
if not hasattr(self, '_initialized'):
self._data: Dict[str, Dict[str, Any]] = {}
self._initialized = True
def start_download(self, url: str) -> None:
"""Initialize progress tracking for a download"""
self._download_progress[url] = {
"active": True,
"start_time": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
"percent": 0,
"speed": "N/A",
"eta": "N/A",
"downloaded_bytes": 0,
"total_bytes": 0,
"retries": 0,
"fragment_count": 0,
"fragment_index": 0,
"video_title": "Unknown",
"extractor": "Unknown",
"format": "Unknown",
"resolution": "Unknown",
"fps": "Unknown",
"last_update": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
}
def update(self, key: str, data: Dict[str, Any]) -> None:
"""Update progress for a key."""
if key not in self._data:
self._data[key] = {
'active': True,
'start_time': datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
'percent': 0
}
self._data[key].update(data)
self._data[key]['last_update'] = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
logger.debug(f"Progress for {key}: {self._data[key].get('percent', 0)}%")
def update_download_progress(self, data: Dict[str, Any]) -> None:
"""Update download progress information"""
def get(self, key: Optional[str] = None) -> Dict[str, Any]:
"""Get progress for a key."""
if key is None:
return self._data
return self._data.get(key, {})
def complete(self, key: str) -> None:
"""Mark progress as complete."""
if key in self._data:
self._data[key]['active'] = False
logger.info(f"Operation completed for {key}")
def clear(self) -> None:
"""Clear all progress data."""
self._data.clear()
logger.info("Progress data cleared")
_tracker = ProgressTracker()
def get_compression(self, file_path: Optional[str] = None) -> Dict[str, Any]:
"""Get compression progress."""
if file_path is None:
return self._compressions
return self._compressions.get(file_path, {})
def complete_download(self, url: str) -> None:
"""Mark download as complete."""
if url in self._downloads:
self._downloads[url]['active'] = False
logger.info(f"Download completed for {url}")
def complete_compression(self, file_path: str) -> None:
"""Mark compression as complete."""
if file_path in self._compressions:
self._compressions[file_path]['active'] = False
logger.info(f"Compression completed for {file_path}")
def clear(self) -> None:
"""Clear all progress data."""
self._downloads.clear()
self._compressions.clear()
logger.info("Progress data cleared")
# Global instance
_tracker = ProgressTrack
# Global instance
_tracker = ProgressTracker()
def get_tracker() -> Progre
"""Clear all progress tracking"""
self._download_progress.clear()
self._compression_progress.clear()
logger.info("Cleared all progress tracking data")
# Create singleton instance
progress_tracker = ProgressTracker()
def get_progress_tracker() -> ProgressTracker:
def mark_compression_complete(self, file_path: str) -> None:
"""Mark a compression operation as complete"""
if file_path in self._compression_progress:
self._compression_progress[file_path]['active'] = False
logger.info(f"Compression completed for {file_path}")
def clear_progress(self) -> None:
"""Clear all progress tracking"""
self._download_progress.clear()
self._compression_progress.clear()
logger.info("Cleared all progress tracking data")
# Create singleton instance
progress_tracker = ProgressTracker()
# Export the singleton instance
def get_progress_tracker() -> ProgressTracker:
Args:
data: Dictionary containing download progress data
"""
try:
# Get URL from info dict
url = data.get("info_dict", {}).get("webpage_url", "unknown")
if url not in self._download_progress:
info_dict = data.get("info_dict", {})
url = info_dict.get("webpage_url")
if not url or url not in self._download_progress:
return
if data["status"] == "downloading":
if data.get("status") == "downloading":
percent_str = data.get("_percent_str", "0").replace("%", "")
try:
percent = float(percent_str)
except ValueError:
percent = 0.0
total_bytes = (
data.get("total_bytes", 0) or
data.get("total_bytes_estimate", 0)
)
self._download_progress[url].update({
"active": True,
"percent": float(data.get("_percent_str", "0").replace("%", "")),
"percent": percent,
"speed": data.get("_speed_str", "N/A"),
"eta": data.get("_eta_str", "N/A"),
"downloaded_bytes": data.get("downloaded_bytes", 0),
"total_bytes": data.get("total_bytes", 0) or data.get("total_bytes_estimate", 0),
"total_bytes": total_bytes,
"retries": data.get("retry_count", 0),
"fragment_count": data.get("fragment_count", 0),
"fragment_index": data.get("fragment_index", 0),
"video_title": data.get("info_dict", {}).get("title", "Unknown"),
"extractor": data.get("info_dict", {}).get("extractor", "Unknown"),
"format": data.get("info_dict", {}).get("format", "Unknown"),
"resolution": data.get("info_dict", {}).get("resolution", "Unknown"),
"fps": data.get("info_dict", {}).get("fps", "Unknown"),
"last_update": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
"video_title": info_dict.get("title", "Unknown"),
"extractor": info_dict.get("extractor", "Unknown"),
"format": info_dict.get("format", "Unknown"),
"resolution": info_dict.get("resolution", "Unknown"),
"fps": info_dict.get("fps", "Unknown"),
"last_update": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
})
logger.debug(
f"Download progress for {url}: "
f"{self._download_progress[url]['percent']}% at {self._download_progress[url]['speed']}, "
f"{percent:.1f}% at {self._download_progress[url]['speed']}, "
f"ETA: {self._download_progress[url]['eta']}"
)
except Exception as e:
logger.error(f"Error updating download progress: {e}")
logger.error(f"Error updating download progress: {e}", exc_info=True)
def end_download(self, url: str) -> None:
"""Mark a download as completed"""
def end_download(self, url: str, status: ProgressStatus = ProgressStatus.COMPLETED) -> None:
"""
Mark a download as completed.
Args:
url: The URL being downloaded
status: The final status of the download
"""
if url in self._download_progress:
self._download_progress[url]["active"] = False
logger.info(f"Download {status.value} for {url}")
def start_compression(
self,
input_file: str,
params: Dict[str, str],
use_hardware: bool,
duration: float,
input_size: int,
target_size: int
) -> None:
"""Initialize progress tracking for compression"""
self._compression_progress[input_file] = {
"active": True,
"filename": input_file,
"start_time": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
"percent": 0,
"elapsed_time": "0:00",
"input_size": input_size,
"current_size": 0,
"target_size": target_size,
"codec": params.get("c:v", "unknown"),
"hardware_accel": use_hardware,
"preset": params.get("preset", "unknown"),
"crf": params.get("crf", "unknown"),
"duration": duration,
"bitrate": params.get("b:v", "unknown"),
"audio_codec": params.get("c:a", "unknown"),
"audio_bitrate": params.get("b:a", "unknown"),
"last_update": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
}
def start_compression(self, params: CompressionParams) -> None:
"""
Initialize progress tracking for compression.
Args:
params: Compression parameters
"""
current_time = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
self._compression_progress[params.input_file] = CompressionProgress(
active=True,
filename=params.input_file,
start_time=current_time,
percent=0.0,
elapsed_time="0:00",
input_size=params.input_size,
current_size=0,
target_size=params.target_size,
codec=params.codec_params.get("c:v", "unknown"),
hardware_accel=params.use_hardware,
preset=params.codec_params.get("preset", "unknown"),
crf=params.codec_params.get("crf", "unknown"),
duration=params.duration,
bitrate=params.codec_params.get("b:v", "unknown"),
audio_codec=params.codec_params.get("c:a", "unknown"),
audio_bitrate=params.codec_params.get("b:a", "unknown"),
last_update=current_time,
current_time=None
)
def update_compression_progress(
self,
@@ -113,14 +209,23 @@ class ProgressTracker:
current_size: int,
current_time: float
) -> None:
"""Update compression progress information"""
"""
Update compression progress information.
Args:
input_file: The input file being compressed
progress: Current progress percentage (0-100)
elapsed_time: Time elapsed as string
current_size: Current file size in bytes
current_time: Current timestamp in seconds
"""
if input_file in self._compression_progress:
self._compression_progress[input_file].update({
"percent": progress,
"percent": max(0.0, min(100.0, progress)),
"elapsed_time": elapsed_time,
"current_size": current_size,
"current_time": current_time,
"last_update": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
"last_update": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
})
logger.debug(
@@ -128,29 +233,73 @@ class ProgressTracker:
f"{progress:.1f}%, Size: {current_size}/{self._compression_progress[input_file]['target_size']} bytes"
)
def end_compression(self, input_file: str) -> None:
"""Mark a compression operation as completed"""
def end_compression(
self,
input_file: str,
status: ProgressStatus = ProgressStatus.COMPLETED
) -> None:
"""
Mark a compression operation as completed.
Args:
input_file: The input file being compressed
status: The final status of the compression
"""
if input_file in self._compression_progress:
self._compression_progress[input_file]["active"] = False
logger.info(f"Compression {status.value} for {input_file}")
def get_download_progress(self, url: str) -> Optional[Dict[str, Any]]:
"""Get progress information for a download"""
def get_download_progress(self, url: Optional[str] = None) -> Optional[DownloadProgress]:
"""
Get progress information for a download.
Args:
url: Optional URL to get progress for. If None, returns all progress.
Returns:
Progress information for the specified download or None if not found
"""
if url is None:
return self._download_progress
return self._download_progress.get(url)
def get_compression_progress(self, input_file: str) -> Optional[Dict[str, Any]]:
"""Get progress information for a compression operation"""
def get_compression_progress(
self,
input_file: Optional[str] = None
) -> Optional[CompressionProgress]:
"""
Get progress information for a compression operation.
Args:
input_file: Optional file to get progress for. If None, returns all progress.
Returns:
Progress information for the specified compression or None if not found
"""
if input_file is None:
return self._compression_progress
return self._compression_progress.get(input_file)
def get_active_downloads(self) -> Dict[str, Dict[str, Any]]:
"""Get all active downloads"""
def get_active_downloads(self) -> Dict[str, DownloadProgress]:
"""
Get all active downloads.
Returns:
Dictionary of active downloads and their progress
"""
return {
url: progress
for url, progress in self._download_progress.items()
if progress.get("active", False)
}
def get_active_compressions(self) -> Dict[str, Dict[str, Any]]:
"""Get all active compression operations"""
def get_active_compressions(self) -> Dict[str, CompressionProgress]:
"""
Get all active compression operations.
Returns:
Dictionary of active compressions and their progress
"""
return {
input_file: progress
for input_file, progress in self._compression_progress.items()
@@ -161,3 +310,4 @@ class ProgressTracker:
"""Clear all progress tracking"""
self._download_progress.clear()
self._compression_progress.clear()
logger.info("Cleared