Skip to content

Commit

Permalink
backup
Browse files Browse the repository at this point in the history
  • Loading branch information
C-Loftus committed Feb 9, 2025
1 parent 189a4d3 commit c072c35
Show file tree
Hide file tree
Showing 11 changed files with 142 additions and 72 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}",
"args": ["--model=en_US-hfc_male-medium.onnx", "internal/binarymanagers/ebookConvert/testdata/test.txt"],
"args": ["internal/binarymanagers/ebookConvert/testdata/test.txt"],
},
]
}
24 changes: 24 additions & 0 deletions cmd/ls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package cmd

import (
"QuickPiperAudiobook/internal/binarymanagers/piper"

"github.com/spf13/cobra"
)

func init() {
rootCmd.AddCommand(versionCmd)
}

var versionCmd = &cobra.Command{
Use: "ls",
Short: "List the models that are installed",
Long: "List all the models that are installed in ~/.config/QuickPiperAudiobook",
Run: func(cmd *cobra.Command, args []string) {
models, err := piper.FindModels("~/.config/QuickPiperAudiobook")
if err != nil {
cmd.PrintErrln(err)
}
cmd.Println(models)
},
}
57 changes: 32 additions & 25 deletions cmd/cli.go → cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,7 @@ import (
)

var (
filePath string
speakDiacritics bool
model string
outDir string
config *viper.Viper
outputMp3 bool
config *viper.Viper
)

var rootCmd = &cobra.Command{
Expand All @@ -31,23 +26,36 @@ var rootCmd = &cobra.Command{
return nil
},
Run: func(cmd *cobra.Command, args []string) {
filePath = args[0]
filePath := args[0]
model := viper.GetString("model")
fmt.Printf("Processing file: %s with model: %s", filePath, model)

outDir := config.GetString("output")
if outDir[0] == '~' {
homeDir, err := os.UserHomeDir()
if err != nil {
log.Fatal(err)
}
outDir = homeDir + outDir[1:]
}

speakDiacritics := config.GetBool("speak-diacritics")
outputMp3 := config.GetBool("mp3")

err := internal.QuickPiperAudiobook(filePath, model, outDir, speakDiacritics, outputMp3)
if err != nil {
log.Fatal(err)
}
},
}

func initConfig() *viper.Viper {
v := viper.New()
v.SetConfigName("config.yaml")
v.SetConfigType("yaml")
v.AddConfigPath("/etc/QuickPiperAudiobook/")
v.AddConfigPath("$HOME/.config/QuickPiperAudiobook")
err := v.ReadInConfig()
func init() {
config = viper.New()
config.SetConfigName("config.yaml")
config.SetConfigType("yaml")
config.AddConfigPath("/etc/QuickPiperAudiobook/")
config.AddConfigPath("$HOME/.config/QuickPiperAudiobook")
err := config.ReadInConfig()

if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
Expand All @@ -57,18 +65,17 @@ func initConfig() *viper.Viper {
}
}

return v
}

func init() {
config = initConfig()
rootCmd.PersistentFlags().StringP("config", "c", "", "config file (default is $HOME/.config/QuickPiperAudiobook/config.yaml)")
rootCmd.Flags().BoolVar(&speakDiacritics, "speak-diacritics", false, "Enable UTF-8 speaking mode")
rootCmd.Flags().BoolP("version", "v", false, "Print the version number")
rootCmd.Flags().BoolP("help", "h", false, "Print this help message")
rootCmd.Flags().StringVarP(&model, "model", "m", "en_US-hfc_male-medium.onnx", "The model to use for speech synthesis")
rootCmd.Flags().StringVarP(&outDir, "output", "o", ".", "The output directory for the audiobook")
rootCmd.Flags().BoolVar(&outputMp3, "mp3", true, "Output the audiobook as an mp3 file (requires ffmpeg)")

_ = rootCmd.PersistentFlags().Bool("speak-diacritics", false, "Enable UTF-8 speaking mode")
_ = rootCmd.PersistentFlags().String("model", "en_US-hfc_male-medium.onnx", "The model to use for speech synthesis")
_ = rootCmd.PersistentFlags().String("output", ".", "The output directory for the audiobook")
_ = rootCmd.PersistentFlags().Bool("mp3", true, "Output the audiobook as an mp3 file (requires ffmpeg)")
err = viper.BindPFlags(rootCmd.PersistentFlags())
if err != nil {
log.Fatalf("Error binding flags: %v\n", err)
}

}

func Execute() {
Expand Down
4 changes: 1 addition & 3 deletions internal/binarymanagers/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ func RunPiped(cmd string, pipedInput io.Reader) (PipedOutput, error) {
return PipedOutput{}, fmt.Errorf("failed getting stderr: %v", err)
}

if pipedInput != nil {
fullCmd.Stdin = pipedInput
}
fullCmd.Stdin = pipedInput

if err := fullCmd.Start(); err != nil {
return PipedOutput{}, fmt.Errorf("command failed: %v", err)
Expand Down
7 changes: 6 additions & 1 deletion internal/binarymanagers/ebookConvert/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func ConvertToText(input io.Reader, fileExt string) (io.Reader, error) {
}

// have to create a temporary file since ebook-convert doesn't accept stdin
tmpInputFile, err := os.CreateTemp("", "ebook-convert-temporary-input-*."+fileExt)
tmpInputFile, err := os.CreateTemp("", "ebook-convert-temporary-input-*"+fileExt)
if err != nil {
return nil, fmt.Errorf("failed to create temporary file: %v", err)
}
Expand All @@ -45,6 +45,11 @@ func ConvertToText(input io.Reader, fileExt string) (io.Reader, error) {

cmd := exec.Command("ebook-convert", tmpInputFile.Name(), tmpOutputFile.Name())

// make sure that tmpInputFile contains some data and is not an empty file
if _, err := tmpInputFile.Stat(); err != nil {
return nil, fmt.Errorf("failed to stat temporary file: %v", err)
}

output, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("failed to convert ebook: %s\nOutput: %s", err, string(output))
Expand Down
48 changes: 7 additions & 41 deletions internal/binarymanagers/ffmpeg/client.go
Original file line number Diff line number Diff line change
@@ -1,61 +1,27 @@
package ffmpeg

import (
"QuickPiperAudiobook/internal/binarymanagers"
"fmt"
"io"
"os"
"os/exec"
)

// OutputToMp3 converts raw PCM audio to MP3 using ffmpeg
func OutputToMp3(input io.Reader, outputName string) error {
// Verify ffmpeg is available
func OutputToMp3(piperRawAudio io.Reader, outputName string) error {
if _, err := exec.LookPath("ffmpeg"); err != nil {
return fmt.Errorf("ffmpeg not found in PATH: %v", err)
}

// Create FFmpeg command
cmd := exec.Command("ffmpeg",
"-f", "s16le", // Raw PCM format
"-ar", "22050", // Sample rate
"-ac", "1", // Mono
"-i", "pipe:0", // Input from stdin
"-acodec", "libmp3lame",
"-b:a", "128k", // MP3 bitrate
"-y", outputName, // Output file
)
cmdStr := fmt.Sprintf("ffmpeg -f s16le -ar 22050 -ac 1 -i pipe:0 -acodec libmp3lame -b:a 128k -y %s", outputName)

// Set up ffmpeg stdin
ffmpegIn, err := cmd.StdinPipe()
_, err := binarymanagers.RunPiped(cmdStr, piperRawAudio)
if err != nil {
return fmt.Errorf("failed to create stdin pipe: %v", err)
return err
}

// Set up ffmpeg stderr for debugging
cmd.Stderr = os.Stderr

// Start ffmpeg
if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to start ffmpeg: %v", err)
}

// Stream PCM data to ffmpeg
_, err = io.Copy(ffmpegIn, input)
if err != nil {
return fmt.Errorf("failed to write PCM data to ffmpeg: %v", err)
}

// Close ffmpeg input to signal end of stream
ffmpegIn.Close()

// Wait for ffmpeg to finish
if err := cmd.Wait(); err != nil {
return fmt.Errorf("ffmpeg command failed: %v", err)
}

// Validate MP3 output
validateCmd := exec.Command("ffmpeg", "-v", "error", "-i", outputName, "-f", "null", "-")
if err := validateCmd.Run(); err != nil {
validateMp3 := exec.Command("ffmpeg", "-v", "error", "-i", outputName, "-f", "null", "-")
if err := validateMp3.Run(); err != nil {
return fmt.Errorf("mp3 output validation failed: %v", err)
}

Expand Down
3 changes: 2 additions & 1 deletion internal/binarymanagers/iconv/textProcessing.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import (
"QuickPiperAudiobook/internal/binarymanagers"
"fmt"
"io"
"os/exec"
)

// Remove diacritics from text so that an english voice can read it
// without explicitly speaking the diacritics and messing with speech
func RemoveDiacritics(input io.Reader) (io.Reader, error) {
if _, err := binarymanagers.Run("iconv --version"); err != nil {
if _, err := exec.LookPath("iconv"); err != nil {
return nil, fmt.Errorf("iconv not found in PATH: %v", err)
}

Expand Down
2 changes: 2 additions & 0 deletions internal/binarymanagers/piper/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ func (piper PiperClient) Run(filename string, inputFile io.Reader, outdir string
if streamOutput {
return output, nil
} else {
output.Handle.Wait()
fmt.Println("Piper output saved to: " + filepathAbs)
return bin.PipedOutput{}, nil
}
}
Expand Down
48 changes: 48 additions & 0 deletions internal/binarymanagers/piper/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
)

// Piper has hundreds of pretrained models on the sample Website
Expand Down Expand Up @@ -41,3 +42,50 @@ func expandModelPath(modelName string, defaultModelDir string) (string, error) {
}
return "", fmt.Errorf("model '%s' was not found in the current directory or the default model directory: '%s'", modelName, defaultModelDir)
}

func FindModels(dir string) ([]string, error) {

if strings.HasPrefix(dir, "~/") {
usr, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("error getting user home directory: %v", err)
}
dir = filepath.Join(usr, dir[2:])
}

// Read the directory
files, err := os.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("error reading directory: %v", err)
}

var models []string
for _, file := range files {
// Skip directories
if file.IsDir() {
continue
}

name := file.Name()

// Check if the file has a .onnx extension
if strings.HasSuffix(name, ".onnx") {
// Construct the path for the associated .json file
jsonFile := name + ".json"
jsonFilePath := filepath.Join(dir, jsonFile)

// Check if the .json file exists
if _, err := os.Stat(jsonFilePath); err == nil {
// If the .json file exists, add the .onnx file path to the result
abs, err := filepath.Abs(name)
if err != nil {
return nil, fmt.Errorf("error getting absolute path: %v", err)
}

models = append(models, abs)
}
}
}

return models, nil
}
12 changes: 12 additions & 0 deletions internal/lib/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package lib

import (
"bytes"
"io"
)

func GetSize(stream io.Reader) int {
buf := new(bytes.Buffer)
buf.ReadFrom(stream)
return buf.Len()
}
7 changes: 7 additions & 0 deletions internal/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package internal
import (
"QuickPiperAudiobook/internal/binarymanagers/ffmpeg"
"QuickPiperAudiobook/internal/binarymanagers/piper"
"QuickPiperAudiobook/internal/lib"
"bytes"
"fmt"
"io"
Expand Down Expand Up @@ -33,6 +34,9 @@ func QuickPiperAudiobook(fileName, model, outDir string, speakDiacritics, output
if err != nil {
return err
}
if lib.GetSize(rawFile) == 0 {
return fmt.Errorf("file is empty")
}

piper, err := piper.NewPiperClient(model)
if err != nil {
Expand All @@ -45,6 +49,9 @@ func QuickPiperAudiobook(fileName, model, outDir string, speakDiacritics, output
if err != nil {
return err
}
if lib.GetSize(reader) == 0 {
return fmt.Errorf("file is empty")
}
} else {
reader = rawFile
}
Expand Down

0 comments on commit c072c35

Please sign in to comment.