Skill: nanophotohq/video-prompt-generator (version 1.0.0)

--- name: video-prompt-generator description: "Generate professional Sora 2 video prompts with the NanoPhoto.AI Prompt Generator API. Use when: (1) User wants a polished video prompt from a topic or scene idea, (2) User wants prompt generation for text-to-video or image-to-video workflows, (3) User mentions Sora 2 prompt generation, prompt writing, cinematic prompt creation, shot planning, or converting a topic into a production-ready video prompt. Supports locale, technique, duration, and public image URL inputs. Prerequisite: Obtain an API key at https://nanophoto.ai/settings/apikeys and configure env.NANOPHOTO_API_KEY." metadata: {"openclaw":{"homepage":"https://nanophoto.ai","requires":{"env":["NANOPHOTO_API_KEY"]},"primaryEnv":"NANOPHOTO_API_KEY"}} --- # Video Prompt Generator Generate polished Sora 2 video prompts through the NanoPhoto.AI Prompt Generator API. ## Prerequisites 1. Obtain an API key at: https://nanophoto.ai/settings/apikeys 2. Configure `NANOPHOTO_API_KEY` before using the skill. 3. Do not paste the API key into chat; store it in the platform's secure env setting for this skill. Preferred OpenClaw setup: - Open the skill settings for this skill - Add an environment variable named `NANOPHOTO_API_KEY` - Paste the API key as its value Equivalent config shape: ```json { "skills": { "entries": { "video-prompt-generator": { "enabled": true, "env": { "NANOPHOTO_API_KEY": "your_api_key_here" } } } } } ``` Other valid ways to provide the key: - **Shell**: `export NANOPHOTO_API_KEY="your_api_key_here"` - **Tool-specific env config**: any runtime that injects `NANOPHOTO_API_KEY` - **OpenClaw config fallback**: the bundled script also falls back to `~/.openclaw/openclaw.json` at `skills.entries.video-prompt-generator.env.NANOPHOTO_API_KEY` Credential declaration summary: - Primary credential: `NANOPHOTO_API_KEY` - Resolution order in the bundled script: `--api-key` → `NANOPHOTO_API_KEY` environment variable → `~/.openclaw/openclaw.json` skill env - No unrelated credentials are required ## Recommended workflow 1. Collect the user's topic or scene idea. 2. Decide whether the prompt is for `textToVideo` or `imageToVideo`. 3. Choose a technique, duration, locale, and optional model override. 4. For image-to-video mode, require one to three public image URLs. 5. Run the bundled script or call the API directly. 6. Return the generated prompt text exactly as produced unless the user asks for adaptation or rewriting. ## Parameter guidance - `topic` - Required - Maximum 500 characters - Keep it specific enough to imply subject, mood, or motion - `mode` - `textToVideo`: default - `imageToVideo`: requires public `imageUrls` - `technique` - Default: `montage` - Choose the technique that best matches the intended visual language - `duration` - `10` - `15` - `locale` - Default: `en` - Supported: `en`, `zh`, `zh-TW`, `ja`, `ko`, `es`, `fr`, `de`, `pt`, `ru`, `ar` ## Preferred command Use the bundled script for reliable prompt generation: ### Text-to-video prompt ```bash python3 scripts/video_prompt_generator.py \ --topic "A serene Japanese garden with cherry blossoms falling into a koi pond" \ --mode textToVideo \ --technique slow-motion \ --duration 15 \ --locale en ``` ### Image-to-video prompt ```bash python3 scripts/video_prompt_generator.py \ --topic "Animate this landscape with gentle wind and floating clouds" \ --mode imageToVideo \ --technique long-take \ --duration 10 \ --locale en \ --image-url https://example.com/landscape.jpg ``` The script resolves credentials in this order: `--api-key`, then `NANOPHOTO_API_KEY` from the environment, then `~/.openclaw/openclaw.json` at `skills.entries.video-prompt-generator.env.NANOPHOTO_API_KEY`. ## Output behavior - The API returns streaming text, but the bundled script prints the final assembled prompt text - Return the prompt directly when the user asked for prompt generation - If the user wants editing or localization after generation, transform the generated prompt in a second step instead of changing API parameters retroactively ## Manual API call ```bash curl -X POST "https://nanophoto.ai/api/sora-2/generate-prompt" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $NANOPHOTO_API_KEY" \ --data-raw '{ "topic": "A futuristic cityscape at sunset with flying vehicles and neon lights", "technique": "aerial-view", "duration": 15, "locale": "en" }' ``` ## Error handling | errorCode | Cause | Action | |-----------|-------|--------| | `LOGIN_REQUIRED` | Invalid or missing API key | Verify key at https://nanophoto.ai/settings/apikeys | | `API_KEY_RATE_LIMIT_EXCEEDED` | Rate limit exceeded | Wait and retry | | `INSUFFICIENT_CREDITS` | Not enough credits | Top up credits | | `INVALID_INPUT` | Missing or invalid topic | Ask for a valid topic under 500 characters | ## Bundled files - `scripts/video_prompt_generator.py`: generate a prompt from a topic using the NanoPhoto Prompt Generator API - `references/api.md`: condensed API reference, inputs, and error behavior

path: PUBLISHING.md
content: # Publishing / provenance notes

- Publisher: NanoPhotoHQ
- Service homepage: https://nanophoto.ai
- API key management page: https://nanophoto.ai/settings/apikeys
- Skill purpose: generate polished Sora 2 video prompts from a topic or image-guided scene idea through the NanoPhoto Prompt Generator API.

## Required credential

This skill requires exactly one credential:

- `NANOPHOTO_API_KEY` — required

Do not paste the API key into chat. Configure it through the platform's secure environment-variable setting for this skill.

## Safety / usage notes

- Only submit topics and image URLs you are authorized to use.
- For image-to-video mode, only provide public image URLs you are comfortable sending to the NanoPhoto service.
- Do not provide private or sensitive images as public URLs.
- This skill does not ask for unrelated credentials.
- This skill does not read messaging credentials or send external notifications.


path: references/api.md
content: # Sora 2 Prompt Generator API Reference

## Endpoint

- `POST https://nanophoto.ai/api/sora-2/generate-prompt`

## Authentication

```text
[REDACTED_SECRET]
```

For OpenClaw users, the recommended setup is:

- `env.NANOPHOTO_API_KEY=YOUR_API_KEY`
- Structured skill metadata should declare `requires.env=["NANOPHOTO_API_KEY"]` and `primaryEnv="NANOPHOTO_API_KEY"`

## Credits

- Costs 2 credits per generation
- Credits are pre-deducted and refunded automatically if generation fails

## Request fields

| Field | Type | Required | Notes |
|------|------|----------|------|
| `topic` | string | Yes | Max 500 characters |
| `mode` | string | No | `textToVideo` or `imageToVideo`; default `textToVideo` |
| `technique` | string | No | Default `montage` |
| `duration` | number | No | `10` or `15`; default `10` |
| `model` | string | No | Default `google/gemini-3-flash-preview` |
| `locale` | string | No | Default `en`; supported: `en`, `zh`, `zh-TW`, `ja`, `ko`, `es`, `fr`, `de`, `pt`, `ru`, `ar` |
| `imageUrls` | string[] | No | Public image URLs only; max 3; required for `imageToVideo` |

## Supported techniques

- `montage`
- `long-take`
- `time-lapse`
- `slow-motion`
- `tracking-shot`
- `aerial-view`
- `pov`
- `split-screen`
- `match-cut`
- `fade-transition`

## Response behavior

- The API returns streaming text (`Content-Type: text/plain; charset=utf-8`)
- The output is the generated Sora 2 prompt text
- The prompt typically includes scene structure, shot design, camera movement, lighting, mood, and timing details

## Error codes

| errorCode | HTTP Status | Description |
|-----------|-------------|-------------|
| `LOGIN_REQUIRED` | 401 | Authentication required |
| `API_KEY_RATE_LIMIT_EXCEEDED` | 429 | Rate limit exceeded |
| `INSUFFICIENT_CREDITS` | 402 | Not enough credits |
| `INVALID_INPUT` | 400 | Topic is missing, empty, or too long |

## Notes

- `imageUrls` must be publicly accessible URLs
- Base64 image upload is not supported
- Keep topics concise but descriptive for best results


path: scripts/video_prompt_generator.py
content: #!/usr/bin/env python3
import argparse
import json
import os
import sys
import urllib.error
import urllib.request
from typing import Optional

API_URL = "https://nanophoto.ai/api/sora-2/generate-prompt"
DEFAULT_OPENCLAW_CONFIG_PATH = os.path.expanduser("~/.openclaw/openclaw.json")
SKILL_NAME = "video-prompt-generator"
ENV_KEY_NAME = "NANOPHOTO_API_KEY"
VALID_MODES = {"textToVideo", "imageToVideo"}
VALID_TECHNIQUES = {
    "montage",
    "long-take",
    "time-lapse",
    "slow-motion",
    "tracking-shot",
    "aerial-view",
    "pov",
    "split-screen",
    "match-cut",
    "fade-transition",
}
VALID_LOCALES = {"en", "zh", "zh-TW", "ja", "ko", "es", "fr", "de", "pt", "ru", "ar"}
DEFAULT_MODEL = "google/gemini-3-flash-preview"
DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"


def fail(message: str, code: int = 1) -> None:
    print(message, file=sys.stderr)
    raise SystemExit(code)


def load_api_key_from_openclaw_config(config_path: str = DEFAULT_OPENCLAW_CONFIG_PATH) -> Optional[str]:
    try:
        with open(config_path, "r", encoding="utf-8") as fh:
            config = json.load(fh)
    except FileNotFoundError:
        return None
    except (OSError, json.JSONDecodeError):
        return None

    return (
        config.get("skills", {})
        .get("entries", {})
        .get(SKILL_NAME, {})
        .get("env", {})
        .get(ENV_KEY_NAME)
    )


def resolve_api_key(explicit_api_key: Optional[str]) -> Optional[str]:
    return explicit_api_key or os.environ.get(ENV_KEY_NAME) or load_api_key_from_openclaw_config()


def build_payload(args: argparse.Namespace) -> dict:
    if not args.topic or not args.topic.strip():
        fail("--topic is required.")
    topic = args.topic.strip()
    if len(topic) > 500:
        fail("--topic must be 500 characters or fewer.")

    payload = {
        "topic": topic,
        "mode": args.mode,
        "technique": args.technique,
        "duration": args.duration,
        "model": args.model,
        "locale": args.locale,
    }

    if args.mode == "imageToVideo":
        if not args.image_url:
            fail("imageToVideo mode requires at least one --image-url.")
        if len(args.image_url) > 3:
            fail("imageToVideo mode supports at most 3 --image-url values.")
        payload["imageUrls"] = args.image_url

    return payload


def generate_prompt(api_key: str, payload: dict, timeout: int, user_agent: str) -> str:
    request = urllib.request.Request(
        API_URL,
        data=json.dumps(payload).encode("utf-8"),
        headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}",
            "User-Agent": user_agent,
            "Accept": "text/plain, application/json;q=0.9, */*;q=0.8",
            "Origin": "https://nanophoto.ai",
            "Referer": "https://nanophoto.ai/",
        },
        method="POST",
    )
    try:
        with urllib.request.urlopen(request, timeout=timeout) as response:
            return response.read().decode("utf-8", "replace")
    except urllib.error.HTTPError as exc:
        body = exc.read().decode("utf-8", "replace")
        fail(f"HTTP {exc.code}: {body}")
    except urllib.error.URLError as exc:
        fail(f"Request failed: {exc}")


def main() -> None:
    parser = argparse.ArgumentParser(description="Generate professional Sora 2 video prompts from topics.")
    parser.add_argument("--topic", required=True, help="Video topic or scene description (max 500 chars)")
    parser.add_argument("--mode", default="textToVideo", choices=sorted(VALID_MODES), help="Prompt generation mode")
    parser.add_argument("--technique", default="montage", choices=sorted(VALID_TECHNIQUES), help="Video technique")
    parser.add_argument("--duration", type=int, default=10, choices=[10, 15], help="Video duration in seconds")
    parser.add_argument("--model", default=DEFAULT_MODEL, help="Prompt generation model")
    parser.add_argument("--locale", default="en", choices=sorted(VALID_LOCALES), help="Output language locale")
    parser.add_argument("--image-url", action="append", default=[], help="Public image URL for imageToVideo mode; may be passed up to 3 times")
    parser.add_argument("--api-key", help="NanoPhoto API key")
    parser.add_argument("--timeout", type=int, default=120, help="Request timeout in seconds (default: 120)")
    parser.add_argument("--user-agent", default=DEFAULT_USER_AGENT, help="HTTP User-Agent override")
    args = parser.parse_args()

    [REDACTED_SECRET])
    if not api_key:
        fail("Missing API key. Pass --api-key, set NANOPHOTO_API_KEY, or configure ~/.openclaw/openclaw.json skills.entries.video-prompt-generator.env.NANOPHOTO_API_KEY.")

    payload = build_payload(args)
    prompt = generate_prompt(api_key, payload, args.timeout, args.user_agent)
    print(prompt)


if __name__ == "__main__":
    main()


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