package ttslaunch import ( "context" "fmt" "os" "path/filepath" "strings" ) // scriptSpawner 把"起那个脚本"包成 spawnFunc(缺省实现)。 func scriptSpawner(path string) spawnFunc { return func(ctx context.Context) error { return LaunchScript(ctx, path) } } // LaunchScript 起一个启动脚本并**立即返回**,不等它结束。 // // 细节与代价: // - 脚本不存在 / 是目录 ⇒ 返回错误(调用方只 WARN:这台机器上没装 emotionTTS // 是正常形态,不是后端的病)。 // - `cmd.Dir` 设为脚本所在目录:`一键启动.bat` 里的相对路径按它自己的习惯解析, // 而不是按后端的进程工作目录。 // - **刻意不使用 ctx**:`exec.CommandContext` 会在 ctx 取消时杀掉子进程,而后端 // 优雅退出**不该带走** TTS —— 服务是独立的,主人的后端重启与它是两件事。 // - 起完由后台 goroutine `Wait` 回收(脚本通常活到 TTS 退出为止,这个 goroutine // 跟着后端一起活,代价可忽略)。 func LaunchScript(_ context.Context, path string) error { path = strings.TrimSpace(path) if path == "" { return fmt.Errorf("ttslaunch: empty script path") } info, err := os.Stat(path) if err != nil { return fmt.Errorf("ttslaunch: script %s: %w", path, err) } if info.IsDir() { return fmt.Errorf("ttslaunch: script %s is a directory", path) } cmd := shellCommand(path) cmd.Dir = filepath.Dir(path) if err := startDetached(cmd); err != nil { return fmt.Errorf("ttslaunch: start %s: %w", path, err) } go func() { _ = cmd.Wait() }() return nil }