package api.service.quality.impl; import api.entity.database.call.Translate; import api.entity.database.quality.*; import api.entity.input.PageInput; import api.entity.input.quality.QcResultPush; import api.entity.input.quality.RepTranslate; import api.entity.view.quality.ExcelSentiment; import api.entity.view.quality.HitRules; import api.mapper.quality.QcResultMapper; import api.service.call.ITranslateService; import api.service.quality.*; import api.service.BaseServiceImpl; import api.service.system.IConfigService; import api.util.annotation.Log; import api.util.enums.RulesCondition; import api.util.helper.DateHelper; import api.util.helper.StringHelper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; //import edu.stanford.nlp.ling.CoreAnnotations; //import edu.stanford.nlp.neural.rnn.RNNCoreAnnotations; //import edu.stanford.nlp.pipeline.Annotation; //import edu.stanford.nlp.pipeline.StanfordCoreNLP; //import edu.stanford.nlp.sentiment.SentimentCoreAnnotations; //import edu.stanford.nlp.util.CoreMap; //import com.hankcs.hanlp.HanLP; import lombok.var; import opennlp.tools.doccat.*; import opennlp.tools.tokenize.SimpleTokenizer; import opennlp.tools.util.CollectionObjectStream; import opennlp.tools.util.ObjectStream; import opennlp.tools.util.model.ModelUtil; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.sound.sampled.*; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; import java.time.Instant; import java.time.LocalDate; import java.util.*; @Transactional @Service public class QcResultServiceImpl extends BaseServiceImpl implements IQcResultService{ public QcResultServiceImpl(){ super(false); } //质检规则主体 @Autowired private IInspectionRulesService inspectionrulesService; //通话翻译主体 @Autowired private ITranslateService translateService; //评分标准主体 @Autowired private IScoringCriteriaService scoringCriteriaService; //系统配置主体 @Autowired private IConfigService configService; //触发规则主体 @Autowired private ITriggerRulesService triggerRulesService; //词汇表 @Autowired private ISearchLexiconService searchLexiconService; @Autowired private IQualityModelService modelService; private Page GetPage(PageInput page) { if (page.getPageNum() != null && page.getPageSize() != null) { Page ipage = new Page<>(); ipage.setCurrent(page.getPageNum()); ipage.setSize(page.getPageSize()); return ipage; } else { return null; } } //分隔音频获取音量音速 @Override public List GetAudio(String callId) throws UnsupportedAudioFileException, IOException { //获取通话记录 LambdaQueryWrapper qw =new LambdaQueryWrapper<>(); //qw.apply("IFNULL(user_name,'') != ''"); qw.apply("IFNULL(file_path,'') != ''"); qw.eq(QcResult::getIsQuality,0); // if (!Objects.equals(callId, "")) // { // qw.eq(QcResult::getCallid,callId); // } // qw.eq(QcResult::getUserName,"8002"); // qw.ge (QcResult::getCallid,580); qw.orderByAsc(QcResult::getQcId); PageInput pageInput=new PageInput(); pageInput.setPageNum(1); pageInput.setPageSize(100); Page page = GetPage(pageInput); IPage iPage = this.getListPage(page, qw); // var calls=this.getList(qw); var calls=iPage.getRecords(); List audio=new ArrayList<>(); if (calls!=null&&calls.size()>0) { for (QcResult call : calls ) { //获取质检规则 LambdaQueryWrapper qe =new LambdaQueryWrapper<>(); qe.le ( InspectionRules::getStartTime, new Date()); qe.ge ( InspectionRules::getEndTime, new Date()); qe.eq(InspectionRules::getIsDelete,0); qe.like(InspectionRules::getApplySeats,call.getUserName()); var Rules=inspectionrulesService.getList(qe); new InspectionRules(); InspectionRules rule; if (Rules!=null&&Rules.size()>0) { rule=Rules.get(0); } else { //当前时间无质检规则直接抛出 continue; } String FileUrl="";String URL=""; //获取录音路径 if (!StringHelper.isEmpty(call.getFilePath())) { URL="http://1.194.161.64:9000/"+call.getFilePath(); FileUrl= downloadFile(URL,"files/luYin/"+ DateHelper.getDate()); } if (Objects.equals(FileUrl, "")) { continue; } call.setRuleId(rule.getId()); LambdaQueryWrapper qt=new LambdaQueryWrapper<>(); //获取语音翻译 qt.eq(Translate::getCallid,call.getCallid()); var mobile=translateService.getList(qt); if (mobile!=null&&mobile.size()>0) { ObjectMapper mapper = new ObjectMapper(); try { if (StringHelper.isNotEmpty(mobile.get(0).getTranslate())) { //解析同声翻译 RepTranslate [] translate = mapper.readValue(mobile.get(0).getTranslate(), RepTranslate[].class); StringBuilder customerSpeech= new StringBuilder();StringBuilder seatSpeech= new StringBuilder(); int duration=0;int Time=0; for (RepTranslate item :translate) { if(item.getNumber().length()>4) { customerSpeech.append(item.getSpeech()); } else { seatSpeech.append(item.getSpeech()); } int[] Millisecond=GetMillisecond(item.getTime()); if (Millisecond!=null&&Millisecond.length>0) { duration+=Millisecond[0]-Time; Time=Millisecond[1]; } } double decibel = calculateRMSVolume(new File(FileUrl));//获取音量 call.setVolume(String.format("%.2f", decibel)); int wordCount=returnCharacters(customerSpeech.toString()).length()+returnCharacters(seatSpeech.toString()).length(); double speechRate=calculateSpeechRate( wordCount, new File(FileUrl));//获取语速 call.setSpeed(String.format("%.2f", speechRate)); //静音时长 call.setMute((long) (duration/1000)); //客户情绪 call.setCustomerEmotion(modelService.OpenNLPDetection(customerSpeech.toString())); //坐席情绪 call.setSeatEmotion(modelService.OpenNLPDetection(seatSpeech.toString())); //分段录音解析音量音速 try { GetRepTranslates(translate, FileUrl); } catch (Exception e) { e.printStackTrace(); } LambdaQueryWrapper qs =new LambdaQueryWrapper<>(); qs.eq(ScoringCriteria::getIsDelete,0);//是否确认 qs.eq(ScoringCriteria::getRulesId,rule.getId());//规则id var scoring=scoringCriteriaService.getList(qs); long score=100;int index=0;List AllTriggerRules=new ArrayList<>(); int start=0;int validity = 0; if (scoring!=null&&scoring.size()>0) { LinkedHashMap> keyWorlds=keyWorlds(scoring); var filterKeyWorlds=filterKeyWorlds(); for (RepTranslate item :translate) { index=index+1; List triggerRules=new ArrayList<>(); StringBuilder trigger= new StringBuilder(); if(item.getNumber().length()>4) { //只质检话务员,客户语音跳过 if ( index!=translate.length) { continue; } } else { start=start+1; } for (ScoringCriteria scoringCriteria :scoring) { String value= RulesCondition.getTypeValue(scoringCriteria.getConditionName()); TriggerRules triggerRules1=new TriggerRules(); triggerRules1.setRulesId(rule.getId());//规则id triggerRules1.setRulesName(rule.getRuleName());//触发规则名称 triggerRules1.setScoringId(scoringCriteria.getId());//触发标准id triggerRules1.setScoringName(value);//触发标准名称 triggerRules1.setQualityId(call.getQcId()); triggerRules1.setSoundName(item.getFilePath()); HitRules hit =new HitRules(); hit.setId(scoringCriteria.getId()); hit.setName(value); hit.setScore(scoringCriteria.getScore()); if (Objects.equals(scoringCriteria.getConditionName(), RulesCondition.prologue.name()) &&start!=1) { //检测开场白非第一条跳过 continue; } else if (Objects.equals(scoringCriteria.getConditionName(), RulesCondition.conclusion.name())&& index!=translate.length) { //检测结束语非不是最后一条跳过 continue; } if(item.getNumber().length()>4&&Objects.equals(scoringCriteria.getConditionName(), RulesCondition.conclusion.name())) { for (int i=translate.length-1;i>=0;i--) { if (translate[i].getNumber().length()<=4) { item=translate[i]; break; } } } else if(item.getNumber().length()>4) { continue; } if(Objects.equals(scoringCriteria.getConditionName(), RulesCondition.ringing.name()) &&start==1) { if (call.getRingTime()!=null&& call.getAnswerTime()!=null) { // 将Date转换为Instant Instant instant1 = call.getRingTime().toInstant(); Instant instant2 = call.getAnswerTime().toInstant(); // 计算时间差 long seconds = Duration.between(instant1, instant2).getSeconds() ; if (scoringCriteria.getMaxValue()!=null &&seconds>scoringCriteria.getMaxValue()) { triggerRules.add(triggerRules1); // trigger.append(",振铃时长").append(seconds).append("命中规则条件超过") // .append(value); } } } else if(Objects.equals(scoringCriteria.getConditionName(), RulesCondition.duration.name()) &&start==1) { if (call.getAnswerTime()!=null&& call.getHangupTime()!=null) { // 将Date转换为Instant Instant instant1 = call.getAnswerTime().toInstant(); Instant instant2 = call.getHangupTime().toInstant(); // 计算时间差 long seconds = Duration.between(instant1, instant2).getSeconds() ; if (scoringCriteria.getMaxValue()!=null &&scoringCriteria.getMaxValue()>0&& scoringCriteria.getMinValue()!=null&&scoringCriteria.getMinValue()>0 ) { if ( seconds>scoringCriteria.getMaxValue() ||seconds0 &&seconds>scoringCriteria.getMaxValue()) { triggerRules.add(triggerRules1); // trigger.append(",通话时长异常").append("命中规则条件") // .append(value); } else if (scoringCriteria.getMinValue()!=null&&scoringCriteria.getMinValue()>0 &&secondsscoringCriteria.getMaxValue()) { triggerRules.add(triggerRules1); // trigger.append(",静音分析").append((long) (duration/1000)).append("命中规则条件超过") // .append(value); } } else if(Objects.equals(scoringCriteria.getConditionName(), RulesCondition.satisfaction.name()) &&start==1) { if (call.getMyd()!=null&&call.getMyd()==3) { triggerRules.add(triggerRules1); // trigger.append(",用户满意度").append("不满意").append("命中规则条件") // .append(value); } } else if(Objects.equals(scoringCriteria.getConditionName(), RulesCondition.emotional.name()) ) { if (StringHelper.isNotEmpty(item.getSpeech()) &&item.getSpeech().length()>10) { if(Objects.equals(modelService.OpenNLPDetection(item.getSpeech()), "消极")) { triggerRules.add(triggerRules1); hit.setHit("消极"); trigger.append(",").append(hit.toJsonString()); } } } else if(Objects.equals(scoringCriteria.getConditionName(), RulesCondition.response.name()) &&start==1) { if (scoringCriteria.getMaxValue()!=null) { if (index==1) { int[] Millisecond=GetMillisecond(item.getTime()); if(Millisecond!=null) { validity=Millisecond[0]/1000; if (validity>scoringCriteria.getMaxValue()) { triggerRules.add(triggerRules1); hit.setHit(String.valueOf(validity)); trigger.append(",").append(hit.toJsonString()); } } } else { int[] Millisecond=GetMillisecond(item.getTime()); if(Millisecond!=null) { int[] Millisecond1=GetMillisecond(translate[index-2].getTime()); validity=(Millisecond[0]-Millisecond1[1])/1000; if (validity>scoringCriteria.getMaxValue()) { triggerRules.add(triggerRules1); hit.setHit(String.valueOf(validity)); trigger.append(",").append(hit.toJsonString()); } } } } } else { //声音过短不质检 if (StringHelper.isNotEmpty(item.getSpeech()) &&(item.getSpeech().length()>2||Objects.equals(scoringCriteria.getConditionName(), RulesCondition.prologue.name()) ||Objects.equals(scoringCriteria.getConditionName(), RulesCondition.conclusion.name()))) { //当前评分为音量 if (Objects.equals(scoringCriteria.getConditionName(), RulesCondition.volume.name())) { if (scoringCriteria.getMaxValue()!=null &&scoringCriteria.getMinValue()!=null ){ if(Double.parseDouble(item.getVolume()) < scoringCriteria.getMinValue() ||Double.parseDouble(item.getVolume())> scoringCriteria.getMaxValue()) { triggerRules.add(triggerRules1); hit.setHit(String.valueOf(item.getVolume())); trigger.append(",").append(hit.toJsonString()); } } else if (scoringCriteria.getMaxValue()!=null && Double.parseDouble(item.getVolume())> scoringCriteria.getMaxValue()) { triggerRules.add(triggerRules1); hit.setHit(String.valueOf(item.getVolume())); trigger.append(",").append(hit.toJsonString()); } else if (scoringCriteria.getMinValue()!=null&& Double.parseDouble(item.getVolume()) < scoringCriteria.getMinValue()) { triggerRules.add(triggerRules1); hit.setHit(String.valueOf(item.getVolume())); trigger.append(",").append(hit.toJsonString()); } } //当前评分为语速 else if (Objects.equals(scoringCriteria.getConditionName(), RulesCondition.speed.name())) { //语速 if (scoringCriteria.getMaxValue()!=null &&scoringCriteria.getMinValue()!=null ){ if (Double.parseDouble(item.getSpeed()) < scoringCriteria.getMinValue() ||Double.parseDouble(item.getSpeed())> scoringCriteria.getMaxValue()) { triggerRules.add(triggerRules1); hit.setHit(String.valueOf(item.getSpeed())); trigger.append(",").append(hit.toJsonString()); } } else if (scoringCriteria.getMaxValue()!=null && Double.parseDouble(item.getSpeed())> scoringCriteria.getMaxValue()) { triggerRules.add(triggerRules1); hit.setHit(String.valueOf(item.getSpeed())); trigger.append(",").append(hit.toJsonString()); } else if (scoringCriteria.getMinValue()!=null&& Double.parseDouble(item.getSpeed()) < scoringCriteria.getMinValue()) { triggerRules.add(triggerRules1); hit.setHit(String.valueOf(item.getSpeed())); trigger.append(",").append(hit.toJsonString()); } } else if (Objects.equals(scoringCriteria.getConditionName(), RulesCondition.interrupt.name())) { if (index>1) { if(translate[index-2].getNumber().length()>4&&translate[index-1].getNumber().length()<=4) { int[] Millisecond=GetMillisecond(item.getTime()); int[] Millisecond1=GetMillisecond(translate[index-2].getTime()); if (Millisecond[0] keywords=keyWorlds.get(scoringCriteria.getId()); if(keywords!=null&&keywords.size()>0) { if (scoringCriteria.getIsContain()!=null&&scoringCriteria.getIsContain()>1) { if (!searchLexiconService.containsAnyParallel(item.getSpeech(),keywords)) { triggerRules.add(triggerRules1); trigger.append(",").append(hit.toJsonString()); } } else { if (searchLexiconService.containsAnyParallel(item.getSpeech(),keywords)) { if (Objects.equals(scoringCriteria.getConditionName(), RulesCondition.swearing.name())) { if (!searchLexiconService.containsAnyParallel(item.getSpeech(),filterKeyWorlds)) { triggerRules.add(triggerRules1); trigger.append(",").append(hit.toJsonString()); } } else { triggerRules.add(triggerRules1); trigger.append(",").append(hit.toJsonString()); } } } } } } } } if (triggerRules.size()>0) { item.setTrigger("["+trigger.deleteCharAt(0).toString()+"]"); AllTriggerRules.addAll(triggerRules); } } } List> criteria=new ArrayList<>(); if (scoring != null) { for (ScoringCriteria scoringCriteria :scoring) { long count=AllTriggerRules.stream().filter(x-> Objects.equals(x.getScoringId(), scoringCriteria.getId())) .count(); Long frequency=scoringCriteria.getFrequency(); if (scoringCriteria.getFrequency()==null||scoringCriteria.getFrequency()<=0) { frequency=1L; } if (count>=frequency) { long multiple=count/frequency*scoringCriteria.getScore(); if (Objects.equals(scoringCriteria.getConditionName(), RulesCondition.volume.name())) { //音量只扣一次分 multiple=scoringCriteria.getScore(); } LinkedHashMap map=new LinkedHashMap<>(); map.put("conditionName",RulesCondition.getTypeValue(scoringCriteria.getConditionName())); // if (scoringCriteria.getScoreType()==0) // { // map.put("score",scoringCriteria.getScore()); // score+=multiple; // } // else // { // map.put("score",-(scoringCriteria.getScore())); // score-=multiple; // } map.put("score",-multiple); score-=multiple; map.put("isCritical",scoringCriteria.getIsCritical()); if (scoringCriteria.getIsCritical()==1) { //是否致命 call.setIsCritical(1L); } map.put("scoring",scoringCriteria); criteria.add(map); } } } if(AllTriggerRules.size()>0) { triggerRulesService.insert(AllTriggerRules); } if (criteria.size()>0) { //评分标准 call.setScoringCriteria(mapper.writeValueAsString(criteria)); } call.setValidity((long) validity); //质检是否成功 call.setIsSuccess(1L); //AI质检分值 call.setScore(score); //合格分数 call.setPassingScore(rule.getPassingScore()); //是否合格 if (score>=rule.getPassingScore()) { call.setIsQualified(1L); call.setAiResult("合格"); } else { call.setIsQualified(2L); call.setAiResult("不合格"); } //保存json文件 call.setTextJson(mapper.writeValueAsString(translate)); } else { call.setIsSuccess(2L); } } catch (Exception e) { call.setIsSuccess(2L); } } else { try { ObjectMapper mapper = new ObjectMapper(); //没有翻译只检测音量音速 double decibel = calculateRMSVolume(new File(FileUrl));//获取音量 call.setVolume(String.format("%.2f", decibel)); //double speechRate = analyzeSpeechSpeed(new File(FileUrl), -40.0);;//获取语速 // call.setSpeed(String.format("%.2f", speechRate)); LambdaQueryWrapper qs =new LambdaQueryWrapper<>(); qs.eq(ScoringCriteria::getIsDelete,0);//是否确认 qs.eq(ScoringCriteria::getRulesId,rule.getId());//规则id var scoring=scoringCriteriaService.getList(qs); List triggerRules=new ArrayList<>(); StringBuilder trigger= new StringBuilder();long score=100; List> criteria=new ArrayList<>(); if (scoring != null) { Long isSuccess=1L; for (ScoringCriteria scoringCriteria :scoring) { if (RulesCondition.getTypeIsCharacters(scoringCriteria.getConditionName())) { isSuccess=2L; break; } } if (isSuccess==1L) { for (ScoringCriteria scoringCriteria :scoring) { String value= RulesCondition.getTypeValue(scoringCriteria.getConditionName()); TriggerRules triggerRules1=new TriggerRules(); triggerRules1.setRulesId(rule.getId());//规则id triggerRules1.setRulesName(rule.getRuleName());//触发规则名称 triggerRules1.setScoringId(scoringCriteria.getId());//触发标准id triggerRules1.setScoringName(value);//触发标准名称 triggerRules1.setQualityId(call.getQcId()); triggerRules1.setSoundName(FileUrl); HitRules hit =new HitRules(); hit.setId(scoringCriteria.getId()); hit.setName(value); hit.setScore(scoringCriteria.getScore()); long count=0; if(Objects.equals(scoringCriteria.getConditionName(), RulesCondition.ringing.name()) ) { if (call.getRingTime()!=null&& call.getAnswerTime()!=null) { // 将Date转换为Instant Instant instant1 = call.getRingTime().toInstant(); Instant instant2 = call.getAnswerTime().toInstant(); // 计算时间差 long seconds = Duration.between(instant1, instant2).getSeconds() ; if (scoringCriteria.getMaxValue()!=null &&seconds>scoringCriteria.getMaxValue()) { triggerRules.add(triggerRules1); // trigger.append(",振铃时长").append("命中规则条件") // .append(value); } } } else if(Objects.equals(scoringCriteria.getConditionName(), RulesCondition.duration.name()) ) { if (call.getAnswerTime()!=null&& call.getHangupTime()!=null) { // 将Date转换为Instant Instant instant1 = call.getAnswerTime().toInstant(); Instant instant2 = call.getHangupTime().toInstant(); // 计算时间差 long seconds = Duration.between(instant1, instant2).getSeconds() ; if (scoringCriteria.getMaxValue()!=null&& scoringCriteria.getMaxValue()>0&& scoringCriteria.getMinValue()!=null&& scoringCriteria.getMinValue()>0 ) { if ( seconds>scoringCriteria.getMaxValue() ||seconds0 &&seconds>scoringCriteria.getMaxValue()) { triggerRules.add(triggerRules1); // trigger.append(",通话时长异常").append("命中规则条件") // .append(value); } else if (scoringCriteria.getMinValue()!=null && scoringCriteria.getMinValue()>0 &&seconds scoringCriteria.getMaxValue()) { triggerRules.add(triggerRules1); // trigger.append(",音量").append(decibel).append("命中规则条件") // .append(value); hit.setHit(String.valueOf(decibel)); trigger.append(",").append(hit.toJsonString()); count=1; } } else if (scoringCriteria.getMaxValue()!=null && decibel> scoringCriteria.getMaxValue()) { triggerRules.add(triggerRules1); // trigger.append(",音量").append(decibel).append("命中规则条件") // .append(value); hit.setHit(String.valueOf(decibel)); trigger.append(",").append(hit.toJsonString()); count=1; } else if (scoringCriteria.getMinValue()!=null&& decibel < scoringCriteria.getMinValue()) { triggerRules.add(triggerRules1); // trigger.append(",音量").append(decibel).append("命中规则条件") // .append(value); hit.setHit(String.valueOf(decibel)); trigger.append(",").append(hit.toJsonString()); count=1; } } //当前评分为语速 // else if (Objects.equals(scoringCriteria.getConditionName(), RulesCondition.speed.getValue())) { // //音量音速固定为减分项 // if (scoringCriteria.getIsContain()!=null&&scoringCriteria.getIsContain()>1) // { // if (speechRate < scoringCriteria.getMinValue() // &&speechRate> scoringCriteria.getMaxValue()){ // triggerRules.add(triggerRules1); // trigger.append(",语速").append(speechRate).append("命中规则条件") // .append(scoringCriteria.getConditionName()); // } // else if (scoringCriteria.getMaxValue()!=null && // speechRate> scoringCriteria.getMaxValue()) // { // triggerRules.add(triggerRules1); // trigger.append(",语速").append(speechRate).append("命中规则条件") // .append(scoringCriteria.getConditionName()); // } // else if (scoringCriteria.getMinValue()!=null&& // speechRate < scoringCriteria.getMinValue()) // { // triggerRules.add(triggerRules1); // trigger.append(",语速").append(speechRate).append("命中规则条件") // .append(scoringCriteria.getConditionName()); // } // } // else // { // if (speechRate>= scoringCriteria.getMinValue() // &&speechRate<= scoringCriteria.getMaxValue()){ // triggerRules.add(triggerRules1); // trigger.append(",语速").append(speechRate).append("命中规则条件") // .append(scoringCriteria.getConditionName()); // } // else if (scoringCriteria.getMaxValue()!=null && // speechRate<= scoringCriteria.getMaxValue()) // { // triggerRules.add(triggerRules1); // trigger.append(",语速").append(speechRate).append("命中规则条件") // .append(scoringCriteria.getConditionName()); // } // else if (scoringCriteria.getMinValue()!=null&& // speechRate >= scoringCriteria.getMinValue()) // { // triggerRules.add(triggerRules1); // trigger.append(",语速").append(speechRate).append("命中规则条件") // .append(scoringCriteria.getConditionName()); // } // } // } if (count==1) { LinkedHashMap map=new LinkedHashMap<>(); map.put("conditionName",value); // if (scoringCriteria.getScoreType()==0) // { // map.put("score",scoringCriteria.getScore()); // score+=scoringCriteria.getScore(); // } // else // { // map.put("score",-(scoringCriteria.getScore())); // score-=scoringCriteria.getScore(); // } map.put("score",-(scoringCriteria.getScore())); score-=scoringCriteria.getScore(); map.put("isCritical",scoringCriteria.getIsCritical()); map.put("scoring",scoringCriteria); if (scoringCriteria.getIsCritical()==1) { //是否致命 call.setIsCritical(1L); } map.put("Id",scoringCriteria.getId()); criteria.add(map); } } } if(triggerRules.size()>0) { triggerRulesService.insert(triggerRules); } if (criteria.size()>0) { //评分标准 call.setScoringCriteria(mapper.writeValueAsString(criteria)); } call.setValidity(0L); //质检是否成功 call.setIsSuccess(isSuccess); //AI质检分值 call.setScore(score); //合格分数 call.setPassingScore(rule.getPassingScore()); //是否合格 if (score>=rule.getPassingScore()) { call.setIsQualified(1L); call.setAiResult("合格"); } else { call.setIsQualified(2L); call.setAiResult("不合格"); } RepTranslate item=new RepTranslate(); if (triggerRules.size()>0) { item.setTrigger("["+trigger.deleteCharAt(0).toString()+"]"); } item.setVolume(String.format("%.2f", decibel)); // item.setSpeed(String.format("%.2f", speechRate)); item.setScore(score); item.setFilePath(FileUrl); //保存json文件 call.setTextJson(mapper.writeValueAsString(item)); } } catch (Exception e) { call.setIsSuccess(2L); } } call.setQcTime(new Date()); call.setIsQuality(1L); this.update(call); try { // 延时1秒(1000毫秒) Thread.sleep(3000); } catch (InterruptedException e) { // 处理中断异常 e.printStackTrace(); // 恢复中断状态 Thread.currentThread().interrupt(); } deleteFiles(FileUrl) ; } } return audio; } private static String returnCharacters(String str) { String result = str.replaceAll("[^\\p{L}\\p{N} ]", ""); return result; } private void deleteFiles(String files) { Path path = Paths.get(files); try { System.gc(); Thread.sleep(100); // 短暂等待 // 删除文件 Files.delete(path); System.out.println("文件删除成功!"); } catch (IOException e) { System.err.println("删除失败: " + e.getMessage()); // 可根据具体异常类型处理不同情况 if (e instanceof java.nio.file.NoSuchFileException) { System.err.println("文件不存在!"); } else if (e instanceof java.nio.file.AccessDeniedException) { System.err.println("权限不足!"); } else { e.printStackTrace(); } } catch (InterruptedException e) { throw new RuntimeException(e); } } public static double calculateRMSVolume(File audioFile) throws UnsupportedAudioFileException, IOException { // 使用 try-with-resources 确保流自动关闭 try (AudioInputStream originalStream = AudioSystem.getAudioInputStream(audioFile)) { AudioInputStream audioStream = originalStream; AudioFormat format = audioStream.getFormat(); // 检查是否需要转换为 PCM 格式 if (format.getEncoding() != AudioFormat.Encoding.PCM_SIGNED && format.getEncoding() != AudioFormat.Encoding.PCM_UNSIGNED) { // 定义目标格式(转换为带符号16位PCM) AudioFormat targetFormat = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED, format.getSampleRate(), 16, format.getChannels(), format.getChannels() * 2, format.getSampleRate(), false); // 获取转换后的流,并包裹在 try-with-resources 中 try (AudioInputStream convertedStream = AudioSystem.getAudioInputStream(targetFormat, originalStream)) { return processAudioStream(convertedStream, targetFormat); } } else { // 直接处理原始流 return processAudioStream(audioStream, format); } } } private static double processAudioStream(AudioInputStream audioStream, AudioFormat format) throws IOException { // 读取所有音频数据 byte[] buffer = new byte[(int) (audioStream.getFrameLength() * format.getFrameSize())]; audioStream.read(buffer); // 将字节转换为样本值 double[] samples = convertBytesToSamples(buffer, format); // 计算RMS double sumSquares = 0.0; for (double sample : samples) { sumSquares += sample * sample; } double rms = Math.sqrt(sumSquares / samples.length); // 转换为分贝(避免除以0) return rms > 0 ? 20 * Math.log10(rms) : Double.NEGATIVE_INFINITY; } private static double[] convertBytesToSamples(byte[] bytes, AudioFormat format) { int sampleSize = format.getSampleSizeInBits() / 8; int numSamples = bytes.length / sampleSize; double[] samples = new double[numSamples]; ByteOrder byteOrder = format.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN; for (int i = 0; i < numSamples; i++) { int offset = i * sampleSize; int value = 0; // 根据位深度解析样本 switch (sampleSize) { case 1: // 8位 value = bytes[offset] & 0xFF; // 转换为无符号 samples[i] = (value - 128) / 128.0; // 归一化到[-1, 1] break; case 2: // 16位 value = ByteBuffer.wrap(bytes, offset, 2) .order(byteOrder) .getShort(); samples[i] = value / 32768.0; // 归一化到[-1, 1] break; default: throw new UnsupportedOperationException("不支持的位深度: " + format.getSampleSizeInBits()); } } return samples; } public static String downloadFile(String fileUrl, String savePath) throws IOException { URL url = new URL(fileUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); String file=""; if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { // 解析文件名 String disposition = conn.getHeaderField("Content-Disposition"); String fileName = (disposition != null) ? disposition.substring(disposition.indexOf("filename=") + 10, disposition.length() - 1) : fileUrl.substring(fileUrl.lastIndexOf("/") + 1); // 创建目录 File saveDir = new File(savePath); if (!saveDir.exists()) saveDir.mkdirs(); file=savePath + File.separator + fileName; // 读写数据 try (InputStream in = new BufferedInputStream(conn.getInputStream()); FileOutputStream out = new FileOutputStream(savePath + File.separator + fileName)) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } } finally { conn.disconnect(); } } return file; } @Override public List excelSentiment() throws JsonProcessingException { var list =translateService.getList(); ObjectMapper mapper = new ObjectMapper(); List sentiments=new ArrayList<>(); for (Translate item:list) { if (StringHelper.isNotEmpty(item.getTranslate())) { RepTranslate [] translate = mapper.readValue(item.getTranslate(), RepTranslate[].class); int index=0; StringBuilder message= new StringBuilder(); for (RepTranslate i :translate) { index=index+1; if (translate.length>index) { if (i.getNumber().length()==translate[index].getNumber().length()) { message.append(i.getSpeech()); } else { ExcelSentiment excelSentiment=new ExcelSentiment(); message.append(i .getSpeech()); if (message.toString().length()<10) { message= new StringBuilder(); continue; } excelSentiment.setContent(message.toString()); if(i.getNumber().length()>4) { excelSentiment.setSeat( "客户"); } else { excelSentiment.setSeat( "坐席"); } sentiments.add(excelSentiment); message= new StringBuilder(); } } else { message.append(i .getSpeech()); if (message.toString().length()<10) { message= new StringBuilder(); continue; } ExcelSentiment excelSentiment=new ExcelSentiment(); excelSentiment.setContent(message.toString()); if(i.getNumber().length()>4) { excelSentiment.setSeat( "客户"); } else { excelSentiment.setSeat( "坐席"); } sentiments.add(excelSentiment); message= new StringBuilder(); } } } } return sentiments; } @Override public boolean QcResultPush(QcResultPush push) { QcResult result=new QcResult(); result.setIsQuality(0L); result.setCallid(push.getCallid()); result.setTel(push.getTel()); result.setTalkStartTime(push.getTalkStartTime()); result.setWaitTime(push.getWaitTime()); result.setRingTime(push.getRingTime()); result.setAnswerTime(push.getAnswerTime()); result.setHangupTime(push.getHangupTime()); result.setFilePath(push.getFilePath()); result.setCallType(push.getCallType()); result.setUserName(push.getUserName()); result.setMyd(push.getMyd()); result.setNickName(push.getNickName()); boolean n=this.insert(result); Translate translate=new Translate(); if (StringHelper.isNotEmpty(push.getTranslate())) { translate.setCallid(push.getCallid()); translate.setTranslate(push.getTranslate()); translate.setTranslateJson(push.getTranslateJson()); translateService.insert(translate); } return n; } private List filterKeyWorlds() { List linke =new ArrayList<>(); linke.add("妈妈的"); return linke; } private LinkedHashMap> keyWorlds(List scoring) { LinkedHashMap> linkedMap = new LinkedHashMap<>(); for (ScoringCriteria scoringCriteria :scoring) { List result = new ArrayList<>(); if (StringHelper.isEmpty(scoringCriteria.getContent())) { continue; } String[] parts = scoringCriteria.getContent().split(","); try { for (String part : parts) { String trimmed = part.trim(); if (!trimmed.isEmpty()) { result.add(Long.parseLong(trimmed)); } } } catch (Exception E) { //关键词中存在汉字直接抛出 continue; } LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); qw.in(SearchLexicon::getCategory,result); qw.in(SearchLexicon::getType,2); List search=searchLexiconService.getList(qw); List keywords =new ArrayList<>(); if(search!=null&&search.size()>0) { for (SearchLexicon item :search) { keywords.add(item.getTerm()); } } linkedMap.put(scoringCriteria.getId(),keywords); } return linkedMap; } //解析同声翻译 private void GetRepTranslates(RepTranslate [] translate, String inputFile) throws Exception { LocalDate currentDate = LocalDate.now(); //获取当前年月日 int year = currentDate.getYear(); int month = currentDate.getMonthValue(); int day = currentDate.getDayOfMonth(); //设置保存路径 String File= "files/luYin/" +year+"/"+month+"/"+day+"/"; //下载录音文件到指定路径 // String fileName= DownloadHelper.downloadFile(File,FileUrl); //获取录音文件 //String inputFile = FileUrl; for (RepTranslate item :translate) { if (StringHelper.isNotEmpty(item.getSpeech()) &&item.getSpeech().length()>2) { if(item.getNumber().length()>4) { //只质检话务员,客户语音跳过 continue; } if (StringHelper.isNotEmpty(item.getTime())) { int[] Millisecond=GetMillisecond(item.getTime()); if (Millisecond!=null) { String newFile=File+Millisecond[0]+"-"+new File(inputFile).getName(); //分割wav splitWav(new File(inputFile), new File(newFile), Millisecond[0], Millisecond[1]); double decibel = calculateRMSVolume(new File(newFile)); //double speechRate = analyzeSpeechSpeed(new File(newFile), -40.0); double speechRate=calculateSpeechRate(returnCharacters(item.getSpeech()).length(), new File(newFile));//获取语速 //分割路径 item.setFilePath(newFile); //音量 item.setVolume( String.format("%.2f", decibel)); //音速 item.setSpeed( String.format("%.2f", speechRate)); //质检完毕删除附件 deleteFiles(newFile); } } } } } //获取音量 public static double calculateDecibel(File wavFile) throws Exception { try (DataInputStream dis = new DataInputStream(Files.newInputStream(wavFile.toPath()))) { // 跳过WAV头(假设头长度为44字节) dis.skipBytes(44); // 读取PCM数据 List samples = new ArrayList<>(); while (dis.available() > 0) { samples.add(dis.readShort()); } // 计算RMS double sum = 0; for (short sample : samples) { sum += (sample * sample); } double rms = Math.sqrt(sum / samples.size()); // 计算分贝(参考值取16位最大值32767) return 20 * Math.log10(rms / 32767.0); } } //获取语速 public static double analyzeSpeechSpeed(File wavFile, double silenceThresholdDb) throws Exception { try (DataInputStream dis = new DataInputStream(Files.newInputStream(wavFile.toPath()))) { dis.skipBytes(44); // 跳过WAV头 int sampleRate = 16000; // 需从WAV头读取实际值 List isSpeech = new ArrayList<>(); List buffer = new ArrayList<>(); // 分帧检测静音(假设帧长度100ms) int frameSize = sampleRate / 10; while (dis.available() >= 2) { short sample = dis.readShort(); buffer.add(sample); if (buffer.size() >= frameSize) { double rms = calculateFrameRms(buffer); double db = 20 * Math.log10(rms / 32767.0); isSpeech.add(db > silenceThresholdDb); buffer.clear(); } } // 统计语音段数量 int speechSegments = 0; boolean lastState = false; for (boolean state : isSpeech) { if (state && !lastState) speechSegments++; lastState = state; } // 计算语速(假设每个语音段对应一个词) double totalTime = (double) isSpeech.size() * 0.1; // 总时间(秒) return (speechSegments / totalTime) * 60; // 词/分钟 } } // 主计算方法 public static double calculateSpeechRate(int wordCount, File wavFile) throws Exception { long durationMs = getAudioDuration(wavFile); if (durationMs == 0) throw new IllegalArgumentException("音频时长为零"); return (wordCount * 60000.0) / durationMs; // 60,000 = 1000ms * 60s } // 安全的头解析方法 private static long parseDurationFromHeader(ByteBuffer buffer) throws IOException { try { buffer.order(ByteOrder.LITTLE_ENDIAN); // 验证RIFF头 byte[] riff = new byte[4]; buffer.get(riff); if (!"RIFF".equals(new String(riff, StandardCharsets.US_ASCII))) { throw new IOException("Invalid WAV file header"); } // 跳过文件总大小 buffer.position(buffer.position() + 4); // 验证WAVE标识 byte[] wave = new byte[4]; buffer.get(wave); if (!"WAVE".equals(new String(wave, StandardCharsets.US_ASCII))) { throw new IOException("Not a WAVE file"); } // 查找fmt和data块 long sampleRate = -1; long dataSize = -1; while (buffer.remaining() > 8) { byte[] chunkIdBytes = new byte[4]; buffer.get(chunkIdBytes); String chunkId = new String(chunkIdBytes, StandardCharsets.US_ASCII); int chunkSize = buffer.getInt(); if (chunkId.equals("fmt ")) { // 解析采样率 buffer.position(buffer.position() + 8); // 跳过格式和声道 sampleRate = buffer.getInt() & 0xFFFFFFFFL; buffer.position(buffer.position() + 6); // 跳过其他参数 } else if (chunkId.equals("data")) { dataSize = chunkSize & 0xFFFFFFFFL; break; } else { // 跳过未知块(处理补位) int padding = chunkSize % 2; buffer.position(buffer.position() + chunkSize + padding); } } if (sampleRate == -1 || dataSize == -1) { throw new IOException("Missing required chunks"); } return (dataSize * 1000L) / (sampleRate * 2); // 假设16位单声道 } finally { // 显式释放内存映射(Java 9+) if (buffer instanceof sun.nio.ch.DirectBuffer) { ((sun.nio.ch.DirectBuffer) buffer).cleaner().clean(); } } } // 语速计算核心 private static double calculateRate(int wordCount, long durationMs) { if (durationMs == 0) throw new IllegalArgumentException("Audio duration cannot be zero"); return (wordCount * 60000.0) / durationMs; } // 获取音频时长(毫秒) private static long getAudioDuration(File wavFile) throws Exception { try (RandomAccessFile raf = new RandomAccessFile(wavFile, "r")) { // 验证RIFF头 if (!readString(raf).equals("RIFF")) { throw new IllegalArgumentException("无效的WAV文件"); } // 2. 跳过RIFF总大小(不需要使用) raf.skipBytes(4); // 3. 验证格式类型必须是"WAVE" String format = readString(raf); if (!format.equals("WAVE")) { throw new IllegalArgumentException("非WAVE格式文件"); } // 查找fmt和data块 long sampleRate = -1; long dataSize = -1; while (true) { String chunkId = readString(raf); long chunkSize = readLittleEndianInt(raf) & 0xFFFFFFFFL; long chunkStart = raf.getFilePointer(); if (chunkId.equals("fmt ")) { // 读取采样率 raf.skipBytes(4); // 跳过音频格式和声道数 sampleRate = readLittleEndianInt(raf) & 0xFFFFFFFFL; raf.skipBytes(6); // 跳过其他fmt参数 } else if (chunkId.equals("data")) { dataSize = chunkSize; break; } if (chunkStart + chunkSize >= raf.length()) break; raf.seek(chunkStart + chunkSize); } if (sampleRate == -1 || dataSize == -1) { throw new IllegalArgumentException("缺少关键数据块"); } // 计算时长:数据大小(字节) / (声道数 * 位深度/8) / 采样率 // 简化计算(假设声道数=1,位深度=16) // 实际应根据fmt块参数动态计算 long totalSamples = dataSize / 2; // 16位=2字节/样本 return (totalSamples * 1000L) / sampleRate; } } private static double calculateFrameRms(List buffer) { double sum = 0; for (short sample : buffer) { sum += sample * sample; } return Math.sqrt(sum / buffer.size()); } //获取分段语音开始毫秒数和结束毫秒数 private static int[] GetMillisecond(String input) { // 去除外层的中括号 String content = input.substring(2, input.length() - 2); // 分割各个子区间 String[] intervals = content.split("],\\["); if (intervals.length == 0) { System.out.println("无有效区间"); return null; } // 提取第一个区间的第一个值 String[] firstParts = intervals[0].split(","); int firstValue = Integer.parseInt(firstParts[0]); // 提取最后一个区间的最后一个值 String[] lastParts = intervals[intervals.length - 1].split(","); int lastValue = Integer.parseInt(lastParts[1]); return new int[]{firstValue,lastValue}; } //分割音频 private static void splitWav(File inputFile, File outputFile, long startMs, long endMs) throws Exception { try (RandomAccessFile raf = new RandomAccessFile(inputFile, "r")) { // 读取RIFF头 if (!readString(raf).equals("RIFF")) { throw new IllegalArgumentException("不是有效的RIFF文件"); } long riffSize = readLittleEndianInt(raf) & 0xFFFFFFFFL; String format = readString(raf); if (!format.equals("WAVE")) { throw new IllegalArgumentException("不是WAVE文件"); } // 查找fmt和data块 int numChannels = -1; long sampleRate = -1; int bitsPerSample = -1; long dataStart = -1; long dataSize = -1; while (true) { String chunkId; try { chunkId = readString(raf); } catch (EOFException e) { break; // 所有块处理完毕 } long chunkSize = readLittleEndianInt(raf) & 0xFFFFFFFFL; long chunkDataStart = raf.getFilePointer(); if (chunkId.equals("fmt ")) { // 解析fmt块 int audioFormat = readLittleEndianShort(raf); numChannels = readLittleEndianShort(raf); sampleRate = readLittleEndianInt(raf) & 0xFFFFFFFFL; long byteRate = readLittleEndianInt(raf); int blockAlign = readLittleEndianShort(raf); bitsPerSample = readLittleEndianShort(raf); } else if (chunkId.equals("data")) { dataStart = chunkDataStart; dataSize = chunkSize; break; // 找到data块后退出循环 } // 跳到下一个块 raf.seek(chunkDataStart + chunkSize); } if (numChannels == -1 || dataStart == -1) { throw new IllegalArgumentException("缺少fmt或data块"); } // 计算参数 int bytesPerFrame = numChannels * (bitsPerSample / 8); long totalSamples = dataSize / bytesPerFrame; long durationMs = (totalSamples * 1000L) / sampleRate; // 调整时间范围 if (startMs < 0) startMs = 0; if (endMs > durationMs) endMs = durationMs; if (startMs >= endMs) { throw new IllegalArgumentException("起始时间必须小于结束时间"); } // 计算样本位置 long startSample = (startMs * sampleRate) / 1000L; long endSample = (endMs * sampleRate) / 1000L; long startByte = startSample * bytesPerFrame; long endByte = endSample * bytesPerFrame; long newDataSize = endByte - startByte; // 转换为Path对象并获取父目录 Path outputPath = outputFile.toPath(); Path parentDir = outputPath.getParent(); // 如果父目录存在则创建所有必要目录 if (parentDir != null) { Files.createDirectories(parentDir); } // 写入新文件 try (DataOutputStream dos = new DataOutputStream(Files.newOutputStream(outputFile.toPath()))) { // RIFF头 writeString(dos, "RIFF"); writeLittleEndianInt(dos, (int) (36 + newDataSize)); // RIFF大小 writeString(dos, "WAVE"); // fmt块 writeString(dos, "fmt "); writeLittleEndianInt(dos, 16); // fmt块大小 writeLittleEndianShort(dos, (short) 1); // PCM格式 writeLittleEndianShort(dos, (short) numChannels); writeLittleEndianInt(dos, (int) sampleRate); writeLittleEndianInt(dos, (int) (sampleRate * numChannels * bitsPerSample / 8)); writeLittleEndianShort(dos, (short) (numChannels * bitsPerSample / 8)); writeLittleEndianShort(dos, (short) bitsPerSample); // data块头 writeString(dos, "data"); writeLittleEndianInt(dos, (int) newDataSize); // 复制音频数据 raf.seek(dataStart + startByte); byte[] buffer = new byte[4096]; long remaining = newDataSize; while (remaining > 0) { int read = raf.read(buffer, 0, (int) Math.min(buffer.length, remaining)); if (read == -1) break; dos.write(buffer, 0, read); remaining -= read; } } } } // 辅助方法:读取小端整数 private static int readLittleEndianInt(DataInput in) throws IOException { int b1 = in.readUnsignedByte(); int b2 = in.readUnsignedByte(); int b3 = in.readUnsignedByte(); int b4 = in.readUnsignedByte(); return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1; } private static short readLittleEndianShort(DataInput in) throws IOException { int b1 = in.readUnsignedByte(); int b2 = in.readUnsignedByte(); return (short) ((b2 << 8) | b1); } private static String readString(DataInput in) throws IOException { byte[] bytes = new byte[4]; in.readFully(bytes); return new String(bytes, StandardCharsets.US_ASCII); } private static void writeLittleEndianInt(DataOutput out, int value) throws IOException { out.write(value & 0xFF); out.write((value >> 8) & 0xFF); out.write((value >> 16) & 0xFF); out.write((value >> 24) & 0xFF); } private static void writeLittleEndianShort(DataOutput out, short value) throws IOException { out.write(value & 0xFF); out.write((value >> 8) & 0xFF); } private static void writeString(DataOutput out, String s) throws IOException { out.write(s.getBytes(StandardCharsets.US_ASCII)); } }