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비디오 시리즈
Wan 2.7 비디오 편집
Alibaba Wan 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 게이트웨이를 통해 Alibaba Cloud Bailianwan2.7-videoedit 모델을 호출하여 기존 비디오에 지시형 편집(인물 교체, 의상 변경, 장면 스타일 조정 등)을 수행합니다. 인터페이스는 비동기 방식입니다: 먼저 작업을 제출한 후 결과를 폴링합니다.
| 인터페이스 | 메서드 | 경로 |
|---|---|---|
| 비디오 편집 작업 생성 | POST | /v1/video/generations |
| 작업 상태 조회 | GET | /v1/video/generations/{task_id} |
{task_id}는 작업 생성 시 반환된 id입니다.
Wan 2.7 비디오 생성(T2V / I2V / R2V)과 통합 비디오 진입점을 공유하지만, 모델과
media 매개변수 의미가 다르므로 혼용하지 마세요.인증
string
필수
Bearer Token, 예:
Bearer sk-xxxxxxxxstring
필수
application/json으로 고정string
요청 추적 ID, 매 요청마다 고유 값 생성 권장
요청 매개변수
string
필수
wan2.7-videoedit로 고정string
필수
비디오 편집 지시문
integer
출력 비디오 길이(초), 예시는
4string
필수
Alibaba Cloud에 전달되는 원본 프롬프트, 최상위
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초 비디오가 생성되었다는 의미가 아닙니다. 입력과 출력의 합산 과금 길이입니다.
Alibaba Cloud 공식 과금: 「입력 비디오 길이 + 출력 비디오 길이」로 과금; 입력 이미지는 과금되지 않습니다. 자세한 내용은 공식 과금 안내를 참조하세요.
일반적인 오류 해결
| 문제 | 설명 |
|---|---|
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
