before stt adding
This commit is contained in:
384
main.go
384
main.go
@@ -5,7 +5,6 @@ import (
|
||||
"crypto/tls"
|
||||
"embed"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
@@ -29,11 +28,42 @@ var staticFiles embed.FS
|
||||
//go:embed README.md
|
||||
var readmeContent string
|
||||
|
||||
//go:embed VERSION.txt
|
||||
var versionContent string
|
||||
|
||||
// getVersion возвращает версию приложения
|
||||
func getVersion() string {
|
||||
if versionContent != "" {
|
||||
return strings.TrimSpace(versionContent)
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Обработка флага -V для вывода версии
|
||||
if len(os.Args) > 1 && (os.Args[1] == "-V" || os.Args[1] == "--version") {
|
||||
fmt.Printf("Go Speech TTS версия: %s\n", getVersion())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Получение конфигурации из переменных окружения
|
||||
port := getEnv("GO_SPEECH_PORT", "8443")
|
||||
|
||||
// Определение режима работы: HTTPS или HTTP
|
||||
// GO_SPEECH_TLS=true включает HTTPS, по умолчанию (если не задано) - HTTP
|
||||
useTLS := getEnv("GO_SPEECH_TLS", "false") == "true"
|
||||
|
||||
port := "8443"
|
||||
if useTLS {
|
||||
port = getEnv("GO_SPEECH_PORT", "8443")
|
||||
} else {
|
||||
port = getEnv("GO_SPEECH_PORT", "8080")
|
||||
}
|
||||
|
||||
certFile := getEnv("GO_SPEECH_CERT_FILE", "certs/server.crt")
|
||||
keyFile := getEnv("GO_SPEECH_KEY_FILE", "certs/server.key")
|
||||
caCertFile := getEnv("GO_SPEECH_CA_CERT", "") // Путь к CA сертификату (опционально)
|
||||
tlsDomains := getEnv("GO_SPEECH_TLS_DOMAINS", "") // Домены для сертификата через запятую
|
||||
|
||||
piperPath := getEnv("GO_SPEECH_PIPER_PATH", "/usr/local/bin/piper")
|
||||
ffmpegPath := getEnv("GO_SPEECH_FFMPEG_PATH", "/usr/bin/ffmpeg")
|
||||
|
||||
@@ -68,45 +98,71 @@ func main() {
|
||||
logger.Info("=== Запуск Go Speech TTS сервера ===")
|
||||
logger.Debug("Режим отладки: ВКЛЮЧЕН")
|
||||
logger.Debug("Конфигурация:")
|
||||
logger.Debug(" PORT: %s", port)
|
||||
logger.Debug(" CERT_FILE: %s", certFile)
|
||||
logger.Debug(" KEY_FILE: %s", keyFile)
|
||||
logger.Debug(" PIPER_PATH: %s", piperPath)
|
||||
logger.Debug(" FFMPEG_PATH: %s", ffmpegPath)
|
||||
logger.Debug(" MODEL_DIR: %s", modelDir)
|
||||
logger.Debug(" CACHE_DIR: %s", cacheDir)
|
||||
logger.Debug(" GO_SPEECH_PORT: %s", port)
|
||||
logger.Debug(" GO_SPEECH_TLS: %v (HTTPS: %v)", getEnv("GO_SPEECH_TLS", "не задано"), useTLS)
|
||||
logger.Debug(" GO_SPEECH_CERT_FILE: %s", certFile)
|
||||
logger.Debug(" GO_SPEECH_KEY_FILE: %s", keyFile)
|
||||
if caCertFile != "" {
|
||||
logger.Debug(" GO_SPEECH_CA_CERT: %s", caCertFile)
|
||||
}
|
||||
if tlsDomains != "" {
|
||||
logger.Debug(" GO_SPEECH_TLS_DOMAINS: %s", tlsDomains)
|
||||
}
|
||||
logger.Debug(" GO_SPEECH_PIPER_PATH: %s", piperPath)
|
||||
logger.Debug(" GO_SPEECH_FFMPEG_PATH: %s", ffmpegPath)
|
||||
logger.Debug(" GO_SPEECH_MODEL_DIR: %s", modelDir)
|
||||
logger.Debug(" GO_SPEECH_CACHE_DIR: %s", cacheDir)
|
||||
logger.Debug(" GO_SPEECH_VOICE: %s", getEnv("GO_SPEECH_VOICE", "ruslan"))
|
||||
logger.Debug(" GO_SPEECH_MODE: %s", getEnv("GO_SPEECH_MODE", "debug"))
|
||||
logger.Info("Директория с моделями: %s", modelDir)
|
||||
logger.Info("Директория кэша: %s", cacheDir)
|
||||
|
||||
// Проверка наличия сертификатов
|
||||
logger.Debug("Проверка SSL сертификатов...")
|
||||
if _, err := os.Stat(certFile); os.IsNotExist(err) {
|
||||
logger.Error("SSL сертификат не найден: %s", certFile)
|
||||
log.Fatalf("SSL сертификат не найден: %s. Создайте сертификаты или укажите путь через CERT_FILE", certFile)
|
||||
}
|
||||
logger.Debug("SSL сертификат найден: %s", certFile)
|
||||
var tlsConfig *tls.Config
|
||||
|
||||
if _, err := os.Stat(keyFile); os.IsNotExist(err) {
|
||||
logger.Error("SSL ключ не найден: %s", keyFile)
|
||||
log.Fatalf("SSL ключ не найден: %s. Создайте ключ или укажите путь через KEY_FILE", keyFile)
|
||||
}
|
||||
logger.Debug("SSL ключ найден: %s", keyFile)
|
||||
// Обработка TLS конфигурации только если включен HTTPS
|
||||
if useTLS {
|
||||
logger.Debug("Режим HTTPS включен, проверка сертификатов...")
|
||||
|
||||
// Загрузка TLS сертификатов
|
||||
logger.Debug("Загрузка TLS сертификатов...")
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
logger.Error("Ошибка загрузки TLS сертификатов: %v", err)
|
||||
log.Fatalf("Ошибка загрузки TLS сертификатов: %v", err)
|
||||
}
|
||||
logger.Debug("TLS сертификаты успешно загружены")
|
||||
// Проверка наличия сертификатов
|
||||
certExists := false
|
||||
keyExists := false
|
||||
|
||||
// Настройка TLS конфигурации
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
MinVersion: tls.VersionTLS12,
|
||||
if _, err := os.Stat(certFile); err == nil {
|
||||
certExists = true
|
||||
logger.Debug("SSL сертификат найден: %s", certFile)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(keyFile); err == nil {
|
||||
keyExists = true
|
||||
logger.Debug("SSL ключ найден: %s", keyFile)
|
||||
}
|
||||
|
||||
// Если сертификаты отсутствуют - генерируем их
|
||||
if !certExists || !keyExists {
|
||||
logger.Info("Сертификаты не найдены, начинаем генерацию...")
|
||||
if err := generateCertificate(certFile, keyFile, caCertFile, tlsDomains); err != nil {
|
||||
logger.Error("Ошибка генерации сертификатов: %v", err)
|
||||
log.Fatalf("Ошибка генерации сертификатов: %v", err)
|
||||
}
|
||||
logger.Info("Сертификаты успешно сгенерированы: %s, %s", certFile, keyFile)
|
||||
}
|
||||
|
||||
// Загрузка TLS сертификатов
|
||||
logger.Debug("Загрузка TLS сертификатов...")
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
logger.Error("Ошибка загрузки TLS сертификатов: %v", err)
|
||||
log.Fatalf("Ошибка загрузки TLS сертификатов: %v", err)
|
||||
}
|
||||
logger.Debug("TLS сертификаты успешно загружены")
|
||||
|
||||
// Настройка TLS конфигурации
|
||||
tlsConfig = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
} else {
|
||||
logger.Info("Режим HTTP (TLS отключен)")
|
||||
}
|
||||
|
||||
// Статические файлы для фронтенда
|
||||
@@ -137,6 +193,13 @@ func main() {
|
||||
w.Write([]byte("OK"))
|
||||
})
|
||||
logger.Debug(" GET /go-speech/api/v1/health - проверка работоспособности")
|
||||
mux.HandleFunc("/go-speech/api/v1/version", func(w http.ResponseWriter, r *http.Request) {
|
||||
logger.Debug("GET /go-speech/api/v1/version - получение версии")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, `{"version":"%s"}`, getVersion())
|
||||
})
|
||||
logger.Debug(" GET /go-speech/api/v1/version - получение версии")
|
||||
|
||||
// Веб-интерфейс
|
||||
mux.HandleFunc("/go-speech/front", func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -152,8 +215,20 @@ func main() {
|
||||
return
|
||||
}
|
||||
defer indexFile.Close()
|
||||
|
||||
// Читаем содержимое index.html
|
||||
indexContent, err := io.ReadAll(indexFile)
|
||||
if err != nil {
|
||||
logger.Error("Ошибка чтения index.html: %v", err)
|
||||
http.Error(w, "Ошибка чтения фронтенда", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Заменяем плейсхолдер версии
|
||||
indexContentStr := strings.ReplaceAll(string(indexContent), "{{VERSION}}", getVersion())
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
io.Copy(w, indexFile)
|
||||
w.Write([]byte(indexContentStr))
|
||||
})
|
||||
logger.Debug(" GET /go-speech/front - веб-интерфейс")
|
||||
|
||||
@@ -161,6 +236,15 @@ func main() {
|
||||
mux.Handle("/go-speech/front/", http.StripPrefix("/go-speech/front/", http.FileServer(http.FS(staticFS))))
|
||||
logger.Debug(" GET /go-speech/front/* - статические ресурсы")
|
||||
|
||||
// Обработчик для шрифтов
|
||||
fontsFS, err := fs.Sub(staticFiles, "static/fonts")
|
||||
if err == nil {
|
||||
mux.Handle("/go-speech/fonts/", http.StripPrefix("/go-speech/fonts/", http.FileServer(http.FS(fontsFS))))
|
||||
logger.Debug(" GET /go-speech/fonts/* - шрифты")
|
||||
} else {
|
||||
logger.Warn("Не удалось загрузить шрифты: %v", err)
|
||||
}
|
||||
|
||||
// Помощь - рендеринг README.md
|
||||
mux.HandleFunc("/go-speech/help", handleHelp)
|
||||
logger.Debug(" GET /go-speech/help - документация")
|
||||
@@ -168,16 +252,25 @@ func main() {
|
||||
server := &http.Server{
|
||||
Addr: "0.0.0.0:" + port,
|
||||
Handler: loggingMiddleware(mux),
|
||||
TLSConfig: tlsConfig,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
IdleTimeout: 60 * time.Second,
|
||||
}
|
||||
|
||||
// Добавляем TLS конфигурацию только если включен HTTPS
|
||||
if useTLS {
|
||||
server.TLSConfig = tlsConfig
|
||||
}
|
||||
|
||||
logger.Debug("Настройки HTTP сервера:")
|
||||
logger.Debug(" ReadTimeout: %v", server.ReadTimeout)
|
||||
logger.Debug(" WriteTimeout: %v", server.WriteTimeout)
|
||||
logger.Debug(" IdleTimeout: %v", server.IdleTimeout)
|
||||
if useTLS {
|
||||
logger.Debug(" TLS: включен")
|
||||
} else {
|
||||
logger.Debug(" TLS: отключен")
|
||||
}
|
||||
|
||||
// Graceful shutdown
|
||||
go func() {
|
||||
@@ -190,17 +283,22 @@ func main() {
|
||||
}
|
||||
logger.Debug("IPv4 listener успешно создан: %v", listener.Addr())
|
||||
|
||||
// Создаем TLS listener
|
||||
logger.Debug("Создание TLS listener...")
|
||||
tlsListener := tls.NewListener(listener, tlsConfig)
|
||||
logger.Debug("TLS listener успешно создан")
|
||||
protocol := "http"
|
||||
if useTLS {
|
||||
protocol = "https"
|
||||
// Создаем TLS listener
|
||||
logger.Debug("Создание TLS listener...")
|
||||
tlsListener := tls.NewListener(listener, tlsConfig)
|
||||
logger.Debug("TLS listener успешно создан")
|
||||
listener = tlsListener
|
||||
}
|
||||
|
||||
logger.Info("Сервер запущен на https://0.0.0.0:%s (IPv4)", port)
|
||||
logger.Info("Простой веб-интерфейс доступен на https://localhost:%s/go-speech/front", port)
|
||||
logger.Info("Справка доступна по пути https://localhost:%s/go-speech/help", port)
|
||||
logger.Info("Сервер запущен на %s://0.0.0.0:%s (IPv4)", protocol, port)
|
||||
logger.Info("Простой веб-интерфейс доступен на %s://localhost:%s/go-speech/front", protocol, port)
|
||||
logger.Info("Справка доступна по пути %s://localhost:%s/go-speech/help", protocol, port)
|
||||
logger.Debug("Запуск server.Serve()...")
|
||||
|
||||
if err := server.Serve(tlsListener); err != nil && err != http.ErrServerClosed {
|
||||
if err := server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||
logger.Error("Ошибка запуска сервера: %v", err)
|
||||
log.Fatalf("Ошибка запуска сервера: %v", err)
|
||||
}
|
||||
@@ -226,203 +324,3 @@ func main() {
|
||||
|
||||
logger.Info("Сервер остановлен")
|
||||
}
|
||||
|
||||
// loggingMiddleware добавляет логирование запросов
|
||||
func loggingMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
logger.Debug("Входящий запрос: %s %s от %s", r.Method, r.URL.Path, r.RemoteAddr)
|
||||
logger.Debug(" User-Agent: %s", r.UserAgent())
|
||||
logger.Debug(" Content-Type: %s", r.Header.Get("Content-Type"))
|
||||
logger.Debug(" Content-Length: %s", r.Header.Get("Content-Length"))
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
|
||||
duration := time.Since(start)
|
||||
logger.Info("%s %s %v", r.Method, r.URL.Path, duration)
|
||||
logger.Debug("Запрос обработан за %v", duration)
|
||||
})
|
||||
}
|
||||
|
||||
// getEnv получает переменную окружения или возвращает значение по умолчанию
|
||||
func getEnv(key, defaultValue string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// GetModelPath формирует путь к модели на основе выбранного голоса
|
||||
func GetModelPath(modelDir, voice string) string {
|
||||
// Если указан полный путь через MODEL_PATH, используем его
|
||||
if modelPath := os.Getenv("MODEL_PATH"); modelPath != "" {
|
||||
return modelPath
|
||||
}
|
||||
|
||||
// Формируем путь к модели на основе голоса
|
||||
modelFile := fmt.Sprintf("ru_RU-%s-medium.onnx", voice)
|
||||
return filepath.Join(modelDir, modelFile)
|
||||
}
|
||||
|
||||
// handleHelp рендерит README.md в HTML
|
||||
func handleHelp(w http.ResponseWriter, r *http.Request) {
|
||||
logger.Debug("GET /go-speech/help - отображение документации")
|
||||
|
||||
// Простой рендеринг markdown в HTML
|
||||
html := markdownToHTML(readmeContent)
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(html))
|
||||
}
|
||||
|
||||
// markdownToHTML конвертирует markdown в простой HTML
|
||||
func markdownToHTML(md string) string {
|
||||
html := `<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Go Speech - Документация</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 20px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
h1, h2, h3 { color: #667eea; margin-top: 30px; }
|
||||
code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', monospace; }
|
||||
pre { background: #f4f4f4; padding: 15px; border-radius: 8px; overflow-x: auto; }
|
||||
pre code { background: none; padding: 0; }
|
||||
a { color: #667eea; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
blockquote { border-left: 4px solid #667eea; padding-left: 20px; margin-left: 0; color: #666; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 20px 0; }
|
||||
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
|
||||
th { background: #667eea; color: white; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
`
|
||||
|
||||
// Простая конвертация markdown в HTML
|
||||
lines := strings.Split(md, "\n")
|
||||
inCodeBlock := false
|
||||
inList := false
|
||||
var codeBlock strings.Builder
|
||||
|
||||
for i, line := range lines {
|
||||
line = strings.TrimRight(line, "\r")
|
||||
trimmedLine := strings.TrimSpace(line)
|
||||
|
||||
// Обработка блоков кода
|
||||
if strings.HasPrefix(trimmedLine, "```") {
|
||||
if inCodeBlock {
|
||||
// Закрываем блок кода
|
||||
html += "<pre><code>" + template.HTMLEscapeString(codeBlock.String()) + "</code></pre>\n"
|
||||
codeBlock.Reset()
|
||||
inCodeBlock = false
|
||||
} else {
|
||||
// Открываем блок кода
|
||||
inCodeBlock = true
|
||||
}
|
||||
if inList {
|
||||
html += "</ul>\n"
|
||||
inList = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if inCodeBlock {
|
||||
codeBlock.WriteString(line + "\n")
|
||||
continue
|
||||
}
|
||||
|
||||
// Заголовки
|
||||
if strings.HasPrefix(trimmedLine, "# ") {
|
||||
if inList {
|
||||
html += "</ul>\n"
|
||||
inList = false
|
||||
}
|
||||
html += "<h1>" + template.HTMLEscapeString(strings.TrimPrefix(trimmedLine, "# ")) + "</h1>\n"
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(trimmedLine, "## ") {
|
||||
if inList {
|
||||
html += "</ul>\n"
|
||||
inList = false
|
||||
}
|
||||
html += "<h2>" + template.HTMLEscapeString(strings.TrimPrefix(trimmedLine, "## ")) + "</h2>\n"
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(trimmedLine, "### ") {
|
||||
if inList {
|
||||
html += "</ul>\n"
|
||||
inList = false
|
||||
}
|
||||
html += "<h3>" + template.HTMLEscapeString(strings.TrimPrefix(trimmedLine, "### ")) + "</h3>\n"
|
||||
continue
|
||||
}
|
||||
|
||||
// Списки
|
||||
if strings.HasPrefix(trimmedLine, "- ") {
|
||||
if !inList {
|
||||
html += "<ul>\n"
|
||||
inList = true
|
||||
}
|
||||
content := strings.TrimPrefix(trimmedLine, "- ")
|
||||
// Обработка inline кода в списках
|
||||
content = processInlineCode(content)
|
||||
html += "<li>" + content + "</li>\n"
|
||||
continue
|
||||
}
|
||||
|
||||
// Закрываем список если он был открыт
|
||||
if inList && trimmedLine == "" {
|
||||
html += "</ul>\n"
|
||||
inList = false
|
||||
}
|
||||
|
||||
// Пустые строки
|
||||
if trimmedLine == "" {
|
||||
if i < len(lines)-1 {
|
||||
html += "<br>\n"
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Обычный текст
|
||||
content := processInlineCode(template.HTMLEscapeString(trimmedLine))
|
||||
html += "<p>" + content + "</p>\n"
|
||||
}
|
||||
|
||||
// Закрываем открытые теги
|
||||
if inList {
|
||||
html += "</ul>\n"
|
||||
}
|
||||
|
||||
html += `</body>
|
||||
</html>`
|
||||
return html
|
||||
}
|
||||
|
||||
// processInlineCode обрабатывает inline код в markdown
|
||||
func processInlineCode(text string) string {
|
||||
// Простая обработка inline кода `code`
|
||||
parts := strings.Split(text, "`")
|
||||
result := strings.Builder{}
|
||||
for i, part := range parts {
|
||||
if i%2 == 0 {
|
||||
result.WriteString(part)
|
||||
} else {
|
||||
result.WriteString("<code>")
|
||||
result.WriteString(part)
|
||||
result.WriteString("</code>")
|
||||
}
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user