80 lines
2.4 KiB
Go
80 lines
2.4 KiB
Go
package vision
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
)
|
|
|
|
// GenerateImagePreview 生成图片预览 (WebP)
|
|
// 支持缩放并裁剪以填充指定尺寸 (Fill 模式)
|
|
func GenerateImagePreview(srcPath, outPath string, width, height int) error {
|
|
c, err := Load(srcPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Fill(width, height)
|
|
return Save(c, outPath)
|
|
}
|
|
|
|
// GenerateVideoPreview 生成视频预览 (4帧动画 WebP)
|
|
// 支持缩放并裁剪以填充指定尺寸 (Fill 模式)
|
|
func GenerateVideoPreview(videoPath, outPath string, width, height int) error {
|
|
v, err := NewVideo()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
duration, err := getVideoDuration(videoPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
times := []float64{0, duration * 0.33, duration * 0.67, duration * 0.90}
|
|
tmpDir, _ := os.MkdirTemp("", "frames")
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
// 使用 ffmpeg 的 scale 和 crop 滤镜实现 Fill 效果
|
|
// force_original_aspect_ratio=increase 确保图片至少覆盖目标尺寸
|
|
// crop=w:h 裁剪中心区域
|
|
vf := fmt.Sprintf("scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d", width, height, width, height)
|
|
|
|
for i, t := range times {
|
|
framePath := filepath.Join(tmpDir, fmt.Sprintf("frame_%d.png", i))
|
|
cmd := exec.Command(v.FFmpegPath, "-ss", fmt.Sprintf("%f", t), "-i", videoPath, "-frames:v", "1", "-vf", vf, framePath)
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
cmd := exec.Command(v.FFmpegPath, "-framerate", "1", "-i", filepath.Join(tmpDir, "frame_%d.png"),
|
|
"-c:v", "libwebp", "-lossless", "0", "-quality", "70", "-loop", "0", outPath)
|
|
return cmd.Run()
|
|
}
|
|
|
|
// GenerateAudioPreview 提取 3 分钟内的音频用于转写/预览
|
|
// 格式: Ogg Opus, 16kHz, 单声道
|
|
func GenerateAudioPreview(mediaPath, outPath string) error {
|
|
v, err := NewVideo()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// -vn: 禁用视频
|
|
// -c:a libopus: 高效音频压缩
|
|
// -ar 16000: 采样率 16k
|
|
// -t 180: 最长 180 秒
|
|
cmd := exec.Command(v.FFmpegPath, "-i", mediaPath, "-vn", "-c:a", "libopus", "-ar", "16000", "-ac", "1", "-t", "180", outPath)
|
|
return cmd.Run()
|
|
}
|
|
|
|
func getVideoDuration(videoPath string) (float64, error) {
|
|
out, err := exec.Command("ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", videoPath).Output()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
var duration float64
|
|
_, err = fmt.Sscanf(string(out), "%f", &duration)
|
|
return duration, err
|
|
}
|