From 7b3b249a7f2320f97e21e94e26a65f4b4ead0b6e Mon Sep 17 00:00:00 2001 From: shikeying <shikeying@163.com> Date: 星期六, 24 九月 2022 16:50:25 +0800 Subject: [PATCH] 视频相似度分析1 --- recommend-video/src/main/java/com/iplatform/recvideo/util/TestUtils.java | 49 +++ recommend-video/src/main/java/com/iplatform/recvideo/api/DemoDebug.java | 55 ++++ deploy-jar-template/src/main/resources/application-dev.yml | 13 recommend-video/src/main/java/com/iplatform/recvideo/support/DefaultSimilarExecutor.java | 110 ++++++++ recommend-video/src/main/java/com/iplatform/recvideo/util/SearchRequest.java | 27 ++ recommend-video/src/main/java/com/iplatform/recvideo/util/PythonInvokeUtils.java | 81 ++++++ recommend-video/src/main/java/com/iplatform/recvideo/util/VideoFileUtils.java | 26 + recommend-video/src/main/java/com/iplatform/recvideo/VideoFolderInfo.java | 5 recommend-video/src/main/java/com/iplatform/recvideo/SimilarVideoInfo.java | 91 +++++++ recommend-video/src/main/java/com/iplatform/recvideo/SimilarExecutor.java | 107 +++++++- recommend-video/doc/table.SQL | 9 recommend-video/src/main/java/com/iplatform/recvideo/Constants.java | 4 deploy-jar-template/pom.xml | 8 recommend-video/src/main/java/com/iplatform/recvideo/config/VideoSimilarProperties.java | 22 + recommend-video/src/main/java/com/iplatform/recvideo/service/VideoExecutorServiceImpl.java | 55 ++++ recommend-video/src/main/java/com/iplatform/recvideo/SimilarVideoOrder.java | 94 +++++++ recommend-video/src/test/java/com/iplatform/recvideo/VideoSimilarTest.java | 22 + 17 files changed, 756 insertions(+), 22 deletions(-) diff --git a/deploy-jar-template/pom.xml b/deploy-jar-template/pom.xml index ed20e5d..ce657ad 100644 --- a/deploy-jar-template/pom.xml +++ b/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> diff --git a/deploy-jar-template/src/main/resources/application-dev.yml b/deploy-jar-template/src/main/resources/application-dev.yml index 96d7b2c..a25556c 100644 --- a/deploy-jar-template/src/main/resources/application-dev.yml +++ b/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涓嶈閰峮ame锛岄厤浜唍ame涓嶈閰峱ath锛屽彧閰峱ath鏃秐ame榛樿涓簊pring.log锛屾兂璺緞鍜屾枃浠跺悕鍚屾椂鐢熸晥鍙厤缃甽ogging.file.name=d:/logs/mylog.log # name: ${spring.application.name}.log #鏃ュ織鏂囦欢鍚� # path: logs #鏃ュ織瀛樺偍璺緞 @@ -86,4 +88,11 @@ video: # 瑙嗛閲囬泦瀛樺偍鏍硅矾寰� - data-folder: D:/dev_tools/ai/ \ No newline at end of file +# data-folder: D:/dev_tools/ai/ + data-folder: /opt/ai/video/ + + # 瑙嗛鐩镐技搴I鏈嶅姟URL + ai-service: http://121.36.40.27:12345 + + # 鎺ㄨ崘鐩镐技瑙嗛(绠楁硶)鍓嶅灏戜釜 + top-n: 40 \ No newline at end of file diff --git a/recommend-video/doc/table.SQL b/recommend-video/doc/table.SQL new file mode 100644 index 0000000..2283614 --- /dev/null +++ b/recommend-video/doc/table.SQL @@ -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 ; \ No newline at end of file diff --git a/recommend-video/src/main/java/com/iplatform/recvideo/Constants.java b/recommend-video/src/main/java/com/iplatform/recvideo/Constants.java index b5f017f..2bc2330 100644 --- a/recommend-video/src/main/java/com/iplatform/recvideo/Constants.java +++ b/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"; } diff --git a/recommend-video/src/main/java/com/iplatform/recvideo/SimilarExecutor.java b/recommend-video/src/main/java/com/iplatform/recvideo/SimilarExecutor.java index 90d4868..1318e53 100644 --- a/recommend-video/src/main/java/com/iplatform/recvideo/SimilarExecutor.java +++ b/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("宸茬粡瀹屾垚鎵规鐩镐技缁撴灉鍐欏叆鏁版嵁搴擄紝涓嶅啀寰�涓嬪鐞嗐�俠atch = " + this.batchId); - return; + logger.info("宸茬粡瀹屾垚鎵规鐩镐技缁撴灉鍐欏叆鏁版嵁搴擄紝澶勭悊鏈�鍚庝竴姝�:鍐欏叆鐢ㄦ埛鎺ㄨ崘琛ㄦ暟鎹�俠atch = " + 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); } diff --git a/recommend-video/src/main/java/com/iplatform/recvideo/SimilarVideoInfo.java b/recommend-video/src/main/java/com/iplatform/recvideo/SimilarVideoInfo.java new file mode 100644 index 0000000..f98a3c3 --- /dev/null +++ b/recommend-video/src/main/java/com/iplatform/recvideo/SimilarVideoInfo.java @@ -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; + } +} diff --git a/recommend-video/src/main/java/com/iplatform/recvideo/SimilarVideoOrder.java b/recommend-video/src/main/java/com/iplatform/recvideo/SimilarVideoOrder.java new file mode 100644 index 0000000..338cd43 --- /dev/null +++ b/recommend-video/src/main/java/com/iplatform/recvideo/SimilarVideoOrder.java @@ -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()); +} diff --git a/recommend-video/src/main/java/com/iplatform/recvideo/VideoFolderInfo.java b/recommend-video/src/main/java/com/iplatform/recvideo/VideoFolderInfo.java index ffd5722..4ee67f6 100644 --- a/recommend-video/src/main/java/com/iplatform/recvideo/VideoFolderInfo.java +++ b/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); diff --git a/recommend-video/src/main/java/com/iplatform/recvideo/api/DemoDebug.java b/recommend-video/src/main/java/com/iplatform/recvideo/api/DemoDebug.java new file mode 100644 index 0000000..4c36014 --- /dev/null +++ b/recommend-video/src/main/java/com/iplatform/recvideo/api/DemoDebug.java @@ -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); + } + } + +} diff --git a/recommend-video/src/main/java/com/iplatform/recvideo/config/VideoSimilarProperties.java b/recommend-video/src/main/java/com/iplatform/recvideo/config/VideoSimilarProperties.java index 5659418..71c79db 100644 --- a/recommend-video/src/main/java/com/iplatform/recvideo/config/VideoSimilarProperties.java +++ b/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; } diff --git a/recommend-video/src/main/java/com/iplatform/recvideo/service/VideoExecutorServiceImpl.java b/recommend-video/src/main/java/com/iplatform/recvideo/service/VideoExecutorServiceImpl.java index bd4408f..9802597 100644 --- a/recommend-video/src/main/java/com/iplatform/recvideo/service/VideoExecutorServiceImpl.java +++ b/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; + } } diff --git a/recommend-video/src/main/java/com/iplatform/recvideo/support/DefaultSimilarExecutor.java b/recommend-video/src/main/java/com/iplatform/recvideo/support/DefaultSimilarExecutor.java new file mode 100644 index 0000000..ea9327b --- /dev/null +++ b/recommend-video/src/main/java/com/iplatform/recvideo/support/DefaultSimilarExecutor.java @@ -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("寮�濮嬭姹俻ython鏈嶅姟:" + 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("姝e湪鍐欏叆'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("姝e湪鍐欏叆涓�娆$敤鎴锋帹鑽愯棰戞暟鎹�, 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; + } +} diff --git a/recommend-video/src/main/java/com/iplatform/recvideo/util/PythonInvokeUtils.java b/recommend-video/src/main/java/com/iplatform/recvideo/util/PythonInvokeUtils.java new file mode 100644 index 0000000..a315bb2 --- /dev/null +++ b/recommend-video/src/main/java/com/iplatform/recvideo/util/PythonInvokeUtils.java @@ -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("瑙f瀽json缁撴灉閿欒:" + jsonData, e); + throw e; + } + } + return null; + } + + /** + * 浠庢枃浠惰矾寰勪腑锛屾埅鍙栨枃浠禝D锛屾枃浠剁敤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; + } +} diff --git a/recommend-video/src/main/java/com/iplatform/recvideo/util/SearchRequest.java b/recommend-video/src/main/java/com/iplatform/recvideo/util/SearchRequest.java new file mode 100644 index 0000000..fea99ad --- /dev/null +++ b/recommend-video/src/main/java/com/iplatform/recvideo/util/SearchRequest.java @@ -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; + } + +} diff --git a/recommend-video/src/main/java/com/iplatform/recvideo/util/TestUtils.java b/recommend-video/src/main/java/com/iplatform/recvideo/util/TestUtils.java new file mode 100644 index 0000000..7261c02 --- /dev/null +++ b/recommend-video/src/main/java/com/iplatform/recvideo/util/TestUtils.java @@ -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; + } + +} diff --git a/recommend-video/src/main/java/com/iplatform/recvideo/util/VideoFileUtils.java b/recommend-video/src/main/java/com/iplatform/recvideo/util/VideoFileUtils.java index 61b3707..21812ca 100644 --- a/recommend-video/src/main/java/com/iplatform/recvideo/util/VideoFileUtils.java +++ b/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("瑙嗛鏂囦欢澶逛笉瀛樺湪锛屾棤娉曡幏鍙栧浘鐗囬泦鍚堜俊鎭�俠atchFolderPath = " + batchFolderPath); diff --git a/recommend-video/src/test/java/com/iplatform/recvideo/VideoSimilarTest.java b/recommend-video/src/test/java/com/iplatform/recvideo/VideoSimilarTest.java index ef88d0e..d1e3849 100644 --- a/recommend-video/src/test/java/com/iplatform/recvideo/VideoSimilarTest.java +++ b/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){ -- Gitblit v1.9.1