diff --git a/Dockerfile b/Dockerfile index 522b606..3f60e2f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,7 +35,8 @@ RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir \ google-genai \ opencv-python \ - requests + requests \ + httpx # Create directories for models and custom nodes if they don't exist RUN mkdir -p models custom_nodes input output @@ -51,4 +52,4 @@ RUN pip install --no-cache-dir -r custom_nodes/comfyui-manager/requirements.txt EXPOSE 8188 # Default command (can be overridden in docker-compose.yml) -CMD ["python", "main.py", "--listen", "0.0.0.0", "--port", "8188", "--cpu"] \ No newline at end of file +CMD ["python", "main.py", "--listen", "0.0.0.0", "--port", "8188", "--cpu", "--base-directory", "/workspace/ComfyUI"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 7db4d81..500dd53 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: context: . dockerfile: Dockerfile ports: - - '8188:8188' + - "8188:8188" volumes: # Mount your custom node directory - .:/workspace/ComfyUI/custom_nodes/ComfyUI-SwissArmyKnife @@ -16,6 +16,6 @@ services: - GEMINI_API_KEY=${GEMINI_API_KEY} - CIVITAI_API_KEY=${CIVITAI_API_KEY} working_dir: /workspace/ComfyUI - command: python main.py --listen 0.0.0.0 --port 8188 --cpu + command: python main.py --listen 0.0.0.0 --port 8188 --cpu --base-directory /workspace/ComfyUI stdin_open: true tty: true diff --git a/docs/infrastructure/TEMP_DIRECTORY_UTILS.md b/docs/infrastructure/TEMP_DIRECTORY_UTILS.md new file mode 100644 index 0000000..7fe48b7 --- /dev/null +++ b/docs/infrastructure/TEMP_DIRECTORY_UTILS.md @@ -0,0 +1,147 @@ +# Temporary Directory Utilities + +## Overview + +ComfyUI-SwissArmyKnife uses a centralized temporary directory utility (`nodes/utils/temp_utils.py`) to ensure all temporary files and directories respect ComfyUI's configuration, especially in Docker environments with custom base directories. + +## Why This Matters + +### Problem with Standard Python tempfile + +- `tempfile.gettempdir()` returns the system temp directory (e.g., `/tmp` on Linux) +- In Docker, this is the container's `/tmp`, not the ComfyUI workspace +- Files are isolated from the host and don't respect `--base-directory` + +### Solution: ComfyUI-Aware Temp Directory + +- Uses ComfyUI's `folder_paths.get_user_directory()` + `/temp` +- Respects `--base-directory` flag in Docker setups +- Falls back to system temp when ComfyUI not available +- Ensures consistent behavior across different deployment scenarios + +## Usage + +### Getting Base Temp Directory + +```python +from ..utils.temp_utils import get_temp_directory + +# Get ComfyUI's temp directory +temp_dir = get_temp_directory() +# Returns: /workspace/ComfyUI/user/temp (in Docker) +# Or: /temp (local install) +``` + +### Getting a Temp Subdirectory + +```python +from ..utils.temp_utils import get_temp_subdirectory + +# Create/get a subdirectory within temp +frames_dir = get_temp_subdirectory("comfyui_frames") +# Returns: /workspace/ComfyUI/user/temp/comfyui_frames +``` + +### Getting a Temp File Path + +```python +from ..utils.temp_utils import get_temp_file_path + +# Get a temp file path with specific extension +temp_video = get_temp_file_path(suffix='.mp4', prefix='trimmed', subdir='videos') +# Returns: /workspace/ComfyUI/user/temp/videos/trimmed_a1b2c3d4.mp4 + +# Use it like a regular file path +with open(temp_video, 'wb') as f: + f.write(video_data) +``` + +## Implementation Details + +### Directory Structure + +When using Docker with `--base-directory /workspace/ComfyUI`: + +``` +/workspace/ComfyUI/ +├── user/ +│ └── temp/ # Base temp directory +│ ├── comfyui_frames/ # Frame extraction subdirectory +│ │ └── frames_video_abc123/ # Per-video frame storage +│ ├── videos/ # Video processing subdirectory +│ │ └── trimmed_xyz789.mp4 # Temporary trimmed videos +│ └── downloads/ # Downloaded media subdirectory +│ └── reddit_media_def456.jpg +``` + +### Fallback Behavior + +1. **ComfyUI Available**: Uses `folder_paths.get_user_directory() + '/temp'` +2. **ComfyUI Unavailable**: Falls back to `tempfile.gettempdir()` + +This ensures compatibility with: + +- Local ComfyUI installations +- Docker deployments with custom base directories +- Standalone testing/development environments + +## Nodes Using Temp Utils + +All nodes that create temporary files have been updated: + +1. **Frame Extractor** (`nodes/media_selection/frame_extractor.py`) + - Uses `get_temp_subdirectory("comfyui_frames")` for frame extraction +2. **Media Selection** (`nodes/media_selection/media_selection.py`) + - Uses `get_temp_file_path(suffix='.mp4', subdir='videos')` for video trimming +3. **Media Describe** (`nodes/media_describe/mediia_describe.py`) + - Uses `get_temp_file_path(suffix=file_ext, subdir='downloads')` for Reddit downloads + - Uses `get_temp_file_path(suffix='.mp4', subdir='videos')` for video trimming + +## Docker Configuration + +In your `docker-compose.yml`: + +```yaml +command: python main.py --listen 0.0.0.0 --port 8188 --cpu --base-directory /workspace/ComfyUI +``` + +With this setup: + +- Temp files: `/workspace/ComfyUI/user/temp/...` +- Mounted to host: `./.comfyui/user/temp/...` +- Persistent across container restarts + +## Benefits + +✅ **Consistent behavior** across Docker and local installations +✅ **Respects ComfyUI configuration** (`--base-directory`, `--temp-directory`) +✅ **Better file organization** with subdirectories +✅ **Works in mounted volumes** (accessible from host) +✅ **Graceful fallback** when ComfyUI not available + +## Migration from Python tempfile + +### Before + +```python +import tempfile + +# Old way - uses system /tmp +base_temp_dir = tempfile.gettempdir() + +# Or +with tempfile.NamedTemporaryFile(suffix='.mp4', delete=False) as temp_file: + temp_path = temp_file.name +``` + +### After + +```python +from ..utils.temp_utils import get_temp_subdirectory, get_temp_file_path + +# New way - uses ComfyUI temp directory +base_temp_dir = get_temp_subdirectory("my_subdir") + +# Or +temp_path = get_temp_file_path(suffix='.mp4', prefix='my_prefix', subdir='my_subdir') +``` diff --git a/docs/nodes/media-selection/README.md b/docs/nodes/media-selection/README.md index 64766a4..dad676b 100644 --- a/docs/nodes/media-selection/README.md +++ b/docs/nodes/media-selection/README.md @@ -262,7 +262,7 @@ Caption 3: She completes the turn with a graceful pose ### Frame Extraction -- Frames are saved to a temporary directory (`/tmp/frame_extract_*`) +- Frames are saved to a temporary directory within ComfyUI's temp directory (respects `--base-directory`) - Frames are named with format: `frame_XXX_tY.YYs.png` - PNG format preserves quality, JPG saves space - Timestamps are calculated from frame number and FPS diff --git a/docs/nodes/media-selection/WORKFLOW_EXAMPLE.md b/docs/nodes/media-selection/WORKFLOW_EXAMPLE.md index 771a1bd..745eac2 100644 --- a/docs/nodes/media-selection/WORKFLOW_EXAMPLE.md +++ b/docs/nodes/media-selection/WORKFLOW_EXAMPLE.md @@ -42,11 +42,13 @@ The Frame Extractor outputs frame paths as a comma-separated string. You'll need **Example output:** ``` -/tmp/frame_extract_abc123/frame_000_t0.83s.png -/tmp/frame_extract_abc123/frame_001_t2.50s.png -/tmp/frame_extract_abc123/frame_002_t4.17s.png +/comfyui_frames/frames__/frame_000_t0.83s.png +/comfyui_frames/frames__/frame_001_t2.50s.png +/comfyui_frames/frames__/frame_002_t4.17s.png ``` +**Note**: The temp directory respects ComfyUI's `--base-directory` and `--temp-directory` flags. + ### Step 4: Process Frames with JoyCaption For each frame: diff --git a/docs/nodes/reddit-media/REDDIT_POST_MEDIA_SOURCE.md b/docs/nodes/reddit-media/REDDIT_POST_MEDIA_SOURCE.md index f0d78f7..fe039eb 100644 --- a/docs/nodes/reddit-media/REDDIT_POST_MEDIA_SOURCE.md +++ b/docs/nodes/reddit-media/REDDIT_POST_MEDIA_SOURCE.md @@ -136,7 +136,7 @@ New imports added to `nodes/nodes.py`: ### Temporary File Management -- Media files are downloaded to temporary files using `tempfile.NamedTemporaryFile` +- Media files are downloaded to ComfyUI's temp directory (respects `--base-directory` and `--temp-directory` flags) - Files are not automatically deleted (delete=False) to allow processing - Temporary files should be cleaned up by the system or calling code diff --git a/nodes/media_describe/mediia_describe.py b/nodes/media_describe/mediia_describe.py index 572d5c2..9b6536e 100644 --- a/nodes/media_describe/mediia_describe.py +++ b/nodes/media_describe/mediia_describe.py @@ -1,5 +1,4 @@ import cv2 -import tempfile import numpy as np from PIL import Image import mimetypes @@ -18,6 +17,7 @@ from google import genai from google.genai import types from ..cache import get_cache, get_file_media_identifier, get_tensor_media_identifier +from ..utils.temp_utils import get_temp_file_path class MediaDescribe: """ @@ -609,10 +609,10 @@ class MediaDescribe: if not file_ext: file_ext = '.mp4' if media_type == 'video' else '.jpg' - # Create temporary file - with tempfile.NamedTemporaryFile(suffix=file_ext, delete=False) as temp_file: + # Create temporary file using ComfyUI-aware temp directory + temp_path = get_temp_file_path(suffix=file_ext, prefix='reddit_media', subdir='downloads') + with open(temp_path, 'wb') as temp_file: temp_file.write(media_response.content) - temp_path = temp_file.name # Create media info file_size = len(media_response.content) @@ -1388,9 +1388,8 @@ Example (structure only): # Check if we need to trim the video (only duration limit) if max_duration > 0 and actual_duration < original_duration: - # Create a temporary trimmed video file - with tempfile.NamedTemporaryFile(suffix='.mp4', delete=False) as temp_file: - trimmed_video_path = temp_file.name + # Create a temporary trimmed video file using ComfyUI-aware temp directory + trimmed_video_path = get_temp_file_path(suffix='.mp4', prefix='trimmed', subdir='videos') # Attempt to trim the video print(f"Attempting to trim video from {original_duration:.2f}s to {actual_duration:.2f}s") diff --git a/nodes/media_selection/frame_extractor.py b/nodes/media_selection/frame_extractor.py index 08b3f43..73af665 100644 --- a/nodes/media_selection/frame_extractor.py +++ b/nodes/media_selection/frame_extractor.py @@ -1,10 +1,10 @@ import cv2 import os import random -import tempfile import numpy as np from PIL import Image import hashlib +from ..utils.temp_utils import get_temp_subdirectory class FrameExtractor: @@ -186,10 +186,8 @@ class FrameExtractor: """ Create a uniquely named directory for frames based on the video file. Uses video filename and a hash of the full path to ensure uniqueness. + Uses ComfyUI's temp directory when available, respecting --base-directory. """ - # Get base temp directory - base_temp_dir = tempfile.gettempdir() - # Extract video filename without extension video_filename = os.path.splitext(os.path.basename(video_path))[0] @@ -198,7 +196,10 @@ class FrameExtractor: # Create directory name: video_filename_hash dir_name = f"frames_{video_filename}_{path_hash}" - frames_dir = os.path.join(base_temp_dir, "comfyui_frames", dir_name) + + # Use ComfyUI-aware temp directory + base_temp_dir = get_temp_subdirectory("comfyui_frames") + frames_dir = os.path.join(base_temp_dir, dir_name) # Create directory if it doesn't exist os.makedirs(frames_dir, exist_ok=True) diff --git a/nodes/media_selection/media_selection.py b/nodes/media_selection/media_selection.py index 7ec1929..2ab404d 100644 --- a/nodes/media_selection/media_selection.py +++ b/nodes/media_selection/media_selection.py @@ -2,12 +2,12 @@ import cv2 import os import random import glob -import tempfile import requests import mimetypes from urllib.parse import urlparse from html import unescape import subprocess +from ..utils.temp_utils import get_temp_file_path class MediaSelection: @@ -277,8 +277,8 @@ class MediaSelection: min_duration = min(1.0, original_duration) actual_duration = max(min_duration, min(max_duration, original_duration)) - with tempfile.NamedTemporaryFile(suffix='.mp4', delete=False) as temp_file: - trimmed_video_path = temp_file.name + # Use ComfyUI-aware temp directory + trimmed_video_path = get_temp_file_path(suffix='.mp4', prefix='trimmed', subdir='videos') print(f"Attempting to trim video from {original_duration:.2f}s to {actual_duration:.2f}s") if self._trim_video(video_path, trimmed_video_path, actual_duration): diff --git a/nodes/utils/temp_utils.py b/nodes/utils/temp_utils.py new file mode 100644 index 0000000..e929fae --- /dev/null +++ b/nodes/utils/temp_utils.py @@ -0,0 +1,97 @@ +""" +Temporary directory utilities for ComfyUI-SwissArmyKnife. +Provides ComfyUI-aware temporary file and directory management that respects +the --base-directory and --temp-directory flags. +""" + +import os +import tempfile +from typing import Optional + +try: + import folder_paths + COMFYUI_AVAILABLE = True +except ImportError: + folder_paths = None + COMFYUI_AVAILABLE = False + + +def get_temp_directory() -> str: + """ + Get the base temporary directory for ComfyUI operations. + + This function respects ComfyUI's configuration: + - Uses ComfyUI's user directory + '/temp' if available + - Falls back to system temp directory if ComfyUI not available + + When using Docker with --base-directory, this ensures temp files + are stored within the ComfyUI workspace rather than the container's /tmp. + + Returns: + str: Path to the base temporary directory + """ + if COMFYUI_AVAILABLE and folder_paths is not None: + try: + # Use ComfyUI's user directory for temp files + user_dir = folder_paths.get_user_directory() + temp_dir = os.path.join(user_dir, "temp") + os.makedirs(temp_dir, exist_ok=True) + return temp_dir + except Exception as e: + print(f"Warning: Could not get ComfyUI temp directory: {e}") + + # Fallback to system temp directory + return tempfile.gettempdir() + + +def get_temp_subdirectory(subdir_name: str) -> str: + """ + Get or create a subdirectory within ComfyUI's temp directory. + + Args: + subdir_name: Name of the subdirectory to create + + Returns: + str: Path to the subdirectory + + Example: + frames_dir = get_temp_subdirectory("comfyui_frames") + """ + base_temp = get_temp_directory() + subdir = os.path.join(base_temp, subdir_name) + os.makedirs(subdir, exist_ok=True) + return subdir + + +def get_temp_file_path(suffix: str = "", prefix: str = "tmp", subdir: Optional[str] = None) -> str: + """ + Generate a temporary file path within ComfyUI's temp directory. + + This is a replacement for tempfile.NamedTemporaryFile that respects + ComfyUI's base directory configuration. + + Args: + suffix: File extension (e.g., '.mp4', '.jpg') + prefix: Filename prefix (default: 'tmp') + subdir: Optional subdirectory within temp (e.g., 'video_processing') + + Returns: + str: Full path to the temporary file (file is NOT created) + + Example: + temp_video = get_temp_file_path(suffix='.mp4', subdir='videos') + # Use the path to write your file + with open(temp_video, 'wb') as f: + f.write(video_data) + """ + if subdir: + base_temp = get_temp_subdirectory(subdir) + else: + base_temp = get_temp_directory() + + # Generate a unique filename + import uuid + unique_id = uuid.uuid4().hex[:8] + filename = f"{prefix}_{unique_id}{suffix}" + + return os.path.join(base_temp, filename)