Wan 2.7 视频编辑
curl --request POST \
--url https://api.gravitex.ai/v1/video/generations \
--header 'Authorization: <authorization>' \
--header 'Content-Type: <content-type>' \
--data '
{
"model": "<string>",
"prompt": "<string>",
"duration": 123,
"metadata.input.prompt": "<string>",
"metadata.input.media": [
{}
],
"metadata.input.media[].type": "<string>",
"metadata.input.media[].url": "<string>",
"metadata.parameters.resolution": "<string>",
"metadata.parameters.prompt_extend": true,
"metadata.parameters.watermark": true
}
'import requests
url = "https://api.gravitex.ai/v1/video/generations"
payload = {
"model": "<string>",
"prompt": "<string>",
"duration": 123,
"metadata.input.prompt": "<string>",
"metadata.input.media": [{}],
"metadata.input.media[].type": "<string>",
"metadata.input.media[].url": "<string>",
"metadata.parameters.resolution": "<string>",
"metadata.parameters.prompt_extend": True,
"metadata.parameters.watermark": True
}
headers = {
"Authorization": "<authorization>",
"Content-Type": "<content-type>"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<authorization>', 'Content-Type': '<content-type>'},
body: JSON.stringify({
model: '<string>',
prompt: '<string>',
duration: 123,
'metadata.input.prompt': '<string>',
'metadata.input.media': [{}],
'metadata.input.media[].type': '<string>',
'metadata.input.media[].url': '<string>',
'metadata.parameters.resolution': '<string>',
'metadata.parameters.prompt_extend': true,
'metadata.parameters.watermark': true
})
};
fetch('https://api.gravitex.ai/v1/video/generations', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.gravitex.ai/v1/video/generations",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'model' => '<string>',
'prompt' => '<string>',
'duration' => 123,
'metadata.input.prompt' => '<string>',
'metadata.input.media' => [
[
]
],
'metadata.input.media[].type' => '<string>',
'metadata.input.media[].url' => '<string>',
'metadata.parameters.resolution' => '<string>',
'metadata.parameters.prompt_extend' => true,
'metadata.parameters.watermark' => true
]),
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>",
"Content-Type: <content-type>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.gravitex.ai/v1/video/generations"
payload := strings.NewReader("{\n \"model\": \"<string>\",\n \"prompt\": \"<string>\",\n \"duration\": 123,\n \"metadata.input.prompt\": \"<string>\",\n \"metadata.input.media\": [\n {}\n ],\n \"metadata.input.media[].type\": \"<string>\",\n \"metadata.input.media[].url\": \"<string>\",\n \"metadata.parameters.resolution\": \"<string>\",\n \"metadata.parameters.prompt_extend\": true,\n \"metadata.parameters.watermark\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<authorization>")
req.Header.Add("Content-Type", "<content-type>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.gravitex.ai/v1/video/generations")
.header("Authorization", "<authorization>")
.header("Content-Type", "<content-type>")
.body("{\n \"model\": \"<string>\",\n \"prompt\": \"<string>\",\n \"duration\": 123,\n \"metadata.input.prompt\": \"<string>\",\n \"metadata.input.media\": [\n {}\n ],\n \"metadata.input.media[].type\": \"<string>\",\n \"metadata.input.media[].url\": \"<string>\",\n \"metadata.parameters.resolution\": \"<string>\",\n \"metadata.parameters.prompt_extend\": true,\n \"metadata.parameters.watermark\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.gravitex.ai/v1/video/generations")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<authorization>'
request["Content-Type"] = '<content-type>'
request.body = "{\n \"model\": \"<string>\",\n \"prompt\": \"<string>\",\n \"duration\": 123,\n \"metadata.input.prompt\": \"<string>\",\n \"metadata.input.media\": [\n {}\n ],\n \"metadata.input.media[].type\": \"<string>\",\n \"metadata.input.media[].url\": \"<string>\",\n \"metadata.parameters.resolution\": \"<string>\",\n \"metadata.parameters.prompt_extend\": true,\n \"metadata.parameters.watermark\": true\n}"
response = http.request(request)
puts response.read_body视频系列
万相 2.7 视频编辑
阿里万相 wan2.7-videoedit 指令式视频编辑
POST
/
v1
/
video
/
generations
Wan 2.7 视频编辑
curl --request POST \
--url https://api.gravitex.ai/v1/video/generations \
--header 'Authorization: <authorization>' \
--header 'Content-Type: <content-type>' \
--data '
{
"model": "<string>",
"prompt": "<string>",
"duration": 123,
"metadata.input.prompt": "<string>",
"metadata.input.media": [
{}
],
"metadata.input.media[].type": "<string>",
"metadata.input.media[].url": "<string>",
"metadata.parameters.resolution": "<string>",
"metadata.parameters.prompt_extend": true,
"metadata.parameters.watermark": true
}
'import requests
url = "https://api.gravitex.ai/v1/video/generations"
payload = {
"model": "<string>",
"prompt": "<string>",
"duration": 123,
"metadata.input.prompt": "<string>",
"metadata.input.media": [{}],
"metadata.input.media[].type": "<string>",
"metadata.input.media[].url": "<string>",
"metadata.parameters.resolution": "<string>",
"metadata.parameters.prompt_extend": True,
"metadata.parameters.watermark": True
}
headers = {
"Authorization": "<authorization>",
"Content-Type": "<content-type>"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<authorization>', 'Content-Type': '<content-type>'},
body: JSON.stringify({
model: '<string>',
prompt: '<string>',
duration: 123,
'metadata.input.prompt': '<string>',
'metadata.input.media': [{}],
'metadata.input.media[].type': '<string>',
'metadata.input.media[].url': '<string>',
'metadata.parameters.resolution': '<string>',
'metadata.parameters.prompt_extend': true,
'metadata.parameters.watermark': true
})
};
fetch('https://api.gravitex.ai/v1/video/generations', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.gravitex.ai/v1/video/generations",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'model' => '<string>',
'prompt' => '<string>',
'duration' => 123,
'metadata.input.prompt' => '<string>',
'metadata.input.media' => [
[
]
],
'metadata.input.media[].type' => '<string>',
'metadata.input.media[].url' => '<string>',
'metadata.parameters.resolution' => '<string>',
'metadata.parameters.prompt_extend' => true,
'metadata.parameters.watermark' => true
]),
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>",
"Content-Type: <content-type>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.gravitex.ai/v1/video/generations"
payload := strings.NewReader("{\n \"model\": \"<string>\",\n \"prompt\": \"<string>\",\n \"duration\": 123,\n \"metadata.input.prompt\": \"<string>\",\n \"metadata.input.media\": [\n {}\n ],\n \"metadata.input.media[].type\": \"<string>\",\n \"metadata.input.media[].url\": \"<string>\",\n \"metadata.parameters.resolution\": \"<string>\",\n \"metadata.parameters.prompt_extend\": true,\n \"metadata.parameters.watermark\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<authorization>")
req.Header.Add("Content-Type", "<content-type>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.gravitex.ai/v1/video/generations")
.header("Authorization", "<authorization>")
.header("Content-Type", "<content-type>")
.body("{\n \"model\": \"<string>\",\n \"prompt\": \"<string>\",\n \"duration\": 123,\n \"metadata.input.prompt\": \"<string>\",\n \"metadata.input.media\": [\n {}\n ],\n \"metadata.input.media[].type\": \"<string>\",\n \"metadata.input.media[].url\": \"<string>\",\n \"metadata.parameters.resolution\": \"<string>\",\n \"metadata.parameters.prompt_extend\": true,\n \"metadata.parameters.watermark\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.gravitex.ai/v1/video/generations")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<authorization>'
request["Content-Type"] = '<content-type>'
request.body = "{\n \"model\": \"<string>\",\n \"prompt\": \"<string>\",\n \"duration\": 123,\n \"metadata.input.prompt\": \"<string>\",\n \"metadata.input.media\": [\n {}\n ],\n \"metadata.input.media[].type\": \"<string>\",\n \"metadata.input.media[].url\": \"<string>\",\n \"metadata.parameters.resolution\": \"<string>\",\n \"metadata.parameters.prompt_extend\": true,\n \"metadata.parameters.watermark\": true\n}"
response = http.request(request)
puts response.read_body简介
通过 Gravitex 网关调用阿里云百炼wan2.7-videoedit 模型,对已有视频做指令式编辑(替换人物、修改服装、调整场景风格等)。接口为异步:先提交任务,再轮询结果。
| 接口 | 方法 | 路径 |
|---|---|---|
| 创建视频编辑任务 | POST | /v1/video/generations |
| 查询任务状态 | GET | /v1/video/generations/{task_id} |
{task_id} 为创建任务返回的 id。
与 万相 2.7 视频生成(T2V / I2V / R2V)共用统一视频入口,但模型与
media 参数语义不同,请勿混用。认证
string
必填
Bearer Token,如
Bearer sk-xxxxxxxxstring
必填
固定为
application/jsonstring
请求跟踪 ID,建议每次请求生成唯一值
请求参数
string
必填
固定为
wan2.7-videoeditstring
必填
视频编辑指令
integer
输出视频时长(秒),示例为
4string
必填
传给阿里云的原始提示词,建议与顶层
prompt 保持一致array
必填
输入媒体数组,至少包含一个视频
string
必填
原视频使用
video;参考图使用 reference_image(最多 4 张)string
必填
公网可访问的 HTTP/HTTPS URL 或符合上游要求的临时 URL
string
720P 或 1080Pboolean
是否启用提示词智能改写
boolean
是否添加水印
请求示例
最小请求(仅输入视频):curl --location 'https://api.gravitex.ai/v1/video/generations' \
--header 'Authorization: Bearer sk-xxxxxxxx' \
--header 'Content-Type: application/json' \
--header 'X-Trace-ID: wan27-videoedit-test-001' \
--data '{
"model": "wan2.7-videoedit",
"prompt": "背景不变,把视频中的小羊换成黑色的",
"duration": 4,
"metadata": {
"input": {
"prompt": "背景不变,把视频中的小羊换成黑色的",
"media": [
{
"type": "video",
"url": "https://your-public-oss.example.com/input.mp4"
}
]
},
"parameters": {
"resolution": "720P",
"prompt_extend": true,
"watermark": false
}
}
}'
media 数组中追加参考图;提示词里可用「图 1」指代。最多支持 4 张参考图。
{
"model": "wan2.7-videoedit",
"prompt": "保持视频中的人物动作不变,把人物衣服替换成参考图中的黑色西装",
"duration": 4,
"metadata": {
"input": {
"prompt": "保持视频中的人物动作不变,把人物衣服替换成参考图中的黑色西装",
"media": [
{
"type": "video",
"url": "https://your-public-oss.example.com/input.mp4"
},
{
"type": "reference_image",
"url": "https://your-public-oss.example.com/reference.png"
}
]
},
"parameters": {
"resolution": "720P",
"prompt_extend": true,
"watermark": false
}
}
}
创建任务响应
创建成功后返回标准化视频任务对象(无code/message/data 外层):
{
"id": "76dc2556-f4****************29247",
"object": "video",
"model": "wan2.7-videoedit",
"status": "queued",
"progress": 0,
"created_at": 1784714517,
"metadata": {
"output": {
"task_id": "76dc2556-f4****************29247",
"task_status": "PENDING"
},
"request_id": "86c168af-****************bf96d"
}
}
id,用于后续查询。
查询任务
curl --location \
'https://api.gravitex.ai/v1/video/generations/76dc2556-f4****************29247' \
--header 'Authorization: Bearer sk-xxxxxxxx' \
--header 'X-Trace-ID: wan27-videoedit-query-001'
code/message/data 外层:
{
"code": "success",
"message": "",
"data": {
"id": "76dc2556-f4****************29247",
"object": "video",
"model": "wan2.7-videoedit",
"status": "completed",
"progress": 100,
"created_at": 1784714517,
"completed_at": 1784714687,
"seconds": "8",
"url": "https://signed-result-url.example.com/result.mp4",
"video_url": "https://signed-result-url.example.com/result.mp4",
"metadata": {
"output": {
"task_status": "SUCCEEDED",
"input_video_duration": 4,
"output_video_duration": 4,
"video_url": "https://signed-result-url.example.com/result.mp4"
}
}
}
}
状态处理
客户端应以data.status 作为平台标准状态:
data.status | 说明 | 客户端处理 |
|---|---|---|
queued | 排队中 | 继续轮询 |
in_progress | 生成中 | 继续轮询 |
completed | 生成成功 | 读取 data.video_url 或 data.url |
failed | 生成失败 | 读取 data.error.message |
data.metadata.output.task_status(如 PENDING、SUCCEEDED),仅用于排查,不建议作为主状态判断依据。
建议轮询间隔 5~15 秒,不要高频轮询。
输出视频 URL
成功后优先读取data.video_url,也可读取 data.url(当前适配器中通常相同)。
输出 URL 为带签名的临时地址,不应永久保存为业务资源地址;需长期保存时,请在有效期内下载或转存到自有 OSS。
时长和计费
实测中请求duration: 4 时,上游 usage 可能如下:
| 字段 | 含义 |
|---|---|
input_video_duration | 输入视频时长 |
output_video_duration | 输出视频时长 |
usage.duration | 输入 + 输出的总计费时长 |
data.seconds | 网关响应对应上游总时长(示例为 8) |
data.seconds = 8 不代表输出生成了 8 秒,而是输入与输出合计计费时长。
阿里云官方计费:按「输入视频时长 + 输出视频时长」计费;输入图像不计费。详见 官方计费说明。
常见错误排查
| 问题 | 说明 |
|---|---|
media 缺少视频 | 至少包含 { "type": "video", "url": "..." } |
| 视频 URL 无法访问 | 勿用需登录页面、权限不足或已过期的 OSS URL;须公网可访问 |
| JSON 不合法 | 布尔值写 true/false,不要写成 **true** 等 |
只传了 video_url | 新接入请用 input.media;网关虽兼容旧 video_url,但不推荐 |
| 与 Wan 2.1 参数混用 | wan2.7-videoedit 用 input.media;旧版 wan2.1-vace-plus 用 input.function = video_repainting + input.video_url,不可混用 |
Python 最小示例
import time
import uuid
import requests
BASE_URL = "https://api.gravitex.ai"
API_KEY = "sk-xxxxxxxx"
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"X-Trace-ID": uuid.uuid4().hex,
}
payload = {
"model": "wan2.7-videoedit",
"prompt": "背景不变,把视频中的小羊换成黑色的",
"duration": 4,
"metadata": {
"input": {
"prompt": "背景不变,把视频中的小羊换成黑色的",
"media": [
{
"type": "video",
"url": "https://your-public-oss.example.com/input.mp4",
}
],
},
"parameters": {
"resolution": "720P",
"prompt_extend": True,
"watermark": False,
},
},
}
response = requests.post(
f"{BASE_URL}/v1/video/generations",
headers=headers,
json=payload,
timeout=30,
)
response.raise_for_status()
task = response.json()
task_id = task["id"]
while True:
result = requests.get(
f"{BASE_URL}/v1/video/generations/{task_id}",
headers={
"Authorization": f"Bearer {API_KEY}",
"X-Trace-ID": uuid.uuid4().hex,
},
timeout=30,
)
result.raise_for_status()
data = result.json()["data"]
status = data["status"]
print(status, data.get("progress", 0))
if status == "completed":
print("video_url:", data.get("video_url") or data.get("url"))
break
if status == "failed":
raise RuntimeError(data.get("error", {}).get("message", "video generation failed"))
time.sleep(10)
⌘I
