6 changed files with 989 additions and 1151 deletions
@ -0,0 +1,500 @@ |
|||||||
|
# DaSiWa API Guide |
||||||
|
|
||||||
|
Полное руководство по использованию DaSiWa I2V/FLF2V API для генерации видео через ComfyUI. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## 📋 Содержание |
||||||
|
|
||||||
|
1. [Обзор](#обзор) |
||||||
|
2. [Аутентификация](#аутентификация) |
||||||
|
3. [Endpoints](#endpoints) |
||||||
|
4. [Параметры генерации](#параметры-генерации) |
||||||
|
5. [Примеры использования](#примеры-использования) |
||||||
|
6. [Коды ошибок](#коды-ошибок) |
||||||
|
7. [Best Practices](#best-practices) |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Обзор |
||||||
|
|
||||||
|
DaSiWa API — асинхронный REST API для генерации видео из изображений с использованием DaSiWa WAN 2.2 Lightspeed моделей через ComfyUI. |
||||||
|
|
||||||
|
**Архитектура:** Submit → Poll → Retrieve (как RunPod) |
||||||
|
|
||||||
|
**Base URL:** `http://<server_ip>:8080` |
||||||
|
|
||||||
|
**Аутентификация:** HMAC-SHA256 с timestamp и nonce |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Аутентификация |
||||||
|
|
||||||
|
Все endpoints (кроме `/health`) требуют HMAC подписи. |
||||||
|
|
||||||
|
### Заголовки запроса |
||||||
|
|
||||||
|
``` |
||||||
|
X-Client-Id: <ваш_client_id> |
||||||
|
X-Timestamp: <unix_timestamp> |
||||||
|
X-Nonce: <случайная_строка_32_символа> |
||||||
|
X-Signature: <hmac_sha256_подпись> |
||||||
|
``` |
||||||
|
|
||||||
|
### Алгоритм подписи |
||||||
|
|
||||||
|
```python |
||||||
|
import hmac |
||||||
|
import hashlib |
||||||
|
import time |
||||||
|
import secrets |
||||||
|
|
||||||
|
timestamp = str(int(time.time())) |
||||||
|
nonce = secrets.token_hex(16) |
||||||
|
body = json.dumps(payload).encode('utf-8') |
||||||
|
|
||||||
|
message = f"{timestamp}.{nonce}.".encode() + body |
||||||
|
signature = hmac.new( |
||||||
|
secret_key.encode(), |
||||||
|
message, |
||||||
|
hashlib.sha256 |
||||||
|
).hexdigest() |
||||||
|
``` |
||||||
|
|
||||||
|
### Защита от replay-атак |
||||||
|
|
||||||
|
- **Timestamp:** запросы старше 5 минут отклоняются |
||||||
|
- **Nonce:** каждый nonce можно использовать только один раз |
||||||
|
- **Signature:** уникальна для каждого запроса |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Endpoints |
||||||
|
|
||||||
|
### `GET /health` |
||||||
|
|
||||||
|
Health check сервера. **Не требует аутентификации.** |
||||||
|
|
||||||
|
**Response:** |
||||||
|
```json |
||||||
|
{ |
||||||
|
"status": "ok", |
||||||
|
"comfyui": "ok", |
||||||
|
"queue": 0, |
||||||
|
"timestamp": 1234567890 |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
**Поля:** |
||||||
|
- `status` — статус API сервера (`ok` / `error`) |
||||||
|
- `comfyui` — статус ComfyUI (`ok` / `unavailable`) |
||||||
|
- `queue` — количество задач в очереди |
||||||
|
- `timestamp` — текущее время сервера (unix) |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
### `POST /run` |
||||||
|
|
||||||
|
Поставить задачу на генерацию видео в очередь. |
||||||
|
|
||||||
|
**Request Body:** |
||||||
|
```json |
||||||
|
{ |
||||||
|
"image_base64": "base64_encoded_image_data", |
||||||
|
"prompt": "woman dancing gracefully", |
||||||
|
"negative_prompt": "blurry, low quality", |
||||||
|
"last_image_base64": "base64_encoded_last_frame", |
||||||
|
"width": 528, |
||||||
|
"height": 768, |
||||||
|
"length": 81, |
||||||
|
"steps": 4, |
||||||
|
"cfg": 1.0, |
||||||
|
"seed": -1, |
||||||
|
"fps": 16, |
||||||
|
"sampler_name": "euler", |
||||||
|
"scheduler": "linear_quadratic" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
**Обязательные поля:** |
||||||
|
- `image_base64` — первый кадр (base64) |
||||||
|
- `prompt` — текстовое описание |
||||||
|
|
||||||
|
**Опциональные поля:** |
||||||
|
- `last_image_base64` — последний кадр для FLF2V режима |
||||||
|
- `negative_prompt` — негативный промпт (default: встроенный) |
||||||
|
- `width` — ширина (default: 528, кратно 16) |
||||||
|
- `height` — высота (default: 768, кратно 16) |
||||||
|
- `length` — количество кадров (default: 81) |
||||||
|
- `steps` — шаги сэмплинга (default: 4) |
||||||
|
- `cfg` — CFG scale (default: 1.0) |
||||||
|
- `seed` — сид (-1 = random, default: -1) |
||||||
|
- `fps` — кадров в секунду (default: 16) |
||||||
|
- `sampler_name` — сэмплер (default: "euler") |
||||||
|
- `scheduler` — планировщик (default: "linear_quadratic") |
||||||
|
|
||||||
|
**Response:** |
||||||
|
```json |
||||||
|
{ |
||||||
|
"id": "550e8400-e29b-41d4-a716-446655440000", |
||||||
|
"status": "IN_QUEUE" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
**Коды ответа:** |
||||||
|
- `200` — задача принята |
||||||
|
- `400` — ошибка валидации (нет изображения) |
||||||
|
- `401` — ошибка аутентификации |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
### `GET /status/<job_id>` |
||||||
|
|
||||||
|
Получить статус задачи. |
||||||
|
|
||||||
|
**Response (IN_QUEUE):** |
||||||
|
```json |
||||||
|
{ |
||||||
|
"id": "550e8400-e29b-41d4-a716-446655440000", |
||||||
|
"status": "IN_QUEUE" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
**Response (IN_PROGRESS):** |
||||||
|
```json |
||||||
|
{ |
||||||
|
"id": "550e8400-e29b-41d4-a716-446655440000", |
||||||
|
"status": "IN_PROGRESS" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
**Response (COMPLETED):** |
||||||
|
```json |
||||||
|
{ |
||||||
|
"id": "550e8400-e29b-41d4-a716-446655440000", |
||||||
|
"status": "COMPLETED", |
||||||
|
"output": { |
||||||
|
"video": "base64_encoded_video_data", |
||||||
|
"seed": 42, |
||||||
|
"mode": "I2V", |
||||||
|
"elapsed": 45.2 |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
**Response (FAILED):** |
||||||
|
```json |
||||||
|
{ |
||||||
|
"id": "550e8400-e29b-41d4-a716-446655440000", |
||||||
|
"status": "FAILED", |
||||||
|
"error": "Video generation failed — no output from ComfyUI" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
**Коды ответа:** |
||||||
|
- `200` — статус получен |
||||||
|
- `404` — задача не найдена |
||||||
|
- `401` — ошибка аутентификации |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
### `POST /purge/<job_id>` |
||||||
|
|
||||||
|
Удалить завершённую задачу из памяти сервера (освободить RAM от base64 видео). |
||||||
|
|
||||||
|
**Response:** |
||||||
|
```json |
||||||
|
{ |
||||||
|
"id": "550e8400-e29b-41d4-a716-446655440000", |
||||||
|
"purged": true |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
**Коды ответа:** |
||||||
|
- `200` — задача удалена |
||||||
|
- `400` — нельзя удалить активную задачу (IN_QUEUE / IN_PROGRESS) |
||||||
|
- `404` — задача не найдена |
||||||
|
- `401` — ошибка аутентификации |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Параметры генерации |
||||||
|
|
||||||
|
### Режимы работы |
||||||
|
|
||||||
|
**I2V (Image to Video):** |
||||||
|
- Генерация видео из одного изображения |
||||||
|
- Передаётся только `image_base64` |
||||||
|
|
||||||
|
**FLF2V (First-Last Frame to Video):** |
||||||
|
- Генерация видео между двумя кадрами |
||||||
|
- Передаются `image_base64` + `last_image_base64` |
||||||
|
|
||||||
|
### Рекомендуемые значения |
||||||
|
|
||||||
|
| Параметр | I2V | FLF2V | Описание | |
||||||
|
|----------|-----|-------|----------| |
||||||
|
| `width` | 528 | 528 | Ширина (кратно 16) | |
||||||
|
| `height` | 768 | 768 | Высота (кратно 16) | |
||||||
|
| `length` | 81 | 81 | Кол-во кадров (~5 сек при 16fps) | |
||||||
|
| `steps` | 4 | 4 | DaSiWa оптимизирован под 4 шага | |
||||||
|
| `cfg` | 1.0 | 1.0 | CFG scale (DaSiWa работает с 1.0) | |
||||||
|
| `fps` | 16 | 16 | Кадров в секунду | |
||||||
|
| `sampler_name` | euler | euler | Сэмплер | |
||||||
|
| `scheduler` | linear_quadratic | linear_quadratic | Планировщик | |
||||||
|
|
||||||
|
### Ограничения |
||||||
|
|
||||||
|
- **Размеры:** должны быть кратны 16 |
||||||
|
- **Length:** рекомендуется кратно 8 + 1 (например: 81, 89, 97) |
||||||
|
- **Steps:** DaSiWa Lightspeed оптимизирован под 4 шага (можно больше, но медленнее) |
||||||
|
- **CFG:** значения > 2.0 могут давать артефакты |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Примеры использования |
||||||
|
|
||||||
|
### Python (с библиотекой requests) |
||||||
|
|
||||||
|
```python |
||||||
|
import requests |
||||||
|
import base64 |
||||||
|
import json |
||||||
|
import time |
||||||
|
from hmac_auth import sign_request |
||||||
|
|
||||||
|
# Загрузка ключей |
||||||
|
with open('keys.json') as f: |
||||||
|
keys = json.load(f) |
||||||
|
|
||||||
|
# Подготовка изображения |
||||||
|
with open('photo.png', 'rb') as f: |
||||||
|
image_b64 = base64.b64encode(f.read()).decode() |
||||||
|
|
||||||
|
# Payload |
||||||
|
payload = { |
||||||
|
"image_base64": image_b64, |
||||||
|
"prompt": "woman dancing gracefully", |
||||||
|
"width": 528, |
||||||
|
"height": 768, |
||||||
|
"length": 81, |
||||||
|
"steps": 4, |
||||||
|
"cfg": 1.0, |
||||||
|
"seed": -1, |
||||||
|
"fps": 16 |
||||||
|
} |
||||||
|
|
||||||
|
# 1. Submit job |
||||||
|
body = json.dumps(payload).encode('utf-8') |
||||||
|
auth_headers = sign_request(body, keys['secret_key'], keys['client_id']) |
||||||
|
headers = {'Content-Type': 'application/json', **auth_headers} |
||||||
|
|
||||||
|
response = requests.post( |
||||||
|
'http://server:8080/run', |
||||||
|
data=body, |
||||||
|
headers=headers |
||||||
|
) |
||||||
|
job_id = response.json()['id'] |
||||||
|
print(f"Job ID: {job_id}") |
||||||
|
|
||||||
|
# 2. Poll status |
||||||
|
while True: |
||||||
|
auth_headers = sign_request(b"", keys['secret_key'], keys['client_id']) |
||||||
|
response = requests.get( |
||||||
|
f'http://server:8080/status/{job_id}', |
||||||
|
headers=auth_headers |
||||||
|
) |
||||||
|
data = response.json() |
||||||
|
|
||||||
|
if data['status'] == 'COMPLETED': |
||||||
|
video_b64 = data['output']['video'] |
||||||
|
video_bytes = base64.b64decode(video_b64) |
||||||
|
with open('output.mp4', 'wb') as f: |
||||||
|
f.write(video_bytes) |
||||||
|
print(f"Video saved! Seed: {data['output']['seed']}") |
||||||
|
break |
||||||
|
elif data['status'] == 'FAILED': |
||||||
|
print(f"Error: {data['error']}") |
||||||
|
break |
||||||
|
else: |
||||||
|
print(f"Status: {data['status']}") |
||||||
|
time.sleep(5) |
||||||
|
|
||||||
|
# 3. Purge job |
||||||
|
auth_headers = sign_request(b"{}", keys['secret_key'], keys['client_id']) |
||||||
|
requests.post( |
||||||
|
f'http://server:8080/purge/{job_id}', |
||||||
|
json={}, |
||||||
|
headers={'Content-Type': 'application/json', **auth_headers} |
||||||
|
) |
||||||
|
``` |
||||||
|
|
||||||
|
### cURL |
||||||
|
|
||||||
|
```bash |
||||||
|
# 1. Submit job |
||||||
|
curl -X POST http://server:8080/run \ |
||||||
|
-H "Content-Type: application/json" \ |
||||||
|
-H "X-Client-Id: your_client_id" \ |
||||||
|
-H "X-Timestamp: $(date +%s)" \ |
||||||
|
-H "X-Nonce: $(openssl rand -hex 16)" \ |
||||||
|
-H "X-Signature: <calculated_signature>" \ |
||||||
|
-d '{ |
||||||
|
"image_base64": "...", |
||||||
|
"prompt": "woman dancing" |
||||||
|
}' |
||||||
|
|
||||||
|
# Response: {"id": "abc-123", "status": "IN_QUEUE"} |
||||||
|
|
||||||
|
# 2. Check status |
||||||
|
curl http://server:8080/status/abc-123 \ |
||||||
|
-H "X-Client-Id: your_client_id" \ |
||||||
|
-H "X-Timestamp: $(date +%s)" \ |
||||||
|
-H "X-Nonce: $(openssl rand -hex 16)" \ |
||||||
|
-H "X-Signature: <calculated_signature>" |
||||||
|
|
||||||
|
# 3. Purge |
||||||
|
curl -X POST http://server:8080/purge/abc-123 \ |
||||||
|
-H "Content-Type: application/json" \ |
||||||
|
-H "X-Client-Id: your_client_id" \ |
||||||
|
-H "X-Timestamp: $(date +%s)" \ |
||||||
|
-H "X-Nonce: $(openssl rand -hex 16)" \ |
||||||
|
-H "X-Signature: <calculated_signature>" \ |
||||||
|
-d '{}' |
||||||
|
``` |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Коды ошибок |
||||||
|
|
||||||
|
| Код | Описание | Решение | |
||||||
|
|-----|----------|---------| |
||||||
|
| `400` | Нет входного изображения | Передайте `image_base64` | |
||||||
|
| `401` | Invalid client ID | Проверьте `client_id` в `keys.json` | |
||||||
|
| `401` | Invalid timestamp | Синхронизируйте время на клиенте и сервере | |
||||||
|
| `401` | Nonce already used | Replay-атака или дублирующий запрос | |
||||||
|
| `401` | Invalid signature | Проверьте `secret_key` и алгоритм подписи | |
||||||
|
| `404` | Job not found | Job ID не существует или уже удалён | |
||||||
|
| `500` | Internal server error | Проверьте логи сервера (`journalctl -u dasiwa-api`) | |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Best Practices |
||||||
|
|
||||||
|
### 1. Polling интервал |
||||||
|
|
||||||
|
- **Рекомендуется:** 5-10 секунд |
||||||
|
- **Не рекомендуется:** < 2 секунд (нагрузка на сервер) |
||||||
|
- Генерация обычно занимает 30-60 секунд |
||||||
|
|
||||||
|
### 2. Timeout |
||||||
|
|
||||||
|
- Установите timeout на polling: 30 минут (1800 секунд) |
||||||
|
- Если задача не завершилась за это время — проверьте логи сервера |
||||||
|
|
||||||
|
### 3. Purge после использования |
||||||
|
|
||||||
|
- Всегда вызывайте `/purge/<id>` после получения видео |
||||||
|
- Base64 видео занимает ~10-50 MB RAM на сервере |
||||||
|
- Без purge память будет расти |
||||||
|
|
||||||
|
### 4. Обработка ошибок |
||||||
|
|
||||||
|
```python |
||||||
|
try: |
||||||
|
result = wait_for_completion(server, job_id, ...) |
||||||
|
except RuntimeError as e: |
||||||
|
if "Timeout" in str(e): |
||||||
|
# Задача зависла — проверьте сервер |
||||||
|
pass |
||||||
|
elif "Job failed" in str(e): |
||||||
|
# Ошибка генерации — проверьте параметры |
||||||
|
pass |
||||||
|
``` |
||||||
|
|
||||||
|
### 5. Retry логика |
||||||
|
|
||||||
|
- При `401` ошибках — не retry (проблема с ключами) |
||||||
|
- При `500` ошибках — retry с exponential backoff |
||||||
|
- При `404` на `/status` — задача потеряна, не retry |
||||||
|
|
||||||
|
### 6. Размер изображений |
||||||
|
|
||||||
|
- Оптимально: 528x768 или 768x528 |
||||||
|
- Большие размеры → больше VRAM → медленнее |
||||||
|
- Маленькие размеры → хуже качество |
||||||
|
|
||||||
|
### 7. Seed для воспроизводимости |
||||||
|
|
||||||
|
- Если нужен тот же результат — используйте тот же seed |
||||||
|
- Seed из ответа `output.seed` — сохраните для повтора |
||||||
|
|
||||||
|
### 8. Мониторинг очереди |
||||||
|
|
||||||
|
```python |
||||||
|
response = requests.get('http://server:8080/health') |
||||||
|
queue_size = response.json()['queue'] |
||||||
|
if queue_size > 5: |
||||||
|
print("Очередь большая, ожидайте дольше") |
||||||
|
``` |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Лимиты и производительность |
||||||
|
|
||||||
|
### Текущие лимиты |
||||||
|
|
||||||
|
- **Одновременные задачи:** 1 (1 GPU = 1 задача) |
||||||
|
- **Размер очереди:** не ограничен (но рекомендуется < 10) |
||||||
|
- **Размер изображения:** max 2048x2048 (теоретически) |
||||||
|
- **Длина видео:** max ~300 кадров (ограничено VRAM) |
||||||
|
|
||||||
|
### Производительность |
||||||
|
|
||||||
|
| Параметры | Время генерации | VRAM | |
||||||
|
|-----------|-----------------|------| |
||||||
|
| 528x768, 81 frames, 4 steps | ~30-45s | ~18 GB | |
||||||
|
| 768x528, 81 frames, 4 steps | ~30-45s | ~18 GB | |
||||||
|
| 528x768, 161 frames, 4 steps | ~60-90s | ~24 GB | |
||||||
|
|
||||||
|
*Время указано для RTX 4090 / A100* |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Troubleshooting |
||||||
|
|
||||||
|
### Задача зависла в IN_PROGRESS |
||||||
|
|
||||||
|
1. Проверьте логи сервера: `journalctl -u dasiwa-api -f` |
||||||
|
2. Проверьте ComfyUI: `curl http://localhost:8188` |
||||||
|
3. Перезапустите сервис: `systemctl restart dasiwa-api` |
||||||
|
|
||||||
|
### Ошибка "Video generation failed" |
||||||
|
|
||||||
|
- ComfyUI не запущен или недоступен |
||||||
|
- Недостаточно VRAM |
||||||
|
- Workflow файл повреждён |
||||||
|
|
||||||
|
### Медленная генерация |
||||||
|
|
||||||
|
- Проверьте загрузку GPU: `nvidia-smi` |
||||||
|
- Убедитесь что модели загружены в VRAM (первый запрос медленнее) |
||||||
|
- Уменьшите `length` или размеры |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Changelog |
||||||
|
|
||||||
|
### v2.0 (2026-03-07) |
||||||
|
- ✨ Асинхронный API (submit + poll) |
||||||
|
- ✨ Endpoints: `/run`, `/status`, `/purge` |
||||||
|
- ✨ Background worker thread |
||||||
|
- ✨ Queue management |
||||||
|
- 🔧 Обновлён на DaSiWa WAN 2.2 Lightspeed |
||||||
|
- 🔧 Упрощён workflow (14 нод вместо 50+) |
||||||
|
|
||||||
|
### v1.0 (2026-03-06) |
||||||
|
- 🎉 Первый релиз |
||||||
|
- ✅ Синхронный `/generate` endpoint |
||||||
|
- ✅ HMAC аутентификация |
||||||
|
- ✅ I2V и FLF2V режимы |
||||||
@ -1,2 +0,0 @@ |
|||||||
# ЭТОТ ФАЙЛ БОЛЬШЕ НЕ ИСПОЛЬЗУЕТСЯ — удали его |
|
||||||
# API работает напрямую через Python на порту 8080 |
|
||||||
Loading…
Reference in new issue