package vision import ( "fmt" "os" "os/exec" "path/filepath" "runtime" "apigo.cc/go/file" ) // Video 代表一个视频操作封装 type Video struct { FFmpegPath string } // NewVideo 创建一个视频处理器,自动查找或下载 ffmpeg func NewVideo() (*Video, error) { p, err := EnsureFFmpeg() if err != nil { return nil, err } return &Video{FFmpegPath: p}, nil } // ExtractFrame 从视频中提取指定时间的帧 func (v *Video) ExtractFrame(videoPath string, offsetSeconds float64) (*Canvas, error) { tmpFile := filepath.Join(os.TempDir(), fmt.Sprintf("frame_%d.png", os.Getpid())) defer os.Remove(tmpFile) cmd := exec.Command(v.FFmpegPath, "-ss", fmt.Sprintf("%f", offsetSeconds), "-i", videoPath, "-frames:v", "1", tmpFile) if err := cmd.Run(); err != nil { return nil, fmt.Errorf("ffmpeg extract failed: %w", err) } return Load(tmpFile) } // WatermarkVideo 给视频添加水印 // videoPath: 输入视频 // markPath: 水印图片路径 // outPath: 输出视频路径 // pos: 水印位置 (使用 FFmpeg 语法, 如 '10:10', 'main_w-overlay_w-10:10') func (v *Video) WatermarkVideo(videoPath, markPath, outPath, pos string) error { if pos == "" { pos = "main_w-overlay_w-10:main_h-overlay_h-10" // 默认右下角 } filter := fmt.Sprintf("overlay=%s", pos) cmd := exec.Command(v.FFmpegPath, "-i", videoPath, "-i", markPath, "-filter_complex", filter, "-codec:a", "copy", outPath) return cmd.Run() } // CreateVideoFromImages 从一系列图片创建视频 func (v *Video) CreateVideoFromImages(imagePattern string, frameRate int, outPath string) error { cmd := exec.Command(v.FFmpegPath, "-framerate", fmt.Sprintf("%d", frameRate), "-i", imagePattern, "-c:v", "libx264", "-pix_fmt", "yuv420p", outPath) return cmd.Run() } // EnsureFFmpeg 确保 ffmpeg 命令可用 func EnsureFFmpeg() (string, error) { // 1. 检查 PATH if p, err := exec.LookPath("ffmpeg"); err == nil { return p, nil } // 2. 检查本地目录 localDir := filepath.Join(os.Getenv("HOME"), ".vision", "bin") localFF := filepath.Join(localDir, "ffmpeg") if runtime.GOOS == "windows" { localFF += ".exe" } if file.Exists(localFF) { return localFF, nil } // 3. 自动下载 return DownloadFFmpeg(localDir) } // DownloadFFmpeg 下载对应系统的 FFmpeg 二进制文件 func DownloadFFmpeg(targetDir string) (string, error) { if err := os.MkdirAll(targetDir, 0755); err != nil { return "", err } var url string switch runtime.GOOS { case "darwin": // 使用针对 macOS 的精简版二进制 (示例 URL,实际应指向可靠镜像) url = "https://evermeet.cx/ffmpeg/get/zip" case "linux": url = "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz" case "windows": url = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip" default: return "", fmt.Errorf("unsupported OS: %s", runtime.GOOS) } // 注意:实际下载逻辑需要处理解压、权限等。 // 为了精简,这里我们只提供语义。在真实场景中可以使用 go/http 下载并解压。 fmt.Printf("FFmpeg not found. Please install it or download from: %s\n", url) return "", fmt.Errorf("ffmpeg not found, please install manually or check %s", url) }