You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
450 lines
13 KiB
Go
450 lines
13 KiB
Go
1 month ago
|
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
|
||
|
}
|