260 lines
7.6 KiB
Go
260 lines
7.6 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/csv"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"github.com/peterh/liner"
|
|
"golang.org/x/text/unicode/norm"
|
|
)
|
|
|
|
// Config represents the structure of the config.json file.
|
|
type Config struct {
|
|
BaseDir string `json:"Base_Dir"`
|
|
Modules []Module `json:"Modules"`
|
|
}
|
|
|
|
// Module represents a module entry in the config.json file.
|
|
type Module struct {
|
|
Name string `json:"Name"`
|
|
Path string `json:"Path"`
|
|
CSVFile string `json:"CSVFile"`
|
|
DefaultLanguage string `json:"Default_Language"`
|
|
DefaultVariants []string `json:"Default_Variants"`
|
|
}
|
|
|
|
func main() {
|
|
// Open the config.json file
|
|
configFilePath := "config.json"
|
|
data, err := ioutil.ReadFile(configFilePath)
|
|
if err != nil {
|
|
log.Fatalf("Error reading file: %v", err)
|
|
}
|
|
|
|
// Parse the JSON data into a Config struct
|
|
var config Config
|
|
err = json.Unmarshal(data, &config)
|
|
if err != nil {
|
|
log.Fatalf("Error parsing JSON: %v", err)
|
|
}
|
|
|
|
fmt.Println("Blechelse - waiting for your orders")
|
|
interactiveMode(config)
|
|
}
|
|
|
|
// Interactive mode with tab autocomplete and playing audio
|
|
func interactiveMode(config Config) {
|
|
line := liner.NewLiner()
|
|
defer line.Close()
|
|
|
|
line.SetCtrlCAborts(true)
|
|
displayTexts := collectAllDisplayTexts(config)
|
|
|
|
// Setting up the autocomplete feature
|
|
line.SetCompleter(func(line string) (c []string) {
|
|
// Split the input line into parts based on commas
|
|
words := strings.Split(line, ",")
|
|
lastWord := words[len(words)-1]
|
|
|
|
// Autocomplete only the last word
|
|
for _, text := range displayTexts {
|
|
if strings.HasPrefix(strings.ToLower(text), strings.ToLower(lastWord)) {
|
|
c = append(c, text)
|
|
}
|
|
}
|
|
return
|
|
})
|
|
|
|
var currentInput []string // Store entered words
|
|
for {
|
|
// Print the current input prompt above the line
|
|
printPrompt(currentInput)
|
|
|
|
// Read user input
|
|
inputText, err := line.Prompt("Enter text (or type 'exit' to quit, 'play' to trigger playback): ")
|
|
if err != nil {
|
|
fmt.Println("Error reading input:", err)
|
|
continue
|
|
}
|
|
|
|
inputText = strings.TrimSpace(inputText)
|
|
|
|
// Exit condition
|
|
if inputText == "exit" {
|
|
fmt.Println("Goodbye!")
|
|
break
|
|
}
|
|
|
|
// If "play" is typed, trigger audio playback
|
|
if inputText == "play" {
|
|
if len(currentInput) > 0 {
|
|
// Concatenate the current input for display and play each part individually
|
|
concatenatedPrompt := strings.Join(currentInput, " ")
|
|
fmt.Printf("Playing: %s\n", concatenatedPrompt)
|
|
// Play each part individually in the order they were added
|
|
autocompleteAndPlay(config, currentInput)
|
|
currentInput = nil // Clear the input after playing
|
|
} else {
|
|
fmt.Println("No input to play.")
|
|
}
|
|
} else if len(inputText) > 0 {
|
|
// If it's not "play", it's a part of the input, so add to the current input
|
|
currentInput = append(currentInput, inputText)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Print the current accumulated prompt above the input line
|
|
func printPrompt(currentInput []string) {
|
|
fmt.Println("Current prompt:")
|
|
for i, word := range currentInput {
|
|
fmt.Printf("%d. %s\n", i+1, word)
|
|
}
|
|
}
|
|
|
|
// Collect all display texts for autocomplete
|
|
func collectAllDisplayTexts(config Config) []string {
|
|
var displayTexts []string
|
|
for _, module := range config.Modules {
|
|
// Unpack both return values (displayTexts and fileNames)
|
|
moduleDisplayTexts, _ := readDisplayTextsAndFileNames(config.BaseDir + "/" + module.CSVFile)
|
|
displayTexts = append(displayTexts, moduleDisplayTexts...)
|
|
}
|
|
return displayTexts
|
|
}
|
|
|
|
// Function to collect all display texts and play matching files
|
|
func autocompleteAndPlay(config Config, inputText []string) {
|
|
var matchingFiles []string // Store matching files in the order of inputText
|
|
var moduleNames []string // Store corresponding module names for each matched file
|
|
|
|
// Normalize each input word (e.g., convert umlauts to standard form)
|
|
for _, part := range inputText {
|
|
normalizedInputText := norm.NFC.String(part)
|
|
|
|
// Collect matching filenames from the second column (display text) and first column (filenames)
|
|
for _, module := range config.Modules {
|
|
displayTexts, fileNames := readDisplayTextsAndFileNames(config.BaseDir + "/" + module.CSVFile)
|
|
for i, text := range displayTexts {
|
|
// Normalize display text before comparing and remove any trailing spaces
|
|
normalizedText := norm.NFC.String(strings.TrimSpace(text))
|
|
|
|
// Explicitly compare both normalized strings (case-sensitive)
|
|
if normalizedText == normalizedInputText {
|
|
matchingFiles = append(matchingFiles, fileNames[i])
|
|
moduleNames = append(moduleNames, module.Name) // Track the module that matched
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no matching filenames found, prompt again
|
|
if len(matchingFiles) == 0 {
|
|
fmt.Println("No matching display text found.")
|
|
return
|
|
}
|
|
|
|
// Debugging: print matched files and their corresponding modules
|
|
fmt.Println("Matched files:")
|
|
for i, fileName := range matchingFiles {
|
|
fmt.Printf(" - File: %s, Module: %s\n", fileName, moduleNames[i])
|
|
}
|
|
|
|
// Play matching audio files in the exact order they were entered
|
|
for i, fileName := range matchingFiles {
|
|
// Get the module name associated with this matched file
|
|
moduleName := moduleNames[i]
|
|
|
|
// Construct the correct audio path using the module information
|
|
audioPath := constructAudioPath(config, fileName, moduleName)
|
|
|
|
// Debugging: print the constructed audio path
|
|
fmt.Printf("Attempting to play audio from path: %s\n", audioPath)
|
|
|
|
// Check if the file exists before trying to play it
|
|
if _, err := os.Stat(audioPath); os.IsNotExist(err) {
|
|
fmt.Printf("File does not exist: %s\n", audioPath)
|
|
continue
|
|
}
|
|
|
|
// Play the audio file
|
|
err := playAudioFiles(audioPath)
|
|
if err != nil {
|
|
fmt.Printf("Error playing audio: %v\n", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Function to construct the audio file path with language and variant considerations
|
|
func constructAudioPath(config Config, fileName, moduleName string) string {
|
|
var audioPath string
|
|
var languagePart, variantPart string
|
|
|
|
// Find the module for which we need to construct the path
|
|
var selectedModule Module
|
|
for _, module := range config.Modules {
|
|
if module.Name == moduleName {
|
|
selectedModule = module
|
|
break
|
|
}
|
|
}
|
|
|
|
// If a language is set in the selected module, use it
|
|
if len(selectedModule.DefaultLanguage) > 0 {
|
|
languagePart = selectedModule.DefaultLanguage + "/"
|
|
}
|
|
|
|
// If a variant is set in the selected module, use it
|
|
if len(selectedModule.DefaultVariants) > 0 {
|
|
variantPart = selectedModule.DefaultVariants[0] + "/"
|
|
}
|
|
|
|
// Construct the path
|
|
audioPath = fmt.Sprintf("%s/%s%s/%s%s", config.BaseDir, languagePart, selectedModule.Path, variantPart, fileName)
|
|
return audioPath
|
|
}
|
|
|
|
// Function to read display texts (second column) and filenames (first column) from a CSV file
|
|
func readDisplayTextsAndFileNames(filePath string) ([]string, []string) {
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
log.Fatalf("Error opening CSV file: %v", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
reader := csv.NewReader(file)
|
|
records, err := reader.ReadAll()
|
|
if err != nil {
|
|
log.Fatalf("Error reading CSV records: %v", err)
|
|
}
|
|
|
|
var displayTexts, fileNames []string
|
|
for _, record := range records {
|
|
if len(record) > 1 {
|
|
fileNames = append(fileNames, record[0]) // First column is the filename
|
|
displayTexts = append(displayTexts, record[1]) // Second column is the display text
|
|
}
|
|
}
|
|
return displayTexts, fileNames
|
|
}
|
|
|
|
// Function to play audio files based on the file name
|
|
func playAudioFiles(filePath string) error {
|
|
// Debugging: print the file path being played
|
|
fmt.Printf("Playing audio from: %s\n", filePath)
|
|
|
|
cmd := exec.Command("ffplay", "-nodisp", "-autoexit", filePath)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
return fmt.Errorf("Error playing audio file: %v", err)
|
|
}
|
|
return nil
|
|
}
|