Compare commits

..

2 Commits

Author SHA1 Message Date
nnwang
68d3452bab Merge branch 'main' of https://git.liulikeji.cn/xingluo/GMapiServer 2026-02-22 09:02:42 +08:00
nnwang
736d7dd8f9 添加边转换边输出frame_urls 2026-02-07 02:21:57 +08:00
5 changed files with 540 additions and 70 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ __pycache__/
app.log
temp_files
frames
server.log

108
README.md
View File

@@ -54,23 +54,98 @@ GMapiServer 是一个轻量级、模块化的 API 服务框架,旨在为开发
3. 视频帧提取接口
• 功能: 从视频中提取指定分辨率、帧率的图片帧,支持强制分辨率调整和填充。
• 功能: 从视频中提取指定分辨率、帧率的图片帧,支持强制分辨率调整和填充支持B站BV号
• 异步接口: POST /api/video_frame/async
• 参数:
• video_url: 视频文件 URL
• video_url: 视频文件 URL (或使用 video_bv 参数替代)
• video_bv: B站视频BV号 (自动转换为URL)
• w: 输出宽度
• h: 输出高度
• fps: 输出帧率
• fps: 输出帧率 (默认: 30)
• force_resolution: 是否强制调整分辨率
• force_resolution: 是否强制调整分辨率 (默认: false)
• pad_to_target: 是否填充到目标分辨率
• pad_to_target: 是否填充到目标分辨率 (默认: false)
• 返回示例:
```json
{
"task_id": "abc12345",
"status_url": "http://localhost:5000/api/task/abc12345",
"message": "任务已创建请使用状态URL查询进度"
}
```
• 实时任务状态响应(处理中):
```json
{
"task_id": "abc12345",
"status": "running",
"progress": 50,
"create_time": 1770393314.1262555,
"start_time": 1770393314.1262555,
"end_time": null,
"type": "video_frame",
"result": {
"audio_urls": {
"audio_dfpwm_url": "/frames/abc12345/audio.dfpwm",
"audio_dfpwm_left_url": "/frames/abc12345/audio_left.dfpwm",
"audio_dfpwm_right_url": "/frames/abc12345/audio_right.dfpwm"
},
"current_frames": 180,
"total_frames": 6059,
"output_resolution": {"w": 640, "h": 360},
"frame_urls": [
"/frames/abc12345/frame_000001.png",
"/frames/abc12345/frame_000002.png",
// ... 当前已生成的帧
]
},
"total_logs": 15,
"last_index": 15,
"new_logs": ["日志1", "日志2"]
}
```
• 任务完成时结果:
```json
{
"task_id": "abc12345",
"status": "completed",
"progress": 100,
"create_time": 1770393314.1262555,
"start_time": 1770393314.1262555,
"end_time": 1770393391.1024792,
"type": "video_frame",
"result": {
"audio_urls": {
"audio_dfpwm_url": "/frames/abc12345/audio.dfpwm",
"audio_dfpwm_left_url": "/frames/abc12345/audio_left.dfpwm",
"audio_dfpwm_right_url": "/frames/abc12345/audio_right.dfpwm"
},
"duration_seconds": 303.019,
"current_frames": 6059,
"total_frames": 6059,
"fps": 20,
"output_resolution": {"w": 640, "h": 360},
"frame_urls": [
"/frames/abc12345/frame_000001.png",
"/frames/abc12345/frame_000002.png",
// ... 所有生成帧
]
},
"total_logs": 164,
"last_index": 164,
"new_logs": ["最后日志"]
}
```
4. 异步任务状态查询
@@ -180,6 +255,29 @@ return {
FILE_EXPIRY = 7200 # 文件过期时间(秒)
🔥 视频帧实时进度功能
GMapiServer 的视频帧提取功能支持实时进度跟踪,让客户端可以在任务处理过程中实时获取已生成的帧并进行下载。
• 实时帧进度跟踪: 自动解析FFmpeg输出的进度信息实时更新当前帧数
• 智能进度计算: 基于帧数自动计算转换进度20-80%
• 音频预处理: 音频在视频处理前提取,客户端可提前下载音频文件
• 实时URL返回: 在处理过程中不断返回已生成的帧URL列表
## 优势特性
1. **减少等待时间**: 客户端无需等待整个视频处理完成
2. **并行下载**: 可以在转换过程中并行下载已生成的帧
3. **实时反馈**: 用户可以看到实时处理进度和日志
4. **资源优化**: 避免了"堵车"效应,提高系统并发能力
## 使用建议
• 客户端可以轮询 `/api/task/<task_id>` 接口获取最新状态
• 通过检查 `result.frame_urls` 列表长度变化确定新生成的帧
• 音频文件可以立即下载(音频处理优先级更高)
🔧 异步任务处理流程
1. 任务创建: 客户端提交任务请求服务器返回任务ID和状态查询URL

46
main.py
View File

@@ -234,7 +234,7 @@ def video_frame_async_api():
@app.route('/api/task/<task_id>', methods=['GET'])
def get_task_status(task_id):
"""查询任务状态和进度(自动返回新增日志)"""
"""查询任务状态和进度(自动返回新增日志和实时帧信息"""
with task_lock:
if task_id not in task_registry:
return jsonify({'error': '任务不存在'}), 404
@@ -257,20 +257,48 @@ def get_task_status(task_id):
'status': task_info['status'],
'type': task_info['type'],
'create_time': task_info['create_time'],
'start_time': task_info.get('start_time'),
'end_time': task_info.get('end_time'),
'progress': task_info.get('progress', 0),
'total_logs': current_log_count,
'new_logs': new_logs,
'last_index': current_log_count # 返回当前日志总数,方便客户端跟踪
'last_index': current_log_count,
'new_logs': new_logs
}
if 'start_time' in task_info:
response['start_time'] = task_info['start_time']
# 视频帧任务特殊处理
if task_info['type'] == 'video_frame':
result_data = {}
if 'end_time' in task_info:
response['end_time'] = task_info['end_time']
# 音频URL
if 'audio_urls' in task_info:
result_data['audio_urls'] = task_info['audio_urls']
# 根据状态返回不同信息
if task_info['status'] == 'completed':
# 当前帧信息
if 'current_frames' in task_info:
result_data['current_frames'] = task_info['current_frames']
# 总帧数
if 'estimated_total_frames' in task_info:
result_data['total_frames'] = task_info['estimated_total_frames']
# 输出分辨率
if 'output_resolution' in task_info:
result_data['output_resolution'] = task_info['output_resolution']
# 帧URL列表
if 'frame_job_dir' in task_info:
import os
import glob
job_dir = task_info['frame_job_dir']
if os.path.exists(job_dir):
frame_files = sorted([f for f in os.listdir(job_dir) if f.endswith('.png')])
frame_urls = [f"/frames/{task_id}/{f}" for f in frame_files]
result_data['frame_urls'] = frame_urls
response['result'] = result_data
# 其他类型任务的result处理
elif task_info['status'] == 'completed':
response['result'] = task_info['result']
elif task_info['status'] == 'error':
response['error'] = task_info['error']

View File

@@ -155638,3 +155638,230 @@ MemoryError
* Running on http://127.0.0.1:5000
* Running on http://192.168.2.200:5000
2026-01-09 21:38:10,853 - INFO - Press CTRL+C to quit
2026-02-06 22:30:38,133 - INFO - 开始执行定期清理任务...
2026-02-06 22:30:38,133 - INFO - 已启动后台清理线程
2026-02-06 22:30:38,134 - INFO - 清理完成,共删除 0 个过期文件夹
2026-02-06 22:30:38,134 - INFO - 共清理 0 个任务记录和 0 个文件记录
2026-02-06 22:30:43,387 - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.2.200:5000
2026-02-06 22:30:43,388 - INFO - Press CTRL+C to quit
2026-02-06 22:31:08,649 - INFO - 收到异步视频帧提取API请求
2026-02-06 22:31:08,678 - INFO - 开始处理视频帧提取任务: 32835e4a
2026-02-06 22:31:08,678 - INFO - 创建异步视频帧提取任务: 32835e4a
2026-02-06 22:31:08,695 - INFO - 127.0.0.1 - - [06/Feb/2026 22:31:08] "POST /api/video_frame/async HTTP/1.1" 202 -
2026-02-06 22:31:16,537 - INFO - 127.0.0.1 - - [06/Feb/2026 22:31:16] "GET /api/task/32835e4a HTTP/1.1" 200 -
2026-02-06 22:31:18,951 - INFO - 127.0.0.1 - - [06/Feb/2026 22:31:18] "GET /api/task/32835e4a HTTP/1.1" 200 -
2026-02-06 22:31:20,228 - INFO - 127.0.0.1 - - [06/Feb/2026 22:31:20] "GET /api/task/32835e4a HTTP/1.1" 200 -
2026-02-06 22:31:21,323 - INFO - 127.0.0.1 - - [06/Feb/2026 22:31:21] "GET /api/task/32835e4a HTTP/1.1" 200 -
2026-02-06 22:31:44,253 - INFO - 127.0.0.1 - - [06/Feb/2026 22:31:44] "GET /api/task/32835e4a HTTP/1.1" 200 -
2026-02-06 22:31:45,386 - INFO - 127.0.0.1 - - [06/Feb/2026 22:31:45] "GET /api/task/32835e4a HTTP/1.1" 200 -
2026-02-06 22:32:00,735 - INFO - 127.0.0.1 - - [06/Feb/2026 22:32:00] "GET /api/task/32835e4a HTTP/1.1" 200 -
2026-02-06 22:32:50,417 - INFO - 127.0.0.1 - - [06/Feb/2026 22:32:50] "GET /api/task/32835e4a HTTP/1.1" 200 -
2026-02-06 22:33:31,467 - INFO - 127.0.0.1 - - [06/Feb/2026 22:33:31] "GET /api/task/32835e4a HTTP/1.1" 200 -
2026-02-06 22:33:36,087 - INFO - 开始执行定期清理任务...
2026-02-06 22:33:36,087 - INFO - 已启动后台清理线程
2026-02-06 22:33:36,088 - INFO - 清理完成,共删除 0 个过期文件夹
2026-02-06 22:33:36,089 - INFO - 共清理 0 个任务记录和 0 个文件记录
2026-02-06 22:33:41,091 - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.2.200:5000
2026-02-06 22:33:41,093 - INFO - Press CTRL+C to quit
2026-02-06 22:33:42,350 - INFO - 收到异步视频帧提取API请求
2026-02-06 22:33:42,355 - INFO - 开始处理视频帧提取任务: a4a4fdb3
2026-02-06 22:33:42,355 - INFO - 创建异步视频帧提取任务: a4a4fdb3
2026-02-06 22:33:42,367 - INFO - 127.0.0.1 - - [06/Feb/2026 22:33:42] "POST /api/video_frame/async HTTP/1.1" 202 -
2026-02-06 22:33:48,889 - INFO - 127.0.0.1 - - [06/Feb/2026 22:33:48] "GET /api/task/a4a4fdb3 HTTP/1.1" 200 -
2026-02-06 22:34:44,421 - INFO - 127.0.0.1 - - [06/Feb/2026 22:34:44] "GET /api/task/a4a4fdb3 HTTP/1.1" 200 -
2026-02-06 22:34:50,862 - INFO - 收到异步视频帧提取API请求
2026-02-06 22:34:50,884 - INFO - 开始处理视频帧提取任务: da312040
2026-02-06 22:34:50,885 - INFO - 创建异步视频帧提取任务: da312040
2026-02-06 22:34:50,897 - INFO - 127.0.0.1 - - [06/Feb/2026 22:34:50] "POST /api/video_frame/async HTTP/1.1" 202 -
2026-02-06 22:34:57,781 - INFO - 127.0.0.1 - - [06/Feb/2026 22:34:57] "GET /api/task/da312040 HTTP/1.1" 200 -
2026-02-06 23:22:31,898 - INFO - 收到异步视频帧提取API请求
2026-02-06 23:22:31,910 - INFO - 开始处理视频帧提取任务: 271b78f4
2026-02-06 23:22:31,911 - INFO - 创建异步视频帧提取任务: 271b78f4
2026-02-06 23:22:31,920 - INFO - 127.0.0.1 - - [06/Feb/2026 23:22:31] "POST /api/video_frame/async HTTP/1.1" 202 -
2026-02-06 23:22:38,025 - INFO - 127.0.0.1 - - [06/Feb/2026 23:22:38] "GET /api/task/271b78f4 HTTP/1.1" 200 -
2026-02-06 23:23:10,137 - INFO - 127.0.0.1 - - [06/Feb/2026 23:23:10] "GET /api/task/271b78f4 HTTP/1.1" 200 -
2026-02-06 23:23:43,303 - INFO - 127.0.0.1 - - [06/Feb/2026 23:23:43] "GET /api/task/271b78f4 HTTP/1.1" 200 -
2026-02-06 23:24:09,824 - INFO - 收到异步视频帧提取API请求
2026-02-06 23:24:09,827 - INFO - 开始处理视频帧提取任务: 63b05a14
2026-02-06 23:24:09,827 - INFO - 创建异步视频帧提取任务: 63b05a14
2026-02-06 23:24:09,828 - INFO - 127.0.0.1 - - [06/Feb/2026 23:24:09] "POST /api/video_frame/async HTTP/1.1" 202 -
2026-02-06 23:24:20,359 - INFO - 127.0.0.1 - - [06/Feb/2026 23:24:20] "GET /api/task/63b05a14 HTTP/1.1" 200 -
2026-02-06 23:24:30,622 - INFO - 127.0.0.1 - - [06/Feb/2026 23:24:30] "GET /api/task/63b05a14 HTTP/1.1" 200 -
2026-02-06 23:25:44,262 - INFO - 127.0.0.1 - - [06/Feb/2026 23:25:44] "GET /api/task/63b05a14 HTTP/1.1" 200 -
2026-02-06 23:26:47,228 - INFO - 127.0.0.1 - - [06/Feb/2026 23:26:47] "GET /api/task/63b05a14 HTTP/1.1" 200 -
2026-02-06 23:35:27,777 - INFO - 开始执行定期清理任务...
2026-02-06 23:35:27,777 - INFO - 已启动后台清理线程
2026-02-06 23:35:27,778 - INFO - 清理完成,共删除 0 个过期文件夹
2026-02-06 23:35:27,778 - INFO - 共清理 0 个任务记录和 0 个文件记录
2026-02-06 23:35:32,970 - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.2.200:5000
2026-02-06 23:35:32,971 - INFO - Press CTRL+C to quit
2026-02-06 23:35:33,484 - INFO - 收到异步视频帧提取API请求
2026-02-06 23:35:33,486 - INFO - 开始处理视频帧提取任务: 56f4b33b
2026-02-06 23:35:33,486 - INFO - 创建异步视频帧提取任务: 56f4b33b
2026-02-06 23:35:33,487 - INFO - 127.0.0.1 - - [06/Feb/2026 23:35:33] "POST /api/video_frame/async HTTP/1.1" 202 -
2026-02-06 23:35:41,217 - INFO - 127.0.0.1 - - [06/Feb/2026 23:35:41] "GET /api/task/56f4b33b HTTP/1.1" 200 -
2026-02-06 23:54:36,540 - INFO - 开始执行定期清理任务...
2026-02-06 23:54:36,540 - INFO - 已启动后台清理线程
2026-02-06 23:54:36,542 - INFO - 清理完成,共删除 0 个过期文件夹
2026-02-06 23:54:36,542 - INFO - 共清理 0 个任务记录和 0 个文件记录
2026-02-06 23:54:41,569 - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.2.200:5000
2026-02-06 23:54:41,575 - INFO - Press CTRL+C to quit
2026-02-06 23:55:10,001 - INFO - 127.0.0.1 - - [06/Feb/2026 23:55:10] "GET /api/task/56f4b33b HTTP/1.1" 404 -
2026-02-06 23:55:14,122 - INFO - 收到异步视频帧提取API请求
2026-02-06 23:55:14,126 - INFO - 开始处理视频帧提取任务: 4e1b83d7
2026-02-06 23:55:14,126 - INFO - 创建异步视频帧提取任务: 4e1b83d7
2026-02-06 23:55:14,131 - INFO - 127.0.0.1 - - [06/Feb/2026 23:55:14] "POST /api/video_frame/async HTTP/1.1" 202 -
2026-02-06 23:55:19,527 - INFO - 127.0.0.1 - - [06/Feb/2026 23:55:19] "GET /api/task/4e1b83d7 HTTP/1.1" 200 -
2026-02-06 23:55:34,479 - INFO - 127.0.0.1 - - [06/Feb/2026 23:55:34] "GET /api/task/4e1b83d7 HTTP/1.1" 200 -
2026-02-06 23:55:45,865 - INFO - 127.0.0.1 - - [06/Feb/2026 23:55:45] "GET /api/task/4e1b83d7 HTTP/1.1" 200 -
2026-02-06 23:55:53,720 - INFO - 127.0.0.1 - - [06/Feb/2026 23:55:53] "GET /api/task/4e1b83d7 HTTP/1.1" 200 -
2026-02-06 23:56:00,220 - INFO - 127.0.0.1 - - [06/Feb/2026 23:56:00] "GET /api/task/4e1b83d7 HTTP/1.1" 200 -
2026-02-06 23:57:33,074 - INFO - 127.0.0.1 - - [06/Feb/2026 23:57:33] "GET /api/task/4e1b83d7 HTTP/1.1" 200 -
2026-02-07 00:54:36,555 - INFO - 开始执行定期清理任务...
2026-02-07 00:54:36,573 - INFO - 清理完成,共删除 0 个过期文件夹
2026-02-07 00:54:36,595 - INFO - 共清理 0 个任务记录和 0 个文件记录
2026-02-07 01:52:29,676 - INFO - 开始执行定期清理任务...
2026-02-07 01:52:29,676 - INFO - 已启动后台清理线程
2026-02-07 01:52:29,677 - INFO - 清理完成,共删除 0 个过期文件夹
2026-02-07 01:52:29,678 - INFO - 共清理 0 个任务记录和 0 个文件记录
2026-02-07 01:52:34,846 - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.2.200:5000
2026-02-07 01:52:34,847 - INFO - Press CTRL+C to quit
2026-02-07 01:52:35,665 - INFO - 收到异步视频帧提取API请求
2026-02-07 01:52:35,685 - INFO - 开始处理视频帧提取任务: 3ab15220
2026-02-07 01:52:35,685 - INFO - 创建异步视频帧提取任务: 3ab15220
2026-02-07 01:52:35,692 - INFO - 127.0.0.1 - - [07/Feb/2026 01:52:35] "POST /api/video_frame/async HTTP/1.1" 202 -
2026-02-07 01:52:41,568 - INFO - 127.0.0.1 - - [07/Feb/2026 01:52:41] "GET /api/task/3ab15220 HTTP/1.1" 200 -
2026-02-07 01:52:43,784 - INFO - 127.0.0.1 - - [07/Feb/2026 01:52:43] "GET /api/task/3ab15220 HTTP/1.1" 200 -
2026-02-07 01:52:44,790 - INFO - 127.0.0.1 - - [07/Feb/2026 01:52:44] "GET /api/task/3ab15220 HTTP/1.1" 200 -
2026-02-07 01:52:45,661 - INFO - 127.0.0.1 - - [07/Feb/2026 01:52:45] "GET /api/task/3ab15220 HTTP/1.1" 200 -
2026-02-07 01:52:50,458 - INFO - 127.0.0.1 - - [07/Feb/2026 01:52:50] "GET /api/task/3ab15220 HTTP/1.1" 200 -
2026-02-07 01:52:51,300 - INFO - 127.0.0.1 - - [07/Feb/2026 01:52:51] "GET /api/task/3ab15220 HTTP/1.1" 200 -
2026-02-07 01:53:02,403 - INFO - 127.0.0.1 - - [07/Feb/2026 01:53:02] "GET /api/task/3ab15220 HTTP/1.1" 200 -
2026-02-07 01:53:03,310 - INFO - 127.0.0.1 - - [07/Feb/2026 01:53:03] "GET /api/task/3ab15220 HTTP/1.1" 200 -
2026-02-07 01:53:04,251 - INFO - 127.0.0.1 - - [07/Feb/2026 01:53:04] "GET /api/task/3ab15220 HTTP/1.1" 200 -
2026-02-07 01:53:05,080 - INFO - 127.0.0.1 - - [07/Feb/2026 01:53:05] "GET /api/task/3ab15220 HTTP/1.1" 200 -
2026-02-07 01:53:05,803 - INFO - 127.0.0.1 - - [07/Feb/2026 01:53:05] "GET /api/task/3ab15220 HTTP/1.1" 200 -
2026-02-07 01:53:06,659 - INFO - 127.0.0.1 - - [07/Feb/2026 01:53:06] "GET /api/task/3ab15220 HTTP/1.1" 200 -
2026-02-07 01:53:07,289 - INFO - 127.0.0.1 - - [07/Feb/2026 01:53:07] "GET /api/task/3ab15220 HTTP/1.1" 200 -
2026-02-07 01:53:16,809 - INFO - 开始执行定期清理任务...
2026-02-07 01:53:16,810 - INFO - 已启动后台清理线程
2026-02-07 01:53:16,810 - INFO - 清理完成,共删除 0 个过期文件夹
2026-02-07 01:53:16,810 - INFO - 共清理 0 个任务记录和 0 个文件记录
2026-02-07 01:53:21,754 - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.2.200:5000
2026-02-07 01:53:21,754 - INFO - Press CTRL+C to quit
2026-02-07 01:53:22,104 - INFO - 收到异步视频帧提取API请求
2026-02-07 01:53:22,105 - INFO - 开始处理视频帧提取任务: ec19c8de
2026-02-07 01:53:22,106 - INFO - 创建异步视频帧提取任务: ec19c8de
2026-02-07 01:53:22,106 - INFO - 127.0.0.1 - - [07/Feb/2026 01:53:22] "POST /api/video_frame/async HTTP/1.1" 202 -
2026-02-07 01:53:27,823 - INFO - 127.0.0.1 - - [07/Feb/2026 01:53:27] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:53:30,306 - INFO - 127.0.0.1 - - [07/Feb/2026 01:53:30] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:53:31,180 - INFO - 127.0.0.1 - - [07/Feb/2026 01:53:31] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:53:37,386 - INFO - 127.0.0.1 - - [07/Feb/2026 01:53:37] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:53:44,861 - INFO - 127.0.0.1 - - [07/Feb/2026 01:53:44] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:53:45,844 - INFO - 127.0.0.1 - - [07/Feb/2026 01:53:45] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:53:55,848 - INFO - 127.0.0.1 - - [07/Feb/2026 01:53:55] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:53:57,028 - INFO - 127.0.0.1 - - [07/Feb/2026 01:53:57] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:53:57,806 - INFO - 127.0.0.1 - - [07/Feb/2026 01:53:57] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:54:11,517 - INFO - 127.0.0.1 - - [07/Feb/2026 01:54:11] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:54:12,356 - INFO - 127.0.0.1 - - [07/Feb/2026 01:54:12] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:54:13,112 - INFO - 127.0.0.1 - - [07/Feb/2026 01:54:13] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:54:13,962 - INFO - 127.0.0.1 - - [07/Feb/2026 01:54:13] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:54:14,796 - INFO - 127.0.0.1 - - [07/Feb/2026 01:54:14] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:54:23,249 - INFO - 127.0.0.1 - - [07/Feb/2026 01:54:23] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:54:24,088 - INFO - 127.0.0.1 - - [07/Feb/2026 01:54:24] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:54:24,797 - INFO - 127.0.0.1 - - [07/Feb/2026 01:54:24] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:54:27,677 - INFO - 127.0.0.1 - - [07/Feb/2026 01:54:27] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:54:28,476 - INFO - 127.0.0.1 - - [07/Feb/2026 01:54:28] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:54:29,159 - INFO - 127.0.0.1 - - [07/Feb/2026 01:54:29] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:54:56,792 - INFO - 127.0.0.1 - - [07/Feb/2026 01:54:56] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:54:57,709 - INFO - 127.0.0.1 - - [07/Feb/2026 01:54:57] "GET /api/task/ec19c8de HTTP/1.1" 200 -
2026-02-07 01:55:03,799 - INFO - 开始执行定期清理任务...
2026-02-07 01:55:03,799 - INFO - 已启动后台清理线程
2026-02-07 01:55:03,799 - INFO - 清理完成,共删除 0 个过期文件夹
2026-02-07 01:55:03,800 - INFO - 共清理 0 个任务记录和 0 个文件记录
2026-02-07 01:55:08,773 - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.2.200:5000
2026-02-07 01:55:08,774 - INFO - Press CTRL+C to quit
2026-02-07 01:55:21,159 - INFO - 收到异步视频帧提取API请求
2026-02-07 01:55:21,160 - INFO - 开始处理视频帧提取任务: 482dc8ca
2026-02-07 01:55:21,160 - INFO - 创建异步视频帧提取任务: 482dc8ca
2026-02-07 01:55:21,160 - INFO - 127.0.0.1 - - [07/Feb/2026 01:55:21] "POST /api/video_frame/async HTTP/1.1" 202 -
2026-02-07 01:55:26,702 - INFO - 127.0.0.1 - - [07/Feb/2026 01:55:26] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:55:54,574 - INFO - 127.0.0.1 - - [07/Feb/2026 01:55:54] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:55:55,493 - INFO - 127.0.0.1 - - [07/Feb/2026 01:55:55] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:55:58,750 - INFO - 127.0.0.1 - - [07/Feb/2026 01:55:58] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:55:59,482 - INFO - 127.0.0.1 - - [07/Feb/2026 01:55:59] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:56:00,212 - INFO - 127.0.0.1 - - [07/Feb/2026 01:56:00] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:56:00,985 - INFO - 127.0.0.1 - - [07/Feb/2026 01:56:00] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:56:01,521 - INFO - 127.0.0.1 - - [07/Feb/2026 01:56:01] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:56:02,180 - INFO - 127.0.0.1 - - [07/Feb/2026 01:56:02] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:56:02,773 - INFO - 127.0.0.1 - - [07/Feb/2026 01:56:02] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:56:03,446 - INFO - 127.0.0.1 - - [07/Feb/2026 01:56:03] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:57:40,035 - INFO - 127.0.0.1 - - [07/Feb/2026 01:57:40] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:57:50,306 - INFO - 127.0.0.1 - - [07/Feb/2026 01:57:50] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:57:53,023 - INFO - 127.0.0.1 - - [07/Feb/2026 01:57:53] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:57:53,842 - INFO - 127.0.0.1 - - [07/Feb/2026 01:57:53] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:57:54,885 - INFO - 127.0.0.1 - - [07/Feb/2026 01:57:54] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:57:55,526 - INFO - 127.0.0.1 - - [07/Feb/2026 01:57:55] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:57:55,928 - INFO - 127.0.0.1 - - [07/Feb/2026 01:57:55] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:57:56,309 - INFO - 127.0.0.1 - - [07/Feb/2026 01:57:56] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:57:56,788 - INFO - 127.0.0.1 - - [07/Feb/2026 01:57:56] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:57:57,192 - INFO - 127.0.0.1 - - [07/Feb/2026 01:57:57] "GET /api/task/482dc8ca HTTP/1.1" 200 -
2026-02-07 01:58:17,463 - INFO - 开始执行定期清理任务...
2026-02-07 01:58:17,463 - INFO - 已启动后台清理线程
2026-02-07 01:58:17,464 - INFO - 清理完成,共删除 0 个过期文件夹
2026-02-07 01:58:17,464 - INFO - 共清理 0 个任务记录和 0 个文件记录
2026-02-07 01:58:22,439 - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.2.200:5000
2026-02-07 01:58:22,440 - INFO - Press CTRL+C to quit
2026-02-07 01:58:23,081 - INFO - 收到异步视频帧提取API请求
2026-02-07 01:58:23,082 - INFO - 开始处理视频帧提取任务: 41ca0a3f
2026-02-07 01:58:23,082 - INFO - 创建异步视频帧提取任务: 41ca0a3f
2026-02-07 01:58:23,083 - INFO - 127.0.0.1 - - [07/Feb/2026 01:58:23] "POST /api/video_frame/async HTTP/1.1" 202 -
2026-02-07 01:58:28,344 - INFO - 127.0.0.1 - - [07/Feb/2026 01:58:28] "GET /api/task/41ca0a3f HTTP/1.1" 200 -
2026-02-07 01:58:35,347 - INFO - 127.0.0.1 - - [07/Feb/2026 01:58:35] "GET /api/task/41ca0a3f HTTP/1.1" 200 -
2026-02-07 01:58:36,870 - INFO - 127.0.0.1 - - [07/Feb/2026 01:58:36] "GET /api/task/41ca0a3f HTTP/1.1" 200 -
2026-02-07 01:59:04,697 - INFO - 127.0.0.1 - - [07/Feb/2026 01:59:04] "GET /api/task/41ca0a3f HTTP/1.1" 200 -
2026-02-07 02:02:15,718 - INFO - 开始执行定期清理任务...
2026-02-07 02:02:15,718 - INFO - 已启动后台清理线程
2026-02-07 02:02:15,719 - INFO - 清理完成,共删除 0 个过期文件夹
2026-02-07 02:02:15,719 - INFO - 共清理 0 个任务记录和 0 个文件记录
2026-02-07 02:02:20,676 - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.2.200:5000
2026-02-07 02:02:20,676 - INFO - Press CTRL+C to quit
2026-02-07 02:02:21,030 - INFO - 收到异步视频帧提取API请求
2026-02-07 02:02:21,030 - INFO - 开始处理视频帧提取任务: d3c23dac
2026-02-07 02:02:21,030 - INFO - 创建异步视频帧提取任务: d3c23dac
2026-02-07 02:02:21,031 - INFO - 127.0.0.1 - - [07/Feb/2026 02:02:21] "POST /api/video_frame/async HTTP/1.1" 202 -
2026-02-07 02:02:27,517 - INFO - 127.0.0.1 - - [07/Feb/2026 02:02:27] "GET /api/task/d3c23dac HTTP/1.1" 200 -
2026-02-07 02:02:36,175 - INFO - 127.0.0.1 - - [07/Feb/2026 02:02:36] "GET /api/task/d3c23dac HTTP/1.1" 200 -
2026-02-07 02:02:40,816 - INFO - 127.0.0.1 - - [07/Feb/2026 02:02:40] "GET /api/task/d3c23dac HTTP/1.1" 200 -
2026-02-07 02:02:59,780 - INFO - 127.0.0.1 - - [07/Feb/2026 02:02:59] "GET /api/task/d3c23dac HTTP/1.1" 200 -
2026-02-07 02:03:15,855 - INFO - 127.0.0.1 - - [07/Feb/2026 02:03:15] "GET /api/task/d3c23dac HTTP/1.1" 200 -
2026-02-07 02:03:18,900 - INFO - 127.0.0.1 - - [07/Feb/2026 02:03:18] "GET /api/task/d3c23dac HTTP/1.1" 200 -
2026-02-07 02:03:20,213 - INFO - 127.0.0.1 - - [07/Feb/2026 02:03:20] "GET /api/task/d3c23dac HTTP/1.1" 200 -
2026-02-07 02:03:21,317 - INFO - 127.0.0.1 - - [07/Feb/2026 02:03:21] "GET /api/task/d3c23dac HTTP/1.1" 200 -

View File

@@ -29,6 +29,27 @@ else:
FFMPEG_PATH = os.path.join(BASE_DIR, 'lib', 'ffmpeg', 'bin', 'ffmpeg')
FFPROBE_PATH = os.path.join(BASE_DIR, 'lib', 'ffmpeg', 'bin', 'ffprobe')
def parse_ffmpeg_frame_progress(line, task_id, task_registry, task_lock):
"""解析FFmpeg进度输出并更新任务状态"""
import re
# 匹配帧数frame= 180 fps= 90
frame_match = re.search(r'frame=\s*(\d+)', line)
if frame_match:
current_frame = int(frame_match.group(1))
# 更新任务状态的当前帧数
with task_lock:
if task_id in task_registry:
task_registry[task_id]['current_frames'] = current_frame
# 如果有总帧数估计,可以计算进度
if 'estimated_total_frames' in task_registry[task_id]:
total_frames = task_registry[task_id]['estimated_total_frames']
if total_frames > 0:
progress = min(80, int((current_frame / total_frames) * 60) + 20) # 20-80%为转换进度
task_registry[task_id]['progress'] = progress
def log_subprocess_output(pipe, task_id, task_registry, task_lock, prefix=""):
"""从管道实时读取并记录日志"""
if not pipe:
@@ -38,12 +59,30 @@ def log_subprocess_output(pipe, task_id, task_registry, task_lock, prefix=""):
if line:
clean_line = line.strip()
if clean_line and task_id and task_registry and task_lock:
add_task_log(task_id, f"{prefix}{clean_line}", task_registry, task_lock)
# 过滤掉一些不必要的进度字符
if '\r' in clean_line:
clean_line = clean_line.replace('\r', '')
# 过滤掉过于频繁的进度更新(只记录有意义的内容)
if prefix.startswith("[yt-dlp") and clean_line.startswith('['):
# 只记录重要的进度信息,过滤掉过于频繁的百分比更新
if '%' not in clean_line or '100%' in clean_line or 'Downloading' in clean_line:
add_task_log(task_id, f"{prefix}{clean_line}", task_registry, task_lock)
elif prefix == "[FFmpeg] " and "frame=" in clean_line:
# FFmpeg进度信息
add_task_log(task_id, f"{prefix}{clean_line}", task_registry, task_lock)
parse_ffmpeg_frame_progress(clean_line, task_id, task_registry, task_lock)
else:
# 其他日志正常记录
add_task_log(task_id, f"{prefix}{clean_line}", task_registry, task_lock)
except Exception as e:
if task_id and task_registry and task_lock:
add_task_log(task_id, f"[日志读取错误] {e}", task_registry, task_lock)
finally:
pipe.close()
try:
pipe.close()
except:
pass
# ======================
# 任务处理函数
@@ -104,27 +143,46 @@ def process_video_frame_extraction(data, file_registry, file_lock, task_id=None,
# === 替换 yt-dlp 下载部分 ===
if task_id and task_registry and task_lock:
add_task_log(task_id, f"开始下载视频: {video_url}", task_registry, task_lock)
with task_lock:
if task_id in task_registry:
task_registry[task_id]['progress'] = 20 # 开始下载进度20%
yt_dlp_cmd = [
sys.executable, '-m', 'yt_dlp',
video_url,
'-o', temp_base,
'-f', 'bv*[height<=720]+ba/b',
'--no-warnings',
'--progress', # 启用进度显示
'--newline', # 确保换行符正常
'--console-title', # 确保进度信息正确输出
'--no-colors', # 禁用颜色,避免控制字符干扰
]
# 使用 Popen 实时捕获 stderr
# 使用 Popen 实时捕获 stdout 和 stderr
proc = subprocess.Popen(
yt_dlp_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
universal_newlines=True,
encoding='utf-8',
errors='replace'
)
# 启动日志线程(只读 stderr因为 yt-dlp 进度在 stderr
# 启动日志线程yt-dlp 进度主要在 stdout
stdout_thread = threading.Thread(
target=log_subprocess_output,
args=(proc.stdout, task_id, task_registry, task_lock, "[yt-dlp] "),
daemon=True
)
stdout_thread.start()
# stderr 也可能有重要信息
stderr_thread = threading.Thread(
target=log_subprocess_output,
args=(proc.stderr, task_id, task_registry, task_lock, "[yt-dlp] "),
args=(proc.stderr, task_id, task_registry, task_lock, "[yt-dlp-error] "),
daemon=True
)
stderr_thread.start()
@@ -132,12 +190,34 @@ def process_video_frame_extraction(data, file_registry, file_lock, task_id=None,
try:
proc.wait(timeout=600)
if proc.returncode != 0:
raise subprocess.CalledProcessError(proc.returncode, yt_dlp_cmd)
# 获取更详细的错误信息
error_details = []
try:
if proc.stderr:
stderr_content = proc.stderr.read()
if stderr_content:
error_details.append(f"stderr: {stderr_content}")
if proc.stdout:
stdout_content = proc.stdout.read()
if stdout_content:
error_details.append(f"stdout: {stdout_content}")
except:
pass
error_msg = f"yt-dlp 下载失败 (返回码: {proc.returncode})"
if error_details:
error_msg += f" - {' '.join(error_details[:500])}" # 限制错误信息长度
raise subprocess.CalledProcessError(proc.returncode, yt_dlp_cmd, output=error_msg)
except subprocess.TimeoutExpired:
proc.kill()
if task_id and task_registry and task_lock:
add_task_log(task_id, "yt-dlp 下载超时超过10分钟", task_registry, task_lock)
raise Exception("yt-dlp 下载超时超过10分钟")
finally:
stderr_thread.join(timeout=5) # 等待日志线程结束
# 等待日志线程结束
stdout_thread.join(timeout=5)
stderr_thread.join(timeout=5)
# 查找实际生成的文件
candidates = glob.glob(temp_base + ".*")
@@ -153,9 +233,79 @@ def process_video_frame_extraction(data, file_registry, file_lock, task_id=None,
if task_id in task_registry:
task_registry[task_id]['progress'] = 40
# 首先处理音频(在视频处理之前)
audio_exists = has_audio(temp_video)
audio_urls = {}
if audio_exists:
if task_id and task_registry and task_lock:
add_task_log(task_id, "提取音频(左/右/混合声道)", task_registry, task_lock)
with task_lock:
if task_id in task_registry:
task_registry[task_id]['progress'] = 40
# 公共参数
dfpwm_args = [
FFMPEG_PATH, '-y',
'-i',temp_video,
'-vn',
'-ar', '48000',
'-ac', '1',
'-f', 'dfpwm'
]
# 混合声道
dfpwm_path_mix = os.path.join(job_dir, "audio.dfpwm")
subprocess.run(
dfpwm_args + [dfpwm_path_mix],
check=True, capture_output=True
)
# 左声道
dfpwm_path_left = os.path.join(job_dir, "audio_left.dfpwm")
subprocess.run(
dfpwm_args + ['-af', 'pan=mono|c0=c0'] + [dfpwm_path_left],
check=True, capture_output=True
)
# 右声道
dfpwm_path_right = os.path.join(job_dir, "audio_right.dfpwm")
subprocess.run(
dfpwm_args + ['-af', 'pan=mono|c0=c1'] + [dfpwm_path_right],
check=True, capture_output=True
)
audio_urls = {
"audio_dfpwm_url": f"/frames/{task_id}/audio.dfpwm",
"audio_dfpwm_left_url": f"/frames/{task_id}/audio_left.dfpwm",
"audio_dfpwm_right_url": f"/frames/{task_id}/audio_right.dfpwm"
}
# 更新音频URL到任务状态
if task_id and task_registry and task_lock:
with task_lock:
if task_id in task_registry:
task_registry[task_id]['audio_urls'] = audio_urls
# 获取视频信息
duration = get_video_duration(temp_video)
# 估计总帧数
estimated_total_frames = int(duration * fps)
# 初始化任务状态的帧相关字段
if task_id and task_registry and task_lock:
with task_lock:
if task_id in task_registry:
task_registry[task_id]['estimated_total_frames'] = estimated_total_frames
task_registry[task_id]['current_frames'] = 0
task_registry[task_id]['generated_frame_urls'] = []
task_registry[task_id]['frame_job_dir'] = job_dir
task_registry[task_id]['frame_params'] = {
'w': w, 'h': h, 'fps': fps,
'force_resolution': force_resolution, 'pad_to_target': pad_to_target
}
# 构建滤镜
vf = build_video_filter(w, h, fps, force_resolution, pad_to_target)
@@ -214,46 +364,12 @@ def process_video_frame_extraction(data, file_registry, file_lock, task_id=None,
out_w, out_h = get_output_resolution(job_dir)
total_frames = len([f for f in os.listdir(job_dir) if f.endswith('.png')])
# 检查音频
audio_exists = has_audio(temp_video)
if audio_exists:
if task_id and task_registry and task_lock:
add_task_log(task_id, "提取音频(左/右/混合声道)", task_registry, task_lock)
with task_lock:
if task_id in task_registry:
task_registry[task_id]['progress'] = 90
# 公共参数
dfpwm_args = [
FFMPEG_PATH, '-y',
'-i', temp_video,
'-vn',
'-ar', '48000',
'-ac', '1',
'-f', 'dfpwm'
]
# 混合声道
dfpwm_path_mix = os.path.join(job_dir, "audio.dfpwm")
subprocess.run(
dfpwm_args + [dfpwm_path_mix],
check=True, capture_output=True
)
# 左声道
dfpwm_path_left = os.path.join(job_dir, "audio_left.dfpwm")
subprocess.run(
dfpwm_args + ['-af', 'pan=mono|c0=c0'] + [dfpwm_path_left],
check=True, capture_output=True
)
# 右声道
dfpwm_path_right = os.path.join(job_dir, "audio_right.dfpwm")
subprocess.run(
dfpwm_args + ['-af', 'pan=mono|c0=c1'] + [dfpwm_path_right],
check=True, capture_output=True
)
# 保存输出分辨率和实际总帧数到任务状态
if task_id and task_registry and task_lock:
with task_lock:
if task_id in task_registry:
task_registry[task_id]['output_resolution'] = {"w": out_w, "h": out_h}
task_registry[task_id]['estimated_total_frames'] = total_frames
# 生成结果
result_data = {