| | |
| | | |
| | | <dependencies> |
| | | |
| | | <dependency> |
| | | <groupId>com.iplatform</groupId> |
| | | <artifactId>recommend-text</artifactId> |
| | | </dependency> |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>com.iplatform</groupId>--> |
| | | <!-- <artifactId>recommend-text</artifactId>--> |
| | | <!-- </dependency>--> |
| | | |
| | | <dependency> |
| | | <groupId>com.iplatform</groupId> |
| | |
| | | name: train_recommend |
| | | datasource: |
| | | # 是否显示dao中打印的SQL语句 |
| | | show-sql: true |
| | | show-sql: false |
| | | username: root |
| | | # password: Bjjmy_63661766 |
| | | password: Bjjmy_2020 |
| | |
| | | springframework: error |
| | | com: |
| | | walker: info |
| | | iplatform: debug |
| | | |
| | | # file: # logging.file.path 和 logging.file.name,只会有一个生效,配了path不要配name,配了name不要配path,只配path时name默认为spring.log,想路径和文件名同时生效可配置logging.file.name=d:/logs/mylog.log |
| | | # name: ${spring.application.name}.log #日志文件名 |
| | | # path: logs #日志存储路径 |
| | |
| | | |
| | | video: |
| | | # 视频采集存储根路径 |
| | | data-folder: D:/dev_tools/ai/ |
| | | # data-folder: D:/dev_tools/ai/ |
| | | data-folder: /opt/ai/video/ |
| | | |
| | | # 视频相似度AI服务URL |
| | | ai-service: http://121.36.40.27:12345 |
| | | |
| | | # 推荐相似视频(算法)前多少个 |
| | | top-n: 40 |
New file |
| | |
| | | |
| | | ALTER TABLE rc_video_t1 |
| | | ADD INDEX inx_src_img (src_img) USING BTREE ; |
| | | |
| | | ALTER TABLE rc_video_t1 |
| | | ADD INDEX inx_src_vid (src_video_id) USING BTREE ; |
| | | |
| | | ALTER TABLE rc_video_t2 |
| | | ADD INDEX inx_t2_src_vid (src_video_id) USING BTREE ; |
| | |
| | | public class Constants { |
| | | |
| | | public static final String IMAGE_SUFFIX = ".jpg"; |
| | | public static final String VIDEO_SUFFIX = ".mp4"; |
| | | |
| | | public static final String AI_SERVICE_VIDEO_SEARCH = "/ai/video/search_img"; |
| | | public static final String AI_SERVICE_VIDEO_LOAD = "/ai/video/gather"; |
| | | } |
| | |
| | | package com.iplatform.recvideo; |
| | | |
| | | import com.iplatform.model.po.Rc_video_t1; |
| | | import com.iplatform.recvideo.util.PythonInvokeUtils; |
| | | import com.iplatform.recvideo.util.TestUtils; |
| | | import com.iplatform.recvideo.util.VideoFileUtils; |
| | | import com.walker.infrastructure.utils.StringUtils; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | |
| | | import java.io.File; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | /** |
| | |
| | | |
| | | private String batchId = null; |
| | | |
| | | // 当前批次要处理的原始视频集合 |
| | | private List<VideoFolderInfo> videoFolderInfoList = null; |
| | | private List<String> videoIdList = new ArrayList<>(); |
| | | |
| | | // 记录当前执行到(该批次)哪个视频文件对应的第几个图片 |
| | | private int currentVideoFolderIndex = -1; |
| | |
| | | // private VideoFolderInfo currentVideoFolderInfo = null; |
| | | // private ImageInfo currentImageInfo = null; |
| | | |
| | | // |
| | | // 是否已完成视频加载调用,完成后才能检索相似视频 |
| | | private boolean pythonLoadVideoDone = false; |
| | | |
| | | private int topN = 40; |
| | | |
| | | // 如果测试模式,数据是假的! |
| | | private boolean testMode = false; |
| | | |
| | | public int getTopN() { |
| | | return topN; |
| | | } |
| | | |
| | | /** |
| | | * 初始化对象调用一次 |
| | | * @param videoDataFolder |
| | | * @param batchId |
| | | */ |
| | | public void startup(String videoDataFolder, String batchId){ |
| | | public void startup(String videoDataFolder, String batchId, int topN, boolean testMode){ |
| | | if(StringUtils.isEmpty(videoDataFolder)){ |
| | | throw new IllegalArgumentException("视频文件夹根目录必须设置!"); |
| | | } |
| | |
| | | } |
| | | this.videoDataFolder = videoDataFolder; |
| | | this.batchId = batchId; |
| | | this.topN = topN; |
| | | this.testMode = testMode; |
| | | // this.videoFolderInfoList = VideoFileUtils.getBatchVideoFolderInfo(this.videoDataFolder, batchId); |
| | | } |
| | | |
| | | public void destroy(){ |
| | | if(this.videoFolderInfoList != null){ |
| | | this.videoFolderInfoList.clear(); |
| | | } |
| | | if(this.videoIdList != null){ |
| | | this.videoIdList.clear(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 在每次调度时钟周期执行一次。例如: 10秒一次。<p></p> |
| | | * 注意:该方法英确保每次调用不会重复数据。 |
| | | */ |
| | | public void execute(){ |
| | | public int execute() throws Exception{ |
| | | if(!this.pythonLoadVideoDone){ |
| | | logger.debug("当前 pythonLoadVideoDone = false, 需要查询数据库是否已加载视频"); |
| | | this.pythonLoadVideoDone = this.pythonLoadVideoDone(this.batchId, videoDataFolder + File.separator + batchId); |
| | | this.pythonLoadVideoDone = this.pythonLoadVideoDone(this.batchId, VideoFileUtils.combineBatchPath(videoDataFolder, batchId)); |
| | | } |
| | | |
| | | // 1: 如果视频还未加载,则先加载视频 |
| | |
| | | if(StringUtils.isNotEmpty(error)){ |
| | | // 终止调用,等待下次调度继续尝试执行 |
| | | logger.error("python调用加载视频返回错误:" + error); |
| | | return; |
| | | return -1; |
| | | } |
| | | this.pythonLoadVideoDone = true; |
| | | |
| | | } catch (Exception ex){ |
| | | logger.error("python调用加载视频异常:" + this.batchId, ex); |
| | | return; |
| | | return -1; |
| | | } |
| | | } |
| | | |
| | | // 2: 加载完视频,需要查询每个图片相似度结果,并存储到数据库 |
| | | // 这里注意,程序必须和AI服务器部署在一起,方便检索视频分析文件夹(本地) |
| | | if(this.videoFolderInfoList == null){ |
| | | if(testMode){ |
| | | this.videoFolderInfoList = TestUtils.getBatchVideoFolderInfo(this.videoDataFolder, batchId); |
| | | } else { |
| | | this.videoFolderInfoList = VideoFileUtils.getBatchVideoFolderInfo(this.videoDataFolder, batchId); |
| | | } |
| | | } |
| | | if(StringUtils.isEmptyList(this.videoFolderInfoList)){ |
| | | logger.warn("视频分析文件夹内容为空,无法继续查询相似度结果! videoFolderInfoList = null"); |
| | | return; |
| | | return -1; |
| | | } |
| | | for(VideoFolderInfo v : this.videoFolderInfoList){ |
| | | this.videoIdList.add(v.getVideoId()); |
| | | } |
| | | |
| | | if(this.isSearchWriteDone()){ |
| | | logger.info("已经完成批次相似结果写入数据库,不再往下处理。batch = " + this.batchId); |
| | | return; |
| | | logger.info("已经完成批次相似结果写入数据库,处理最后一步:写入用户推荐表数据。batch = " + this.batchId); |
| | | try { |
| | | this.writeRcVideoUser(this.batchId, this.videoIdList); |
| | | // 返回1表示整个流程执行完毕。 |
| | | return 1; |
| | | } catch (Exception ex){ |
| | | throw new Exception("writeRcVideoUser():" + ex.getMessage(), ex); |
| | | } |
| | | } |
| | | |
| | | // 开始检索相似度 |
| | |
| | | this.currentVideoFolderIndex ++; |
| | | } |
| | | |
| | | try { |
| | | this.processOneSearchAndWrite(); |
| | | return 0; |
| | | } catch (Exception e) { |
| | | throw new Exception("processOneSearchAndWrite(): " + e.getMessage(), e); |
| | | } |
| | | } |
| | | |
| | | private void processOneSearchAndWrite() throws Exception{ |
| | |
| | | } |
| | | ImageInfo imageInfo = currentVideoFolderInfo.getImageInfoList().get(this.currentImageIndex); |
| | | |
| | | this.acquirePythonSearchSimilarOnce(imageInfo.getImagePath(), "30"); |
| | | List<Rc_video_t1> videoT1_list = this.acquirePythonSearchSimilarOnce(imageInfo.getVideoId() |
| | | , imageInfo.getImagePath(), String.valueOf(this.topN)); |
| | | if(!StringUtils.isEmptyList(videoT1_list)){ |
| | | try { |
| | | this.writeRcVideoT1(videoT1_list, PythonInvokeUtils.getFileNameWithoutSuffix(imageInfo.getImagePath(), Constants.IMAGE_SUFFIX)); |
| | | } catch (Exception ex){ |
| | | throw new Exception("writeRcVideoT1()执行错误:" + ex.getMessage(), ex); |
| | | } |
| | | } |
| | | |
| | | // 每个视频的最后一张图片 |
| | | if((this.currentImageIndex + 1) >= currentVideoFolderInfo.getImageInfoSize()){ |
| | | try { |
| | | this.writeRcVideoT2(currentVideoFolderInfo); |
| | | } catch (Exception ex){ |
| | | throw new Exception("writeRcVideoT2()执行错误:" + ex.getMessage(), ex); |
| | | } |
| | | if((this.currentVideoFolderIndex + 1) < this.videoFolderInfoList.size()){ |
| | | logger.debug("一个视频图像集合检索处理完毕,切换到下一个,currentImageIndex = " + this.currentImageIndex); |
| | | this.currentVideoFolderIndex ++; |
| | | this.currentImageIndex = -1; |
| | | } else { |
| | | // |
| | | // 让最后一个图像集合索引值超过界限,表示最后一个视频已处理完毕。 |
| | | this.currentImageIndex ++; |
| | | logger.debug("所有视频包含的所有图像处理完毕,currentVideoFolderIndex = " + this.currentVideoFolderIndex); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | this.currentImageIndex ++; |
| | | } |
| | | |
| | |
| | | * 请求AI服务,检索给定图片的相似度结果集合。 |
| | | * @return |
| | | */ |
| | | protected abstract List<Rc_video_t1> acquirePythonSearchSimilarOnce(String imagePath, String topN); |
| | | protected abstract List<Rc_video_t1> acquirePythonSearchSimilarOnce(String videoId |
| | | , String imagePath, String topN) throws Exception; |
| | | |
| | | /** |
| | | * 第一个临时表'rc_video_t1',每张图像包含多个相似视频记录。 |
| | | * @param list |
| | | * @param srcImageId 原始视频ID |
| | | */ |
| | | protected abstract void writeRcVideoT1(List<Rc_video_t1> list, String srcImageId); |
| | | |
| | | /** |
| | | * 分析表'rc_video_t1',并把给定视频相似记录写入第二个临时表'rc_video_t2' |
| | | * @param videoFolderInfo |
| | | */ |
| | | protected abstract void writeRcVideoT2(VideoFolderInfo videoFolderInfo); |
| | | |
| | | /** |
| | | * 分析给定批次所有视频用户推荐的视频信息,并写入表:'rc_video_user' |
| | | * @param batchId |
| | | * @param recVideoIdList 推荐视频id集合 |
| | | */ |
| | | protected abstract void writeRcVideoUser(String batchId, List<String> recVideoIdList); |
| | | } |
New file |
| | |
| | | package com.iplatform.recvideo; |
| | | |
| | | import java.text.DecimalFormat; |
| | | |
| | | /** |
| | | * 相似视频对象定义。 |
| | | * @author 时克英 |
| | | * @date 2022-09-24 |
| | | */ |
| | | public class SimilarVideoInfo implements Comparable<SimilarVideoInfo> { |
| | | |
| | | private String id; |
| | | |
| | | private double distance = 0; |
| | | |
| | | // 这个相似视频,对应的源视频图像张数 |
| | | private int count = 0; |
| | | |
| | | private double score = 0; |
| | | |
| | | private DecimalFormat df = new DecimalFormat("#.00"); |
| | | |
| | | public String getId() { |
| | | return id; |
| | | } |
| | | |
| | | public double getDistance() { |
| | | return distance; |
| | | } |
| | | |
| | | public SimilarVideoInfo(String id, double distance){ |
| | | this.id = id; |
| | | this.distance = distance; |
| | | } |
| | | |
| | | /** |
| | | * 返回相似视频的排名分值,目前简单根据数量百分比计算。 |
| | | * @return |
| | | */ |
| | | public double getScore(){ |
| | | if(this.score > 0){ |
| | | return this.score; |
| | | } |
| | | if(count == 0){ |
| | | return 0; |
| | | } |
| | | double s = (this.count)/100.0; |
| | | String score = df.format(s); |
| | | this.score = Double.parseDouble(score); |
| | | return this.score; |
| | | } |
| | | |
| | | public void increase(){ |
| | | this.count ++; |
| | | } |
| | | |
| | | public void setDistance(double distance){ |
| | | this.distance = distance; |
| | | } |
| | | |
| | | @Override |
| | | public String toString(){ |
| | | return new StringBuilder("[id=").append(id) |
| | | .append(", dis=").append(this.distance) |
| | | .append("]").toString(); |
| | | } |
| | | |
| | | @Override |
| | | public int hashCode(){ |
| | | return this.id.hashCode(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean equals(Object obj){ |
| | | if(obj == null){ |
| | | return false; |
| | | } |
| | | if(obj instanceof SimilarVideoInfo){ |
| | | SimilarVideoInfo e = (SimilarVideoInfo)obj; |
| | | if(e.id.equals(this.id)){ |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | @Override |
| | | public int compareTo(SimilarVideoInfo o) { |
| | | return o.count - this.count; |
| | | } |
| | | } |
New file |
| | |
| | | package com.iplatform.recvideo; |
| | | |
| | | import com.iplatform.model.po.Rc_video_t1; |
| | | import com.walker.infrastructure.utils.StringUtils; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * 把一个原视频对应的多个相似视频集合,根据图像相似度数量归并排序。<p></p> |
| | | * 最终返回排序后的相似视频列表,用于写入到数据库中。 |
| | | * @author 时克英 |
| | | * @date 2022-09-24 |
| | | */ |
| | | public class SimilarVideoOrder { |
| | | |
| | | // 参与运算的最大距离,超过该距离说明相似度很低,不再参与运算。 |
| | | private double maxDistance = 1.0; |
| | | |
| | | // 计算多个相似视频id,综合距离平均值, key = 相似视频ID |
| | | private Map<String, List<Double>> simiVideoDistanceMap = new HashMap<>(); |
| | | // 最终排序用的相似视频集合结果, key = 相似视频ID |
| | | private Map<String, SimilarVideoInfo> orderVideoList = new HashMap<>(); |
| | | |
| | | public SimilarVideoOrder(List<Rc_video_t1> list){ |
| | | for(Rc_video_t1 e : list){ |
| | | this.addSimiVideoDistance(e.getSim_video_id(), e.getDistance()); |
| | | this.addOrderVideo(e.getSim_video_id()); |
| | | } |
| | | } |
| | | |
| | | public List<SimilarVideoInfo> calculateOrder(){ |
| | | SimilarVideoInfo similarVideoInfo = null; |
| | | double averageDistance = 0; |
| | | for(Map.Entry<String, List<Double>> entry : this.simiVideoDistanceMap.entrySet()){ |
| | | similarVideoInfo = this.orderVideoList.get(entry.getKey()); |
| | | if(similarVideoInfo == null){ |
| | | throw new IllegalArgumentException("similarVideoInfo不存在,这不科学:(" + entry.getKey()); |
| | | } |
| | | averageDistance = this.getAverageDistance(entry.getValue(), entry.getKey()); |
| | | similarVideoInfo.setDistance(averageDistance); |
| | | } |
| | | |
| | | // |
| | | List<SimilarVideoInfo> resultList = new ArrayList<>(32); |
| | | |
| | | for(SimilarVideoInfo e : this.orderVideoList.values()){ |
| | | e.getScore(); |
| | | resultList.add(e); |
| | | } |
| | | Collections.sort(resultList); |
| | | return resultList; |
| | | } |
| | | |
| | | private double getAverageDistance(List<Double> list, String simiVideoId){ |
| | | if(StringUtils.isEmptyList(list)){ |
| | | return 0; |
| | | } |
| | | double total = 0; |
| | | for(double d : list){ |
| | | if(d > this.maxDistance){ |
| | | // logger.debug("distance过大,不参与排序运算:" + d + ", simiVideoId = " + simiVideoId); |
| | | continue; |
| | | } |
| | | total += d; |
| | | } |
| | | return total/list.size(); |
| | | } |
| | | |
| | | private void addOrderVideo(String simiVideoId){ |
| | | SimilarVideoInfo v = this.orderVideoList.get(simiVideoId); |
| | | if(v == null){ |
| | | v = new SimilarVideoInfo(simiVideoId, 0); |
| | | this.orderVideoList.put(simiVideoId, v); |
| | | } |
| | | v.increase(); |
| | | } |
| | | |
| | | private void addSimiVideoDistance(String videoId, double distance){ |
| | | List<Double> v = this.simiVideoDistanceMap.get(videoId); |
| | | if(v == null){ |
| | | v = new ArrayList<>(16); |
| | | this.simiVideoDistanceMap.put(videoId, v); |
| | | } |
| | | v.add(distance); |
| | | } |
| | | |
| | | protected final transient Logger logger = LoggerFactory.getLogger(this.getClass()); |
| | | } |
| | |
| | | this.videoId = videoId; |
| | | } |
| | | |
| | | /** |
| | | * 添加图像信息。 |
| | | * @param imagePath 图片绝对路径 |
| | | * @param imageName 图片的名字(如: landscape_01_1.jpg) |
| | | */ |
| | | public void addImageInfo(String imagePath, String imageName){ |
| | | ImageInfo imageInfo = new ImageInfo(); |
| | | imageInfo.setImagePath(imagePath); |
New file |
| | |
| | | package com.iplatform.recvideo.api; |
| | | |
| | | import com.iplatform.recvideo.config.VideoSimilarProperties; |
| | | import com.iplatform.recvideo.service.VideoExecutorServiceImpl; |
| | | import com.iplatform.recvideo.support.DefaultSimilarExecutor; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | import org.springframework.web.client.RestTemplate; |
| | | |
| | | @RestController |
| | | @RequestMapping("/debug/video") |
| | | public class DemoDebug { |
| | | |
| | | private RestTemplate restTemplate; |
| | | private VideoSimilarProperties videoSimilarProperties; |
| | | private VideoExecutorServiceImpl videoExecutorService; |
| | | |
| | | private DefaultSimilarExecutor similarExecutor = null; |
| | | |
| | | @Autowired |
| | | public DemoDebug(RestTemplate restTemplate |
| | | , VideoSimilarProperties videoSimilarProperties |
| | | , VideoExecutorServiceImpl videoExecutorService){ |
| | | this.restTemplate = restTemplate; |
| | | this.videoSimilarProperties = videoSimilarProperties; |
| | | this.videoExecutorService = videoExecutorService; |
| | | } |
| | | |
| | | /** |
| | | * 该测试用于模拟调度任务,每次调用一次执行器方法,批量处理一张图片数据。<p></p> |
| | | * 最终完成一批多个视频的相似度检索并写入数据库中。 |
| | | * @return |
| | | * @date 2022-09-24 |
| | | */ |
| | | @RequestMapping("/test1") |
| | | public String testSimilarExecutor(){ |
| | | // String |
| | | if(similarExecutor == null){ |
| | | this.similarExecutor = new DefaultSimilarExecutor(); |
| | | this.similarExecutor.setVideoExecutorService(this.videoExecutorService); |
| | | this.similarExecutor.setRestTemplate(this.restTemplate); |
| | | this.similarExecutor.setRemoteUrl(this.videoSimilarProperties.getAiService()); |
| | | this.similarExecutor.startup(this.videoSimilarProperties.getDataFolder() |
| | | , "20220921", this.videoSimilarProperties.getTopN(), true); // 注意:这里测试模式 |
| | | } |
| | | try { |
| | | this.similarExecutor.execute(); |
| | | return "成功执行一次"; |
| | | } catch (Exception e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | } |
| | | |
| | | } |
| | |
| | | |
| | | private String dataFolder; |
| | | |
| | | private int topN; |
| | | |
| | | private String aiService; |
| | | |
| | | public String getAiService() { |
| | | return aiService; |
| | | } |
| | | |
| | | public void setAiService(String aiService) { |
| | | this.aiService = aiService; |
| | | } |
| | | |
| | | |
| | | public int getTopN() { |
| | | return topN; |
| | | } |
| | | |
| | | public void setTopN(int topN) { |
| | | this.topN = topN; |
| | | } |
| | | |
| | | |
| | | public String getDataFolder() { |
| | | return dataFolder; |
| | | } |
| | |
| | | package com.iplatform.recvideo.service; |
| | | |
| | | import com.iplatform.model.po.Rc_video_t1; |
| | | import com.iplatform.model.po.Rc_video_t2; |
| | | import com.walker.jdbc.service.BaseServiceImpl; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @Service |
| | | public class VideoExecutorServiceImpl extends BaseServiceImpl { |
| | | |
| | | private static final String SQL_CHECK_VIDEO_STATUS = "select * from milvus_video_status where id=?"; |
| | | private static final String SQL_CLEAR_VIDEO_T1 = "delete from rc_video_t1 where src_img=?"; |
| | | private static final String SQL_CLEAR_VIDEO_T2 = "delete from rc_video_t2 where src_video_id=?"; |
| | | |
| | | /** |
| | | * 写入视频相似度第一级临时数据,每个图像包含多个相似视频记录。 |
| | | * @param list |
| | | * @param srcImageId 原始图像ID |
| | | */ |
| | | public void execBatchInsertVideoT1(List<Rc_video_t1> list, String srcImageId){ |
| | | // 写入新数据之前,先清除老视频数据,后续分析避免数据重复(每个视频) |
| | | this.execute(SQL_CLEAR_VIDEO_T1, new Object[]{srcImageId}); |
| | | this.insert(list); |
| | | } |
| | | |
| | | /** |
| | | * 写入视频相似度第二临时数据,每个原始视频对应多个相似视频,已排序存储。 |
| | | * @param list |
| | | * @param srcVideoId |
| | | */ |
| | | public void execBatchInsertVideoT2(List<Rc_video_t2> list, String srcVideoId){ |
| | | this.execute(SQL_CLEAR_VIDEO_T2, new Object[]{srcVideoId}); |
| | | this.insert(list); |
| | | } |
| | | |
| | | /** |
| | | * 根据原始视频ID返回相似记录集合。 |
| | | * @param srcVideoId |
| | | * @return |
| | | */ |
| | | public List<Rc_video_t1> queryVideoT_1List(String srcVideoId){ |
| | | return this.select(new Rc_video_t1(), "where src_video_id=?", new Object[]{srcVideoId}); |
| | | } |
| | | |
| | | /** |
| | | * 查询视频加载状态表,是否已经完成加载(第一步),如果记录不存在说明还没有开始加载。 |
| | | * @param videoStatusId |
| | | * @return |
| | | */ |
| | | public boolean queryLoadVideoDone(String videoStatusId){ |
| | | Map<String, Object> map = this.get(SQL_CHECK_VIDEO_STATUS, new Object[]{videoStatusId}); |
| | | if(map == null){ |
| | | return false; |
| | | } |
| | | int status = Integer.parseInt(map.get("status").toString()); |
| | | if(status == 1){ |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | } |
New file |
| | |
| | | package com.iplatform.recvideo.support; |
| | | |
| | | import com.iplatform.model.po.Rc_video_t1; |
| | | import com.iplatform.model.po.Rc_video_t2; |
| | | import com.iplatform.recvideo.Constants; |
| | | import com.iplatform.recvideo.SimilarExecutor; |
| | | import com.iplatform.recvideo.SimilarVideoInfo; |
| | | import com.iplatform.recvideo.SimilarVideoOrder; |
| | | import com.iplatform.recvideo.VideoFolderInfo; |
| | | import com.iplatform.recvideo.service.VideoExecutorServiceImpl; |
| | | import com.iplatform.recvideo.util.PythonInvokeUtils; |
| | | import com.walker.infrastructure.utils.NumberGenerator; |
| | | import com.walker.infrastructure.utils.StringUtils; |
| | | import org.springframework.web.client.RestTemplate; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Iterator; |
| | | import java.util.List; |
| | | |
| | | public class DefaultSimilarExecutor extends SimilarExecutor { |
| | | |
| | | private RestTemplate restTemplate; |
| | | |
| | | // python AI 服务地址前缀 |
| | | private String remoteUrl; |
| | | |
| | | private VideoExecutorServiceImpl videoExecutorService = null; |
| | | |
| | | public void setVideoExecutorService(VideoExecutorServiceImpl videoExecutorService) { |
| | | this.videoExecutorService = videoExecutorService; |
| | | } |
| | | |
| | | public void setRemoteUrl(String remoteUrl) { |
| | | this.remoteUrl = remoteUrl; |
| | | } |
| | | |
| | | public void setRestTemplate(RestTemplate restTemplate) { |
| | | this.restTemplate = restTemplate; |
| | | } |
| | | |
| | | @Override |
| | | protected boolean pythonLoadVideoDone(String batchId, String batchFolder) { |
| | | return this.videoExecutorService.queryLoadVideoDone(batchFolder); |
| | | } |
| | | |
| | | @Override |
| | | protected String requestStartPythonLoadVideo(String batchId) throws Exception { |
| | | logger.info("开始请求python服务:" + Constants.AI_SERVICE_VIDEO_LOAD); |
| | | return null; |
| | | } |
| | | |
| | | @Override |
| | | protected List<Rc_video_t1> acquirePythonSearchSimilarOnce(String srcVideoId, String imagePath, String topN) throws Exception { |
| | | String url = this.remoteUrl + Constants.AI_SERVICE_VIDEO_SEARCH; |
| | | return PythonInvokeUtils.acquireImageSearchResult(srcVideoId, imagePath, topN, url, this.restTemplate); |
| | | } |
| | | |
| | | @Override |
| | | protected void writeRcVideoT1(List<Rc_video_t1> list, String srcImageId) { |
| | | // 删除 distance == 0 的,这个是自身视频信息(推荐的要排除自己) |
| | | Rc_video_t1 e = null; |
| | | for(Iterator<Rc_video_t1> it = list.iterator(); it.hasNext();){ |
| | | e = it.next(); |
| | | if(e.getDistance().doubleValue() == 0){ |
| | | it.remove(); |
| | | } |
| | | } |
| | | this.videoExecutorService.execBatchInsertVideoT1(list, srcImageId); |
| | | logger.debug("成功写入集合'Rc_video_t1':" + list.size()); |
| | | } |
| | | |
| | | @Override |
| | | protected void writeRcVideoT2(VideoFolderInfo videoFolderInfo) { |
| | | logger.debug("正在写入'Rc_video_t2',原始视频:" + videoFolderInfo.getVideoId()); |
| | | List<Rc_video_t1> list = this.videoExecutorService.queryVideoT_1List(videoFolderInfo.getVideoId()); |
| | | if(StringUtils.isEmptyList(list)){ |
| | | logger.warn("writeRcVideoT2: Rc_video_t1检索结果为空,无法继续写入'Rc_video_t2'"); |
| | | return; |
| | | } |
| | | SimilarVideoOrder similarVideoOrder = new SimilarVideoOrder(list); |
| | | List<SimilarVideoInfo> similarVideoInfoList = similarVideoOrder.calculateOrder(); |
| | | List<Rc_video_t2> video2List = this.toRcVideoT2List(videoFolderInfo.getVideoId(), similarVideoInfoList); |
| | | if(video2List == null){ |
| | | logger.warn("writeRcVideoT2(): similarVideoInfoList为空,不能执行, srcVideoId = " + videoFolderInfo.getVideoId()); |
| | | return; |
| | | } |
| | | this.videoExecutorService.execBatchInsertVideoT2(video2List, videoFolderInfo.getVideoId()); |
| | | } |
| | | |
| | | @Override |
| | | protected void writeRcVideoUser(String batchId, List<String> recVideoIdList) { |
| | | logger.info("正在写入一次用户推荐视频数据, batchId = " + batchId); |
| | | } |
| | | |
| | | private List<Rc_video_t2> toRcVideoT2List(String srcVideoId, List<SimilarVideoInfo> similarVideoInfoList){ |
| | | if(StringUtils.isEmptyList(similarVideoInfoList)){ |
| | | return null; |
| | | } |
| | | List<Rc_video_t2> resultList = new ArrayList<>(similarVideoInfoList.size()); |
| | | Rc_video_t2 rc_video_t2 = null; |
| | | for(SimilarVideoInfo e : similarVideoInfoList){ |
| | | rc_video_t2 = new Rc_video_t2(NumberGenerator.getLongSequenceId()); |
| | | rc_video_t2.setSrc_video_id(srcVideoId); |
| | | rc_video_t2.setSim_video_id(e.getId()); |
| | | rc_video_t2.setScore(e.getScore()); |
| | | resultList.add(rc_video_t2); |
| | | } |
| | | return resultList; |
| | | } |
| | | } |
New file |
| | |
| | | package com.iplatform.recvideo.util; |
| | | |
| | | import com.iplatform.model.po.Rc_video_t1; |
| | | import com.iplatform.recvideo.Constants; |
| | | import com.walker.infrastructure.utils.JsonUtils; |
| | | import com.walker.infrastructure.utils.NumberGenerator; |
| | | import com.walker.infrastructure.utils.StringUtils; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.http.ResponseEntity; |
| | | import org.springframework.web.client.RestTemplate; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | public class PythonInvokeUtils { |
| | | |
| | | private static final transient Logger logger = LoggerFactory.getLogger(PythonInvokeUtils.class); |
| | | |
| | | public static List<Rc_video_t1> acquireImageSearchResult(String videoId, String imgPath |
| | | , String topN, String url, RestTemplate restTemplate) throws Exception{ |
| | | SearchRequest request = new SearchRequest(); |
| | | request.setImg_path(imgPath); |
| | | request.setTop_n(topN); |
| | | |
| | | ResponseEntity<String> entity = restTemplate.postForEntity(url, request, String.class); |
| | | if(entity != null){ |
| | | String jsonData = entity.getBody(); |
| | | if(StringUtils.isEmpty(jsonData)){ |
| | | return null; |
| | | } |
| | | |
| | | List<Rc_video_t1> data = new ArrayList<>(); |
| | | |
| | | try { |
| | | List<Map<String, Object>> list = JsonUtils.jsonStringToObject(jsonData, List.class); |
| | | for(Map<String, Object> obj : list){ |
| | | data.add(transferToVideoT1(obj, videoId, imgPath)); |
| | | } |
| | | logger.debug("++++++ 请求返回 Rc_video_t1: " + data.size()); |
| | | return data; |
| | | |
| | | } catch (Exception e) { |
| | | logger.error("解析json结果错误:" + jsonData, e); |
| | | throw e; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 从文件路径中,截取文件ID,文件用id命名。 |
| | | * @param videoPath |
| | | * @return |
| | | */ |
| | | public static final String getFileNameWithoutSuffix(String videoPath, String suffix){ |
| | | String[] array = videoPath.split("/"); |
| | | if(array == null || array.length == 0){ |
| | | logger.error("视频名称截取id错误:" + videoPath); |
| | | return null; |
| | | } |
| | | String fileName = array[array.length-1]; |
| | | // String[] idValue = fileName.split("."); |
| | | return fileName.replaceAll(suffix, StringUtils.EMPTY_STRING); |
| | | } |
| | | |
| | | private static Rc_video_t1 transferToVideoT1(Map<String, Object> map, String videoId, String imgPath){ |
| | | String videoPath = map.get("video").toString(); |
| | | String distance = map.get("dis").toString(); |
| | | String srcImageId = getFileNameWithoutSuffix(imgPath, Constants.IMAGE_SUFFIX); |
| | | |
| | | Rc_video_t1 textBlock = new Rc_video_t1(); |
| | | textBlock.setSim_video_id(getFileNameWithoutSuffix(videoPath, Constants.VIDEO_SUFFIX)); |
| | | textBlock.setDistance(Double.parseDouble(distance)); |
| | | textBlock.setSrc_video_id(videoId); |
| | | textBlock.setSrc_img(srcImageId); |
| | | textBlock.setId(NumberGenerator.getLongSequenceId()); |
| | | return textBlock; |
| | | } |
| | | } |
New file |
| | |
| | | package com.iplatform.recvideo.util; |
| | | |
| | | import java.io.Serializable; |
| | | |
| | | public class SearchRequest implements Serializable { |
| | | |
| | | private String img_path; |
| | | |
| | | private String top_n; |
| | | |
| | | public String getImg_path() { |
| | | return img_path; |
| | | } |
| | | |
| | | public void setImg_path(String img_path) { |
| | | this.img_path = img_path; |
| | | } |
| | | |
| | | public String getTop_n() { |
| | | return top_n; |
| | | } |
| | | |
| | | public void setTop_n(String top_n) { |
| | | this.top_n = top_n; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.iplatform.recvideo.util; |
| | | |
| | | import com.iplatform.recvideo.VideoFolderInfo; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | public class TestUtils { |
| | | |
| | | public static final List<VideoFolderInfo> getBatchVideoFolderInfo(String videoDataFolder, String batchId){ |
| | | List<VideoFolderInfo> resultList = new ArrayList<>(); |
| | | |
| | | VideoFolderInfo videoFolderInfo = new VideoFolderInfo("landscape_01"); |
| | | videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_01/landscape_01_0.jpg", "landscape_01_0.jpg"); |
| | | videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_01/landscape_01_1.jpg", "landscape_01_1.jpg"); |
| | | videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_01/landscape_01_2.jpg", "landscape_01_2.jpg"); |
| | | videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_01/landscape_01_3.jpg", "landscape_01_3.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_01/landscape_01_4.jpg", "landscape_01_4.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_01/landscape_01_5.jpg", "landscape_01_5.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_01/landscape_01_6.jpg", "landscape_01_6.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_01/landscape_01_7.jpg", "landscape_01_7.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_01/landscape_01_8.jpg", "landscape_01_8.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_01/landscape_01_9.jpg", "landscape_01_9.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_01/landscape_01_10.jpg", "landscape_01_10.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_01/landscape_01_11.jpg", "landscape_01_11.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_01/landscape_01_12.jpg", "landscape_01_12.jpg"); |
| | | resultList.add(videoFolderInfo); |
| | | |
| | | videoFolderInfo = new VideoFolderInfo("landscape_02"); |
| | | videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_02/landscape_02_0.jpg", "landscape_02_0.jpg"); |
| | | videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_02/landscape_02_1.jpg", "landscape_02_1.jpg"); |
| | | videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_02/landscape_02_2.jpg", "landscape_02_2.jpg"); |
| | | videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_02/landscape_02_3.jpg", "landscape_02_3.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_02/landscape_02_4.jpg", "landscape_02_4.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_02/landscape_02_5.jpg", "landscape_02_5.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_02/landscape_02_6.jpg", "landscape_02_6.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_02/landscape_02_7.jpg", "landscape_02_7.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_02/landscape_02_8.jpg", "landscape_02_8.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_02/landscape_02_9.jpg", "landscape_02_9.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_02/landscape_02_10.jpg", "landscape_02_10.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_02/landscape_02_11.jpg", "landscape_02_11.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_02/landscape_02_12.jpg", "landscape_02_12.jpg"); |
| | | // videoFolderInfo.addImageInfo("/opt/ai/video/20220921/landscape_02/landscape_02_13.jpg", "landscape_02_13.jpg"); |
| | | resultList.add(videoFolderInfo); |
| | | |
| | | return resultList; |
| | | } |
| | | |
| | | } |
| | |
| | | package com.iplatform.recvideo.util; |
| | | |
| | | import com.iplatform.recvideo.Constants; |
| | | import com.iplatform.recvideo.ImageInfo; |
| | | import com.iplatform.recvideo.VideoFolderInfo; |
| | | import com.walker.infrastructure.utils.StringUtils; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | |
| | |
| | | |
| | | protected static final transient Logger logger = LoggerFactory.getLogger(VideoFileUtils.class); |
| | | |
| | | /** |
| | | * 拼接视频批量(存储)文件夹路径,如: /opt/ai/video/20220921,其中'20220921'为批量号 |
| | | * @param videoDataFolder |
| | | * @param batchId |
| | | * @return |
| | | */ |
| | | public static final String combineBatchPath(String videoDataFolder, String batchId){ |
| | | String batchFolderPath = null; |
| | | if(videoDataFolder.endsWith(StringUtils.FOLDER_SEPARATOR)){ |
| | | batchFolderPath = videoDataFolder + batchId; |
| | | } else { |
| | | batchFolderPath = videoDataFolder + StringUtils.FOLDER_SEPARATOR + batchId; |
| | | } |
| | | return batchFolderPath; |
| | | } |
| | | |
| | | public static final List<VideoFolderInfo> getBatchVideoFolderInfo(String videoDataFolder, String batchId){ |
| | | String batchFolderPath = videoDataFolder + File.separator + batchId; |
| | | // String batchFolderPath = videoDataFolder + File.separator + batchId; |
| | | String batchFolderPath = combineBatchPath(videoDataFolder, batchId); |
| | | // if(videoDataFolder.endsWith(StringUtils.FOLDER_SEPARATOR)){ |
| | | // batchFolderPath = videoDataFolder + batchId; |
| | | // } else { |
| | | // batchFolderPath = videoDataFolder + StringUtils.FOLDER_SEPARATOR + batchId; |
| | | // } |
| | | File batchFolder = new File(batchFolderPath); |
| | | if(!batchFolder.exists()){ |
| | | logger.error("视频文件夹不存在,无法获取图片集合信息。batchFolderPath = " + batchFolderPath); |
| | |
| | | package com.iplatform.recvideo; |
| | | |
| | | import com.iplatform.model.po.Rc_video_t1; |
| | | import com.iplatform.recvideo.util.PythonInvokeUtils; |
| | | import com.iplatform.recvideo.util.VideoFileUtils; |
| | | import org.junit.Test; |
| | | import org.springframework.web.client.RestTemplate; |
| | | |
| | | import java.util.List; |
| | | |
| | | public class VideoSimilarTest { |
| | | |
| | | @Test |
| | | public void testPythonSearch() throws Exception{ |
| | | String url = "http://121.36.40.27:12345/ai/video/search_img"; |
| | | RestTemplate restTemplate = new RestTemplate(); |
| | | List<Rc_video_t1> data = PythonInvokeUtils.acquireImageSearchResult("landscape_01" |
| | | , "/opt/ai/video/20220921/landscape_01/landscape_01_1.jpg", "30", url, restTemplate); |
| | | if(data != null){ |
| | | for(Rc_video_t1 e : data){ |
| | | System.out.println(e); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // @Test |
| | | public void testGetVideoId(){ |
| | | String id = PythonInvokeUtils.getFileNameWithoutSuffix("/opt/ai/video/20220921/landscape_01.mp4", Constants.VIDEO_SUFFIX); |
| | | System.out.println(id); |
| | | } |
| | | |
| | | // @Test |
| | | public void testVideoFolderInfo(){ |
| | | List<VideoFolderInfo> list = VideoFileUtils.getBatchVideoFolderInfo("D:/dev_tools/ai", "video"); |
| | | if(list != null){ |