This commit is contained in:
Lucy 2025-03-30 17:13:48 +02:00
parent b9bebe7dd3
commit 059b8ff976
4 changed files with 234 additions and 51 deletions

View file

@ -14,6 +14,12 @@
"CSVFile": "gleise_zahlen.csv",
"Default_Variants": ["hoch"],
"Default_Language": "dt"
},
{
"Name": "Module",
"Path": "module",
"CSVFile": "module_dt.csv",
"Default_Language": "dt"
}
]
}

9
go.mod
View file

@ -2,9 +2,12 @@ module main
go 1.24.1
require github.com/posener/complete v1.2.3
require (
github.com/peterh/liner v1.2.2
golang.org/x/text v0.23.0
)
require (
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.0.0 // indirect
github.com/mattn/go-runewidth v0.0.3 // indirect
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 // indirect
)

24
go.sum
View file

@ -1,16 +1,8 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw=
github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=

246
main.go
View file

@ -7,22 +7,26 @@ import (
"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"`
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"`
Name string `json:"Name"`
Path string `json:"Path"`
CSVFile string `json:"CSVFile"`
DefaultLanguage string `json:"Default_Language"`
DefaultVariants []string `json:"Default_Variants"`
}
func main() {
@ -40,39 +44,217 @@ func main() {
log.Fatalf("Error parsing JSON: %v", err)
}
// Print the display texts of all modules as a comma-separated list
fmt.Println("Display Texts of Modules:")
for _, module := range config.Modules {
displayTexts := []string{}
csvFilePath := config.BaseDir + "/" + module.CSVFile
fmt.Println("Blechelse - waiting for your orders")
interactiveMode(config)
}
// Open and read the CSV file
csvFile, err := os.Open(csvFilePath)
// 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 {
log.Fatalf("Error opening CSV file %s: %v", csvFilePath, err)
}
defer csvFile.Close()
reader := csv.NewReader(csvFile)
records, err := reader.ReadAll()
if err != nil {
log.Fatalf("Error reading CSV file %s: %v", csvFilePath, err)
fmt.Println("Error reading input:", err)
continue
}
// Ignore the first line
if len(records) > 1 {
records = records[1:]
inputText = strings.TrimSpace(inputText)
// Exit condition
if inputText == "exit" {
fmt.Println("Goodbye!")
break
}
for _, record := range records {
displayTexts = append(displayTexts, record[1]) // Assuming the display text is in the second column (index 1)
// 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)
}
fmt.Printf("- Module Name: %s (%s)\n", module.Name, commaSeparated(displayTexts))
}
}
// Helper function to join a slice of strings with commas
func commaSeparated(slice []string) string {
return fmt.Sprintf("[%s]", strings.Join(slice, ", "))
// 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
}