Pruna P-Video-Replace
Fast person swap in your video from 1–3 photos. Motion, camera and sound stay as filmed. Up to 30 seconds, 720p or 1080p.
Pruna P-Video-Replace takes your video and 1–3 photos and puts the character from the photos in place of the person in the clip.
Motion, timing, camera and scene stay as in the source; the clip audio is kept. It is fast: about 3–4 seconds of generation per second of video.
The video is up to 30 seconds (longer is trimmed to 30); the result has the same length. Resolution 720p or 1080p, frame rate original, 24 or 48. The prompt is optional. You pay per second of your video.
One model, pruna-p-video-replace · ~2 min. The mode is the input.mode field.
Modes
One ID, pruna-p-video-replace. input.mode chooses what happens to the clip. Omitted, it is motion transfer. The official ids work too: higgsfield/genjutsu/object-swap/v1.0 and higgsfield/genjutsu/restyle/v1.0 select the mode, so mode can be left out.
mode | What it does | What to send |
|---|---|---|
motion-transfer | Motion, camera and timing come from the video; the look comes from the photos | video_url + image_urls (1–3) |
object-swap | Replaces a person, outfit or object in the clip. The rest of the shot stays | video_url + image_urls (1–3) |
restyle | The same clip in a chosen style. The source motion and audio stay | video_url + preset_id. image_urls — optional, up to 5 |
Shared by all three: the video is at least 4 seconds, longer than 30 seconds is trimmed to 30, and the result has the same length. prompt may be omitted. resolution: 480p, 720p (default) or 1080p. The mode does not change the price.
Object swap needs a source frame of at least 409,600 pixels (width × height). Ordinary HD is fine; a tiny preview is not.
Styles for restyle
Style list: GET https://mixmedia.tech/api/v1/genjutsu/presets. No key. preset_id is the item's id, not its name and not the preview image.
{
"code": 200,
"msg": "success",
"data": {
"items": [
{
"id": "c2143317-f28d-4c3c-a0b8-39bd547e08a7",
"name": "Cel-Shaded CG Anime",
"preview_url": "https://…/style.webp"
}
]
}
}
The list changes. If a saved preset_id is no longer accepted, fetch the list again and use a current id.
Price
| Resolution | Price per second of your video |
|---|---|
| 720p | $0.039 |
| 1080p | $0.078 |
- Price = per-second price × clip length, rounded up to $0.001. Charged when the task is created and fully refunded if it fails.
- You pay per second of your video (2 to 30 s). A clip longer than 30 s is trimmed to 30; the result has the same length.
promptmay be omitted.
Media limits
| Type | Limits |
|---|---|
| Images | 1–3 for motion transfer and object swap; 0–5 for restyle. JPG / PNG / WebP; public https or files/upload — up to 30 MB per file |
| Video | one clip, video_url; 2 s to 30 s (longer is trimmed to 30); MP4 / MOV → MP4 on the server; upload ≤ 200 MB |
Photos — public https or upload; video and audio — files/upload only.
Parameters (field input)
| Parameter | Description | Values (default in bold) |
|---|---|---|
prompt | What happens in the clip: scene, motion, camera, light, style | text, up to 30000 chars (optional, default empty) |
video_url | Motion video (MP4): one clip, at least 4 s. Longer than 30 s is trimmed to 30. Upload it via files/upload first | "https://…" |
image_urls | Character photos (https): look, face, clothes | ["https://…"], 1–3 |
resolution | Video resolution | 720p · 1080p |
target_fps | Частота кадров | original 24 48 |
save_audio | Сохранить звук ролика | true false (default true) |
ignore_audio | Не учитывать звук при генерации | true false (default false) |
turbo | Турбо (быстрее, чуть ниже качество) | true false (default false) |
seed | Variant number: the same number with the same prompt gives a similar result; empty or -1 = random | -1…4294967295 |
Examples
Motion transfer
curl -X POST https://mixmedia.tech/api/v1/jobs/createTask \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"pruna-p-video-replace","input":{"mode":"motion-transfer","prompt":"Keep the face from the photo, take the motion from the video","video_url":"https://mixmedia.tech/files/<id>.mp4","image_urls":["https://mixmedia.tech/static/img/previews/gpt-t2i.jpg"],"resolution":"720p"}}'
import os, requests
r = requests.post(
"https://mixmedia.tech/api/v1/jobs/createTask",
headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
json={
"model": "pruna-p-video-replace",
"input": {
"mode": "motion-transfer",
"prompt": "Keep the face from the photo, take the motion from the video",
"video_url": "https://mixmedia.tech/files/<id>.mp4",
"image_urls": [
"https://mixmedia.tech/static/img/previews/gpt-t2i.jpg"
],
"resolution": "720p"
}
},
)
print(r.json()) # {"code": 200, "data": {"taskId": "..."}}
const r = await fetch("https://mixmedia.tech/api/v1/jobs/createTask", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
"model": "pruna-p-video-replace",
"input": {
"mode": "motion-transfer",
"prompt": "Keep the face from the photo, take the motion from the video",
"video_url": "https://mixmedia.tech/files/<id>.mp4",
"image_urls": [
"https://mixmedia.tech/static/img/previews/gpt-t2i.jpg"
],
"resolution": "720p"
}
}),
});
console.log(await r.json()); // { code: 200, data: { taskId: "..." } }
Object swap
curl -X POST https://mixmedia.tech/api/v1/jobs/createTask \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"pruna-p-video-replace","input":{"mode":"object-swap","prompt":"Replace the person with the character in the photo","video_url":"https://mixmedia.tech/files/<id>.mp4","image_urls":["https://mixmedia.tech/static/img/previews/gpt-t2i.jpg"],"resolution":"720p"}}'
import os, requests
r = requests.post(
"https://mixmedia.tech/api/v1/jobs/createTask",
headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
json={
"model": "pruna-p-video-replace",
"input": {
"mode": "object-swap",
"prompt": "Replace the person with the character in the photo",
"video_url": "https://mixmedia.tech/files/<id>.mp4",
"image_urls": [
"https://mixmedia.tech/static/img/previews/gpt-t2i.jpg"
],
"resolution": "720p"
}
},
)
print(r.json()) # {"code": 200, "data": {"taskId": "..."}}
const r = await fetch("https://mixmedia.tech/api/v1/jobs/createTask", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
"model": "pruna-p-video-replace",
"input": {
"mode": "object-swap",
"prompt": "Replace the person with the character in the photo",
"video_url": "https://mixmedia.tech/files/<id>.mp4",
"image_urls": [
"https://mixmedia.tech/static/img/previews/gpt-t2i.jpg"
],
"resolution": "720p"
}
}),
});
console.log(await r.json()); // { code: 200, data: { taskId: "..." } }
Restyle
curl -X POST https://mixmedia.tech/api/v1/jobs/createTask \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"pruna-p-video-replace","input":{"mode":"restyle","video_url":"https://mixmedia.tech/files/<id>.mp4","preset_id":"<preset_id>","resolution":"720p"}}'
import os, requests
r = requests.post(
"https://mixmedia.tech/api/v1/jobs/createTask",
headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
json={
"model": "pruna-p-video-replace",
"input": {
"mode": "restyle",
"video_url": "https://mixmedia.tech/files/<id>.mp4",
"preset_id": "<preset_id>",
"resolution": "720p"
}
},
)
print(r.json()) # {"code": 200, "data": {"taskId": "..."}}
const r = await fetch("https://mixmedia.tech/api/v1/jobs/createTask", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
"model": "pruna-p-video-replace",
"input": {
"mode": "restyle",
"video_url": "https://mixmedia.tech/files/<id>.mp4",
"preset_id": "<preset_id>",
"resolution": "720p"
}
}),
});
console.log(await r.json()); // { code: 200, data: { taskId: "..." } }
Create and result
POST /api/v1/jobs/createTaskwithmodelandinput.- Poll
GET /api/v1/jobs/recordInfo?taskId=…every 3–5 s (or a callback).
Successful createTask (HTTP 200)
{
"code": 200,
"msg": "success",
"data": {
"taskId": "task_...",
"task_id": "task_...",
"status": "submitted"
}
}
Completed task (recordInfo)
{
"code": 200,
"msg": "success",
"data": {
"taskId": "task_...",
"task_id": "task_...",
"state": "success",
"status": "completed",
"costUsd": "0.384",
"refunded": false,
"resultJson": {
"videos": [
{
"url": "https://…/out.mp4"
}
],
"duration": 8
}
}
}
Aliases: task_id = taskId; status: pending / processing / completed / failed alongside state. Clip URL is in resultJson. size is an alias of aspect_ratio in input.
Photos can be any public https links or uploads via files/upload. Videos and audio — via files/upload only.
Error handling
API responses are JSON {"code", "msg", "data"}; errors also have a string error field (type). On /api/v1/* the HTTP status matches body code.
Example response bodies
400
{
"code": 400,
"msg": "input.duration: must be between 4 and 30, or \"auto\" / -1",
"data": null,
"error": "invalid_request_error"
}
401
{
"code": 401,
"msg": "Authentication failed. Please check your API key",
"data": null,
"error": "authentication_error"
}
402
{
"code": 402,
"msg": "Insufficient balance. Please top up and try again. Available: $0.01, required: $0.015. Top up at https://mixmedia.tech/cabinet/billing",
"data": {
"balance": "0.0100",
"required": "0.0150",
"currency": "USD",
"topUpUrl": "https://mixmedia.tech/cabinet/billing"
},
"error": "payment_required"
}
429
{
"code": 429,
"msg": "Rate limit exceeded: 10 createTask/upload requests per 10 s per key. Retry after 8s",
"data": null,
"error": "rate_limit_error"
}
500
{
"code": 500,
"msg": "Internal server error",
"data": null,
"error": "server_error"
}
Codes on createTask / upload
| code | error | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_request_error | Invalid parameter or body | See msg |
| 400 | unsupported_model | Unknown model | Model list |
| 400 | nsfw_content_detected | Moderation / content policy — msg / failMsg is the upstream text verbatim | Read the refusal text, change the prompt or files |
| 401 | authentication_error | Missing or invalid key | Check the key |
| 402 | payment_required | Not enough balance | Top up |
| 403 | permission_error | No access | Contact support |
| 429 | rate_limit_error | Too many requests | Wait for Retry-After |
| 505 | model_disabled | Model temporarily disabled | Pick another |
| 500 | server_error | Our failure | Retry later |
Example msg values: Authentication failed. Please check your API key (401); Insufficient balance. Please top up and try again… (402).
Task errors (recordInfo)
If the task was created and then failed: state: "fail" (alias status: "failed"), failCode / failMsg, and error: { code, message, type }. Money is refunded (refunded: true).
| failCode | error.type | Meaning |
|---|---|---|
| 400 | nsfw_content_detected / invalid_request_error | Moderation — failMsg is upstream text verbatim |
| 422 | invalid_request_error | The model rejected the parameters (length, mode, resolution, file) |
| 501 | server_error | The model could not generate (failMsg) |
| 504 | timeout_error | Timed out — retry |
| 500 / 503 | server_error | Service temporarily unavailable |
Rate limits
| What | Limit |
|---|---|
| createTask and upload | 20 per 10 s per key |
| recordInfo / list / balance | 100 per 10 s per key |
Over the limit — HTTP 429 and Retry-After.