Simple blogging system written in Go https://goblog.app
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

186 lines
3.8 KiB

package main
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"sync"
"unicode"
"github.com/google/uuid"
"go.goblog.app/app/pkgs/mp3merge"
)
func (a *goBlog) createPostTTSAudio(p *post) error {
// Get required values
lang := a.cfg.Blogs[p.Blog].Lang
if lang == "" {
lang = "en"
}
text := a.renderMdTitle(p.Title()) + "\n\n" + cleanHTMLText(string(a.postHtml(p, false)))
// Generate audio file
tmpDir, err := os.MkdirTemp("", "")
if err != nil {
return err
}
defer func() {
_ = os.RemoveAll(tmpDir)
}()
outputFileName := filepath.Join(tmpDir, "audio.mp3")
err = a.createTTSAudio(lang, text, outputFileName)
if err != nil {
return err
}
// Save new audio file
file, err := os.Open(outputFileName)
if err != nil {
return err
}
fileHash, err := getSHA256(file)
if err != nil {
return err
}
loc, err := a.saveMediaFile(fileHash+".mp3", file)
if err != nil {
return err
}
// Set post parameter
if loc != "" {
err = a.db.replacePostParam(p.Path, "tts", []string{loc})
if err != nil {
return err
}
}
return nil
}
func (a *goBlog) createTTSAudio(lang, text, outputFile string) error {
// Create temporary directory
tmpDir, err := os.MkdirTemp("", "")
if err != nil {
return err
}
defer func() {
_ = os.RemoveAll(tmpDir)
}()
// Split text
textParts := []string{}
var textPartBuilder strings.Builder
textRunes := []rune(text)
for i, r := range textRunes {
textPartBuilder.WriteRune(r)
newText := false
if strings.ContainsRune(",.:!?)", r) && i+1 < len(textRunes) && unicode.IsSpace(textRunes[i+1]) {
newText = true
} else if r == '\n' {
newText = true
} else if textPartBuilder.Len() > 500 && unicode.IsSpace(r) {
newText = true
}
if newText {
textParts = append(textParts, textPartBuilder.String())
textPartBuilder.Reset()
}
}
textParts = append(textParts, textPartBuilder.String())
// Start request for every text part
allFiles := []string{}
var wg sync.WaitGroup
var ttsErr error
ctx, cancel := context.WithCancel(context.Background())
for _, s := range textParts {
s := strings.TrimSpace(s)
if s == "" {
continue
}
fileName := filepath.Join(tmpDir, uuid.NewString()+".mp3")
allFiles = append(allFiles, fileName)
wg.Add(1)
go func() {
defer wg.Done()
err := a.downloadTTSAudio(ctx, lang, s, fileName)
if err != nil && ttsErr == nil {
ttsErr = err
cancel()
}
}()
}
wg.Wait()
cancel()
if ttsErr != nil {
return ttsErr
}
// Merge MP3s
if err = mp3merge.MergeMP3(outputFile, allFiles); err != nil {
return err
}
return nil
}
func (a *goBlog) downloadTTSAudio(ctx context.Context, lang, text, outputFile string) error {
// Check parameters
if lang == "" {
return errors.New("language not provided")
}
if text == "" {
return errors.New("empty text")
}
if outputFile == "" {
return errors.New("output file not provided")
}
// Encode params
ttsUrlVals := url.Values{}
ttsUrlVals.Set("client", "tw-ob")
ttsUrlVals.Set("tl", lang)
ttsUrlVals.Set("q", strings.TrimSpace(text))
// Create request
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://translate.google.com/translate_tts?"+ttsUrlVals.Encode(), nil)
if err != nil {
return err
}
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.1; rv:60.0) Gecko/20100101 Firefox/60.0")
// Do request
res, err := a.httpClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return fmt.Errorf("TTS: got status: %s, text: %s", res.Status, text)
}
// Save response
if err = os.MkdirAll(path.Dir(outputFile), os.ModePerm); err != nil {
return err
}
out, err := os.Create(outputFile)
if err != nil {
return err
}
defer func() {
_ = out.Close()
}()
if _, err = io.Copy(out, res.Body); err != nil {
return err
}
return nil
}