Skill: xxxxxxxxxxxxxxxxxxx20gex/video-intent-studio (version 1.0.0)

--- name: video-intent-studio description: Guide staged text-to-video generation from a rough user idea to ranked video type options, parameter tuning, prompt preview, and final Volcengine Ark video generation via bundled helper scripts. Use when a user wants help deciding video style, duration, ratio, motion, or prompt wording before generating a video, and when a reusable multi-turn workflow with simple state tracking is needed. --- # Video Intent Studio Follow a user-led workflow for video generation. Do not choose the final prompt for the user. Suggest options, keep the current prompt transparent, and ask for confirmation before generation. ## Core behavior - Respond in the user's language. Default to Chinese if the user is writing in Chinese. - Keep the workflow layered: intent -> ranked video types -> parameter tuning -> prompt confirmation -> generation. - Present 3 to 5 options at the type-selection step. Keep the option set stable and only reorder by relevance. - Show the current prompt preview whenever parameters change. - Let the user revise or exit at every stage. - Keep a simple state object in memory or scratch space: ```python state = { "user_input": "", "selected_type": None, "params": { "duration": 8, "ratio": "16:9", "motion": "medium", "style": "original", "brightness": "normal", "subtitle": "off", "dream_filter": "off", }, "final_prompt": "", "stage": "init", } ``` ## Skill directory and script paths When this `SKILL.md` is loaded, resolve the skill directory from the absolute path of this file. - Suggestion and prompt builder: - `<skill-dir>/scripts/video_agent_backend.py` - Final generator: - `<skill-dir>/scripts/generate_ark_video.py` - Type catalog and defaults: - `<skill-dir>/references/video-types.md` - Usage walkthrough and examples: - `<skill-dir>/references/usage-guide.md` Use absolute paths when running the scripts. ## Recommended workflow 1. Capture the user's raw idea. - Keep the original wording in state as `user_input`. - Do not rewrite it into a final prompt yet. 2. Rank video types. - Run: ```bash python "<skill-dir>/scripts/video_agent_backend.py" suggest --input "user idea" ``` - Present the top 3 to 5 results as numbered options. - For each option, include: - type name - one-sentence use case - default duration and ratio - short reason why it matches 3. After the user chooses a type, build a prompt preview. - Run: ```bash python "<skill-dir>/scripts/video_agent_backend.py" build ^ --input "user idea" ^ --type cinematic-story ``` - Show: - current prompt preview - current parameter summary - a short numbered list of tunable options 4. If the user adjusts settings, rerun `build` with explicit parameters. - Supported parameters: - `--duration 5|8|10|12` - `--ratio 9:16|16:9|1:1|4:3` - `--motion light|medium|strong` - `--style realistic|anime|cinematic|original` - `--brightness moody|normal|bright` - `--subtitle off|on` - `--dream-filter off|on` - `--notes "extra user constraint"` 5. Before generation, show the final prompt and ask for confirmation. - Use a short confirmation question such as: - "Final prompt and parameters are ready. Generate now?" 6. After explicit confirmation, generate the video. - Run: ```bash python "<skill-dir>/scripts/generate_ark_video.py" ^ --prompt "final prompt text" ^ --output "C:\path\to\result.mp4" ``` 7. Report success or failure clearly. - On success, give the downloaded file path and task id if available. - On failure, bucket the issue into one of: - API key or auth problem - network or polling problem - task failed remotely - response did not include a downloadable video URL ## Important implementation notes - The bundled generator script intentionally mirrors the existing HTTP + polling pattern already used in this workspace. - The current API request sends a text prompt payload only. Duration, ratio, motion, style, and other controls are encoded into the prompt text unless you later extend the API payload. - The generator script reads credentials from environment variables first: - `ARK_API_KEY` - `VOLCENGINE_ARK_API_KEY` - Optional environment variables: - `ARK_VIDEO_MODEL` - `ARK_VIDEO_TASKS_URL` ## Conversation rules - Do not skip the type-selection step unless the user explicitly says they already know the type. - Do not ask broad open-ended questions if a numbered choice is possible. - If the user says "more realistic", "more cinematic", "shorter", "vertical", or similar, treat that as a parameter update and keep moving. - If the user says "generate", "go", or "就这样生成", show the final prompt once and ask for one explicit confirmation unless they already confirmed in the same message. ## When to read references - Read [references/video-types.md](references/video-types.md) when you need the fixed type list, defaults, or category-specific prompt leads. - Read [references/usage-guide.md](references/usage-guide.md) when you need example conversations, sample commands, or the user-facing tutorial flow.

path: scripts/generate_ark_video.py
content: #!/usr/bin/env python3
"""Generate a video through the existing Ark HTTP + polling workflow."""

from __future__ import annotations

import argparse
import json
import os
import sys
import time
import urllib.error
import urllib.request


DEFAULT_MODEL = "doubao-seedance-1-5-pro-251215"
DEFAULT_TASKS_URL = "https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks"
API_KEY_CANDIDATES = ("ARK_API_KEY", "VOLCENGINE_ARK_API_KEY", "ARK API KEY")


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Generate video with Volcengine Ark task polling")
    parser.add_argument("--prompt", required=True, help="Final prompt to submit")
    parser.add_argument("--output", help="Output mp4 path")
    parser.add_argument("--api-key", help="Ark API key, otherwise read from environment")
    parser.add_argument("--model", default=os.environ.get("ARK_VIDEO_MODEL", DEFAULT_MODEL), help="Model name")
    parser.add_argument(
        "--tasks-url",
        default=os.environ.get("ARK_VIDEO_TASKS_URL", DEFAULT_TASKS_URL),
        help="Task endpoint URL",
    )
    parser.add_argument("--poll-interval", type=float, default=2.0, help="Polling interval in seconds")
    parser.add_argument("--timeout", type=int, default=300, help="Max wait time in seconds")
    parser.add_argument("--dry-run", action="store_true", help="Print payload without sending the request")
    return parser.parse_args()


def read_windows_env_registry() -> str | None:
    if os.name != "nt":
        return None

    try:
        import winreg
    except ImportError:
        return None

    registry_paths = (
        (winreg.HKEY_CURRENT_USER, r"Environment"),
        (winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment"),
    )

    for root, path in registry_paths:
        try:
            with winreg.OpenKey(root, path) as key:
                for name in API_KEY_CANDIDATES:
                    try:
                        value, _value_type = winreg.QueryValueEx(key, name)
                    except FileNotFoundError:
                        continue
                    if value:
                        return value
        except OSError:
            continue

    return None


def resolve_api_key(explicit_key: str | None) -> str:
    [REDACTED_SECRET]
    if not api_key:
        for name in API_KEY_CANDIDATES:
            [REDACTED_SECRET])
            if api_key:
                break
    if not [REDACTED_SECRET] = read_windows_env_registry()
    if not api_key:
        raise SystemExit(
            "Missing API key. Set ARK_API_KEY or VOLCENGINE_ARK_API_KEY, "
            "or pass --api-key."
        )
    return api_key


def request_json(method: str, url: str, api_key: str, data: dict | None = None) -> dict:
    req = urllib.request.Request(url, method=method)
    req.add_header("Content-Type", "application/json")
    req.add_header("Authorization", f"Bearer {api_key}")
    payload = None
    if data is not None:
        payload = json.dumps(data).encode("utf-8")

    try:
        with urllib.request.urlopen(req, data=payload) as response:
            body = response.read().decode("utf-8")
    except urllib.error.HTTPError as exc:
        detail = exc.read().decode("utf-8", errors="replace")
        raise RuntimeError(f"HTTP {exc.code}: {detail}") from exc
    except urllib.error.URLError as exc:
        raise RuntimeError(f"Network error: {exc.reason}") from exc

    return json.loads(body)


def extract_task_id(task_payload: dict) -> str | None:
    return task_payload.get("id") or task_payload.get("data", {}).get("id")


def extract_video_url(payload: object) -> str | None:
    if isinstance(payload, dict):
        for key, value in payload.items():
            if key in ("url", "video_url") and isinstance(value, str) and value.startswith("http"):
                return value
            nested = extract_video_url(value)
            if nested:
                return nested
    elif isinstance(payload, list):
        for item in payload:
            nested = extract_video_url(item)
            if nested:
                return nested
    return None


def default_output_path(task_id: str) -> str:
    desktop = os.path.join(os.path.expanduser("~"), "Desktop")
    return os.path.join(desktop, f"ark_video_{task_id}.mp4")


def download_file(url: str, output_path: str) -> None:
    output_dir = os.path.dirname(output_path)
    if output_dir:
        os.makedirs(output_dir, exist_ok=True)
    urllib.request.urlretrieve(url, output_path)


def main() -> None:
    args = parse_args()
    payload = {
        "model": args.model,
        "content": [{"type": "text", "text": args.prompt}],
    }

    if args.dry_run:
        print(json.dumps({"status": "dry_run", "payload": payload}, ensure_ascii=False, indent=2))
        return

    [REDACTED_SECRET])

    try:
        task_payload = request_json("POST", args.tasks_url, api_key, payload)
        task_id = extract_task_id(task_payload)
        if not task_id:
            raise RuntimeError(f"Task created without id: {json.dumps(task_payload, ensure_ascii=False)}")

        start_time = time.time()
        last_status_payload = None

        while True:
            last_status_payload = request_json("GET", f"{args.tasks_url}/{task_id}", api_key)
            status = (
                last_status_payload.get("status")
                or last_status_payload.get("data", {}).get("status")
                or ""
            ).lower()

            if status in ("succeeded", "success", "completed"):
                break
            if status in ("failed", "error"):
                raise RuntimeError(
                    f"Remote task failed: {json.dumps(last_status_payload, ensure_ascii=False)}"
                )
            if time.time() - start_time > args.timeout:
                raise RuntimeError(
                    f"Polling timed out after {args.timeout}s: {json.dumps(last_status_payload, ensure_ascii=False)}"
                )
            time.sleep(args.poll_interval)

        video_url = extract_video_url(last_status_payload)
        if not video_url:
            raise RuntimeError(
                f"Task succeeded but no video url was found: {json.dumps(last_status_payload, ensure_ascii=False)}"
            )

        output_path = args.output or default_output_path(task_id)
        download_file(video_url, output_path)
        result = {
            "status": "succeeded",
            "task_id": task_id,
            "output_path": output_path,
            "video_url": video_url,
        }
        print(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as exc:
        print(json.dumps({"status": "failed", "error": str(exc)}, ensure_ascii=False, indent=2))
        sys.exit(1)


if __name__ == "__main__":
    main()


path: scripts/video_agent_backend.py
content: #!/usr/bin/env python3
"""Helpers for staged video suggestion and prompt building."""

from __future__ import annotations

import argparse
import json
from copy import deepcopy


VIDEO_TYPES = [
    {
        "id": "cinematic-story",
        "number": 1,
        "name": "电影感剧情短片",
        "summary": "适合情绪推进、悬疑感、广告式叙事和镜头表达。",
        "template_lead": "Cinematic tone, story-first framing, layered lighting, deliberate camera movement.",
        "defaults": {
            "duration": 10,
            "ratio": "16:9",
            "motion": "medium",
            "style": "cinematic",
            "brightness": "normal",
            "subtitle": "off",
            "dream_filter": "off",
        },
        "keywords": {
            "剧情": 5,
            "故事": 5,
            "电影": 5,
            "悬疑": 4,
            "情感": 4,
            "预告片": 4,
            "叙事": 4,
            "广告": 3,
            "cinematic": 5,
            "story": 5,
            "drama": 4,
            "film": 4,
            "trailer": 4,
            "emotional": 4,
        },
    },
    {
        "id": "vertical-social",
        "number": 2,
        "name": "竖屏短视频爆款",
        "summary": "适合短视频平台、强节奏开头和竖屏传播场景。",
        "template_lead": "Vertical video, immediate hook in the first second, punchy rhythm, mobile-native framing.",
        "defaults": {
            "duration": 6,
            "ratio": "9:16",
            "motion": "strong",
            "style": "original",
            "brightness": "bright",
            "subtitle": "off",
            "dream_filter": "off",
        },
        "keywords": {
            "竖屏": 6,
            "短视频": 6,
            "抖音": 7,
            "小红书": 7,
            "快手": 7,
            "爆款": 5,
            "吸睛": 4,
            "开头": 3,
            "hook": 5,
            "viral": 5,
            "vertical": 6,
            "social": 4,
            "reel": 5,
            "short-form": 5,
        },
    },
    {
        "id": "landscape-atmosphere",
        "number": 3,
        "name": "唯美风景/氛围片",
        "summary": "适合风景、治愈、旅行、星空和氛围类内容。",
        "template_lead": "Atmospheric beauty, soft light, immersive scenery, slow cinematic drift.",
        "defaults": {
            "duration": 12,
            "ratio": "16:9",
            "motion": "light",
            "style": "cinematic",
            "brightness": "bright",
            "subtitle": "off",
            "dream_filter": "on",
        },
        "keywords": {
            "风景": 6,
            "治愈": 6,
            "旅行": 5,
            "星空": 6,
            "海边": 5,
            "森林": 5,
            "草地": 5,
            "雨": 3,
            "日落": 6,
            "雪山": 6,
            "氛围": 5,
            "landscape": 6,
            "atmosphere": 5,
            "nature": 5,
            "travel": 5,
            "sky": 4,
            "sunset": 6,
            "rain": 3,
        },
    },
    {
        "id": "commercial-product",
        "number": 4,
        "name": "动态产品展示/商业",
        "summary": "适合电商、品牌宣传、3C 产品和商业展示。",
        "template_lead": "Premium commercial look, product-focused composition, polished reflections, smooth transitions.",
        "defaults": {
            "duration": 8,
            "ratio": "16:9",
            "motion": "medium",
            "style": "realistic",
            "brightness": "bright",
            "subtitle": "off",
            "dream_filter": "off",
        },
        "keywords": {
            "产品": 7,
            "手机": 8,
            "耳机": 7,
            "口红": 7,
            "汽车": 7,
            "品牌": 6,
            "电商": 6,
            "展示": 5,
            "商业": 6,
            "宣传": 5,
            "product": 7,
            "commercial": 6,
            "brand": 6,
            "showcase": 5,
            "device": 6,
            "promo": 5,
        },
    },
    {
        "id": "abstract-experimental",
        "number": 5,
        "name": "抽象艺术/实验风",
        "summary": "适合 MV、赛博朋克、超现实和强风格实验画面。",
        "template_lead": "Bold experimental visuals, surreal motion, graphic forms, high style intensity.",
        "defaults": {
            "duration": 10,
            "ratio": "16:9",
            "motion": "strong",
            "style": "original",
            "brightness": "normal",
            "subtitle": "off",
            "dream_filter": "off",
        },
        "keywords": {
            "抽象": 7,
            "艺术": 6,
            "实验": 6,
            "赛博朋克": 7,
            "蒸汽波": 7,
            "超现实": 7,
            "几何": 5,
            "mv": 5,
            "abstract": 7,
            "experimental": 6,
            "surreal": 7,
            "cyberpunk": 7,
            "vaporwave": 7,
            "music video": 5,
        },
    },
]


STYLE_TEXT = {
    "realistic": "realistic texture, believable lighting, grounded physical detail",
    "anime": "anime-inspired shapes, crisp outlines, stylized color design",
    "cinematic": "cinematic lighting, depth, lens language, polished grading",
    "original": "preserve the user's original tone without over-stylizing",
}

MOTION_TEXT = {
    "light": "gentle drifting movement",
    "medium": "clear but controlled camera motion",
    "strong": "dynamic camera motion with fast visual energy",
}

BRIGHTNESS_TEXT = {
    "moody": "moody contrast and darker atmosphere",
    "normal": "balanced exposure and natural contrast",
    "bright": "bright and airy presentation",
}

SUBTITLE_TEXT = {
    "off": "no on-screen subtitle",
    "on": "include concise on-screen subtitle guidance if the model supports it",
}

DREAM_FILTER_TEXT = {
    "off": "no dreamy diffusion effect",
    "on": "enabled for a soft poetic glow",
}


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Video intent suggestion and prompt builder")
    subparsers = parser.add_subparsers(dest="command", required=True)

    suggest = subparsers.add_parser("suggest", help="Rank fixed video types for a user idea")
    suggest.add_argument("--input", required=True, help="Raw user idea")
    suggest.add_argument("--limit", type=int, default=4, help="How many ranked options to return (3-5 recommended)")
    suggest.add_argument("--format", choices=("json", "markdown"), default="json", help="Output format")

    build = subparsers.add_parser("build", help="Build a prompt preview for a chosen video type")
    build.add_argument("--input", required=True, help="Raw user idea")
    build.add_argument("--type", required=True, dest="type_id", help="Video type id")
    build.add_argument("--duration", type=int, choices=(5, 8, 10, 12), help="Target duration in seconds")
    build.add_argument("--ratio", choices=("9:16", "16:9", "1:1", "4:3"), help="Aspect ratio")
    build.add_argument("--motion", choices=("light", "medium", "strong"), help="Motion intensity")
    build.add_argument("--style", choices=("realistic", "anime", "cinematic", "original"), help="Style preference")
    build.add_argument("--brightness", choices=("moody", "normal", "bright"), help="Brightness preference")
    build.add_argument("--subtitle", choices=("off", "on"), help="Subtitle toggle")
    build.add_argument("--dream-filter", choices=("off", "on"), dest="dream_filter", help="Dream filter toggle")
    build.add_argument("--notes", action="append", default=[], help="Additional user notes")
    build.add_argument("--format", choices=("json", "markdown"), default="json", help="Output format")

    return parser.parse_args()


def normalize(text: str) -> str:
    return " ".join(text.lower().strip().split())


def score_video_type(user_input: str, video_type: dict) -> tuple[int, list[str]]:
    text = normalize(user_input)
    score = 0
    hits = []
    for keyword, weight in video_type["keywords"].items():
        if keyword in text:
            score += weight
            hits.append(keyword)
    return score, hits


def build_reason(score: int, hits: list[str]) -> str:
    if hits:
        preview = ", ".join(hits[:4])
        return f"Matched keywords: {preview}"
    if score <= 0:
        return "No strong keyword hit, kept as a stable fallback option"
    return "Matched general intent"


def rank_video_types(user_input: str, limit: int) -> list[dict]:
    ranked = []
    for video_type in VIDEO_TYPES:
        score, hits = score_video_type(user_input, video_type)
        ranked.append((score, video_type["number"], video_type, hits))

    ranked.sort(key=lambda item: (-item[0], item[1]))
    safe_limit = min(max(limit, 3), 5)

    results = []
    for rank, item in enumerate(ranked[:safe_limit], start=1):
        score, _number, video_type, hits = item
        results.append(
            {
                "rank": rank,
                "id": video_type["id"],
                "number": video_type["number"],
                "name": video_type["name"],
                "summary": video_type["summary"],
                "reason": build_reason(score, hits),
                "match_score": score,
                "defaults": deepcopy(video_type["defaults"]),
                "template_lead": video_type["template_lead"],
            }
        )
    return results


def get_video_type(type_id: str) -> dict:
    for video_type in VIDEO_TYPES:
        if video_type["id"] == type_id:
            return deepcopy(video_type)
    raise SystemExit(f"Unknown video type: {type_id}")


def merge_params(video_type: dict, args: argparse.Namespace) -> dict:
    params = deepcopy(video_type["defaults"])
    for key in ("duration", "ratio", "motion", "style", "brightness", "subtitle", "dream_filter"):
        value = getattr(args, key, None)
        if value is not None:
            params[key] = value
    return params


def build_prompt(user_input: str, video_type: dict, params: dict, notes: list[str]) -> str:
    sections = [
        video_type["template_lead"],
        f"Core idea: {user_input.strip()}.",
        f"Duration: {params['duration']}s.",
        f"Aspect ratio: {params['ratio']}.",
        f"Camera motion: {MOTION_TEXT[params['motion']]}.",
        f"Style: {STYLE_TEXT[params['style']]}.",
        f"Brightness: {BRIGHTNESS_TEXT[params['brightness']]}.",
        f"Dream filter: {DREAM_FILTER_TEXT[params['dream_filter']]}.",
        f"Subtitle: {SUBTITLE_TEXT[params['subtitle']]}.",
    ]
    for note in notes:
        cleaned = note.strip()
        if cleaned:
            sections.append(f"Additional user note: {cleaned}.")
    return " ".join(sections)


def make_state(user_input: str, stage: str, selected_type: str | None, params: dict, final_prompt: str) -> dict:
    return {
        "user_input": user_input,
        "selected_type": selected_type,
        "params": deepcopy(params),
        "final_prompt": final_prompt,
        "stage": stage,
    }


def format_markdown_suggestions(user_input: str, suggestions: list[dict]) -> str:
    lines = [f"Input idea: {user_input}", "", "Ranked video types:"]
    for item in suggestions:
        defaults = item["defaults"]
        lines.append(
            f"{item['rank']}. {item['name']} (`{item['id']}`) - {item['summary']} "
            f"Default: {defaults['duration']}s, {defaults['ratio']}. {item['reason']}."
        )
    return "\n".join(lines)


def format_markdown_build(video_type: dict, params: dict, prompt: str, notes: list[str]) -> str:
    lines = [
        f"Selected type: {video_type['name']} (`{video_type['id']}`)",
        "",
        "Current parameters:",
        f"- duration: {params['duration']}s",
        f"- ratio: {params['ratio']}",
        f"- motion: {params['motion']}",
        f"- style: {params['style']}",
        f"- brightness: {params['brightness']}",
        f"- subtitle: {params['subtitle']}",
        f"- dream_filter: {params['dream_filter']}",
    ]
    if notes:
        lines.append(f"- notes: {', '.join(note for note in notes if note.strip())}")
    lines.extend(["", "Prompt preview:", prompt])
    return "\n".join(lines)


def main() -> None:
    args = parse_args()

    if args.command == "suggest":
        suggestions = rank_video_types(args.input, args.limit)
        payload = {
            "user_input": args.input,
            "options": suggestions,
            "state": make_state(
                user_input=args.input,
                stage="choose_type",
                selected_type=None,
                params={},
                final_prompt="",
            ),
        }
        if args.format == "markdown":
            print(format_markdown_suggestions(args.input, suggestions))
            return
        print(json.dumps(payload, ensure_ascii=False, indent=2))
        return

    if args.command == "build":
        video_type = get_video_type(args.type_id)
        params = merge_params(video_type, args)
        prompt = build_prompt(args.input, video_type, params, args.notes)
        payload = {
            "user_input": args.input,
            "selected_type": {
                "id": video_type["id"],
                "number": video_type["number"],
                "name": video_type["name"],
                "summary": video_type["summary"],
            },
            "params": params,
            "notes": [note for note in args.notes if note.strip()],
            "prompt": prompt,
            "state": make_state(
                user_input=args.input,
                stage="confirm",
                selected_type=video_type["id"],
                params=params,
                final_prompt=prompt,
            ),
        }
        if args.format == "markdown":
            print(format_markdown_build(video_type, params, prompt, args.notes))
            return
        print(json.dumps(payload, ensure_ascii=False, indent=2))
        return


if __name__ == "__main__":
    main()


path: references/usage-guide.md
content: # Usage Guide

This file is the quick tutorial for using the skill in a ClawHub-style workflow.

## 1. What this skill does

Turn one rough idea into a staged video generation flow:

1. Understand intent
2. Offer 3 to 5 video type options
3. Let the user pick a type
4. Tune key parameters
5. Show the full prompt and ask for confirmation
6. Generate through the bundled HTTP + polling script

## 2. Folder layout

```text
video-intent-studio/
|- SKILL.md
|- agents/openai.yaml
|- references/
|  |- usage-guide.md
|  `- video-types.md
`- scripts/
   |- generate_ark_video.py
   `- video_agent_backend.py
```

## 3. Environment setup

Set the API key before generation.

```powershell
$env:ARK_API_KEY="your-volcengine-ark-key"
```

Optional model override:

```powershell
$env:ARK_VIDEO_MODEL="doubao-seedance-1-5-pro-251215"
```

## 4. Suggested interaction pattern

### Step A: rank video types

```powershell
python "<skill-dir>/scripts/video_agent_backend.py" suggest --input "一个治愈的海边日落视频"
```

Use the result to answer in a style like:

```text
我先给你 4 个更贴近这个需求的视频方向，请选编号：
1. 唯美风景/氛围片：更适合海边、日落、治愈感，默认 12 秒，16:9
2. 电影感剧情短片：如果你想要更强叙事和情绪推进，默认 10 秒，16:9
3. 竖屏短视频爆款：如果你想发短视频平台，默认 6 秒，9:16
4. 抽象艺术/实验风：如果你想做更梦幻、更超现实的画面，默认 10 秒，16:9
```

### Step B: build a prompt preview after the user picks a type

```powershell
python "<skill-dir>/scripts/video_agent_backend.py" build `
  --input "一个治愈的海边日落视频" `
  --type landscape-atmosphere `
  --duration 12 `
  --ratio 16:9 `
  --motion light `
  --style cinematic `
  --brightness bright `
  --dream-filter on
```

Use the result to answer in a style like:

```text
当前我帮你整理成这个方向：

Prompt 预览：
Atmospheric beauty, soft light, immersive scenery, slow cinematic drift. Core idea: 一个治愈的海边日落视频. Duration: 12s. Aspect ratio: 16:9. Camera motion: gentle drifting movement. Style: cinematic lighting and depth. Brightness: bright and airy. Dream filter: enabled for a soft poetic glow. Subtitle: no on-screen subtitle.

当前参数：
- 时长：12 秒
- 比例：16:9
- 动态：轻微运镜
- 风格：更电影感
- 亮度：更明亮
- 梦幻滤镜：开

你可以直接回复：
- 1 改成 9:16
- 2 更写实一点
- 3 时长改 8 秒
- 4 就这样生成
```

### Step C: final confirmation

Before generation, always show the full prompt and parameters once more.

Example:

```text
最终 prompt 已整理好：
[full prompt]

参数总结：
- 时长：12 秒
- 比例：16:9
- 运镜：轻微
- 风格：电影感

确认生成吗？回复 是 / Y / 生成 即可。
```

### Step D: generate

```powershell
python "<skill-dir>/scripts/generate_ark_video.py" `
  --prompt "Atmospheric beauty, soft light, immersive scenery..." `
  --output "C:\Users\WUJIEAI\Desktop\video\output\sunset.mp4"
```

The script prints JSON including:

- `task_id`
- `status`
- `output_path`
- `video_url`

## 5. Example user journeys

### Example 1: product video

User says:

```text
我想做一个手机产品展示视频，质感高级一点
```

Recommended order:

1. `commercial-product`
2. `cinematic-story`
3. `vertical-social`
4. `abstract-experimental`

### Example 2: scenic mood clip

User says:

```text
想做治愈星空和草地的感觉
```

Recommended order:

1. `landscape-atmosphere`
2. `cinematic-story`
3. `abstract-experimental`
4. `vertical-social`

### Example 3: short-form vertical clip

User says:

```text
做一个适合抖音的竖屏爆款视频，要一开始就抓人
```

Recommended order:

1. `vertical-social`
2. `commercial-product`
3. `cinematic-story`
4. `abstract-experimental`

## 6. Extension points

This skill is intentionally designed to be easy to extend.

- Add image-to-video:
  - extend `generate_ark_video.py` to accept input image fields
  - add new references and a new type or mode flag
- Add first-frame / last-frame controls:
  - keep the same staged workflow
  - add new CLI arguments and include them in prompt preview and confirmation
- Add multiple model backends:
  - expose `--model`
  - add a backend switch while keeping `suggest` and `build` unchanged

## 7. Troubleshooting

- If generation fails immediately, check `ARK_API_KEY`.
- If polling times out, retry with a longer timeout or inspect the printed task payload.
- If no video URL is returned, save the JSON output and inspect the remote response format before changing the extractor.


path: references/video-types.md
content: # Video Types

Use these five stable categories. Reorder them by relevance, but keep the taxonomy stable so users can learn the system.

| No. | Type ID | Type name | Default duration | Default ratio | Best for | Prompt lead |
| --- | --- | --- | --- | --- | --- | --- |
| 1 | `cinematic-story` | Movie-like story short | 10s | 16:9 | emotion, suspense, ad-like storytelling, dramatic reveal | `Cinematic tone, story-first framing, layered lighting, deliberate camera movement.` |
| 2 | `vertical-social` | Vertical short-form hit | 6s | 9:16 | Douyin/Xiaohongshu/Kuaishou style hooks, fast visual rhythm, phone-first viewing | `Vertical video, immediate hook in the first second, punchy rhythm, mobile-native framing.` |
| 3 | `landscape-atmosphere` | Scenic or atmosphere film | 12s | 16:9 | healing landscapes, travel mood, sky, rain, nature, wallpaper-like shots | `Atmospheric beauty, soft light, immersive scenery, slow cinematic drift.` |
| 4 | `commercial-product` | Product showcase or commercial | 8s | 16:9 | phone, cosmetics, devices, brand promo, clean product transitions | `Premium commercial look, product-focused composition, polished reflections, smooth transitions.` |
| 5 | `abstract-experimental` | Abstract or experimental visual | 10s | 16:9 | cyberpunk, MV visuals, surreal graphics, vaporwave, concept art motion | `Bold experimental visuals, surreal motion, graphic forms, high style intensity.` |

## Ranking hints

Use these keyword families as ranking hints. The bundled script uses similar logic.

- `cinematic-story`
  - Chinese hints: `剧情`, `故事`, `电影`, `悬疑`, `情感`, `预告片`, `叙事`
  - English hints: `cinematic`, `story`, `drama`, `trailer`, `emotional`, `film`

- `vertical-social`
  - Chinese hints: `竖屏`, `短视频`, `抖音`, `小红书`, `快手`, `爆款`, `开头吸睛`
  - English hints: `vertical`, `social`, `hook`, `viral`, `short-form`, `reel`

- `landscape-atmosphere`
  - Chinese hints: `风景`, `治愈`, `旅行`, `星空`, `海边`, `雨`, `日落`, `雪山`, `氛围`
  - English hints: `landscape`, `atmosphere`, `nature`, `travel`, `sky`, `sunset`, `rain`

- `commercial-product`
  - Chinese hints: `产品`, `手机`, `耳机`, `口红`, `汽车`, `品牌`, `电商`, `展示`, `商业`
  - English hints: `product`, `commercial`, `brand`, `showcase`, `device`, `promo`

- `abstract-experimental`
  - Chinese hints: `抽象`, `艺术`, `实验`, `赛博朋克`, `蒸汽波`, `超现实`, `几何`, `MV`
  - English hints: `abstract`, `experimental`, `surreal`, `cyberpunk`, `vaporwave`, `music video`

## Default parameter recommendations

| Type ID | Motion | Style | Brightness | Subtitle | Dream filter |
| --- | --- | --- | --- | --- | --- |
| `cinematic-story` | `medium` | `cinematic` | `normal` | `off` | `off` |
| `vertical-social` | `strong` | `original` | `bright` | `off` | `off` |
| `landscape-atmosphere` | `light` | `cinematic` | `bright` | `off` | `on` |
| `commercial-product` | `medium` | `realistic` | `bright` | `off` | `off` |
| `abstract-experimental` | `strong` | `original` | `normal` | `off` | `off` |

## Prompting rule

Wrap the user's idea with a category lead and parameter language, but do not invent story elements the user did not ask for. The prompt should feel like a clarified version of the user's intent, not a replacement for it.


path: agents/openai.yaml
content: interface:
  display_name: "Video Intent Studio"
  short_description: "Guide staged AI video ideation and Ark generation"
  default_prompt: "Use $video-intent-studio to turn a rough idea into ranked video options, tuned parameters, and a final Ark video generation prompt."


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