DreamTail v1.0.0 with IP-Adapter FaceID support

- SDXL image generation using RealVisXL_V4.0
- IP-Adapter FaceID integration for consistent face generation
- Simplified API (removed client_id requirement)
- New params: face_image, face_strength
- 'vixy' shortcut for face-locked generation
- Queue-based async job processing
- FastAPI with proper error handling

Co-authored-by: Alex <alex@k4zka.online>
This commit is contained in:
2026-01-01 19:54:59 -06:00
commit e4294b57e6
18 changed files with 1895 additions and 0 deletions

111
dreamtail_storage/cleanup_task.py Executable file
View File

@@ -0,0 +1,111 @@
"""
Cleanup Task
Periodically deletes images older than the retention period.
"""
import asyncio
import logging
from datetime import datetime, timedelta
from pathlib import Path
import config
logger = logging.getLogger(__name__)
class CleanupTask:
"""Background task to clean up old images."""
def __init__(self):
self.images_dir = config.IMAGES_DIR
self.retention_days = config.IMAGE_RETENTION_DAYS
self.interval_hours = config.CLEANUP_INTERVAL_HOURS
self.running = False
self._task = None
async def start(self):
"""Start the cleanup task."""
if self.running:
logger.warning("Cleanup task already running")
return
self.running = True
self._task = asyncio.create_task(self._run())
logger.info(f"Cleanup task started (retention: {self.retention_days} days, interval: {self.interval_hours}h)")
async def stop(self):
"""Stop the cleanup task."""
if not self.running:
return
self.running = False
if self._task:
self._task.cancel()
try:
await self._task
except asyncio.CancelledError:
pass
logger.info("Cleanup task stopped")
async def _run(self):
"""Main cleanup loop."""
while self.running:
try:
await self._cleanup_old_images()
# Sleep for the configured interval
await asyncio.sleep(self.interval_hours * 3600)
except asyncio.CancelledError:
break
except Exception as e:
logger.error(f"Error in cleanup task: {e}")
await asyncio.sleep(300) # Wait 5 minutes before retry
async def _cleanup_old_images(self):
"""Delete images older than retention period."""
try:
cutoff_time = datetime.now() - timedelta(days=self.retention_days)
cutoff_timestamp = cutoff_time.timestamp()
deleted_count = 0
deleted_size = 0
# Find all image files
image_files = list(self.images_dir.glob(f"*.{config.IMAGE_FORMAT.lower()}"))
for file_path in image_files:
try:
# Check file modification time
file_mtime = file_path.stat().st_mtime
if file_mtime < cutoff_timestamp:
file_size = file_path.stat().st_size
file_path.unlink()
deleted_count += 1
deleted_size += file_size
logger.debug(f"Deleted old image: {file_path.name}")
except Exception as e:
logger.error(f"Error deleting {file_path.name}: {e}")
if deleted_count > 0:
logger.info(
f"Cleanup completed: deleted {deleted_count} images "
f"({deleted_size / 1024 / 1024:.1f} MB) older than {self.retention_days} days"
)
else:
logger.debug(f"Cleanup completed: no images older than {self.retention_days} days")
except Exception as e:
logger.error(f"Error during cleanup: {e}")
async def cleanup_now(self):
"""Trigger immediate cleanup (for testing or manual trigger)."""
logger.info("Manual cleanup triggered")
await self._cleanup_old_images()
# Global cleanup task instance
cleanup_task = CleanupTask()