vision/video_ffmpeg.go

107 lines
3.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}