# RunningHub Sora2 使用指南

**版本：** v2.1.0  
**更新时间：** 2025-10-20

---

## 📋 模型列表

系统已预配置以下RunningHub Sora2模型（共12个）：

### 文生视频模型（webappId: 1973555977595301890）

| 模型名称 | 说明 | 时长 | 分辨率 | 积分消耗 |
|---------|------|------|--------|---------|
| `rh_sora2_text_portrait` | 竖屏视频 | 10秒 | 704x1280 | 160 |
| `rh_sora2_text_landscape` | 横屏视频 | 10秒 | 1280x704 | 160 |
| `rh_sora2_text_portrait_hd` | 高清竖屏 | 10秒 | 1024x1792 | 420 |
| `rh_sora2_text_landscape_hd` | 高清横屏 | 10秒 | 1792x1024 | 420 |
| `rh_sora2_text_portrait_15s` | 竖屏视频（长） | 15秒 | 704x1280 | 260 |
| `rh_sora2_text_landscape_15s` | 横屏视频（长） | 15秒 | 1280x704 | 260 |

### 图生视频模型（webappId: 1973555366057390081）

| 模型名称 | 说明 | 时长 | 分辨率 | 积分消耗 |
|---------|------|------|--------|---------|
| `rh_sora2_img_portrait` | 竖屏视频 | 10秒 | 704x1280 | 180 |
| `rh_sora2_img_landscape` | 横屏视频 | 10秒 | 1280x704 | 180 |
| `rh_sora2_img_portrait_hd` | 高清竖屏 | 10秒 | 1024x1792 | 480 |
| `rh_sora2_img_landscape_hd` | 高清横屏 | 10秒 | 1792x1024 | 480 |
| `rh_sora2_img_portrait_15s` | 竖屏视频（长） | 15秒 | 704x1280 | 280 |
| `rh_sora2_img_landscape_15s` | 横屏视频（长） | 15秒 | 1280x704 | 280 |

---

## 🚀 使用示例

### 1. 文生视频（Text to Video）

只需要提供 `prompt`，系统会自动选择横竖屏和时长。

```bash
curl -X POST "http://localhost:8081/user/ai/tasks/submit" \
  -H "Authorization: Bearer YOUR_JWT_OR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "modelName": "rh_sora2_text_portrait",
    "prompt": "第一镜（0–3秒）静谧晨光\n窗外的城市尚在沉睡，镜头推向一只放在床头的 Apple Watch。屏幕亮起的瞬间，柔和的光映照出主人的脸庞，他睁开眼，呼吸与心率同步闪烁。\n第二镜（3–7秒）节奏苏醒\n主角在晨跑，步伐稳健。手表屏幕显示心率曲线与路线图，汗水顺着手臂滑落。阳光从高楼间洒下，镜头追随腕间的微光与动作的力量。\n第三镜（7–10秒）自我回归\n跑步结束，他停在桥上深吸一口气，城市的天际线在他背后延伸。镜头慢慢拉远，Apple Watch的屏幕定格在闪烁的数字上，文字浮现：掌控每一秒的呼吸。"
  }'
```

**实际发送到RunningHub的请求：**
```json
{
  "webappId": "1973555977595301890",
  "apiKey": "5c44cef12da3470e9f24da70c63787dc",
  "nodeInfoList": [
    {
      "nodeId": "1",
      "fieldName": "prompt",
      "fieldValue": "第一镜（0–3秒）静谧晨光...",
      "description": "输入文本"
    },
    {
      "nodeId": "1",
      "fieldName": "model",
      "fieldValue": "portrait",
      "fieldData": "[{\"name\":\"portrait\",...}]",
      "description": "横竖模式"
    },
    {
      "nodeId": "1",
      "fieldName": "duration_seconds",
      "fieldValue": "10",
      "fieldData": "[[10, 15], {\"default\": 10}]",
      "description": "时长（秒）"
    }
  ]
}
```

---

### 2. 图生视频（Image to Video）

需要提供 `imageUrl` 或 `imageBase64`，以及可选的 `prompt`。

#### 方式1：使用图片URL（推荐）

```bash
curl -X POST "http://localhost:8081/user/ai/tasks/submit" \
  -H "Authorization: Bearer YOUR_JWT_OR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "modelName": "rh_sora2_img_landscape",
    "prompt": "镜头一（0s–3s）从空中俯拍，镜头缓缓向下俯冲穿越云层，紫蓝色霓虹反射在摩天大楼玻璃幕墙上...",
    "imageUrl": "https://example.com/my-reference-image.jpg"
  }'
```

#### 方式2：使用图片Base64

```bash
curl -X POST "http://localhost:8081/user/ai/tasks/submit" \
  -H "Authorization: Bearer YOUR_JWT_OR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "modelName": "rh_sora2_img_portrait_hd",
    "prompt": "让这个场景动起来，添加生动的细节",
    "imageBase64": "data:image/jpeg;base64,/9j/4AAQSkZJRg..."
  }'
```

**实际发送到RunningHub的请求：**
```json
{
  "webappId": "1973555366057390081",
  "apiKey": "5c44cef12da3470e9f24da70c63787dc",
  "nodeInfoList": [
    {
      "nodeId": "2",
      "fieldName": "image",
      "fieldValue": "https://example.com/my-reference-image.jpg",
      "description": "上传图像（不支持真人做图像的输入）"
    },
    {
      "nodeId": "1",
      "fieldName": "prompt",
      "fieldValue": "镜头一（0s–3s）从空中俯拍...",
      "description": "输入文本"
    },
    {
      "nodeId": "1",
      "fieldName": "model",
      "fieldValue": "landscape",
      "fieldData": "[{\"name\":\"portrait\",...}]",
      "description": "横竖模式"
    },
    {
      "nodeId": "1",
      "fieldName": "duration_seconds",
      "fieldValue": "10",
      "fieldData": "[[10, 15], {\"default\": 10}]",
      "description": "时长（秒）"
    }
  ]
}
```

---

## 🔧 关键实现细节

### 1. 自动任务类型识别

系统根据 `providerConfig.taskType` 自动识别任务类型：

```java
// points_config表中的配置示例
{
  "webappId": "1973555366057390081",
  "taskType": "image2video",  // 或 "text2video"
  "model": "landscape",
  "duration": 10
}
```

### 2. 图片参数处理

- **优先级**：`imageUrl` > `imageBase64`
- **支持格式**：
  - 完整URL：`https://example.com/image.jpg`
  - RunningHub文件名：`825b8cb2f5603b068704ef435df77d570f081be814a40f652f080b8d4bc6ba03.png`
  - Base64编码：`data:image/jpeg;base64,/9j/4AAQSkZJRg...`

```java
// RunningHubProviderImpl中的逻辑
if (isImage2Video) {
    String imageValue = request.getImageUrl();
    if (imageValue == null || imageValue.trim().isEmpty()) {
        imageValue = request.getImageBase64();
    }
    
    // 添加到nodeInfoList
    nodeInfoList.add(RunningHubNodeInfo.builder()
        .nodeId("2")
        .fieldName("image")
        .fieldValue(imageValue)  // 支持完整URL
        .description("上传图像（不支持真人做图像的输入）")
        .build());
}
```

### 3. 节点顺序

系统按照RunningHub要求的顺序构建节点：

**文生视频：**
1. prompt节点（nodeId="1"）
2. model节点（nodeId="1"）
3. duration_seconds节点（nodeId="1"）

**图生视频：**
1. image节点（nodeId="2"）
2. prompt节点（nodeId="1"，可选）
3. model节点（nodeId="1"）
4. duration_seconds节点（nodeId="1"）

---

## 📊 任务流程

### 文生视频流程

```
用户提交
    ↓
提取prompt
    ↓
读取points_config配置
taskType="text2video"
webappId="1973555977595301890"
    ↓
构建nodeInfoList:
- prompt节点
- model节点
- duration节点
    ↓
调用RunningHub API
    ↓
获得taskId
status='processing'
    ↓
5秒轮询一次
    ↓
检测到SUCCESS
    ↓
获取视频URL
status='completed'
```

### 图生视频流程

```
用户提交
    ↓
提取prompt + imageUrl
    ↓
读取points_config配置
taskType="image2video"
webappId="1973555366057390081"
    ↓
构建nodeInfoList:
- image节点（完整URL）
- prompt节点
- model节点
- duration节点
    ↓
调用RunningHub API
    ↓
获得taskId
status='processing'
    ↓
5秒轮询一次
    ↓
检测到SUCCESS
    ↓
获取视频URL
status='completed'
```

---

## ⚠️ 注意事项

### 1. 图片要求

- **图生视频模型不支持真人图像作为输入**
- 建议使用风景、物体、场景类图片
- 支持完整的HTTP/HTTPS URL，无需预先上传到RunningHub

### 2. Prompt建议

- **文生视频**：详细描述镜头、场景、动作、时间节点
- **图生视频**：描述如何让图片"动起来"，添加什么动态效果

### 3. 处理时间

- 文生视频：约2-5分钟
- 图生视频：约2-5分钟（取决于服务器负载）

### 4. 积分消耗

- 任务提交时立即扣除积分（冻结）
- 任务成功：确认消耗积分
- 任务失败：自动退还积分

---

## 🧪 测试步骤

### 测试1：文生视频（竖屏10秒）

```bash
# 1. 提交任务
TASK_RESPONSE=$(curl -X POST "http://localhost:8081/user/ai/tasks/submit" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "modelName": "rh_sora2_text_portrait",
    "prompt": "一个人在海边奔跑，镜头从远到近"
  }')

echo $TASK_RESPONSE

# 2. 提取taskNo
TASK_NO=$(echo $TASK_RESPONSE | jq -r '.data.taskNo')

# 3. 等待2-5分钟后查询
curl "http://localhost:8081/user/ai/tasks/$TASK_NO" \
  -H "Authorization: Bearer YOUR_TOKEN"
```

### 测试2：图生视频（横屏高清）

```bash
# 1. 提交任务
curl -X POST "http://localhost:8081/user/ai/tasks/submit" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "modelName": "rh_sora2_img_landscape_hd",
    "prompt": "镜头缓缓向下俯冲穿越云层，紫蓝色霓虹反射在摩天大楼玻璃幕墙上",
    "imageUrl": "https://example.com/city-skyline.jpg"
  }'

# 2. 查看日志确认图片URL正确传递
tail -f logs/application.log | grep "RunningHub Provider"
```

---

## 📝 数据库配置

所有模型配置已通过 `V5__add_provider_support.sql` 自动创建：

```sql
-- 查看所有RunningHub模型
SELECT model_name, description, points_cost, provider_config
FROM points_config
WHERE provider_type = 'runninghub'
ORDER BY points_cost;

-- 查看某个模型的配置
SELECT provider_config
FROM points_config
WHERE model_name = 'rh_sora2_img_landscape';

-- 结果示例：
{
  "webappId": "1973555366057390081",
  "taskType": "image2video",
  "model": "landscape",
  "duration": 10
}
```

---

## 🔍 故障排查

### 问题1：图生视频任务失败

**错误信息：** "图生视频任务必须提供imageUrl或imageBase64"

**解决方案：**
- 确认请求中包含 `imageUrl` 或 `imageBase64` 字段
- 检查图片URL是否可访问
- 确认使用的是图生视频模型（包含 `_img_` 的模型名）

### 问题2：任务一直处于processing状态

**可能原因：**
- RunningHub服务器负载高
- 网络连接问题
- 任务确实在处理中

**解决方案：**
```bash
# 1. 查看轮询日志
tail -f logs/application.log | grep "RunningHub轮询"

# 2. 检查数据库中的provider_task_id
SELECT task_no, provider_task_id, status, update_time
FROM ai_task
WHERE task_no = 'YOUR_TASK_NO';

# 3. 手动查询RunningHub状态（仅用于调试）
curl -X POST "https://www.runninghub.cn/task/openapi/status" \
  -H "Content-Type: application/json" \
  -d '{
    "apiKey": "YOUR_API_KEY",
    "taskId": "PROVIDER_TASK_ID"
  }'
```

---

## 📊 性能监控

```sql
-- 查看RunningHub任务统计
SELECT 
    status,
    COUNT(*) as count,
    AVG(TIMESTAMPDIFF(SECOND, start_time, complete_time)) as avg_seconds
FROM ai_task
WHERE provider_type = 'runninghub'
  AND create_time > DATE_SUB(NOW(), INTERVAL 1 DAY)
GROUP BY status;

-- 查看最近的失败任务
SELECT task_no, model_name, error_message, create_time
FROM ai_task
WHERE provider_type = 'runninghub'
  AND status = 'failed'
ORDER BY create_time DESC
LIMIT 10;
```

---

**RunningHub Sora2集成完成！** 🎉

系统现已支持文生视频和图生视频两种模式，共12个预配置模型可供使用！
