Spaces:
Paused
Paused
Switch apply-step from Wan-Animate to Wan2.1-VACE masked inpaint: regenerates ONLY the clothing region, copies face/hair/skin/motion/background from the source at original sharpness. UI: video + prompt -> video. VACE on 'large' (49 frames @6fps, ~265 quota incl remote FireRed) fits a FREE account. Boot probes reference_images + pack size. Removed dead wan_engine helpers; stopped purging the VACE model cache.
b95bfc8 verified | # API Guide — Change Clothes in a Video | |
| Space: **`kulkas2pintu/Video_to_video_WAN`** | |
| Host: `https://kulkas2pintu-video-to-video-wan.hf.space` | |
| Changes a person's clothing in a video from a text prompt: | |
| 1. trims the clip, | |
| 2. finds the frame where the person shows the most of their full body, | |
| 3. edits that frame's outfit with **FireRed** using your prompt, | |
| 4. re-inserts that re-clothed person into the video with **Wan 2.2 Animate**, | |
| keeping the original motion and scene. | |
| --- | |
| ## ZeroGPU quota — read this first | |
| This Space runs on ZeroGPU. Quota is **not** wall-clock seconds — it depends on | |
| the GPU size a call uses: | |
| ``` | |
| xlarge : quota = declared_duration × 2 (full GPU) | |
| large : quota = declared_duration × 1.5 (half GPU, 1.5× the wall time) | |
| ``` | |
| The FireRed step runs on a **separate Space** and the Wan step runs here. Both | |
| are billed to **your** account, out of the same daily budget. | |
| | Account | Daily quota | = real GPU seconds | | |
| |---|---:|---:| | |
| | Unauthenticated | 120 | 60 | | |
| | **Free** | **300** | **150** | | |
| | PRO | 2400 | 1200 | | |
| Admission is checked against the *declared* duration, so the **tier** you pick | |
| decides whether your call is even accepted: | |
| | tier | FireRed (remote) | Wan (here) | Total | Free (300)? | | |
| |---|---:|---:|---:|:--| | |
| | **`free`** | ~19 s | 255 s (`large`) | **~274 s** | ✅ fits | | |
| | `pro` | ~19 s | 500 s (`xlarge`) | ~519 s | ❌ rejected | | |
| **A rejected call costs nothing** (no worker is spawned), so an over-quota | |
| attempt is harmless — it just fails fast. | |
| --- | |
| ## Endpoints | |
| | Endpoint | Purpose | GPU cost | | |
| |---|---|---| | |
| | `/quota_probe` | Reports what a run will reserve. | **0** (not a GPU call) | | |
| | `/change_clothes` | Run the full pipeline. | see table above | | |
| > `/quota_probe` used to be a real GPU call reserving 580 quota — which a free | |
| > account could never pass. It is now a plain informational endpoint costing | |
| > nothing, and it returns a **descriptive string, not `"ok"`**. | |
| --- | |
| ## `/change_clothes` | |
| ### Inputs (positional order) | |
| | # | Name | Type | Required | Default | Notes | | |
| |---|------|------|----------|---------|-------| | |
| | 1 | `video` | video file | yes | — | Must be `{"video": handle_file(path_or_url), "subtitles": None}` — a bare `handle_file()` fails with a `VideoData` validation error. | | |
| | 2 | `prompt` | `str` | yes | — | The new outfit, e.g. `"a red hoodie and black jeans"`. | | |
| | 3 | `resolution_choice` | `"Low Res"` \| `"Medium Res"` | no | `"Low Res"` | `Low Res` = 640×368, `Medium Res` = 832×480. **Forced to Low Res when `tier="free"`.** | | |
| | 4 | `tier` | `"free"` \| `"pro"` | no | `"free"` | Quota/quality profile — see below. | | |
| `tier` was added last, so existing 3-argument callers keep working (they get | |
| `"free"`). | |
| ### What each tier produces | |
| | tier | clip out | frames | segment | steps | resolution | | |
| |---|---|---:|---:|---:|---| | |
| | `free` | **10.0 s** @ 8 fps | 80 | 81 | 5 | Low Res (forced) | | |
| | `pro` | **9.5 s** @ 8 fps | 76 | 77 | 8 | your choice | | |
| > There are **no** `seed` / `guidance_scale` parameters. FireRed's settings are | |
| > fixed internally (4 steps, CFG 1.0, random seed). | |
| ### Outputs (in order) | |
| | # | Name | Type | Notes | | |
| |---|------|------|-------| | |
| | 1 | `result` | video | The finished video (filepath). | | |
| | 2 | `edited_reference_frame_firered` | image | The re-clothed still FireRed produced. | | |
| | 3 | `status` | `str` (markdown) | Status, timings, and quota reserved. | | |
| --- | |
| ## Python (`gradio_client`) | |
| ```bash | |
| pip install gradio_client | |
| ``` | |
| ### Use `submit()`, not `predict()` — this is a long job | |
| A run takes several minutes (CPU pose extraction, then the GPU stages, plus any | |
| queue wait). `predict()` blocks the whole time and is easy to time out. | |
| ```python | |
| import time | |
| from gradio_client import Client, handle_file | |
| client = Client("kulkas2pintu/Video_to_video_WAN", hf_token="hf_...") | |
| # Optional: see what a run will reserve (costs nothing, returns a description) | |
| print(client.predict("free", api_name="/quota_probe")) | |
| job = client.submit( | |
| {"video": handle_file("my_clip.mp4"), "subtitles": None}, # 1 video | |
| "a red hoodie and black jeans", # 2 prompt | |
| "Low Res", # 3 resolution_choice | |
| "free", # 4 tier | |
| api_name="/change_clothes", | |
| ) | |
| while not job.done(): | |
| print("status:", job.status().code) | |
| time.sleep(10) | |
| video_path, ref_image, status = job.result() | |
| print(status) | |
| print("result video:", video_path) | |
| ``` | |
| `video_path` is a temp file the client downloaded — copy it somewhere permanent | |
| if you want to keep it. `handle_file` also accepts a URL. | |
| --- | |
| ## JavaScript (`@gradio/client`) | |
| ```bash | |
| npm i @gradio/client | |
| ``` | |
| ```js | |
| import { Client } from "@gradio/client"; | |
| const app = await Client.connect("kulkas2pintu/Video_to_video_WAN", { | |
| hf_token: "hf_...", | |
| }); | |
| const videoBlob = new Blob([await (await fetch("my_clip.mp4")).arrayBuffer()]); | |
| const out = await app.predict("/change_clothes", [ | |
| videoBlob, // 1 video | |
| "a red hoodie and black jeans", // 2 prompt | |
| "Low Res", // 3 resolution_choice | |
| "free", // 4 tier | |
| ]); | |
| console.log(out.data); // [result video, reference image, status] | |
| ``` | |
| --- | |
| ## Raw HTTP (curl) | |
| ```bash | |
| HOST=https://kulkas2pintu-video-to-video-wan.hf.space | |
| TOKEN=hf_... | |
| # (0) upload the video, get its server path | |
| FILE=$(curl -s -H "Authorization: Bearer $TOKEN" \ | |
| -F "files=@my_clip.mp4" "$HOST/gradio_api/upload" \ | |
| | python -c "import sys,json;print(json.load(sys.stdin)[0])") | |
| # (1) start the job -> returns an event id (NOTE: 4 data items) | |
| EVENT=$(curl -s -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ | |
| -d "{\"data\":[{\"video\":{\"path\":\"$FILE\"},\"subtitles\":null},\"a red hoodie and black jeans\",\"Low Res\",\"free\"]}" \ | |
| "$HOST/gradio_api/call/change_clothes" \ | |
| | python -c "import sys,json;print(json.load(sys.stdin)['event_id'])") | |
| # (2) stream the result | |
| curl -s -N -H "Authorization: Bearer $TOKEN" "$HOST/gradio_api/call/change_clothes/$EVENT" | |
| ``` | |
| --- | |
| ## Authentication | |
| - The Space is public, but ZeroGPU is quota-metered per account. | |
| - Pass an `hf_token`; GPU time is billed against **that token's account**. | |
| - With `tier="free"`, a free account fits **one 10-second generation per day** | |
| (~274 of 300 quota). A second run that day will be rejected. | |
| - With `tier="pro"` you need a PRO account (~519 quota per run of 2400/day). | |
| --- | |
| ## Errors you may hit | |
| | Error | Meaning | What to do | | |
| |---|---|---| | |
| | `You have exceeded your ... quota (Xs requested vs Ys left)` | Not enough quota for the tier you chose. | Use `tier="free"`, or wait for the daily reset. Nothing was charged. | | |
| | `'GPU task aborted'` | The GPU call exceeded its reserved time (or OOM'd). | Retry; report the `generate:` timing from the logs. | | |
| | `AcceleratorError: uncorrectable ECC error` | The assigned ZeroGPU card is **faulty hardware**. | Restart the Space to re-schedule onto a healthy node. Not a code bug. | | |
| | `FREE tier needs ...segment_frame_length...` | The installed diffusers build can't do the cheap free path. | Use `tier="pro"`, or pin `diffusers==0.39.0`. | | |
| | HTTP `500` / `503` on the page | Container busy/restarting — normal for a heavy ZeroGPU Space. | Retry in a moment. | | |
| --- | |
| ## Notes & limits | |
| - **Output is 8 fps.** Frame counts are capped so generation fits exactly one | |
| pipeline segment inside the ZeroGPU per-call time limit. | |
| - **One person.** The pipeline picks the most full-body person frame. | |
| - **Cold start** additionally downloads/loads model weights (several minutes). | |
| - **Identity vs. realism.** Wan Animate regenerates the person from the | |
| reference, so the face can drift slightly and the background is re-rendered. | |
| - Queue holds up to 20 jobs; GPU runs are serialized. | |