视频保存与下载
任务完成后,从查询响应的 video_url 获取视频链接并下载。
获取视频链接
# 查询任务状态
curl https://api.maomaotoken.com/v1/videos/{task_id} \
-H "Authorization: Bearer YOUR_API_KEY"响应示例:
{
"id": "task_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"status": "completed",
"progress": 100,
"video_url": "https://cdn.example.com/videos/output.mp4",
"completed_at": 1791000042
}下载视频
使用 cURL
curl -L 'https://cdn.example.com/videos/output.mp4' \
-o my_video.mp4-L 选项用于跟随重定向。
使用 wget
wget 'https://cdn.example.com/videos/output.mp4' \
-O my_video.mp4使用 Python
import requests
video_url = "https://cdn.example.com/videos/output.mp4"
response = requests.get(video_url, stream=True)
response.raise_for_status()
with open('my_video.mp4', 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
print("下载完成!")使用 Node.js
const https = require('https');
const fs = require('fs');
const videoUrl = "https://cdn.example.com/videos/output.mp4";
const outputPath = "./my_video.mp4";
https.get(videoUrl, (response) => {
if (response.statusCode !== 200) {
throw new Error(`Failed to download: ${response.statusCode}`);
}
const file = fs.createWriteStream(outputPath);
response.pipe(file);
file.on('finish', () => {
file.close();
console.log('下载完成!');
});
file.on('error', (err) => {
fs.unlink(outputPath, () => {}); // 清理失败的文件
throw err;
});
});下载前的准备
1. 确认任务已完成
// 确保 status 为 completed
const task = await queryTask(taskId);
if (task.status !== 'completed') {
throw new Error(`任务未完成,当前状态: ${task.status}`);
}2. 重新查询获取最新链接
// 不要使用缓存的旧链接,重新查询获取最新的 video_url
const latestTask = await fetch(
`https://api.maomaotoken.com/v1/videos/${taskId}`,
{ headers: { 'Authorization': `Bearer ${apiKey}` } }
).then(r => r.json());
const videoUrl = latestTask.video_url;下载验证
检查 HTTP 状态码
# 检查响应是否为 200 OK
curl -I 'https://cdn.example.com/videos/output.mp4'
# 输出示例
# HTTP/1.1 200 OK
# Content-Type: video/mp4
# Content-Length: 25000000验证 Content-Type
# 确保返回的是视频文件而非 HTML
curl -I 'https://cdn.example.com/videos/output.mp4' | grep Content-Type
# ✓ 正确
# Content-Type: video/mp4
# ✗ 错误(说明链接已失效或返回了错误页面)
# Content-Type: text/html验证文件大小
# 检查下载的文件大小是否合理
ls -lh my_video.mp4
# 查看服务器返回的大小
curl -I 'https://cdn.example.com/videos/output.mp4' | grep Content-Length计算文件校验和
# macOS / Linux
md5sum my_video.mp4
# 与服务器返回的校验和对比(如支持)下载失败处理
1. 重新查询任务
// 如果下载失败,重新查询任务确认状态仍为 completed
const task = await queryTask(taskId);
if (task.status === 'completed') {
const freshUrl = task.video_url; // 获取新的 URL
// 使用新 URL 重试下载
} else if (task.status === 'failed') {
throw new Error(`任务失败: ${task.error.message}`);
} else {
throw new Error(`任务未完成: ${task.status}`);
}2. 检查链接有效性
# 检查链接是否过期或拒绝访问
curl -v 'https://cdn.example.com/videos/output.mp4' 2>&1 | head -20
# ✗ 常见错误响应
# < HTTP/1.1 404 Not Found (链接已过期)
# < HTTP/1.1 403 Forbidden (无权限访问)
# < HTTP/1.1 500 Server Error (服务器异常)3. 验证认证信息
某些配置可能需要在下载时携带认证:
# 尝试添加认证头
curl -L 'https://cdn.example.com/videos/output.mp4' \
-H "Authorization: Bearer YOUR_API_KEY" \
-o my_video.mp44. 重试策略
const downloadWithRetry = async (url, maxRetries = 3) => {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url);
if (response.status === 200) {
return response.arrayBuffer();
}
if (response.status === 404 || response.status === 403) {
throw new Error(`无法访问: ${response.status}`);
}
// 5xx 错误可重试
console.warn(`服务器错误 ${response.status},${i + 1} 秒后重试...`);
await new Promise(r => setTimeout(r, (i + 1) * 1000));
} catch (error) {
if (i === maxRetries - 1) throw error;
console.warn(`下载失败,${i + 1} 秒后重试...`);
await new Promise(r => setTimeout(r, (i + 1) * 1000));
}
}
};保存建议
1. 使用对象存储
不要依赖 API 返回的链接作为永久存储,应保存到自己的对象存储:
// 下载后保存到阿里云 OSS
const OSS = require('ali-oss');
const client = new OSS({
region: 'oss-cn-hangzhou',
accessKeyId: YOUR_KEY_ID,
accessKeySecret: YOUR_KEY_SECRET,
bucket: 'my-bucket'
});
const videoBuffer = await downloadWithRetry(videoUrl);
const result = await client.putObject(
`videos/${taskId}.mp4`,
Buffer.from(videoBuffer)
);
console.log('已保存到 OSS:', result.url);2. 保存元数据
// 记录任务信息以便追查
const metadata = {
taskId: task.id,
model: task.model,
createdAt: new Date(task.created_at * 1000),
completedAt: new Date(task.completed_at * 1000),
videoUrl: task.video_url,
localPath: '/path/to/my_video.mp4',
fileSize: fs.statSync('/path/to/my_video.mp4').size,
savedAt: new Date().toISOString()
};
// 保存到数据库或文件
saveToDatabase(metadata);3. 定期清理
# 清理超过 30 天未使用的本地副本
find ./videos -type f -mtime +30 -delete链接有效期
- ⏰ 生成的视频链接有有效期
- 📅 具体时长以线上服务策略为准
- 💾 不要将链接当作永久地址
- ✅ 建议任务完成后立即下载并保存到自己的存储
常见问题
链接可能已过期。重新查询任务获取最新的 video_url。
检查文件大小是否合理、Content-Type 是否为 video/mp4,并确认下载过程没有中断。
可能是网络带宽限制。可以尝试使用专业下载工具,或在服务器上直接下载。
可以。下载完成后对比 Content-Length 和实际文件大小,或检查校验和(如服务提供)。
取决于下载链路配置。可以尝试使用支持断点续传的下载工具,例如 aria2 或 wget -c。