/ pkg / localtts / edgetts.go
edgetts.go
  1  package localtts
  2  
  3  import (
  4  	"context"
  5  	"fmt"
  6  	"io/ioutil"
  7  	"krillin-ai/internal/storage"
  8  	"krillin-ai/log"
  9  	"os"
 10  	"os/exec"
 11  	"path/filepath"
 12  	"strings"
 13  	"time"
 14  
 15  	"go.uber.org/zap"
 16  )
 17  
 18  type EdgeTtsClient struct {
 19  }
 20  
 21  func NewEdgeTtsClient() *EdgeTtsClient {
 22  	return &EdgeTtsClient{}
 23  }
 24  
 25  func (c *EdgeTtsClient) Text2Speech(text, voice, outputFile string) error {
 26  	// 清理语音名称中的额外空格
 27  	voice = strings.TrimSpace(voice)
 28  
 29  	// 确保输出目录存在
 30  	outputDir := filepath.Dir(outputFile)
 31  	if err := os.MkdirAll(outputDir, 0755); err != nil {
 32  		log.GetLogger().Error("创建输出目录失败", zap.String("dir", outputDir), zap.Error(err))
 33  		return fmt.Errorf("创建输出目录失败: %w", err)
 34  	}
 35  
 36  	// 获取绝对路径
 37  	absOutputFile, err := filepath.Abs(outputFile)
 38  	if err != nil {
 39  		log.GetLogger().Error("获取输出文件绝对路径失败", zap.Error(err))
 40  		return fmt.Errorf("获取输出文件绝对路径失败: %w", err)
 41  	}
 42  	absOutputDir := filepath.Dir(absOutputFile)
 43  
 44  	// 创建临时文件来存储文本内容,避免命令行参数转义问题
 45  	tempFile, err := ioutil.TempFile(absOutputDir, "edge_tts_text_*.txt")
 46  	if err != nil {
 47  		log.GetLogger().Error("创建临时文件失败", zap.Error(err))
 48  		return fmt.Errorf("创建临时文件失败: %w", err)
 49  	}
 50  	tempFileName := tempFile.Name()
 51  
 52  	// 确保在函数结束时清理临时文件
 53  	defer func() {
 54  		tempFile.Close()
 55  		if err := os.Remove(tempFileName); err != nil {
 56  			log.GetLogger().Warn("清理临时文件失败", zap.String("file", tempFileName), zap.Error(err))
 57  		}
 58  	}()
 59  
 60  	// 将文本写入临时文件
 61  	if _, err := tempFile.WriteString(text); err != nil {
 62  		log.GetLogger().Error("写入临时文件失败", zap.Error(err))
 63  		return fmt.Errorf("写入临时文件失败: %w", err)
 64  	}
 65  	tempFile.Close() // 确保文件被写入
 66  
 67  	// 重试机制
 68  	maxRetries := 3
 69  	for attempt := 1; attempt <= maxRetries; attempt++ {
 70  		log.GetLogger().Info("edge-tts转录尝试",
 71  			zap.Int("attempt", attempt),
 72  			zap.Int("maxRetries", maxRetries),
 73  			zap.String("text_length", fmt.Sprintf("%d", len(text))))
 74  
 75  		err := c.attemptTTS(tempFileName, voice, absOutputFile, attempt)
 76  		if err == nil {
 77  			// 成功生成
 78  			log.GetLogger().Info("edge-tts转录完成", zap.String("output file", absOutputFile))
 79  			if _, err := os.Stat(absOutputFile); os.IsNotExist(err) {
 80  				log.GetLogger().Error("edge-tts 输出文件不存在", zap.String("output file", absOutputFile))
 81  				return fmt.Errorf("edge-tts 输出文件不存在: %s", absOutputFile)
 82  			}
 83  			return nil
 84  		}
 85  
 86  		log.GetLogger().Warn("edge-tts转录失败,准备重试",
 87  			zap.Int("attempt", attempt),
 88  			zap.Error(err))
 89  
 90  		// 如果不是最后一次尝试,等待一段时间再重试
 91  		if attempt < maxRetries {
 92  			waitTime := time.Duration(attempt) * 2 * time.Second
 93  			log.GetLogger().Info("等待重试", zap.Duration("waitTime", waitTime))
 94  			time.Sleep(waitTime)
 95  		}
 96  	}
 97  
 98  	return fmt.Errorf("edge-tts转录失败,已重试%d次", maxRetries)
 99  }
100  
101  func (c *EdgeTtsClient) attemptTTS(tempFileName, voice, absOutputFile string, attempt int) error {
102  	// 使用新的edge-tts命令参数(文件输入方式)
103  	cmdArgs := []string{
104  		"--text-file", tempFileName,
105  		"--voice", voice,
106  		"--output", absOutputFile,
107  		"--format", "wav",
108  		"--sample_rate", "44100",
109  	}
110  
111  	// 创建带超时的上下文
112  	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) // 60秒超时
113  	defer cancel()
114  
115  	cmd := exec.CommandContext(ctx, storage.EdgeTtsPath, cmdArgs...)
116  	log.GetLogger().Info("edge-tts转录开始",
117  		zap.String("cmd", cmd.String()),
118  		zap.String("temp_file", tempFileName),
119  		zap.String("output_file", absOutputFile),
120  		zap.Int("attempt", attempt))
121  
122  	output, err := cmd.CombinedOutput()
123  	if err != nil {
124  		if ctx.Err() == context.DeadlineExceeded {
125  			log.GetLogger().Error("edge-tts cmd 超时", zap.String("output", string(output)), zap.Error(err))
126  			return fmt.Errorf("edge-tts 执行超时")
127  		}
128  		log.GetLogger().Error("edge-tts cmd 执行失败", zap.String("output", string(output)), zap.Error(err))
129  		return err
130  	}
131  
132  	return nil
133  }