shikeying
2022-09-24 7b3b249a7f2320f97e21e94e26a65f4b4ead0b6e
视频相似度分析1
8个文件已添加
9个文件已修改
778 ■■■■■ 已修改文件
deploy-jar-template/pom.xml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-jar-template/src/main/resources/application-dev.yml 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
recommend-video/doc/table.SQL 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
recommend-video/src/main/java/com/iplatform/recvideo/Constants.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
recommend-video/src/main/java/com/iplatform/recvideo/SimilarExecutor.java 107 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
recommend-video/src/main/java/com/iplatform/recvideo/SimilarVideoInfo.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
recommend-video/src/main/java/com/iplatform/recvideo/SimilarVideoOrder.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
recommend-video/src/main/java/com/iplatform/recvideo/VideoFolderInfo.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
recommend-video/src/main/java/com/iplatform/recvideo/api/DemoDebug.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
recommend-video/src/main/java/com/iplatform/recvideo/config/VideoSimilarProperties.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
recommend-video/src/main/java/com/iplatform/recvideo/service/VideoExecutorServiceImpl.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
recommend-video/src/main/java/com/iplatform/recvideo/support/DefaultSimilarExecutor.java 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
recommend-video/src/main/java/com/iplatform/recvideo/util/PythonInvokeUtils.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
recommend-video/src/main/java/com/iplatform/recvideo/util/SearchRequest.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
recommend-video/src/main/java/com/iplatform/recvideo/util/TestUtils.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
recommend-video/src/main/java/com/iplatform/recvideo/util/VideoFileUtils.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
recommend-video/src/test/java/com/iplatform/recvideo/VideoSimilarTest.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
deploy-jar-template/pom.xml
@@ -18,10 +18,10 @@
    <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>
deploy-jar-template/src/main/resources/application-dev.yml
@@ -3,7 +3,7 @@
    name: train_recommend
  datasource:
    # 是否显示dao中打印的SQL语句
    show-sql: true
    show-sql: false
    username: root
#    password: Bjjmy_63661766
    password: Bjjmy_2020
@@ -39,6 +39,8 @@
      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  #日志存储路径
@@ -86,4 +88,11 @@
  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
recommend-video/doc/table.SQL
New file
@@ -0,0 +1,9 @@
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 ;
recommend-video/src/main/java/com/iplatform/recvideo/Constants.java
@@ -3,4 +3,8 @@
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";
}
recommend-video/src/main/java/com/iplatform/recvideo/SimilarExecutor.java
@@ -1,12 +1,15 @@
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;
/**
@@ -25,7 +28,9 @@
    private String batchId = null;
    // 当前批次要处理的原始视频集合
    private List<VideoFolderInfo> videoFolderInfoList = null;
    private List<String> videoIdList = new ArrayList<>();
    // 记录当前执行到(该批次)哪个视频文件对应的第几个图片
    private int currentVideoFolderIndex = -1;
@@ -33,15 +38,24 @@
//    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("视频文件夹根目录必须设置!");
        }
@@ -50,17 +64,28 @@
        }
        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: 如果视频还未加载,则先加载视频
@@ -70,29 +95,42 @@
                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){
            this.videoFolderInfoList = VideoFileUtils.getBatchVideoFolderInfo(this.videoDataFolder, batchId);
            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);
            }
        }
        // 开始检索相似度
@@ -100,6 +138,12 @@
            this.currentVideoFolderIndex ++;
        }
        try {
            this.processOneSearchAndWrite();
            return 0;
        } catch (Exception e) {
            throw new Exception("processOneSearchAndWrite(): " + e.getMessage(), e);
        }
    }
    private void processOneSearchAndWrite() throws Exception{
@@ -113,20 +157,34 @@
        }
        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 ++;
    }
@@ -167,5 +225,26 @@
     * 请求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);
}
recommend-video/src/main/java/com/iplatform/recvideo/SimilarVideoInfo.java
New file
@@ -0,0 +1,91 @@
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;
    }
}
recommend-video/src/main/java/com/iplatform/recvideo/SimilarVideoOrder.java
New file
@@ -0,0 +1,94 @@
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());
}
recommend-video/src/main/java/com/iplatform/recvideo/VideoFolderInfo.java
@@ -18,6 +18,11 @@
        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);
recommend-video/src/main/java/com/iplatform/recvideo/api/DemoDebug.java
New file
@@ -0,0 +1,55 @@
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);
        }
    }
}
recommend-video/src/main/java/com/iplatform/recvideo/config/VideoSimilarProperties.java
@@ -7,6 +7,28 @@
    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;
    }
recommend-video/src/main/java/com/iplatform/recvideo/service/VideoExecutorServiceImpl.java
@@ -1,9 +1,64 @@
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;
    }
}
recommend-video/src/main/java/com/iplatform/recvideo/support/DefaultSimilarExecutor.java
New file
@@ -0,0 +1,110 @@
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;
    }
}
recommend-video/src/main/java/com/iplatform/recvideo/util/PythonInvokeUtils.java
New file
@@ -0,0 +1,81 @@
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;
    }
}
recommend-video/src/main/java/com/iplatform/recvideo/util/SearchRequest.java
New file
@@ -0,0 +1,27 @@
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;
    }
}
recommend-video/src/main/java/com/iplatform/recvideo/util/TestUtils.java
New file
@@ -0,0 +1,49 @@
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;
    }
}
recommend-video/src/main/java/com/iplatform/recvideo/util/VideoFileUtils.java
@@ -1,8 +1,8 @@
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;
@@ -14,8 +14,30 @@
    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);
recommend-video/src/test/java/com/iplatform/recvideo/VideoSimilarTest.java
@@ -1,13 +1,35 @@
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){