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"` DefaultType string `json:"DefaultType"` // New config value for DefaultType DisplayTextColumn int `json:"DisplayTextColumn"` // New config value for DisplayTextColumn } 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, using the module's DisplayTextColumn func collectAllDisplayTexts(config Config) []string { var displayTexts []string for _, module := range config.Modules { // Pass both the file path and DisplayTextColumn to readDisplayTextsAndFileNames moduleDisplayTexts, _ := readDisplayTextsAndFileNames(config.BaseDir + "/" + module.CSVFile, module.DisplayTextColumn) 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 // 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 { // Pass both the file path and DisplayTextColumn to readDisplayTextsAndFileNames displayTexts, fileNames := readDisplayTextsAndFileNames(config.BaseDir + "/" + module.CSVFile, module.DisplayTextColumn) for i, text := range displayTexts { // Normalize display text before comparing normalizedText := norm.NFC.String(text) // Exact match: check if the input exactly matches the display text (case-insensitive) if strings.EqualFold(normalizedText, normalizedInputText) { // Include the module name to be used for path construction matchingFiles = append(matchingFiles, fmt.Sprintf("%s:%s", module.Name, fileNames[i])) } } } } // If no matching filenames found, prompt again if len(matchingFiles) == 0 { fmt.Println("No matching display text found.") return } // Debugging: print matched files fmt.Println("Matched files:") for _, fileName := range matchingFiles { fmt.Println(" - ", fileName) } // Play matching audio files in the exact order they were entered for _, fileName := range matchingFiles { // Extract module and filename parts := strings.SplitN(fileName, ":", 2) if len(parts) != 2 { continue } moduleName, file := parts[0], parts[1] // Find the correct module by name var module Module for _, m := range config.Modules { if m.Name == moduleName { module = m break } } // Construct the audio path using the correct module audioPath := constructAudioPath(config, module, file) // 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, module Module, fileName string) string { var audioPath string var languagePart, typePart, variantPart string // If a language is set in the module, use it if len(module.DefaultLanguage) > 0 { languagePart = module.DefaultLanguage + "/" } // If DefaultType is set in the module, use it if len(module.DefaultType) > 0 { typePart = module.DefaultType + "/" } // If DefaultVariants is set in the module, use it if len(module.DefaultVariants) > 0 { variantPart = module.DefaultVariants[0] + "/" } // Only add DefaultType and DefaultVariants to the path if they are set if languagePart != "" || typePart != "" || variantPart != "" { audioPath = fmt.Sprintf("%s/%s%s/%s%s/%s", config.BaseDir, languagePart, module.Path, variantPart, typePart, fileName) } else { // If neither DefaultType nor DefaultVariants is set, omit both from the path audioPath = fmt.Sprintf("%s/%s/%s", config.BaseDir, module.Path, fileName) } return audioPath } // Function to read display texts (specified column) and filenames from a CSV file func readDisplayTextsAndFileNames(filePath string, displayTextColumn int) ([]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 if displayTextColumn-1 < len(record) { displayTexts = append(displayTexts, strings.TrimSpace(record[displayTextColumn-1])) // The specified 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) // Open or create a log file in append mode logFile, err := os.OpenFile("file_paths_log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return fmt.Errorf("Error opening log file: %v", err) } defer logFile.Close() // Write the file path to the log file _, err = fmt.Fprintf(logFile, "%s\n", filePath) if err != nil { return fmt.Errorf("Error writing to log file: %v", err) } // Play the audio file using ffplay cmd := exec.Command("ffplay", "-nodisp", "-hide_banner", "-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 }