Spaces:
Sleeping
Sleeping
File size: 6,981 Bytes
343eed9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | import os
import subprocess
import shutil
from pathlib import Path
from typing import List, Dict, Optional
import numpy as np
class AssetProcessor:
"""Base class for all asset-level transformations (ImageMagick, Inkscape, etc.)."""
def __init__(self, name: str):
self.name = name
self.available = self._check_availability()
def _check_availability(self) -> bool:
return shutil.which(self.name.lower()) is not None
def process(self, input_path: Path, output_path: Path, options: Dict = None) -> bool:
"""Process a single asset."""
raise NotImplementedError("Subclasses must implement process()")
class ImageMagickProcessor(AssetProcessor):
def __init__(self):
# On Linux it's often 'magick' or 'convert'
name = "magick" if shutil.which("magick") else "convert"
super().__init__(name)
def process(self, input_path: Path, output_path: Path, options: Dict = None) -> bool:
if not self.available:
return False
# Example options: {"filters": "-sepia-tone 80% -charcoal 2"}
filters = options.get("filters", "").split() if options else []
cmd = [self.name, str(input_path)] + filters + [str(output_path)]
try:
subprocess.run(cmd, check=True, capture_output=True)
return True
except Exception as e:
print(f" [AssetProcessor] ImageMagick error: {e}")
return False
class InkscapeProcessor(AssetProcessor):
def __init__(self):
super().__init__("inkscape")
def process(self, input_path: Path, output_path: Path, options: Dict = None) -> bool:
if not self.available:
return False
# Export SVG to PNG
cmd = [self.name, "--export-type=png", "--export-filename=" + str(output_path), str(input_path)]
try:
subprocess.run(cmd, check=True, capture_output=True)
return True
except Exception as e:
print(f" [AssetProcessor] Inkscape error: {e}")
return False
class AsepriteProcessor(AssetProcessor):
def __init__(self):
super().__init__("aseprite")
def process(self, input_path: Path, output_path: Path, options: Dict = None) -> bool:
if not self.available:
return False
# Example: Pixel art conversion
# aseprite -b input.png --save-as output.png
cmd = [self.name, "-b", str(input_path), "--save-as", str(output_path)]
try:
subprocess.run(cmd, check=True, capture_output=True)
return True
except Exception as e:
print(f" [AssetProcessor] Aseprite error: {e}")
return False
class OpenCVAssetProcessor(AssetProcessor):
"""Bridge to VFXProcessor for static asset processing."""
def __init__(self):
super().__init__("python") # Uses python/cv2
try:
import cv2
self.available = True
except ImportError:
self.available = False
def process(self, input_path: Path, output_path: Path, options: Dict = None) -> bool:
if not self.available: return False
try:
import cv2
from vfx.opencv_effects import VFXProcessor
img = cv2.imread(str(input_path))
if img is None: return False
# Convert BGR to RGB for VFXProcessor
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
profile = options.get("profile", "none")
vfx = VFXProcessor(profile)
processed_rgb = vfx.process_frame(img_rgb, 0.0)
# Convert back to BGR for saving
processed_bgr = cv2.cvtColor(processed_rgb, cv2.COLOR_RGB2BGR)
cv2.imwrite(str(output_path), processed_bgr)
return True
except Exception as e:
print(f" [AssetProcessor] OpenCV error: {e}")
return False
def get_processor(name: str) -> Optional[AssetProcessor]:
processors = {
"imagemagick": ImageMagickProcessor,
"inkscape": InkscapeProcessor,
"aseprite": AsepriteProcessor,
"opencv": OpenCVAssetProcessor
}
cls = processors.get(name.lower())
return cls() if cls else None
def apply_artistic_profile(story_dir: Path, profile_name: str):
"""
Applies an artistic profile to all images in a story directory.
Profiles: 'pixel_art', 'sketch', 'watercolor', 'vhs_static'
"""
img_dir = story_dir / "assets" / "images"
if not img_dir.exists(): return
output_dir = story_dir / "assets" / "images_processed"
if profile_name == "none":
if output_dir.exists():
print(f" 🧹 Profil 'none' demandé : suppression des images processées.")
shutil.rmtree(output_dir)
return
output_dir.mkdir(exist_ok=True)
# --- CACHE CHECK ---
cache_file = output_dir / ".last_profile"
if cache_file.exists():
try:
last_profile = cache_file.read_text().strip()
# If profile is same and some images exist, skip
if last_profile == profile_name and any(output_dir.glob("scene_*.png")):
print(f" ✨ Profil '{profile_name}' déjà appliqué. On saute le re-processing.")
return
except: pass
processor = None
options = {}
# ... (rest of the logic remains)
if profile_name == "pixel_art":
processor = get_processor("aseprite")
elif profile_name == "sketch":
processor = get_processor("imagemagick")
options = {"filters": "-colorspace gray -edge 1 -negate"}
elif profile_name == "oil_paint":
processor = get_processor("imagemagick")
options = {"filters": "-paint 3"}
elif profile_name == "charcoal":
processor = get_processor("imagemagick")
options = {"filters": "-charcoal 2"}
elif profile_name == "vintage":
processor = get_processor("imagemagick")
options = {"filters": "-sepia-tone 80%"}
elif profile_name == "night_vision":
processor = get_processor("imagemagick")
options = {"filters": "-colorspace gray -fill #00ff00 -tint 100"}
elif profile_name == "vhs_static":
processor = get_processor("opencv")
options = {"profile": "vhs_pro"}
if not processor or not processor.available:
print(f" [AssetProcessor] Profile '{profile_name}' not available (tool missing).")
return
print(f" 🎨 Applying Artistic Profile: {profile_name}...")
processed_count = 0
for img_path in img_dir.glob("scene_*.png"):
out_path = output_dir / img_path.name
if processor.process(img_path, out_path, options):
processed_count += 1
# Save to cache if at least one image was processed
if processed_count > 0:
cache_file.write_text(profile_name)
|