diff --git a/preview.go b/preview.go index 27bdc82..d3a60eb 100644 --- a/preview.go +++ b/preview.go @@ -10,6 +10,7 @@ import ( // GenerateImagePreview 生成图片预览 // 支持缩放并裁剪以填充指定尺寸 (Fill 模式) +// 根据 outPath 后缀自动转换格式 (.webp, .jpg, .png 等) func GenerateImagePreview(srcPath, outPath string, width, height int) error { // 使用统一的 Load() 加载,内部已处理好 HEIC/sips/FFmpeg 的复杂格式兼容 c, err := Load(srcPath) @@ -18,14 +19,15 @@ func GenerateImagePreview(srcPath, outPath string, width, height int) error { } c.Fill(width, height) - if strings.HasSuffix(strings.ToLower(outPath), ".webp") { - // 借用 FFmpeg 将生成的画布转为高质量 WebP + ext := strings.ToLower(filepath.Ext(outPath)) + if ext == ".webp" { + // 借用 FFmpeg 将生成的画布转为高质量 WebP (比标准库或第三方库压缩更好) tmpFile := filepath.Join(os.TempDir(), fmt.Sprintf("preview_%d.png", os.Getpid())) defer os.Remove(tmpFile) if err := Save(c, tmpFile); err != nil { return err } - + v, err := NewVideo() if err == nil { cmd := exec.Command(v.FFmpegPath, "-i", tmpFile, "-c:v", "libwebp", "-quality", "80", "-y", outPath) @@ -122,20 +124,30 @@ func GenerateVideoPreview(videoPath, outPath string, width, height int, frameInt return nil } -// GenerateAudioPreview 提取 3 分钟内的音频用于预览或语音转写 -// 格式: Ogg Opus, 16kHz, 单声道, 12kbps (极致压缩,保留人声特征) +// GenerateAudioPreview 提取音频用于预览或语音转写 +// 支持根据 outPath 后缀输出格式: +// - .ogg: 使用 libopus (16kHz, 单声道, 12kbps), 极致压缩且保留人声特征,适合转写 +// - .wav: 标准 PCM (16kHz, 单声道), 无损但体积较大,部分转写引擎强制要求 +// - 其他: 默认使用 libopus 转为 ogg func GenerateAudioPreview(mediaPath, outPath string) error { v, err := NewVideo() if err != nil { return err } - // -vn: 禁用视频 - // -c:a libopus: 高效音频压缩 - // -ar 16000: 采样率 16k (转写标准) - // -ac 1: 单声道 - // -b:a 12k: 极致压缩 - // -t 180: 最长 180 秒 (足以获得内容概要) - cmd := exec.Command(v.FFmpegPath, "-i", mediaPath, "-vn", "-c:a", "libopus", "-ar", "16000", "-ac", "1", "-b:a", "12k", "-t", "180", "-y", outPath) + + ext := strings.ToLower(filepath.Ext(outPath)) + // 通用参数: 禁用视频, 16kHz 采样率 (STT 标准), 单声道 + args := []string{"-i", mediaPath, "-vn", "-ar", "16000", "-ac", "1"} + + if ext == ".wav" { + // WAV 格式,保留 PCM + args = append(args, "-y", outPath) + } else { + // 默认或 .ogg 使用 libopus 极致压缩,最长 180 秒 + args = append(args, "-c:a", "libopus", "-b:a", "12k", "-t", "180", "-y", outPath) + } + + cmd := exec.Command(v.FFmpegPath, args...) return cmd.Run() } diff --git a/preview_test.go b/preview_test.go index 8575229..dce03b9 100644 --- a/preview_test.go +++ b/preview_test.go @@ -105,10 +105,19 @@ func TestPreviewer(t *testing.T) { t.Run("GenerateAudioPreview", func(t *testing.T) { err := GenerateAudioPreview(videoPath, oggPath) if err != nil { - t.Errorf("GenerateAudioPreview failed: %v", err) + t.Errorf("GenerateAudioPreview (ogg) failed: %v", err) } if _, err := os.Stat(oggPath); os.IsNotExist(err) { t.Error("Ogg output not created") } + + wavPath := filepath.Join(tmpDir, "preview.wav") + err = GenerateAudioPreview(videoPath, wavPath) + if err != nil { + t.Errorf("GenerateAudioPreview (wav) failed: %v", err) + } + if _, err := os.Stat(wavPath); os.IsNotExist(err) { + t.Error("Wav output not created") + } }) }