ce431d7eec
Delegate expected output filenames to modules so resume works for rembg and composite; normalize G'MIC multi-frame output; add OpenRouter style reference support with tests. Add Crusaders, orange, and team gallery pipelines plus SOUL/AGENTS context files. Co-authored-by: Cursor <cursoragent@cursor.com>
78 lines
2.5 KiB
Python
78 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Match new team member photos to an existing player gallery style via OpenRouter."""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from imagepipeline import Pipeline
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
ENV_FILE = REPO_ROOT / ".env"
|
|
|
|
# Folder with new member photos to style-match.
|
|
INPUT = Path("/home/frank/tmp/spieler")
|
|
|
|
# One existing gallery player image as style reference.
|
|
TEMPLATE_IMAGE = Path("/home/frank/Downloads/2026_AHC_44_Julius-Nikolaus_Dittus-524-scaled.webp")
|
|
|
|
OUTPUT_BASE = Path.home() / "pipeline_output"
|
|
|
|
OPENROUTER_MODEL = "google/gemini-3-pro-image"
|
|
|
|
GALLERY_MATCH_PROMPT = (
|
|
"Match the second image to the first image's gallery style so it fits seamlessly "
|
|
"alongside the other players. Match color grading, white balance, contrast, "
|
|
"saturation, lighting direction, background treatment, sharpness, and overall "
|
|
"polish. "
|
|
"CRITICAL: Do not alter the person's face, identity, facial features, expression, "
|
|
"hair, pose, body shape, or clothing details. Do not crop, reframe, or change the "
|
|
"aspect ratio. Do not add or remove people or objects. "
|
|
"Keep the exact same image dimensions and composition — only adjust global style "
|
|
"and color to match the reference."
|
|
)
|
|
|
|
|
|
def _load_env_file(path: Path) -> None:
|
|
if not path.is_file():
|
|
return
|
|
for raw_line in path.read_text(encoding="utf-8").splitlines():
|
|
line = raw_line.strip()
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
key, sep, value = line.partition("=")
|
|
if not sep:
|
|
continue
|
|
key = key.strip()
|
|
value = value.strip()
|
|
if len(value) >= 2 and value[0] == value[-1] and value[0] in "\"'":
|
|
value = value[1:-1]
|
|
os.environ.setdefault(key, value)
|
|
|
|
|
|
def main() -> None:
|
|
_load_env_file(ENV_FILE)
|
|
|
|
with Pipeline(
|
|
name="team_gallery_match",
|
|
input_dir=INPUT,
|
|
output_base=OUTPUT_BASE,
|
|
) as p:
|
|
p.step(
|
|
"openrouter_edit",
|
|
inputs="input",
|
|
prompt=GALLERY_MATCH_PROMPT,
|
|
model=OPENROUTER_MODEL,
|
|
template_image=TEMPLATE_IMAGE,
|
|
# Downscale for API, then upscale back to original dimensions.
|
|
# Use 0 only if you accept higher cost/latency for ~10 MB sources.
|
|
max_edge=4096,
|
|
skip_existing=True,
|
|
)
|
|
output_root = p.run()
|
|
|
|
print(f"Pipeline finished. Output: {output_root}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|