Face Detector container now able to be created, started and stopped from within miku-bot container
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
Y# face_detector_manager.py
|
||||
# face_detector_manager.py
|
||||
"""
|
||||
Manages on-demand starting/stopping of anime-face-detector container
|
||||
to free up VRAM when not needed.
|
||||
@@ -20,14 +20,72 @@ class FaceDetectorManager:
|
||||
FACE_DETECTOR_API = "http://anime-face-detector:6078/detect"
|
||||
HEALTH_ENDPOINT = "http://anime-face-detector:6078/health"
|
||||
CONTAINER_NAME = "anime-face-detector"
|
||||
STARTUP_TIMEOUT = 30 # seconds
|
||||
STARTUP_TIMEOUT = 60 # seconds - increased to allow for model loading
|
||||
|
||||
def __init__(self):
|
||||
self.is_running = False
|
||||
|
||||
def _container_exists(self) -> bool:
|
||||
"""Check if the anime-face-detector container exists (created but may not be running)"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["docker", "ps", "-a", "--filter", f"name=^/{self.CONTAINER_NAME}$", "--format", "{{.Names}}"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5
|
||||
)
|
||||
return self.CONTAINER_NAME in result.stdout
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking if container exists: {e}")
|
||||
return False
|
||||
|
||||
def _create_container(self, debug: bool = False) -> bool:
|
||||
"""Create the anime-face-detector container using docker run"""
|
||||
try:
|
||||
if debug:
|
||||
logger.info("Creating anime-face-detector container...")
|
||||
|
||||
# Run docker run command to create the container (without starting it)
|
||||
# This replicates the docker-compose configuration for anime-face-detector
|
||||
cmd = [
|
||||
"docker", "create",
|
||||
"--name", self.CONTAINER_NAME,
|
||||
"--network", "miku-discord_default", # Use the same network as miku-bot
|
||||
"--runtime", "nvidia",
|
||||
"-e", "NVIDIA_VISIBLE_DEVICES=all",
|
||||
"-e", "NVIDIA_DRIVER_CAPABILITIES=compute,utility",
|
||||
"-p", "7860:7860",
|
||||
"-p", "6078:6078",
|
||||
"--restart", "no",
|
||||
"--gpus", "all",
|
||||
"miku-discord-anime-face-detector:latest"
|
||||
]
|
||||
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
if debug:
|
||||
logger.error(f"Failed to create container: {result.stderr}")
|
||||
return False
|
||||
|
||||
if debug:
|
||||
logger.info("Container created successfully")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
if debug:
|
||||
logger.error(f"Error creating container: {e}")
|
||||
return False
|
||||
|
||||
async def start_container(self, debug: bool = False) -> bool:
|
||||
"""
|
||||
Start the anime-face-detector container.
|
||||
Creates the container if it doesn't exist, then starts it.
|
||||
|
||||
Returns:
|
||||
True if started successfully, False otherwise
|
||||
@@ -36,10 +94,14 @@ class FaceDetectorManager:
|
||||
if debug:
|
||||
logger.debug("Starting anime-face-detector container...")
|
||||
|
||||
# Start container using docker compose
|
||||
# Step 1: Check if container exists, create if it doesn't
|
||||
if not self._container_exists():
|
||||
if not self._create_container(debug=debug):
|
||||
return False
|
||||
|
||||
# Step 2: Start the container
|
||||
result = subprocess.run(
|
||||
["docker", "compose", "up", "-d", self.CONTAINER_NAME],
|
||||
cwd="/app", # Assumes we're in the bot container, adjust path as needed
|
||||
["docker", "start", self.CONTAINER_NAME],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
@@ -81,8 +143,7 @@ class FaceDetectorManager:
|
||||
logger.debug("Stopping anime-face-detector container...")
|
||||
|
||||
result = subprocess.run(
|
||||
["docker", "compose", "stop", self.CONTAINER_NAME],
|
||||
cwd="/app",
|
||||
["docker", "stop", self.CONTAINER_NAME],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=15
|
||||
|
||||
@@ -104,23 +104,24 @@ class ProfilePictureManager:
|
||||
try:
|
||||
if debug:
|
||||
logger.info("Starting face detector container...")
|
||||
|
||||
# Use Docker socket API to start container
|
||||
import aiofiles
|
||||
import json as json_lib
|
||||
|
||||
# Docker socket path
|
||||
# Prefer using the face_detector_manager helper (robust compose/docker logic)
|
||||
try:
|
||||
from .face_detector_manager import face_detector_manager
|
||||
started = await face_detector_manager.start_container(debug=debug)
|
||||
return started
|
||||
except Exception as e:
|
||||
if debug:
|
||||
logger.debug(f"face_detector_manager not available or failed to create/start container: {e}; falling back to Docker socket API")
|
||||
|
||||
# Fallback: Use Docker socket API to start container
|
||||
socket_path = "/var/run/docker.sock"
|
||||
|
||||
# Check if socket exists
|
||||
if not os.path.exists(socket_path):
|
||||
if debug:
|
||||
logger.error("Docker socket not available")
|
||||
return False
|
||||
|
||||
# Use aiohttp UnixConnector to communicate with Docker socket
|
||||
|
||||
from aiohttp import UnixConnector
|
||||
|
||||
|
||||
async with aiohttp.ClientSession(
|
||||
connector=UnixConnector(path=socket_path)
|
||||
) as session:
|
||||
@@ -132,9 +133,9 @@ class ProfilePictureManager:
|
||||
error_text = await response.text()
|
||||
logger.error(f"Failed to start container: {response.status} - {error_text}")
|
||||
return False
|
||||
|
||||
# Wait for API to be ready
|
||||
for i in range(30): # 30 second timeout
|
||||
|
||||
# Wait for API to be ready (fallback timeout)
|
||||
for i in range(60): # 60 second timeout (fallback)
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
@@ -148,9 +149,9 @@ class ProfilePictureManager:
|
||||
except:
|
||||
pass
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
if debug:
|
||||
logger.warning("Face detector didn't become ready in time")
|
||||
logger.warning("Face detector didn't become ready in time (fallback)")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
@@ -163,16 +164,24 @@ class ProfilePictureManager:
|
||||
try:
|
||||
if debug:
|
||||
logger.info("Stopping face detector to free VRAM...")
|
||||
|
||||
# Prefer using face_detector_manager if available
|
||||
try:
|
||||
from .face_detector_manager import face_detector_manager
|
||||
await face_detector_manager.stop_container(debug=debug)
|
||||
return
|
||||
except Exception as e:
|
||||
if debug:
|
||||
logger.debug(f"face_detector_manager not available or failed to stop container: {e}; falling back to Docker socket API")
|
||||
|
||||
socket_path = "/var/run/docker.sock"
|
||||
|
||||
|
||||
if not os.path.exists(socket_path):
|
||||
if debug:
|
||||
logger.error("Docker socket not available")
|
||||
return
|
||||
|
||||
|
||||
from aiohttp import UnixConnector
|
||||
|
||||
|
||||
async with aiohttp.ClientSession(
|
||||
connector=UnixConnector(path=socket_path)
|
||||
) as session:
|
||||
@@ -186,7 +195,7 @@ class ProfilePictureManager:
|
||||
if debug:
|
||||
error_text = await response.text()
|
||||
logger.warning(f"Failed to stop container: {response.status} - {error_text}")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
if debug:
|
||||
logger.error(f"Error stopping face detector: {e}")
|
||||
|
||||
Reference in New Issue
Block a user