添加视频帧提取功能和阿里云OSS存储支持

- 新增从视频素材提取首帧/尾帧的功能,支持画面连续性编辑
- 添加阿里云OSS存储支持,可配置本地或OSS存储方式
- 导入视频素材时自动探测并更新视频时长信息
- 前端添加从素材提取尾帧的UI界面
- 添加FramePrompt模型的数据库迁移

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
empty
2026-01-18 21:44:39 +08:00
parent fe595db96e
commit d970107a34
13 changed files with 351 additions and 19 deletions

View File

@@ -853,3 +853,72 @@ func (f *FFmpeg) generateSilence(outputPath string, duration float64) (string, e
f.log.Infow("Silence audio generated successfully", "output", outputPath)
return outputPath, nil
}
// ExtractFrame 从视频中提取指定位置的帧
// position: "first" 提取首帧, "last" 提取尾帧
// 返回提取的图片文件路径
func (f *FFmpeg) ExtractFrame(videoURL, outputPath, position string) (string, error) {
f.log.Infow("Extracting frame from video", "url", videoURL, "position", position, "output", outputPath)
// 下载视频文件
downloadPath := filepath.Join(f.tempDir, fmt.Sprintf("video_%d.mp4", time.Now().Unix()))
localVideoPath, err := f.downloadVideo(videoURL, downloadPath)
if err != nil {
return "", fmt.Errorf("failed to download video: %w", err)
}
defer os.Remove(localVideoPath)
// 确保输出目录存在
outputDir := filepath.Dir(outputPath)
if err := os.MkdirAll(outputDir, 0755); err != nil {
return "", fmt.Errorf("failed to create output directory: %w", err)
}
var cmd *exec.Cmd
if position == "last" {
// 提取尾帧:先获取视频时长,然后提取最后一帧
duration, err := f.GetVideoDuration(localVideoPath)
if err != nil {
return "", fmt.Errorf("failed to get video duration: %w", err)
}
// 提取最后一帧时长减去0.1秒的位置)
seekTime := duration - 0.1
if seekTime < 0 {
seekTime = 0
}
cmd = exec.Command("ffmpeg",
"-ss", fmt.Sprintf("%.2f", seekTime),
"-i", localVideoPath,
"-vframes", "1",
"-q:v", "2",
"-y",
outputPath,
)
} else {
// 默认提取首帧
cmd = exec.Command("ffmpeg",
"-i", localVideoPath,
"-vframes", "1",
"-q:v", "2",
"-y",
outputPath,
)
}
output, err := cmd.CombinedOutput()
if err != nil {
f.log.Errorw("FFmpeg frame extraction failed", "error", err, "output", string(output))
return "", fmt.Errorf("ffmpeg frame extraction failed: %w, output: %s", err, string(output))
}
// 检查输出文件是否存在
if _, err := os.Stat(outputPath); os.IsNotExist(err) {
return "", fmt.Errorf("frame extraction failed: output file not created")
}
f.log.Infow("Frame extracted successfully", "output", outputPath, "position", position)
return outputPath, nil
}