# PonyFlash Python SDK

AI-native image, video, speech, and music generation SDK.

**Zero friction file handling** — pass `open()` file objects, `Path` objects, URLs, `bytes`, or `file_id` strings. The SDK auto-uploads via presigned URLs and cleans up temp files when the task completes.

## Installation

```bash
pip install ponyflash
```

## Quick Start

```python
from ponyflash import PonyFlash

client = PonyFlash(api_key="pf_xxx")

# Text-to-image
gen = client.images.generate(
    model="nano-banana-pro",
    prompt="A sunset over mountains",
    resolution="2K",
)
print(gen.url)                         # first output URL
print(f"Credits used: {gen.credits}")  # credits consumed
```

## Video Generation

```python
from pathlib import Path

# Text-to-video
gen = client.video.generate(
    model="video-gen-1",
    prompt="A timelapse of a city at night",
    size="1920x1080",
    duration=8,
)
print(gen.url)

# First-frame to video (local file)
with open("my_photo.jpg", "rb") as f:
    gen = client.video.generate(
        model="video-gen-1",
        first_frame=f,
        prompt="Camera slowly zooms in",
    )

# First-frame to video (public URL)
gen = client.video.generate(
    model="video-gen-1",
    first_frame="https://example.com/photo.jpg",
    prompt="Camera slowly zooms in",
)

# OmniHuman: portrait + audio → talking video
with open("portrait.jpg", "rb") as img, open("speech.wav", "rb") as audio:
    gen = client.video.generate(
        model="omnihuman-1.5",
        first_frame=img,
        audio=audio,
        prompt="Natural speaking with subtle hand gestures",
        size="1280x720",
    )

# OmniHuman with fast mode and seed
with open("portrait.jpg", "rb") as img, open("speech.wav", "rb") as audio:
    gen = client.video.generate(
        model="omnihuman-1.5",
        first_frame=img,
        audio=audio,
        seed=42,
        fast_mode=True,
    )

# Motion Transfer: person image + dance video → person performs the dance
with open("my_avatar.jpg", "rb") as img, open("dance_clip.mp4", "rb") as vid:
    gen = client.video.generate(
        model="motion-transfer-1",
        first_frame=img,
        motion_video=vid,
        size="1280x720",
    )
```

## Image Generation

```python
# Text-to-image
gen = client.images.generate(
    model="nano-banana-pro",
    prompt="A sunset",
    resolution="2K",
    aspect_ratio="16:9",
)

# Image-to-image (local file)
with open("source.png", "rb") as f:
    gen = client.images.generate(
        model="nano-banana-pro",
        prompt="Make it look like a watercolor painting",
        reference_images=[f],
    )

# Image-to-image (public URL)
gen = client.images.generate(
    model="nano-banana-pro",
    prompt="Make it look like a watercolor painting",
    reference_images=["https://example.com/source.png"],
)

# Inpainting with mask
with open("photo.jpg", "rb") as img, open("mask.png", "rb") as mask:
    gen = client.images.generate(
        model="nano-banana-pro",
        prompt="Replace the sky with aurora borealis",
        reference_images=[img],
        mask=mask,
    )
```

## Speech Synthesis (TTS)

```python
gen = client.speech.generate(
    model="speech-2.8-hd",
    input="欢迎使用 PonyFlash，这是一段语音合成示例。",
    voice="English_Graceful_Lady",
    language="zh-CN",
)
print(gen.url)

# With emotion and pitch control
gen = client.speech.generate(
    model="speech-2.8-hd",
    input="今天天气真好，我好开心！",
    voice="English_Insightful_Speaker",
    emotion="happy",
    pitch=2,
    speed=1.1,
)
```

## Music Generation

```python
gen = client.music.generate(
    model="suno-v4.5",
    prompt="A melancholic indie folk ballad with acoustic guitar",
    title="Autumn Leaves",
    duration=180,
)

# Extend from reference audio
with open("my_song_clip.mp3", "rb") as f:
    gen = client.music.generate(
        model="suno-v4.5",
        prompt="Continue with an energetic chorus",
        reference_audio=f,
        continue_at=60.0,
    )
```

## Downloading Results

```python
import httpx

gen = client.images.generate(
    model="nano-banana-pro",
    prompt="A cat wearing sunglasses",
    resolution="2K",
)

# Download the generated image
resp = httpx.get(gen.url)
with open("output.png", "wb") as f:
    f.write(resp.content)

# Multiple outputs
for i, url in enumerate(gen.urls):
    resp = httpx.get(url)
    with open(f"output_{i}.png", "wb") as f:
        f.write(resp.content)
```

## File Input Types

Every file parameter (`reference_images`, `mask`, `first_frame`, `audio`, `motion_video`, `reference_audio`, ...) accepts:

| Input | Example | Behavior |
|-------|---------|----------|
| Open file object | `open("photo.jpg", "rb")` | **Recommended.** Auto-uploaded, auto-cleaned. |
| `Path` object | `Path("photo.jpg")` | Same as above. |
| `bytes` | `image_bytes` | Same as above. |
| `(filename, bytes)` tuple | `("photo.jpg", data)` | Same as above. |
| URL string | `"https://example.com/photo.jpg"` | Passed directly to backend. No upload. |
| `file_id` string | `"file_abc123"` | Reuses a previously uploaded file. |

> `generate()` auto-cleans temp files after the task completes. `submit()` does not — use it when you need `request_id` for manual polling.

## Non-blocking: `submit()` + `generations.wait()`

```python
task = client.images.submit(model="nano-banana-pro", prompt="A sunset")
print(task.request_id)           # "req_img_001"
print(task.estimated_credits)    # 20

# ... do other work ...

gen = client.generations.wait(task.request_id)
print(gen.url)
```

## Async

```python
from ponyflash import AsyncPonyFlash

client = AsyncPonyFlash(api_key="pf_xxx")

gen = await client.images.generate(model="nano-banana-pro", prompt="A sunset")
print(gen.url)
```

## Configuration

```python
client = PonyFlash(
    api_key="pf_xxx",                           # or PONYFLASH_API_KEY env var
    base_url="https://custom.example.com/v1",    # or PONYFLASH_BASE_URL env var
    max_retries=3,
)

# Polling timeout is per-resource, not per-client:
gen = client.video.generate(
    model="video-gen-1",
    prompt="...",
    timeout=900.0,     # wait up to 15 min for the task to complete (default: 600s)
)
```

> **Two kinds of timeout:**
> - `PonyFlash(timeout=...)` — per-HTTP-request timeout (default 300s). Only affects individual API calls.
> - `generate(timeout=...)` — polling timeout, how long to wait for the task to finish. Defaults vary by resource: images 120s, video/music 600s, speech 300s.

## Advanced: Manual File Management

The file API is available for advanced use cases:

```python
from pathlib import Path

# Upload explicitly (useful when reusing across multiple requests)
file_id = client.files.upload(Path("large_video.mp4"))

# Use the file_id in multiple requests without re-uploading
gen1 = client.video.generate(model="video-gen-1", video=file_id, prompt="Style A")
gen2 = client.video.generate(model="video-gen-1", video=file_id, prompt="Style B")

# Clean up when done
client.files.delete(file_id)

# List and inspect files
files = client.files.list()
info = client.files.get(file_id)
print(info.status, info.expires_at)
```
