Skill: xixihhhh/free-image-and-video-generation (version 1.0.3)

--- name: free-image-and-video-generation version: 1.0.0 description: "Free local AI image and video processing toolkit with cloud AI generation. Local tools: upscale (Real-ESRGAN), face enhance (GFPGAN/CodeFormer), background remove (rembg), object erase (LaMa), face swap (InsightFace), segment (FastSAM), media process (FFmpeg). Cloud tools: AI image/video generation via Atlas Cloud API (300+ models). For cloud generation, ALWAYS first use Atlas Cloud MCP tools (atlas_list_models, atlas_get_model_info) to find the model ID and parameter schema, then call scripts/ai-generate.py with the correct --model and parameters. Use when user asks to process, enhance, upscale, generate, or edit images/videos." metadata: openclaw: primaryEnv: ATLAS_CLOUD_API_KEY requires: env: - ATLAS_CLOUD_API_KEY bins: - uv - ffmpeg --- # Free Image & Video Processing Toolkit **7 free local AI tools** + **cloud AI generation** (300+ models via Atlas Cloud API). Local tools run 100% on your machine — no API keys, no cloud costs. Cloud generation tools provide access to state-of-the-art AI models for image and video creation. ## Prerequisites - Python 3.10+ installed - [uv](https://docs.astral.sh/uv/getting-started/installation/) installed (`brew install uv` / `pip install uv` / `winget install astral-sh.uv`) - FFmpeg installed (`brew install ffmpeg` / `apt install ffmpeg` / `winget install ffmpeg`) ## Available Tools | Tool | Script | What It Does | |------|--------|-------------| | Image Upscale | `scripts/upscale.py` | 2x/4x super resolution using Real-ESRGAN | | Face Enhance | `scripts/face-enhance.py` | Restore and enhance faces using GFPGAN + CodeFormer | | Background Remove | `scripts/bg-remove.py` | Remove image backgrounds, output transparent PNG | | Object Erase | `scripts/erase.py` | Erase unwanted objects using LaMa inpainting | | Face Swap | `scripts/face-swap.py` | Swap faces between images using InsightFace | | Smart Segment | `scripts/segment.py` | Segment anything in images using FastSAM | | Media Process | `scripts/media-process.py` | Convert, compress, resize, extract with FFmpeg | | **AI Generate** | **`scripts/ai-generate.py`** | **Generate images/videos with 300+ cloud AI models** | ## Usage All scripts use `uv run` for zero-setup execution — dependencies are automatically installed on first run. ### Image Upscale (Real-ESRGAN) Upscale low-resolution images by 2x or 4x with AI super resolution. ```bash # 4x upscale (default) uv run scripts/upscale.py input.jpg # 2x upscale uv run scripts/upscale.py input.jpg --scale 2 # Upscale with face enhancement uv run scripts/upscale.py input.jpg --face-enhance # Batch upscale a folder uv run scripts/upscale.py ./photos/ --scale 4 # Custom output path uv run scripts/upscale.py input.jpg -o upscaled.png ``` ### Face Enhance (GFPGAN + CodeFormer) Restore old photos, enhance blurry faces, fix low-quality portraits. ```bash # Enhance faces in an image (GFPGAN, default) uv run scripts/face-enhance.py photo.jpg # Use CodeFormer (better fidelity control) uv run scripts/face-enhance.py photo.jpg --method codeformer # Adjust fidelity (0=quality, 1=fidelity, default 0.5) uv run scripts/face-enhance.py photo.jpg --method codeformer --fidelity 0.7 # Also upscale background (2x) uv run scripts/face-enhance.py photo.jpg --bg-upscale 2 # Batch process uv run scripts/face-enhance.py ./old-photos/ ``` ### Background Remove (rembg) Remove backgrounds from images, output transparent PNG. Supports multiple AI models. ```bash # Remove background (default u2net model) uv run scripts/bg-remove.py product.jpg # Use specific model uv run scripts/bg-remove.py photo.jpg --model isnet-general-use # Batch process folder uv run scripts/bg-remove.py ./products/ -o ./transparent/ # Keep only the foreground (alpha matting for fine edges) uv run scripts/bg-remove.py portrait.jpg --alpha-matting # Available models: u2net, u2netp, u2net_human_seg, u2net_cloth_seg, # silueta, isnet-general-use, isnet-anime, sam ``` ### Object Erase (LaMa Inpainting) Remove unwanted objects from images using a mask. ```bash # Erase objects (white area in mask = erase) uv run scripts/erase.py image.png --mask mask.png # Auto-generate mask from coordinates (x,y,width,height) uv run scripts/erase.py image.png --region 100,200,150,150 # Batch erase with matching masks (image1.png + image1_mask.png) uv run scripts/erase.py ./images/ --mask-dir ./masks/ ``` ### Face Swap (InsightFace) Swap faces between two images. ```bash # Swap face from source to target uv run scripts/face-swap.py --source face.jpg --target photo.jpg # Swap specific face index (when multiple faces detected) uv run scripts/face-swap.py --source face.jpg --target group.jpg --face-index 0 # Custom output uv run scripts/face-swap.py --source face.jpg --target photo.jpg -o result.png ``` ### Smart Segment (FastSAM) Segment any object in an image using text prompt, point, or bounding box. ```bash # Segment everything uv run scripts/segment.py image.jpg # Segment by text prompt uv run scripts/segment.py image.jpg --text "the dog" # Segment by point (x, y) uv run scripts/segment.py image.jpg --point 400,300 # Segment by bounding box (x1,y1,x2,y2) uv run scripts/segment.py image.jpg --box 100,100,400,400 # Output mask only uv run scripts/segment.py image.jpg --text "car" --mask-only ``` ### Media Process (FFmpeg) Convert, compress, resize, extract frames, merge audio/video — powered by FFmpeg. ```bash # Convert format uv run scripts/media-process.py convert input.mp4 output.webm # Compress video (target size in MB) uv run scripts/media-process.py compress input.mp4 --target-size 10 # Resize video uv run scripts/media-process.py resize input.mp4 --width 1280 --height 720 # Extract frames as images uv run scripts/media-process.py frames input.mp4 --fps 1 --output ./frames/ # Extract audio uv run scripts/media-process.py audio input.mp4 -o audio.mp3 # Create GIF from video uv run scripts/media-process.py gif input.mp4 --start 5 --duration 3 --fps 15 # Trim video uv run scripts/media-process.py trim input.mp4 --start 00:01:00 --end 00:02:30 # Merge multiple videos uv run scripts/media-process.py merge video1.mp4 video2.mp4 video3.mp4 -o combined.mp4 # Add watermark uv run scripts/media-process.py watermark input.mp4 --image logo.png --position bottom-right # Get media info uv run scripts/media-process.py info input.mp4 ``` ### AI Generate (Atlas Cloud API) — Gold Sponsor Generate images and videos using 300+ state-of-the-art AI models. Requires an Atlas Cloud API key. **IMPORTANT for AI agents**: Before calling this script, you MUST first use Atlas Cloud MCP tools to find the correct model ID and its required parameters: 1. Call `atlas_list_models` to browse available models, or `atlas_search_docs` to search for a specific model 2. Call `atlas_get_model_info` with the model ID to get the exact parameter schema (different models use different parameters — some use `size`, others use `aspect_ratio` + `resolution`, etc.) 3. Then call the script with `--model <full_model_id>` and the correct parameters ```bash # Generate image (pass full model ID from Atlas Cloud) uv run scripts/ai-generate.py image "A cat astronaut on the moon" --model black-forest-labs/flux-schnell --size 1024*1024 # Models using aspect_ratio + resolution (e.g. Nano Banana 2, Imagen4) uv run scripts/ai-generate.py image "Anime girl with blue hair" --model google/nano-banana-2/text-to-image --aspect-ratio 1:1 --resolution 1k # Models using size presets (e.g. Seedream) uv run scripts/ai-generate.py image "Product photo on marble" --model bytedance/seedream-v5.0-lite --size 2048*2048 # Edit existing image uv run scripts/ai-generate.py image "Make the sky sunset orange" --model bytedance/seedream-v5.0-lite/edit --image photo.jpg # Generate video uv run scripts/ai-generate.py video "Timelapse of cherry blossoms" --model alibaba/wan-2.6/text-to-video --size 1280*720 # Image-to-video uv run scripts/ai-generate.py video "The person starts walking" --model alibaba/wan-2.6/image-to-video --image portrait.jpg # Pass extra model-specific parameters as JSON uv run scripts/ai-generate.py image "A logo" --model google/imagen4-ultra --extra '{"num_images": 4}' # NSFW mode uv run scripts/ai-generate.py image "Artistic figure study" --model black-forest-labs/flux-dev-lora --nsfw ``` **Setup**: Set `ATLAS_CLOUD_API_KEY` in environment variable or project `.env` file. Get your key at [atlascloud.ai](https://www.atlascloud.ai?utm_source=github&utm_campaign=free-image-and-video-generation-skill). Note: when using cloud generation, your prompts and image data will be sent to the Atlas Cloud API for processing. ## Output All tools save output to `./output/` by default. Use `-o` or `--output` to specify a custom path. ## Models Models are automatically downloaded on first use and cached locally: | Tool | Model | Size | Cache Location | |------|-------|------|----------------| | Upscale | RealESRGAN_x4plus | ~64MB | `~/.cache/realesrgan/` | | Face Enhance | GFPGANv1.4 | ~348MB | `~/.cache/gfpgan/` | | Face Enhance | CodeFormer | ~376MB | `~/.cache/codeformer/` | | Background Remove | u2net | ~176MB | `~/.u2net/` | | Object Erase | LaMa | ~200MB | `~/.cache/lama/` | | Face Swap | buffalo_l + inswapper | ~500MB | `~/.insightface/` | | Smart Segment | FastSAM-s | ~23MB | auto-downloaded by ultralytics | Total first-run download: ~1.5GB. All subsequent runs use cached models. ## Tips - **GPU Acceleration**: All tools automatically use CUDA/MPS if available, falling back to CPU - **Batch Processing**: Most tools accept a folder path for batch processing - **Memory**: Face swap and segmentation may need 4GB+ RAM for large images - **First Run**: First execution downloads AI models — subsequent runs are instant ## Workflow Examples Combine local processing with cloud AI generation: ```bash # 1. Generate a product image with AI uv run scripts/ai-generate.py image "Minimalist perfume bottle, studio lighting" --model bytedance/seedream-v5.0-lite --size 2048*2048 # 2. Upscale to 4x resolution uv run scripts/upscale.py ./output/seedream-v5.0-lite_*.png --scale 4 # 3. Remove background for e-commerce uv run scripts/bg-remove.py ./output/*_x4.png --alpha-matting # 4. Generate a product video uv run scripts/ai-generate.py video "A perfume bottle rotating slowly" --model kwaivgi/kling-v3.0-pro/text-to-video --duration 5 # 5. Add watermark to the video uv run scripts/media-process.py watermark ./output/text-to-video_*.mp4 --image logo.png ```

path: scripts/face-swap.py
content: #!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.10,<3.13"
# dependencies = [
#   "insightface>=0.7.3",
#   "onnxruntime>=1.16.0",
#   "opencv-python-headless>=4.8",
#   "numpy<2",
#   "pillow>=10.0",
# ]
# ///

"""
Face Swap - Swap faces between images using InsightFace (ArcFace + Inswapper)
Supports single image and face index selection for group photos
"""

import argparse
import os
import sys
import cv2
import numpy as np
from pathlib import Path

import insightface
from insightface.app import FaceAnalysis


def download_inswapper():
    model_dir = os.path.expanduser("~/.insightface/models")
    model_path = os.path.join(model_dir, "inswapper_128.onnx")
    if not os.path.exists(model_path):
        os.makedirs(model_dir, exist_ok=True)
        print("Downloading inswapper model...")
        url = "https://huggingface.co/deepinsight/inswapper/resolve/main/inswapper_128.onnx"
        import urllib.request
        urllib.request.urlretrieve(url, model_path)
        print("Model downloaded")
    return model_path


def main():
    parser = argparse.ArgumentParser(description="AI Face Swap (InsightFace)")
    parser.add_argument("--source", required=True, help="Source face image")
    parser.add_argument("--target", required=True, help="Target image (receives face)")
    parser.add_argument("-o", "--output", help="Output path (default: ./output/swapped.png)")
    parser.add_argument("--face-index", type=int, default=0,
                        help="Face index in target (for group photos, default: 0)")
    args = parser.parse_args()

    source_path = Path(args.source)
    target_path = Path(args.target)

    if not source_path.exists():
        print(f"Error: source image not found {source_path}")
        sys.exit(1)
    if not target_path.exists():
        print(f"Error: target image not found {target_path}")
        sys.exit(1)

    output_dir = Path("./output")
    output_dir.mkdir(parents=True, exist_ok=True)
    output_path = args.output or str(output_dir / f"{target_path.stem}_swapped.png")

    print("Face Swap - InsightFace")
    print(f"Source: {source_path}")
    print(f"Target: {target_path}")
    print()

    print("Loading models...")
    app = FaceAnalysis(name="buffalo_l", providers=["CPUExecutionProvider"])
    app.prepare(ctx_id=0, det_size=(640, 640))

    model_path = download_inswapper()
    swapper = insightface.model_zoo.get_model(model_path, providers=["CPUExecutionProvider"])

    source_img = cv2.imread(str(source_path))
    target_img = cv2.imread(str(target_path))

    if source_img is None or target_img is None:
        print("Error: cannot read image(s)")
        sys.exit(1)

    source_faces = app.get(source_img)
    target_faces = app.get(target_img)

    if not source_faces:
        print("Error: no face detected in source image")
        sys.exit(1)
    if not target_faces:
        print("Error: no face detected in target image")
        sys.exit(1)

    print(f"Source: {len(source_faces)} face(s) detected")
    print(f"Target: {len(target_faces)} face(s) detected")

    if args.face_index >= len(target_faces):
        print(f"Error: face index {args.face_index} out of range (total {len(target_faces)} faces)")
        sys.exit(1)

    source_face = source_faces[0]
    target_face = target_faces[args.face_index]

    result = swapper.get(target_img, target_face, source_face, paste_back=True)
    cv2.imwrite(output_path, result)

    print(f"\nDone! Saved to {output_path}")


if __name__ == "__main__":
    main()


path: scripts/upscale.py
content: #!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.10,<3.13"
# dependencies = [
#   "realesrgan-ncnn-py>=2.0.0",
#   "pillow>=10.0",
# ]
# ///

"""
Image Super Resolution - Real-ESRGAN (NCNN backend)
2x/4x AI upscaling with Vulkan GPU acceleration, no PyTorch required
"""

import argparse
import os
import sys
from pathlib import Path
from PIL import Image

from realesrgan_ncnn_py import Realesrgan


MODEL_MAP = {
    "x4plus": 0,        # General 4x upscale (default)
    "x4plus-anime": 1,  # Anime-optimized 4x
    "animevideo-x2": 2, # Anime video 2x
}


def process_image(input_path: str, output_path: str, realesrgan):
    try:
        image = Image.open(input_path).convert("RGB")
        w, h = image.size
        result = realesrgan.process_pil(image)
        result.save(output_path, quality=95)
        nw, nh = result.size
        print(f"  {os.path.basename(input_path)}: {w}x{h} -> {nw}x{nh}")
        return True
    except Exception as e:
        print(f"  Failed {os.path.basename(input_path)}: {e}")
        return False


def main():
    parser = argparse.ArgumentParser(description="Real-ESRGAN Image Super Resolution (NCNN)")
    parser.add_argument("input", help="Input image path or folder")
    parser.add_argument("-o", "--output", help="Output path (default: ./output/)")
    parser.add_argument("--model", default="x4plus", choices=list(MODEL_MAP.keys()),
                        help="Model variant (default: x4plus)")
    parser.add_argument("--gpu", type=int, default=0,
                        help="GPU ID, -1 for CPU (default: 0)")
    args = parser.parse_args()

    input_path = Path(args.input)
    output_dir = Path(args.output) if args.output else Path("./output")
    output_dir.mkdir(parents=True, exist_ok=True)

    image_exts = {".jpg", ".jpeg", ".png", ".webp", ".bmp", ".tiff"}
    if input_path.is_dir():
        files = sorted([f for f in input_path.iterdir() if f.suffix.lower() in image_exts])
    elif input_path.is_file():
        files = [input_path]
    else:
        print(f"Error: not found {input_path}")
        sys.exit(1)

    if not files:
        print("No image files found")
        sys.exit(1)

    scale_label = "2x" if args.model == "animevideo-x2" else "4x"
    print(f"Real-ESRGAN {scale_label} Super Resolution (NCNN)")
    print(f"Model: {args.model}")
    print(f"Device: {'CPU' if args.gpu == -1 else f'GPU {args.gpu}'}")
    print(f"Processing {len(files)} image(s)")
    print()

    realesrgan = Realesrgan(gpuid=args.gpu, model=MODEL_MAP[args.model])

    success = 0
    for f in files:
        out_name = f.stem + f"_{scale_label}.png"
        out_path = str(output_dir / out_name)
        if process_image(str(f), out_path, realesrgan):
            success += 1

    print(f"\nDone! {success}/{len(files)} image(s) saved to {output_dir}")


if __name__ == "__main__":
    main()


path: scripts/media-process.py
content: #!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.10"
# dependencies = [
#   "pillow>=10.0",
# ]
# ///

"""
Media Processing Tool - FFmpeg-powered audio/video processing
Convert, compress, trim, merge, extract frames, create GIFs, and more
Requires FFmpeg installed: brew install ffmpeg / apt install ffmpeg
"""

import argparse
import os
import sys
import json
import subprocess
import shutil
from pathlib import Path


def check_ffmpeg():
    if not shutil.which("ffmpeg"):
        print("Error: FFmpeg not found")
        print("Install:")
        print("  macOS:   brew install ffmpeg")
        print("  Ubuntu:  sudo apt install ffmpeg")
        print("  Windows: winget install ffmpeg")
        sys.exit(1)


def run_ffmpeg(args, desc=""):
    cmd = ["ffmpeg", "-y"] + args
    if desc:
        print(f"  {desc}")
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        print(f"  FFmpeg error: {result.stderr[:500]}")
        return False
    return True


def get_media_info(input_path):
    cmd = ["ffprobe", "-v", "quiet", "-print_format", "json",
           "-show_format", "-show_streams", input_path]
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        return {}
    return json.loads(result.stdout)


def cmd_convert(args):
    check_ffmpeg()
    output = args.output or str(Path("./output") / Path(args.inputs[0]).with_suffix(f".{args.format or 'mp4'}").name)
    Path(output).parent.mkdir(parents=True, exist_ok=True)
    ffargs = ["-i", args.inputs[0]]
    if args.format == "webm":
        ffargs += ["-c:v", "libvpx-vp9", "-c:a", "libopus"]
    elif args.format == "gif":
        ffargs += ["-vf", "fps=15,scale=480:-1:flags=lanczos"]
    ffargs.append(output)
    if run_ffmpeg(ffargs, f"Converting -> {output}"):
        print(f"\nDone! Saved to {output}")


def cmd_compress(args):
    check_ffmpeg()
    info = get_media_info(args.inputs[0])
    if not info:
        print("Error: cannot read media info")
        sys.exit(1)
    duration = float(info.get("format", {}).get("duration", 0))
    if duration <= 0:
        print("Error: cannot get video duration")
        sys.exit(1)
    target_bits = args.target_size * 8 * 1024 * 1024 * 0.95
    video_bitrate = int(target_bits / duration)
    output = args.output or str(Path("./output") / f"{Path(args.inputs[0]).stem}_compressed.mp4")
    Path(output).parent.mkdir(parents=True, exist_ok=True)
    original_size = os.path.getsize(args.inputs[0]) / (1024 * 1024)
    print(f"  Original: {original_size:.1f} MB -> Target: {args.target_size} MB")
    ffargs = ["-i", args.inputs[0], "-c:v", "libx264", "-b:v", str(video_bitrate),
              "-c:a", "aac", "-b:a", "128k", "-preset", "medium", output]
    if run_ffmpeg(ffargs, "Compressing..."):
        new_size = os.path.getsize(output) / (1024 * 1024)
        print(f"  Result: {new_size:.1f} MB ({(1 - new_size/original_size)*100:.0f}% reduction)")
        print(f"\nDone! Saved to {output}")


def cmd_resize(args):
    check_ffmpeg()
    output = args.output or str(Path("./output") / f"{Path(args.inputs[0]).stem}_resized.mp4")
    Path(output).parent.mkdir(parents=True, exist_ok=True)
    scale = f"scale={args.width}:{args.height}"
    ffargs = ["-i", args.inputs[0], "-vf", scale, "-c:a", "copy", output]
    if run_ffmpeg(ffargs, f"Resizing -> {args.width}x{args.height}"):
        print(f"\nDone! Saved to {output}")


def cmd_frames(args):
    check_ffmpeg()
    output_dir = Path(args.output) if args.output else Path("./output/frames")
    output_dir.mkdir(parents=True, exist_ok=True)
    ffargs = ["-i", args.inputs[0], "-vf", f"fps={args.fps}", str(output_dir / "frame_%04d.png")]
    if run_ffmpeg(ffargs, f"Extracting frames at {args.fps} fps..."):
        count = len(list(output_dir.glob("frame_*.png")))
        print(f"\nDone! Extracted {count} frames to {output_dir}")


def cmd_audio(args):
    check_ffmpeg()
    ext = Path(args.output).suffix if args.output else ".mp3"
    output = args.output or str(Path("./output") / f"{Path(args.inputs[0]).stem}{ext}")
    Path(output).parent.mkdir(parents=True, exist_ok=True)
    ffargs = ["-i", args.inputs[0], "-vn", "-acodec"]
    if ext == ".mp3":
        ffargs += ["libmp3lame", "-q:a", "2"]
    elif ext == ".wav":
        ffargs += ["pcm_s16le"]
    else:
        ffargs += ["copy"]
    ffargs.append(output)
    if run_ffmpeg(ffargs, "Extracting audio..."):
        print(f"\nDone! Saved to {output}")


def cmd_gif(args):
    check_ffmpeg()
    output = args.output or str(Path("./output") / f"{Path(args.inputs[0]).stem}.gif")
    Path(output).parent.mkdir(parents=True, exist_ok=True)
    vf_parts = [f"fps={args.fps}"]
    if args.width:
        vf_parts.append(f"scale={args.width}:-1:flags=lanczos")
    vf = ",".join(vf_parts)
    ffargs = ["-i", args.inputs[0]]
    if args.start is not None:
        ffargs = ["-ss", str(args.start)] + ffargs
    if args.duration is not None:
        ffargs += ["-t", str(args.duration)]
    ffargs += ["-vf", vf, "-loop", "0", output]
    if run_ffmpeg(ffargs, "Creating GIF..."):
        size = os.path.getsize(output) / (1024 * 1024)
        print(f"\nDone! Saved to {output} ({size:.1f} MB)")


def cmd_trim(args):
    check_ffmpeg()
    output = args.output or str(Path("./output") / f"{Path(args.inputs[0]).stem}_trimmed.mp4")
    Path(output).parent.mkdir(parents=True, exist_ok=True)
    ffargs = ["-i", args.inputs[0], "-ss", args.start]
    if args.end:
        ffargs += ["-to", args.end]
    elif args.duration:
        ffargs += ["-t", str(args.duration)]
    ffargs += ["-c", "copy", output]
    if run_ffmpeg(ffargs, f"Trimming {args.start} -> {args.end or 'end'}"):
        print(f"\nDone! Saved to {output}")


def cmd_merge(args):
    check_ffmpeg()
    output = args.output or str(Path("./output") / "merged.mp4")
    Path(output).parent.mkdir(parents=True, exist_ok=True)
    list_file = Path("./output/.merge_list.txt")
    list_file.parent.mkdir(parents=True, exist_ok=True)
    with open(list_file, "w") as f:
        for inp in args.inputs:
            f.write(f"file '{os.path.abspath(inp)}'\n")
    ffargs = ["-f", "concat", "-safe", "0", "-i", str(list_file), "-c", "copy", output]
    if run_ffmpeg(ffargs, f"Merging {len(args.inputs)} files..."):
        print(f"\nDone! Saved to {output}")
    list_file.unlink(missing_ok=True)


def cmd_watermark(args):
    check_ffmpeg()
    output = args.output or str(Path("./output") / f"{Path(args.inputs[0]).stem}_watermarked.mp4")
    Path(output).parent.mkdir(parents=True, exist_ok=True)
    positions = {
        "top-left": "10:10", "top-right": "main_w-overlay_w-10:10",
        "bottom-left": "10:main_h-overlay_h-10",
        "bottom-right": "main_w-overlay_w-10:main_h-overlay_h-10",
        "center": "(main_w-overlay_w)/2:(main_h-overlay_h)/2",
    }
    pos = positions.get(args.position, positions["bottom-right"])
    ffargs = ["-i", args.inputs[0], "-i", args.image,
              "-filter_complex", f"overlay={pos}", "-c:a", "copy", output]
    if run_ffmpeg(ffargs, f"Adding watermark ({args.position})..."):
        print(f"\nDone! Saved to {output}")


def cmd_info(args):
    check_ffmpeg()
    info = get_media_info(args.inputs[0])
    if not info:
        print("Error: cannot read media info")
        sys.exit(1)
    fmt = info.get("format", {})
    print(f"\nFile: {os.path.basename(args.inputs[0])}")
    print(f"Format: {fmt.get('format_long_name', 'unknown')}")
    print(f"Duration: {float(fmt.get('duration', 0)):.1f}s")
    print(f"Size: {int(fmt.get('size', 0)) / (1024*1024):.1f} MB")
    print(f"Bitrate: {int(fmt.get('bit_rate', 0)) / 1000:.0f} kbps")
    for stream in info.get("streams", []):
        if stream.get("codec_type") == "video":
            print(f"\nVideo:")
            print(f"  Codec: {stream.get('codec_name')}")
            print(f"  Resolution: {stream.get('width')}x{stream.get('height')}")
            fps = stream.get("r_frame_rate", "0/1")
            if "/" in fps:
                n, d = fps.split("/")
                print(f"  FPS: {int(n)/int(d):.2f}")
        elif stream.get("codec_type") == "audio":
            print(f"\nAudio:")
            print(f"  Codec: {stream.get('codec_name')}")
            print(f"  Sample rate: {stream.get('sample_rate')} Hz")
            print(f"  Channels: {stream.get('channels')}")


def main():
    parser = argparse.ArgumentParser(description="FFmpeg Media Processing Tool")
    subparsers = parser.add_subparsers(dest="command", help="Command")

    p = subparsers.add_parser("convert", help="Format conversion")
    p.add_argument("inputs", nargs=1); p.add_argument("-o", "--output"); p.add_argument("--format")

    p = subparsers.add_parser("compress", help="Video compression")
    p.add_argument("inputs", nargs=1); p.add_argument("--target-size", type=float, required=True, help="Target size in MB"); p.add_argument("-o", "--output")

    p = subparsers.add_parser("resize", help="Resize video")
    p.add_argument("inputs", nargs=1); p.add_argument("--width", type=int, default=1280); p.add_argument("--height", type=int, default=-1); p.add_argument("-o", "--output")

    p = subparsers.add_parser("frames", help="Extract frames")
    p.add_argument("inputs", nargs=1); p.add_argument("--fps", type=float, default=1); p.add_argument("-o", "--output")

    p = subparsers.add_parser("audio", help="Extract audio")
    p.add_argument("inputs", nargs=1); p.add_argument("-o", "--output")

    p = subparsers.add_parser("gif", help="Create GIF")
    p.add_argument("inputs", nargs=1); p.add_argument("--start", type=float); p.add_argument("--duration", type=float); p.add_argument("--fps", type=int, default=15); p.add_argument("--width", type=int); p.add_argument("-o", "--output")

    p = subparsers.add_parser("trim", help="Trim video")
    p.add_argument("inputs", nargs=1); p.add_argument("--start", required=True); p.add_argument("--end"); p.add_argument("--duration", type=float); p.add_argument("-o", "--output")

    p = subparsers.add_parser("merge", help="Merge videos")
    p.add_argument("inputs", nargs="+"); p.add_argument("-o", "--output")

    p = subparsers.add_parser("watermark", help="Add watermark")
    p.add_argument("inputs", nargs=1); p.add_argument("--image", required=True); p.add_argument("--position", default="bottom-right", choices=["top-left", "top-right", "bottom-left", "bottom-right", "center"]); p.add_argument("-o", "--output")

    p = subparsers.add_parser("info", help="Show media info")
    p.add_argument("inputs", nargs=1)

    args = parser.parse_args()
    if not args.command:
        parser.print_help()
        sys.exit(0)

    print(f"FFmpeg Media Processing - {args.command}")
    commands = {
        "convert": cmd_convert, "compress": cmd_compress, "resize": cmd_resize,
        "frames": cmd_frames, "audio": cmd_audio, "gif": cmd_gif,
        "trim": cmd_trim, "merge": cmd_merge, "watermark": cmd_watermark, "info": cmd_info,
    }
    commands[args.command](args)


if __name__ == "__main__":
    main()


path: scripts/face-enhance.py
content: #!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.10,<3.13"
# dependencies = [
#   "realesrgan-ncnn-py>=2.0.0",
#   "pillow>=10.0",
# ]
# ///

"""
Face / Image Enhancement - Real-ESRGAN (NCNN backend)
Enhance details by upscaling then downscaling, great for old photos and blurry faces
"""

import argparse
import os
import sys
from pathlib import Path
from PIL import Image, ImageEnhance

from realesrgan_ncnn_py import Realesrgan


def enhance_image(image, realesrgan, sharpness, contrast):
    original_size = image.size
    result = realesrgan.process_pil(image)
    result = result.resize(original_size, Image.LANCZOS)
    if sharpness != 1.0:
        result = ImageEnhance.Sharpness(result).enhance(sharpness)
    if contrast != 1.0:
        result = ImageEnhance.Contrast(result).enhance(contrast)
    return result


def process_image(input_path, output_path, realesrgan, mode, sharpness, contrast):
    try:
        image = Image.open(input_path).convert("RGB")
        w, h = image.size
        if mode == "enhance":
            result = enhance_image(image, realesrgan, sharpness, contrast)
            print(f"  {os.path.basename(input_path)}: {w}x{h} -> enhanced")
        else:
            result = realesrgan.process_pil(image)
            nw, nh = result.size
            print(f"  {os.path.basename(input_path)}: {w}x{h} -> {nw}x{nh}")
        result.save(output_path, quality=95)
        return True
    except Exception as e:
        print(f"  Failed {os.path.basename(input_path)}: {e}")
        return False


def main():
    parser = argparse.ArgumentParser(description="AI Face/Image Enhancement (Real-ESRGAN NCNN)")
    parser.add_argument("input", help="Input image path or folder")
    parser.add_argument("-o", "--output", help="Output path (default: ./output/)")
    parser.add_argument("--mode", choices=["enhance", "upscale"], default="enhance",
                        help="Mode: enhance=detail boost keep size, upscale=4x enlarge (default: enhance)")
    parser.add_argument("--sharpness", type=float, default=1.3,
                        help="Sharpness factor, 1.0=unchanged (default: 1.3)")
    parser.add_argument("--contrast", type=float, default=1.1,
                        help="Contrast factor, 1.0=unchanged (default: 1.1)")
    parser.add_argument("--gpu", type=int, default=0,
                        help="GPU ID, -1 for CPU (default: 0)")
    args = parser.parse_args()

    input_path = Path(args.input)
    output_dir = Path(args.output) if args.output else Path("./output")
    output_dir.mkdir(parents=True, exist_ok=True)

    image_exts = {".jpg", ".jpeg", ".png", ".webp", ".bmp", ".tiff"}
    if input_path.is_dir():
        files = sorted([f for f in input_path.iterdir() if f.suffix.lower() in image_exts])
    elif input_path.is_file():
        files = [input_path]
    else:
        print(f"Error: not found {input_path}")
        sys.exit(1)

    if not files:
        print("No image files found")
        sys.exit(1)

    mode_desc = "Detail enhancement (keep original size)" if args.mode == "enhance" else "4x upscale"
    print(f"Face/Image Enhancement - Real-ESRGAN (NCNN)")
    print(f"Mode: {mode_desc}")
    print(f"Device: {'CPU' if args.gpu == -1 else f'GPU {args.gpu}'}")
    print(f"Processing {len(files)} image(s)")
    print()

    realesrgan = Realesrgan(gpuid=args.gpu, model=0)

    success = 0
    for f in files:
        out_name = f.stem + "_enhanced.png"
        out_path = str(output_dir / out_name)
        if process_image(str(f), out_path, realesrgan, args.mode, args.sharpness, args.contrast):
            success += 1

    print(f"\nDone! {success}/{len(files)} image(s) saved to {output_dir}")


if __name__ == "__main__":
    main()


path: scripts/erase.py
content: #!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.10,<3.13"
# dependencies = [
#   "simple-lama-inpainting>=0.1.0",
#   "pillow>=10.0",
#   "numpy<2",
# ]
# ///

"""
Object Eraser - Remove unwanted objects using LaMa (Large Mask Inpainting)
Supports mask images and coordinate-based region specification
"""

import argparse
import os
import sys
import numpy as np
from pathlib import Path
from PIL import Image, ImageDraw

from simple_lama_inpainting import SimpleLama


def create_region_mask(image_size, regions):
    mask = Image.new("L", image_size, 0)
    draw = ImageDraw.Draw(mask)
    for region in regions:
        parts = [int(x.strip()) for x in region.split(",")]
        if len(parts) == 4:
            x, y, w, h = parts
            draw.rectangle([x, y, x + w, y + h], fill=255)
        else:
            print(f"  Warning: invalid region '{region}', format: x,y,width,height")
    return mask


def process_image(input_path, output_path, mask_path, regions, lama):
    try:
        image = Image.open(input_path).convert("RGB")
        if mask_path:
            mask = Image.open(mask_path).convert("L")
            if mask.size != image.size:
                mask = mask.resize(image.size)
        elif regions:
            mask = create_region_mask(image.size, regions)
        else:
            print(f"  Skipped {os.path.basename(input_path)}: no mask or region specified")
            return False

        result = lama(image, mask)
        result.save(output_path)
        print(f"  {os.path.basename(input_path)} -> {os.path.basename(output_path)}")
        return True
    except Exception as e:
        print(f"  Skipped {os.path.basename(input_path)}: {e}")
        return False


def main():
    parser = argparse.ArgumentParser(description="AI Object Eraser (LaMa Inpainting)")
    parser.add_argument("input", help="Input image path or folder")
    parser.add_argument("-o", "--output", help="Output path (default: ./output/)")
    parser.add_argument("--mask", help="Mask image path (white area = erase)")
    parser.add_argument("--mask-dir", help="Mask folder for batch (e.g. img1_mask.png)")
    parser.add_argument("--region", action="append",
                        help="Rectangle region to erase: x,y,width,height (can specify multiple)")
    args = parser.parse_args()

    input_path = Path(args.input)
    output_dir = Path(args.output) if args.output else Path("./output")
    output_dir.mkdir(parents=True, exist_ok=True)

    image_exts = {".jpg", ".jpeg", ".png", ".webp", ".bmp", ".tiff"}

    print("Object Eraser - LaMa Inpainting")
    print("Loading model...")
    lama = SimpleLama()

    if input_path.is_dir():
        files = sorted([f for f in input_path.iterdir() if f.suffix.lower() in image_exts])
        mask_dir = Path(args.mask_dir) if args.mask_dir else None

        print(f"Processing {len(files)} image(s)\n")
        success = 0
        for f in files:
            mask_path = None
            if mask_dir:
                for suffix in ["_mask", ""]:
                    candidate = mask_dir / (f.stem + suffix + ".png")
                    if candidate.exists():
                        mask_path = str(candidate)
                        break
            out_name = f.stem + "_erased" + f.suffix
            out_path = str(output_dir / out_name)
            if process_image(str(f), out_path, mask_path, args.region, lama):
                success += 1
        print(f"\nDone! {success}/{len(files)} image(s) saved to {output_dir}")

    elif input_path.is_file():
        print()
        out_name = input_path.stem + "_erased" + input_path.suffix
        out_path = str(output_dir / out_name)
        if process_image(str(input_path), out_path, args.mask, args.region, lama):
            print(f"\nDone! Saved to {out_path}")
        else:
            sys.exit(1)
    else:
        print(f"Error: not found {input_path}")
        sys.exit(1)


if __name__ == "__main__":
    main()


path: scripts/segment.py
content: #!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.10,<3.13"
# dependencies = [
#   "ultralytics>=8.3.0",
#   "opencv-python-headless>=4.8",
#   "numpy<2",
#   "pillow>=10.0",
#   "torch>=2.0",
#   "torchvision>=0.15",
# ]
# ///

"""
Smart Segmentation Tool - Segment any object in images using FastSAM
Supports text prompts, point prompts, bounding box prompts
"""

import argparse
import os
import sys
from pathlib import Path

from ultralytics import FastSAM


def main():
    parser = argparse.ArgumentParser(description="Smart Segmentation (FastSAM)")
    parser.add_argument("input", help="Input image path")
    parser.add_argument("-o", "--output", help="Output directory (default: ./output/)")
    parser.add_argument("--text", help="Segment by text prompt (e.g. 'the dog')")
    parser.add_argument("--point", help="Segment by point x,y")
    parser.add_argument("--box", help="Segment by bounding box x1,y1,x2,y2")
    parser.add_argument("--model", default="FastSAM-s.pt",
                        choices=["FastSAM-s.pt", "FastSAM-x.pt"],
                        help="Model size (default: FastSAM-s.pt, 23MB)")
    parser.add_argument("--conf", type=float, default=0.4, help="Confidence threshold (default: 0.4)")
    parser.add_argument("--iou", type=float, default=0.9, help="IoU threshold (default: 0.9)")
    args = parser.parse_args()

    input_path = Path(args.input)
    if not input_path.exists():
        print(f"Error: file not found {input_path}")
        sys.exit(1)

    output_dir = Path(args.output) if args.output else Path("./output")
    output_dir.mkdir(parents=True, exist_ok=True)

    print("Smart Segmentation - FastSAM")
    print(f"Input: {input_path}")
    print(f"Model: {args.model}")

    # Detect device
    import torch
    device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
    print(f"Device: {device}")
    print()

    # Load model and run inference
    model = FastSAM(args.model)

    # Build prompts
    prompts = {}
    if args.text:
        print(f"Text prompt: '{args.text}'")
        prompts["texts"] = args.text
    elif args.point:
        parts = [int(x) for x in args.point.split(",")]
        prompts["points"] = [[parts[0], parts[1]]]
        prompts["labels"] = [1]
        print(f"Point prompt: {prompts['points']}")
    elif args.box:
        parts = [int(x) for x in args.box.split(",")]
        prompts["bboxes"] = [[parts[0], parts[1], parts[2], parts[3]]]
        print(f"Box prompt: {prompts['bboxes']}")
    else:
        print("Segment Everything mode")

    # Run prediction
    results = model(
        str(input_path),
        device=device,
        retina_masks=True,
        imgsz=1024,
        conf=args.conf,
        iou=args.iou,
    )

    # Save results
    for r in results:
        if prompts:
            r = r.prompt(**prompts)
        out_path = str(output_dir / f"{input_path.stem}_segmented.png")
        r.save(out_path)
        print(f"\nResult saved to {out_path}")

    print(f"\nDone! Results saved to {output_dir}")


if __name__ == "__main__":
    main()


path: scripts/bg-remove.py
content: #!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.10,<3.13"
# dependencies = [
#   "rembg[cpu]>=2.0.50",
#   "pillow>=10.0",
# ]
# ///

"""
Background Removal - Remove image backgrounds using rembg (U2-Net)
Outputs transparent PNG, supports multiple AI models and batch processing
"""

import argparse
import os
import sys
from pathlib import Path
from PIL import Image
from rembg import remove, new_session


MODELS = [
    "u2net",              # General purpose (default, 176MB)
    "u2netp",             # Lightweight (4MB, faster)
    "u2net_human_seg",    # Human segmentation
    "u2net_cloth_seg",    # Clothing segmentation
    "silueta",            # Lightweight general
    "isnet-general-use",  # High accuracy general
    "isnet-anime",        # Anime images
]


def process_image(input_path, output_path, session, alpha_matting):
    try:
        img = Image.open(input_path)
        result = remove(
            img,
            session=session,
            alpha_matting=alpha_matting,
            alpha_matting_foreground_threshold=240 if alpha_matting else None,
            alpha_matting_background_threshold=10 if alpha_matting else None,
        )
        if not output_path.lower().endswith(".png"):
            output_path = os.path.splitext(output_path)[0] + ".png"
        result.save(output_path)
        print(f"  {os.path.basename(input_path)} -> {os.path.basename(output_path)}")
        return True
    except Exception as e:
        print(f"  Skipped {os.path.basename(input_path)}: {e}")
        return False


def main():
    parser = argparse.ArgumentParser(description="AI Background Removal (rembg)")
    parser.add_argument("input", help="Input image path or folder")
    parser.add_argument("-o", "--output", help="Output path (default: ./output/)")
    parser.add_argument("--model", default="u2net", choices=MODELS,
                        help="AI model (default: u2net)")
    parser.add_argument("--alpha-matting", action="store_true",
                        help="Enable fine edge processing (better for hair, fur)")
    parser.add_argument("--list-models", action="store_true", help="List available models")
    args = parser.parse_args()

    if args.list_models:
        print("Available models:")
        for m in MODELS:
            print(f"  {m}")
        sys.exit(0)

    input_path = Path(args.input)
    output_dir = Path(args.output) if args.output else Path("./output")
    output_dir.mkdir(parents=True, exist_ok=True)

    image_exts = {".jpg", ".jpeg", ".png", ".webp", ".bmp", ".tiff"}
    if input_path.is_dir():
        files = sorted([f for f in input_path.iterdir() if f.suffix.lower() in image_exts])
    elif input_path.is_file():
        files = [input_path]
    else:
        print(f"Error: not found {input_path}")
        sys.exit(1)

    if not files:
        print("No image files found")
        sys.exit(1)

    print(f"Background Removal - Model: {args.model}")
    if args.alpha_matting:
        print("Alpha matting enabled (fine edge processing)")
    print(f"Processing {len(files)} image(s)")
    print()

    session = new_session(args.model)

    success = 0
    for f in files:
        out_name = f.stem + "_nobg.png"
        out_path = str(output_dir / out_name)
        if process_image(str(f), out_path, session, args.alpha_matting):
            success += 1

    print(f"\nDone! {success}/{len(files)} image(s) saved to {output_dir}")


if __name__ == "__main__":
    main()


path: scripts/ai-generate.py
content: #!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.10"
# dependencies = [
#   "requests>=2.31",
#   "pillow>=10.0",
# ]
# ///

"""
AI Image & Video Generation via Atlas Cloud API
Supports any model available on Atlas Cloud (300+ models)
AI agents should use Atlas Cloud MCP tools to search models before calling this script
Powered by Atlas Cloud (Gold Sponsor)
"""

import argparse
import os
import sys
import time
import json
import requests
from pathlib import Path


API_BASE = "https://api.atlascloud.ai/api/v1/model"


def get_api_key():
    key = os.environ.get("ATLAS_CLOUD_API_KEY") or os.environ.get("ATLASCLOUD_API_KEY")
    if not key:
        env_files = [".env"]
        for env_file in env_files:
            if os.path.exists(env_file):
                with open(env_file) as f:
                    for line in f:
                        line = line.strip()
                        if line.startswith("ATLAS_CLOUD_API_KEY=") or line.startswith("ATLASCLOUD_API_KEY="):
                            key = line.split("=", 1)[1].strip().strip('"').strip("'")
                            break
            if key:
                break
    if not key:
        print("Error: Atlas Cloud API Key not found")
        print()
        print("Setup (choose one):")
        print("  1. Environment: export ATLAS_CLOUD_API_KEY=your_key")
        print("  2. .env file:   echo 'ATLAS_CLOUD_API_KEY=your_key' >> .env")
        print()
        print("Get your free API key: https://www.atlascloud.ai?utm_source=github&utm_campaign=free-image-and-video-generation-skill")
        print("New users get free credits, no credit card required")
        sys.exit(1)
    return key


def submit_request(endpoint, payload, api_key):
    url = f"{API_BASE}/{endpoint}"
    headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
    resp = requests.post(url, json=payload, headers=headers, timeout=30)
    if resp.status_code != 200:
        print(f"API error ({resp.status_code}): {resp.text[:500]}")
        sys.exit(1)
    data = resp.json()
    if data.get("code") != 200:
        print(f"API error: {data.get('message', 'Unknown error')}")
        sys.exit(1)
    request_id = data.get("data", {}).get("id")
    if not request_id:
        print(f"Unexpected API response: {json.dumps(data, indent=2)}")
        sys.exit(1)
    return request_id


def poll_result(request_id, api_key, max_wait=300):
    url = f"{API_BASE}/result/{request_id}"
    headers = {"Authorization": f"Bearer {api_key}"}
    start_time = time.time()
    while time.time() - start_time < max_wait:
        try:
            resp = requests.get(url, headers=headers, timeout=30)
        except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
            time.sleep(3)
            continue
        if resp.status_code != 200:
            time.sleep(3)
            continue
        data = resp.json().get("data", resp.json())
        status = data.get("status", "")
        if status in ("completed", "succeeded"):
            return data.get("outputs") or data.get("output") or []
        if status == "failed":
            error = data.get("error") or data.get("message") or "Unknown error"
            print(f"\nGeneration failed: {error}")
            sys.exit(1)
        elapsed = int(time.time() - start_time)
        print(f"\r  Waiting... {elapsed}s", end="", flush=True)
        time.sleep(3)
    print(f"\nTimeout: no result after {max_wait}s")
    sys.exit(1)


def download_file(url, output_path):
    resp = requests.get(url, stream=True, timeout=60)
    resp.raise_for_status()
    with open(output_path, "wb") as f:
        for chunk in resp.iter_content(chunk_size=8192):
            f.write(chunk)


def cmd_image(args):
    [REDACTED_SECRET])
    output_dir = Path(args.output) if args.output else Path("./output")
    output_dir.mkdir(parents=True, exist_ok=True)

    print(f"AI Image Generation")
    print(f"Model: {args.model}")
    print(f"Prompt: {args.prompt[:80]}{'...' if len(args.prompt) > 80 else ''}")
    print()

    # 构建请求参数，直接透传用户指定的参数
    payload = {
        "model": args.model,
        "prompt": args.prompt,
    }
    # 根据用户传入的参数动态添加
    if args.size:
        payload["size"] = args.size.replace("x", "*")
    if args.aspect_ratio:
        payload["aspect_ratio"] = args.aspect_ratio
    if args.resolution:
        payload["resolution"] = args.resolution
    if args.count:
        payload["num_images"] = args.count
    if args.seed and args.seed > 0:
        payload["seed"] = args.seed
    if args.nsfw:
        payload["enable_safety_checker"] = False
    if args.negative:
        payload["negative_prompt"] = args.negative
    if args.image:
        if os.path.isfile(args.image):
            import base64
            with open(args.image, "rb") as f:
                img_b64 = base64.b64encode(f.read()).decode()
            ext = Path(args.image).suffix.lower().replace(".", "")
            mime = "jpeg" if ext == "jpg" else ext
            payload["image"] = f"data:image/{mime};base64,{img_b64}"
        else:
            payload["image"] = args.image
        if args.strength:
            payload["strength"] = args.strength
    # 支持额外的JSON参数
    if args.extra:
        try:
            extra = json.loads(args.extra)
            payload.update(extra)
        except json.JSONDecodeError:
            print(f"Warning: --extra is not valid JSON, ignored")

    print("  Submitting request...")
    request_id = submit_request("generateImage", payload, api_key)
    print(f"  Request ID: {request_id}")

    outputs = poll_result(request_id, api_key)
    if not outputs:
        print("\nNo images generated")
        sys.exit(1)

    print(f"\n  Downloading {len(outputs)} image(s)...")
    model_short = args.model.split("/")[-1].replace(" ", "_")
    for i, url in enumerate(outputs):
        if isinstance(url, dict):
            url = url.get("url", url.get("image", ""))
        filename = f"{model_short}_{int(time.time())}_{i+1:03d}.png"
        filepath = str(output_dir / filename)
        download_file(url, filepath)
        print(f"  Saved: {filepath}")

    print(f"\nDone! {len(outputs)} image(s) saved to {output_dir}")


def cmd_video(args):
    [REDACTED_SECRET])
    output_dir = Path(args.output) if args.output else Path("./output")
    output_dir.mkdir(parents=True, exist_ok=True)

    print(f"AI Video Generation")
    print(f"Model: {args.model}")
    print(f"Prompt: {args.prompt[:80]}{'...' if len(args.prompt) > 80 else ''}")
    print()

    payload = {
        "model": args.model,
        "prompt": args.prompt,
    }
    if args.size:
        payload["size"] = args.size.replace("x", "*")
    if args.aspect_ratio:
        payload["aspect_ratio"] = args.aspect_ratio
    if args.resolution:
        payload["resolution"] = args.resolution
    if args.duration:
        payload["duration"] = args.duration
    if args.seed and args.seed > 0:
        payload["seed"] = args.seed
    if args.negative:
        payload["negative_prompt"] = args.negative
    if args.image:
        if os.path.isfile(args.image):
            import base64
            with open(args.image, "rb") as f:
                img_b64 = base64.b64encode(f.read()).decode()
            ext = Path(args.image).suffix.lower().replace(".", "")
            mime = "jpeg" if ext == "jpg" else ext
            payload["image"] = f"data:image/{mime};base64,{img_b64}"
        else:
            payload["image"] = args.image
    if args.extra:
        try:
            extra = json.loads(args.extra)
            payload.update(extra)
        except json.JSONDecodeError:
            print(f"Warning: --extra is not valid JSON, ignored")

    print("  Submitting request...")
    request_id = submit_request("generateVideo", payload, api_key)
    print(f"  Request ID: {request_id}")

    outputs = poll_result(request_id, api_key, max_wait=600)
    if not outputs:
        print("\nNo video generated")
        sys.exit(1)

    print(f"\n  Downloading video...")
    model_short = args.model.split("/")[-1].replace(" ", "_")
    for i, url in enumerate(outputs):
        if isinstance(url, dict):
            url = url.get("url", url.get("video", ""))
        filename = f"{model_short}_{int(time.time())}_{i+1:03d}.mp4"
        filepath = str(output_dir / filename)
        download_file(url, filepath)
        size_mb = os.path.getsize(filepath) / (1024 * 1024)
        print(f"  Saved: {filepath} ({size_mb:.1f} MB)")

    print(f"\nDone! Video saved to {output_dir}")


def cmd_models(args):
    print("Fetching models from Atlas Cloud API...\n")
    try:
        resp = requests.get("https://api.atlascloud.ai/api/v1/models", timeout=15)
        resp.raise_for_status()
        models = resp.json().get("data", [])
    except Exception as e:
        print(f"Error fetching models: {e}")
        sys.exit(1)

    # 按类型过滤
    type_filter = args.type.capitalize() if args.type else None
    if type_filter:
        models = [m for m in models if m.get("type") == type_filter]

    # 按关键词搜索
    if args.search:
        keyword = args.search.lower()
        models = [m for m in models if keyword in m.get("model", "").lower()
                  or keyword in m.get("displayName", "").lower()
                  or keyword in m.get("organization", "").lower()]

    # 只显示Image和Video类型（除非指定了其他类型）
    if not type_filter:
        models = [m for m in models if m.get("type") in ("Image", "Video")]

    if not models:
        print("No models found")
        sys.exit(0)

    # 显示价格优势对比
    print("=" * 85)
    print("  Atlas Cloud  |  300+ AI Models, One API  |  Up to 90% cheaper than alternatives")
    print("=" * 85)
    print()
    print("  Why Atlas Cloud?")
    print("  - Unified API for ALL major AI models (Google, ByteDance, Alibaba, Kuaishou, etc.)")
    print("  - No need to manage multiple API keys or accounts")
    print("  - SOC I & II Certified | HIPAA Compliant | Up to 88% cheaper than alternatives")
    print("  - New users get FREE credits, no credit card required")
    print()
    print("  Price Comparison (popular models):")
    print()
    print(f"  {'Model':<30} {'Atlas Cloud':<18} {'fal.ai':<18} {'Official API':<18} {'You Save'}")
    print(f"  {'─'*30} {'─'*18} {'─'*18} {'─'*18} {'─'*12}")
    print(f"  {'Flux Schnell':<30} {'$0.003/image':<18} {'$0.025/image':<18} {'N/A':<18} {'88%'}")
    print(f"  {'Seedream v5.0':<30} {'$0.032/image':<18} {'N/A':<18} {'N/A':<18} {'Exclusive'}")
    print(f"  {'Nano Banana 2':<30} {'$0.072/image':<18} {'N/A':<18} {'$0.08/image':<18} {'10%'}")
    print(f"  {'Imagen4 Ultra':<30} {'$0.054/image':<18} {'N/A':<18} {'$0.06/image':<18} {'10%'}")
    print(f"  {'Wan 2.6 (5s)':<30} {'$0.070/5s':<18} {'$0.50/5s':<18} {'N/A':<18} {'86%'}")
    print(f"  {'Kling v3.0 Pro (5s)':<30} {'$0.204/5s':<18} {'N/A':<18} {'$0.70/5s':<18} {'71%'}")
    print()
    print("  * Prices shown are base prices with current promotions. Final price may vary by resolution, duration, etc.")
    print()

    # 按类型分组显示全部模型
    image_models = [m for m in models if m.get("type") == "Image"]
    video_models = [m for m in models if m.get("type") == "Video"]

    for label, group in [("Image", image_models), ("Video", video_models)]:
        if not group:
            continue
        print(f"\n  {label} Generation ({len(group)} models):\n")
        print(f"    {'Model ID':<55} {'Name':<40} {'Discount'}")
        print(f"    {'─'*55} {'─'*40} {'─'*10}")
        for m in group:
            model_id = m.get("model", "")
            display_name = m.get("displayName", "")
            discount = m.get("price", {}).get("discount", "")
            discount_str = f"{discount}% off" if discount else ""
            print(f"    {model_id:<55} {display_name:<40} {discount_str}")

    print(f"\n  Total: {len(models)} image/video models available")
    print(f"\n  Get API Key: https://www.atlascloud.ai?utm_source=github&utm_campaign=free-image-and-video-generation-skill")
    print(f"  New users get FREE credits, no credit card required\n")


def main():
    parser = argparse.ArgumentParser(
        description="Atlas Cloud AI Generation - 300+ Models, Unified API",
        epilog="Get API Key: https://www.atlascloud.ai?utm_source=github&utm_campaign=free-image-and-video-generation-skill"
    )
    subparsers = parser.add_subparsers(dest="command", help="Command")

    # image
    p = subparsers.add_parser("image", help="AI image generation")
    p.add_argument("prompt", help="Image description prompt")
    p.add_argument("--model", required=True,
                   help="Model ID (e.g. black-forest-labs/flux-schnell, google/nano-banana-2/text-to-image)")
    p.add_argument("--size", help="Image size (e.g. 1024*1024)")
    p.add_argument("--aspect-ratio", help="Aspect ratio (e.g. 1:1, 16:9, 9:16)")
    p.add_argument("--resolution", help="Resolution (e.g. 1k, 2k, 4k)")
    p.add_argument("--count", type=int, help="Number of images")
    p.add_argument("--seed", type=int, help="Random seed")
    p.add_argument("--nsfw", action="store_true", help="Disable safety filter")
    p.add_argument("--negative", help="Negative prompt")
    p.add_argument("--image", help="Reference image (edit/img2img mode)")
    p.add_argument("--strength", type=float, help="Edit strength 0-1")
    p.add_argument("--extra", help="Extra parameters as JSON string")
    p.add_argument("-o", "--output", help="Output directory")

    # video
    p = subparsers.add_parser("video", help="AI video generation")
    p.add_argument("prompt", help="Video description prompt")
    p.add_argument("--model", required=True,
                   help="Model ID (e.g. alibaba/wan-2.6/text-to-video, kwaivgi/kling-v3.0-pro/text-to-video)")
    p.add_argument("--size", help="Video size (e.g. 1280*720)")
    p.add_argument("--aspect-ratio", help="Aspect ratio (e.g. 16:9, 9:16)")
    p.add_argument("--resolution", help="Resolution (e.g. 720p, 1080p)")
    p.add_argument("--duration", type=int, help="Duration in seconds")
    p.add_argument("--seed", type=int, help="Random seed")
    p.add_argument("--negative", help="Negative prompt")
    p.add_argument("--image", help="Reference image (image-to-video mode)")
    p.add_argument("--extra", help="Extra parameters as JSON string")
    p.add_argument("-o", "--output", help="Output directory")

    # models
    p = subparsers.add_parser("models", help="List available models from Atlas Cloud API")
    p.add_argument("--type", choices=["image", "video", "text", "audio"],
                   help="Filter by model type")
    p.add_argument("--search", help="Search models by keyword")

    args = parser.parse_args()
    if not args.command:
        parser.print_help()
        print("\nQuick start:")
        print('  uv run scripts/ai-generate.py models                                    # List all image/video models')
        print('  uv run scripts/ai-generate.py models --type image                       # List image models only')
        print('  uv run scripts/ai-generate.py models --search "nano banana"             # Search models')
        print('  uv run scripts/ai-generate.py image "A cat" --model black-forest-labs/flux-schnell')
        print('  uv run scripts/ai-generate.py video "Sunset" --model alibaba/wan-2.6/text-to-video')
        print()
        print("Get API Key: https://www.atlascloud.ai?utm_source=github&utm_campaign=free-image-and-video-generation-skill")
        sys.exit(0)

    commands = {"image": cmd_image, "video": cmd_video, "models": cmd_models}
    commands[args.command](args)


if __name__ == "__main__":
    main()


path: .claude/settings.local.json
content: {
  "permissions": {
    "allow": [
      "WebSearch"
    ]
  }
}


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)