- Implemented core model components in `modules.py` including various convolutional layers and normalization techniques. - Added transformation functions in `transforms.py` for piecewise rational quadratic transformations. - Created utility functions in `utils.py` for checkpoint management, logging, and hyperparameter handling. - Introduced monotonic alignment functionality with Cython optimization in `monotonic_align`. - Developed a minimal inference server in `server.py` to handle synthesis requests. - Updated requirements to include necessary dependencies for Cython and scipy.
70 lines
2.0 KiB
Go
70 lines
2.0 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
|
|
"gitea.nik4nao.com/nik/home-services/tts-gateway/internal/core/domain"
|
|
"gitea.nik4nao.com/nik/home-services/tts-gateway/internal/core/ports/driven"
|
|
)
|
|
|
|
// ErrSpeakerNotFound is returned when the requested speaker name has no
|
|
// matching entry in domain.Speakers.
|
|
var ErrSpeakerNotFound = errors.New("speaker not found")
|
|
|
|
type TTSApp struct {
|
|
normalizer driven.TextNormalizer
|
|
engine driven.TTSEngine
|
|
encoder driven.AudioEncoder
|
|
nameToID map[string]int
|
|
}
|
|
|
|
// NewTTSApp constructs the synthesis application service.
|
|
func NewTTSApp(normalizer driven.TextNormalizer, engine driven.TTSEngine, encoder driven.AudioEncoder) *TTSApp {
|
|
nameToID := make(map[string]int, len(domain.Speakers))
|
|
for _, s := range domain.Speakers {
|
|
nameToID[s.Name] = s.ID
|
|
}
|
|
return &TTSApp{normalizer: normalizer, engine: engine, encoder: encoder, nameToID: nameToID}
|
|
}
|
|
|
|
// Synthesize normalizes text, runs the TTS engine, and encodes the result.
|
|
func (a *TTSApp) Synthesize(ctx context.Context, speakerName, text string, params domain.SynthesisParams) (*domain.AudioClip, error) {
|
|
speakerID, ok := a.nameToID[speakerName]
|
|
if !ok {
|
|
return nil, ErrSpeakerNotFound
|
|
}
|
|
|
|
symbolIDs, err := a.normalizer.Normalize(ctx, text)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pcm, sampleRate, err := a.engine.Synthesize(ctx, speakerID, symbolIDs, params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
data, mimeType, err := a.encoder.Encode(ctx, pcm, sampleRate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &domain.AudioClip{Data: data, MimeType: mimeType}, nil
|
|
}
|
|
|
|
// ListSpeakers returns speaker names filtered by a case-insensitive substring
|
|
// match, or the full roster when search is empty - matching uma-tts-api's
|
|
// /speakers endpoint.
|
|
func (a *TTSApp) ListSpeakers(_ context.Context, search string) []string {
|
|
search = strings.ToLower(search)
|
|
names := make([]string, 0, len(domain.Speakers))
|
|
for _, s := range domain.Speakers {
|
|
if search == "" || strings.Contains(strings.ToLower(s.Name), search) {
|
|
names = append(names, s.Name)
|
|
}
|
|
}
|
|
return names
|
|
}
|