package stt import ( "encoding/binary" "encoding/json" "fmt" "io" "log" "net/http" "os" "path/filepath" "strconv" "strings" "time" "mybatch/icsconf" encry "mybatch/icsencry" "mybatch/icserror" httprequest "mybatch/icshttp" "mybatch/icslog" "github.com/gorilla/websocket" ) type RequestData struct { StartTime string `json:"starttime"` Ext string `json:"ext"` ConnID string `json:"connid"` } type FileListResponse struct { TotalCnt string `json:"totalCnt"` ReturnCode string `json:"returnCode"` ReturnMsg string `json:"returnMsg"` List []FileInfo `json:"list"` } type FileInfo struct { No int `json:"no"` ConnID string `json:"connid"` Ext string `json:"ext"` StartTime string `json:"starttime"` } type Transcript struct { Text string `json:"text"` StartTime int `json:"startTime"` EndTime int `json:"endTime"` EndPoint bool `json:"endPoint"` } type ResponseData struct { Uid string `json:"uid"` Ext string `json:"ext"` SpeakerTag string `json:"speakerTag"` Transcripts []Transcript `json:"transcripts"` } type TAMessage struct { Cmd string `json:"cmd"` // STT 이벤트 상태 구분값 S:시작, E:종료, T:Text, P:Partial ConnID string `json:"connid"` //Cti 생성콜 key Tel string `json:"tel"` //고객 전화번호 Ext string `json:"ext"` //상담사 내선번호 CallStartTime string `json:"callStartTime"` //콜 시작시간 unixtime IgGbn string `json:"igGbn"` //통화유형 0:알수없음, 1:인바운드, 2:아웃바운드 DateTime string `json:"datetime"` //STT 시작시간 Stt string `json:"stt"` //STT 결과 Dir string `json:"dir"` //STT 화자구분 R:고객, T:상담사 } var ( cfg icsconf.AppInfo l *log.Logger ) func init() { cfg = icsconf.Getconfig() l = icslog.InitializeLogger() } func ProcessPostRequestForDataList() { var starttime uint64 currentDate := time.Now().Format("20060102") currentDateUint64, _ := strconv.ParseUint(currentDate, 10, 64) starttime = currentDateUint64 - 1 starttimeStr := strconv.FormatUint(starttime, 10) data := RequestData{ StartTime: starttimeStr, Ext: "", ConnID: "", } jsonData, _ := json.Marshal(data) encryptedRequestBody := encry.Encrypting(jsonData) url := "https://192.168.0.69:8080/sttRest.do" method := "POST" requestBody := fmt.Sprintf(`{"data":"%s"}`, encryptedRequestBody) response, err := httprequest.HttpRequest(url, method, requestBody) if err != nil { l.Println("error occured while requesting http post for datalist:", err) return } defer response.Body.Close() body, _ := io.ReadAll(response.Body) fmt.Println("응답 on ProcessPostRequestForDataList: ", string(body)) var parsedRes FileListResponse if err := json.Unmarshal([]byte(body), &parsedRes); err != nil { l.Println("Error parsing JSON:", err) return } var reqDataForVoiceFile RequestData if len(parsedRes.List) > 0 { for _, item := range parsedRes.List { reqDataForVoiceFile = RequestData{ StartTime: item.StartTime, Ext: item.Ext, ConnID: item.ConnID, } // Requesting For wav file and download it at local postReqForWavFileAndDownload(reqDataForVoiceFile) } } else { l.Println("No items in the datalist.") } } func postReqForWavFileAndDownload(parsedRes RequestData) { jsonData, _ := json.Marshal(parsedRes) encryptedRequestBody := encry.Encrypting(jsonData) url := "https://192.168.0.69:8080/sttRest.do" method := "POST" requestBody := fmt.Sprintf(`{"data":"%s"}`, encryptedRequestBody) response, err := httprequest.HttpRequest(url, method, requestBody) if err != nil { l.Println("error occured while requesting http post for datalist:", err) return } st := parsedRes.StartTime wavFilePath := cfg.Directories.WAVDirectory fileName := fmt.Sprintf(`%s/%s.wav`, wavFilePath, st) file, err := os.Create(fileName) if err != nil { l.Println("error occured while creating file:", err) return } defer file.Close() _, err = io.Copy(file, response.Body) if err != nil { l.Println("error occured while writing to file:", err) return } // 잘 쪼개졌다면 각 left, right을 stt 돌리기 pcmResult, folderName := devideWavTo2Pcm(fileName) if pcmResult { sttCallRes, err := controlSTT(parsedRes, folderName) if err != nil { l.Println("runSTT() failed with the error: ", err) } // stt 콜이 성공이라면 pcm 파일 지우기 if sttCallRes { pcmDir := filepath.Join(cfg.Directories.PCMDirectory, st) deletePcmFolder(pcmDir) } } } func deletePcmFolder(foldername string) (bool, *icserror.IcsError) { fmt.Println("foldername in deletePcmFolder func: ", foldername) if _, err := os.Stat(foldername); os.IsNotExist(err) { l.Println("pcm folder does not exist with the dir: ", foldername) //error here l.Println("error: ", err) return false, icserror.ICSERRFileOpen } err := os.RemoveAll(foldername) if err != nil { l.Panicln("error occured while deleting folder: ", foldername) return false, icserror.ICSERRWriteFile } l.Printf(`folder: %s has been successfully deleted`, foldername) return true, nil } func devideWavTo2Pcm(fileName string) (result bool, folderName string) { // fileName: /home/prac/svc/icsbc/voice/wav/20250913223412.wav fileNameWithoutWav := fileName[:len(fileName)-4] lastSlashIdx := strings.LastIndex(fileNameWithoutWav, "/") starttime := fileNameWithoutWav[lastSlashIdx+1:] //starttime: 20250913223412 pcmDir := cfg.Directories.PCMDirectory starttimeDir := fmt.Sprintf("%s/%s", pcmDir, starttime) os.MkdirAll(starttimeDir, os.ModePerm) leftOutputFile := fmt.Sprintf(`%s/%s-left.pcm`, starttimeDir, starttime) rightOutputFile := fmt.Sprintf(`%s/%s-right.pcm`, starttimeDir, starttime) fmt.Println("inFile reading wav path: ", fileName) inFile, err := os.Open(fileName) if err != nil { l.Println("failed to open input file: %v", err) return false, "" } defer inFile.Close() leftFile, err := os.Create(leftOutputFile) if err != nil { l.Println("failed to create left channel file: %v", err) return false, "" } defer leftFile.Close() rightFile, err := os.Create(rightOutputFile) if err != nil { l.Println("failed to create right channel file: %v", err) return false, "" } defer rightFile.Close() // Skip WAV header (44 bytes) wavHeader := make([]byte, 44) if _, err := inFile.Read(wavHeader); err != nil { l.Println("failed to read WAV header: %v", err) return false, "" } // Check if it's a valid WAV file if string(wavHeader[:4]) != "RIFF" || string(wavHeader[8:12]) != "WAVE" { l.Println("invalid WAV file format") return false, "" } // Check if WAV file is stereo numChannels := binary.LittleEndian.Uint16(wavHeader[22:24]) if numChannels != 2 { l.Println("unsupported channel count: this function only supports stereo (2 channels)") return false, "" } buf := make([]byte, 4) // 2 bytes per sample * 2 channels for { n, err := inFile.Read(buf) if err == io.EOF { break } if err != nil { l.Println("failed to read from input file: %v", err) return false, "" } if n < 4 { l.Printf("unexpected sample size read: %d\n", n) // Break or handle the case based on your needs for i := n; i < 4; i++ { buf[i] = 0 } break } leftSample := buf[:2] rightSample := buf[2:] if err := binary.Write(leftFile, binary.LittleEndian, leftSample); err != nil { l.Println("failed to write to left channel file: %v", err) return false, "" } if err := binary.Write(rightFile, binary.LittleEndian, rightSample); err != nil { l.Println("failed to write to right channel file: %v", err) return false, "" } } l.Println("WAV file split successfully") return true, starttime } // server 연결, stt 호출, 연결 해제 func controlSTT(parsedRes RequestData, folderName string) (bool, *icserror.IcsError) { pcmDirectory := cfg.Directories.PCMDirectory pcmDirectory = filepath.Join(pcmDirectory, folderName) // walking the file that has each left and right pcm files filepath.Walk(pcmDirectory, func(path string, info os.FileInfo, err error) error { if err != nil { l.Println("Error occured while walking pcm folder. error: ", err) l.Println("The pcm file path: ", pcmDirectory) return err } if !info.IsDir() && filepath.Ext(path) == "left.pcm" { stterr := connectSelvasServerRunSTT(parsedRes, path, "left") if stterr != nil { l.Println("Error occured on recognizeSpeechFromPcmFile(). error: ", stterr) return err } } if !info.IsDir() && filepath.Ext(path) == "right.pcm" { stterr := connectSelvasServerRunSTT(parsedRes, path, "right") if stterr != nil { l.Println("Error occured on recognizeSpeechFromPcmFile(). error: ", stterr) return err } } return nil }) return true, nil } func connectSelvasServerRunSTT(parsedRes RequestData, filePath string, leftright string) *icserror.IcsError { var sttRes NewSTTResult var sttErr *icserror.IcsError voicedata, err := os.ReadFile(filePath) if err != nil { return icserror.ICSERRCONFOpenFile } ip := cfg.STT.SrcIP port, _ := strconv.Atoi(cfg.STT.Port) callId := parsedRes.ConnID custId := parsedRes.Ext recordFilePath := cfg.Directories.RecDirectory // connecting with STT server s, sttErr := NewSTTS(ip, port, callId, custId, recordFilePath) if sttErr != nil { return icserror.ICSERRSTTConnectFail } else if s != nil { fmt.Println("STT session:", s) } l.Println("STT server is connected with: ", ip, ", port: ", port) // STT 호출 sttRes, sttErr = s.SendSTT(voicedata, true, nil) if sttErr != nil { l.Println("calling sendSTT() on sttselvas.go has failed with error: ", sttErr) return icserror.ICSERRSTTSendFail } // STT server and chanel close s.CloseChanelAndServer() // STT 호출 성공시 결과를 websocket, ta rest로 송신 if sttRes.Res != "" { wssres, wsserr := webSocketSend(parsedRes, sttRes, leftright) httpres, httperr := taRestSend(parsedRes, sttRes, s.GetReqTime(), leftright) if wssres && httpres { l.Println("stt results have successfully sent through both wss and http(ta)") } if wsserr != nil { l.Println("sending stt result through websocket has failed with the error: ", wsserr) } if httperr != nil { l.Println("sending stt result through http for ta has failed with the error: ", httperr) } } return nil } func webSocketSend(parsedRes RequestData, sttRes NewSTTResult, leftright string) (bool, *icserror.IcsError) { speaker := "X" // T: 상담사 if leftright == "left" { speaker = "R" //R: 고객 } url := "ws://192.168.0.69:8090/wss" headers := http.Header{ "Origin": {"https://192.168.0.69"}, } conn, _, err := websocket.DefaultDialer.Dial(url, headers) if err != nil { l.Println("Failed to connect to WebSocket: %v", err) return false, icserror.ICSERRWEBSOCKETConnectFailError } defer conn.Close() resData := ResponseData{ Uid: parsedRes.ConnID, Ext: parsedRes.Ext, SpeakerTag: speaker, Transcripts: []Transcript{ { Text: sttRes.Res, StartTime: sttRes.NStart, EndTime: sttRes.NEnd, EndPoint: true, }, }, } jsonData, _ := json.Marshal(resData) encryptedResponseBody := encry.Encrypting(jsonData) responseBody := fmt.Sprintf(`{"data":"%s"}`, encryptedResponseBody) err = conn.WriteMessage(websocket.TextMessage, []byte(responseBody)) if err != nil { l.Println("failed to send msg via websocket with the err: ", err) return false, icserror.ICSERRWEBSOCKETWriteError } else { l.Println("Sent the msg via websocket. message body: ", responseBody) return true, nil } } func taRestSend(parsedRes RequestData, sttRes NewSTTResult, reqTime time.Time, leftright string) (bool, *icserror.IcsError) { dir := "T" // T: 상담사 if leftright == "left" { dir = "R" //R: 고객 } resData := TAMessage{ Cmd: "T", ConnID: parsedRes.ConnID, Tel: "", // 정보없음 Ext: parsedRes.Ext, CallStartTime: parsedRes.StartTime, IgGbn: "1", // 인바운드 DateTime: reqTime.Format("2006-01-02 15:04:05"), Stt: sttRes.Res, Dir: dir, } jsonData, _ := json.Marshal(resData) encryptedRequestBody := encry.Encrypting(jsonData) url := "https://192.168.0.69:8080/taRest.do" method := "POST" requestBody := fmt.Sprintf(`{"data":"%s"}`, encryptedRequestBody) response, err := httprequest.HttpRequest(url, method, requestBody) if err != nil { l.Println("error occured while requesting http post for datalist:", err) return false, icserror.ICSERRHTTPClientExcecution } l.Println("TA msg has been successfully sent. response: ", response) return true, nil }