Ver código fonte

视频合成

wangyunpeng 2 semanas atrás
pai
commit
c829581b15

+ 100 - 12
core/src/main/java/com/tzld/supply/api/SpiderApiService.java

@@ -1,14 +1,16 @@
 package com.tzld.supply.api;
 
 import com.alibaba.fastjson.JSONObject;
-import com.tzld.supply.model.entity.ContentRankResponse;
-import com.tzld.supply.model.entity.ContentSearchResponse;
+import com.alibaba.fastjson.TypeReference;
+import com.tzld.supply.model.entity.*;
 import lombok.extern.slf4j.Slf4j;
 import okhttp3.*;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.PostConstruct;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
@@ -18,6 +20,9 @@ public class SpiderApiService {
 
     private OkHttpClient client;
 
+    @Value("${spider.api.base-url:http://crawapi.piaoquantv.com}")
+    private String baseUrl;
+
     @PostConstruct
     public void init() {
         client = new OkHttpClient().newBuilder()
@@ -36,7 +41,7 @@ public class SpiderApiService {
      * @throws IOException 如果请求失败
      */
     public ContentRankResponse sendContentRankRequest(String sortType, String category, int cursor) {
-        String url = "http://crawapi.piaoquantv.com/crawler/jin_ri_re_bang/content_rank";
+        String url = baseUrl + "/crawler/jin_ri_re_bang/content_rank";
         JSONObject param = new JSONObject();
         param.put("sort_type", sortType);
         param.put("category", category);
@@ -67,7 +72,7 @@ public class SpiderApiService {
      */
     public List<ContentSearchResponse.DataItem> searchContentDetail(String title) {
         // 搜索内容详情
-        String url = "http://crawapi.piaoquantv.com/crawler/bai_du/keyword";
+        String url = baseUrl + "/crawler/bai_du/keyword";
         JSONObject param = new JSONObject();
         param.put("keyword", title);
         RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), param.toJSONString());
@@ -107,24 +112,107 @@ public class SpiderApiService {
     }
 
     /**
-     * 搜索内容图片请求
-     * @param title 标题
+     * 搜索内容图片请求 baidu
+     * @param keyword 搜索关键词
      * @return ContentSearchResponse.DataItem 响应对象
      */
-    public List<ContentSearchResponse.DataItem> searchContentImage(String title) {
-        // todo 搜索内容图片
+    public List<SpiderBaiduImageItem> searchContentImage(String keyword) {
+        // 搜索内容图片
+        String url = baseUrl + "/crawler/bai_du/search_image";
+        JSONObject param = new JSONObject();
+        param.put("keyword", keyword);
+        RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), param.toJSONString());
+        Request request = new Request.Builder()
+                .url(url)
+                .post(body)
+                .build();
+        try {
+            Response response = client.newCall(request).execute();
+            if (response.isSuccessful()) {
+                // 请求成功,解析响应并返回
+                String responseBody = response.body().string();
+                // 将 JSON 响应映射到 ContentSearchResponse 对象
+                JSONObject jsonObject = JSONObject.parseObject(responseBody);
+
+                SpiderContentSearchResponse<SpiderBaiduImageItem> contentSearchResponse =
+                        JSONObject.parseObject(jsonObject.getString("data"), new TypeReference<SpiderContentSearchResponse<SpiderBaiduImageItem>>() {});
+                if (contentSearchResponse != null && contentSearchResponse.getData() != null) {
+                    return contentSearchResponse.getData();
+                }
+            }
+        } catch (IOException e) {
+            log.error("搜索内容图片失败: {}", e.getMessage(), e);
+        }
 
         return null;
     }
 
     /**
      * 搜索内容视频请求
-     * @param title 标题
-     * @return ContentSearchResponse.DataItem 响应对象
+     * @param keyword 搜索关键词
+     * @return List<SpiderHKVideoSearchItem> 响应对象
      */
-    public List<ContentSearchResponse.DataItem> searchContentVideo(String title) {
-        // todo 搜索内容视频
+    public List<SpiderHKVideoDetailItem> searchContentVideo(String keyword) {
+        // 搜索内容视频
+        String url = baseUrl + "/crawler/hao_kan_shi_pin/keyword";
+        JSONObject param = new JSONObject();
+        param.put("keyword", keyword);
+        RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), param.toJSONString());
+        Request request = new Request.Builder()
+                .url(url)
+                .post(body)
+                .build();
+        try {
+            Response response = client.newCall(request).execute();
+            if (response.isSuccessful()) {
+                // 请求成功,解析响应并返回
+                String responseBody = response.body().string();
+                // 将 JSON 响应映射到 ContentSearchResponse 对象
+                JSONObject jsonObject = JSONObject.parseObject(responseBody);
+
+                SpiderContentSearchResponse<SpiderHKVideoSearchItem> contentSearchResponse =
+                        JSONObject.parseObject(jsonObject.getString("data"), new TypeReference<SpiderContentSearchResponse<SpiderHKVideoSearchItem>>() {});
+                if (contentSearchResponse != null && contentSearchResponse.getData() != null) {
+                    List<SpiderHKVideoDetailItem> videoDetailItems = new ArrayList<>();
+                    for (SpiderHKVideoSearchItem item : contentSearchResponse.getData()) {
+                        videoDetailItems.add(searchContentVideoDetail(item.getVid()));
+                    }
+                    return videoDetailItems;
+                }
+            }
+        } catch (IOException e) {
+            log.error("搜索内容图片失败: {}", e.getMessage(), e);
+        }
+        return null;
+    }
 
+    public SpiderHKVideoDetailItem searchContentVideoDetail(String videoId) {
+        // 搜索内容视频详情
+        String url = baseUrl + "/crawler/hao_kan_shi_pin/detail";
+        JSONObject param = new JSONObject();
+        param.put("content_id", videoId);
+        RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), param.toJSONString());
+        Request request = new Request.Builder()
+                .url(url)
+                .post(body)
+                .build();
+        try {
+            Response response = client.newCall(request).execute();
+            if (response.isSuccessful()) {
+                // 请求成功,解析响应并返回
+                String responseBody = response.body().string();
+                // 将 JSON 响应映射到 ContentSearchResponse 对象
+                JSONObject jsonObject = JSONObject.parseObject(responseBody);
+
+                SpiderContentSearchResponse<SpiderHKVideoDetailItem> contentSearchResponse =
+                        JSONObject.parseObject(jsonObject.getString("data"), new TypeReference<SpiderContentSearchResponse<SpiderHKVideoDetailItem>>() {});
+                if (contentSearchResponse != null && contentSearchResponse.getData() != null) {
+                    return contentSearchResponse.getData().get(0);
+                }
+            }
+        } catch (IOException e) {
+            log.error("搜索内容视频详情失败: {}", e.getMessage(), e);
+        }
         return null;
     }
 }

+ 41 - 12
core/src/main/java/com/tzld/supply/job/ContentMediaSearchJob.java

@@ -9,9 +9,11 @@ import com.tzld.supply.common.enums.SpiderContentStatusEnum;
 import com.tzld.supply.dao.mapper.supply.spider.SpiderContentMediaMapper;
 import com.tzld.supply.dao.mapper.supply.spider.ext.SpiderMapperExt;
 import com.tzld.supply.model.entity.AlgFaceRecognizeResult;
-import com.tzld.supply.model.entity.ContentSearchResponse;
+import com.tzld.supply.model.entity.SpiderBaiduImageItem;
+import com.tzld.supply.model.entity.SpiderHKVideoDetailItem;
 import com.tzld.supply.model.po.supply.spider.SpiderContent;
 import com.tzld.supply.model.po.supply.spider.SpiderContentMedia;
+import com.tzld.supply.model.po.supply.spider.SpiderContentMediaExample;
 import com.tzld.supply.util.AliOssFileTool;
 import com.tzld.supply.util.DateUtils;
 import com.xxl.job.core.biz.model.ReturnT;
@@ -52,15 +54,20 @@ public class ContentMediaSearchJob {
             return ReturnT.SUCCESS;
         }
         for (SpiderContent content : contentList) {
-            List<ContentSearchResponse.DataItem> dataItems = spiderApiService.searchContentImage(content.getTitle());
+            // 检查是否已经存在图片
+            Long mediaCount = getSpiderContentMediaCount(content.getId(), "image");
+            if (mediaCount > 0) {
+                continue;
+            }
+            List<SpiderBaiduImageItem> dataItems = spiderApiService.searchContentImage(content.getTitle());
             if (CollectionUtil.isEmpty(dataItems)) {
                 continue;
             }
             List<SpiderContentMedia> saveList = new ArrayList<>();
             Long now = System.currentTimeMillis();
-            for (ContentSearchResponse.DataItem dataItem : dataItems) {
-                // 2. 调用saveInPublic转存到OSS
-                String fileName = String.format("supply/image/%s_%d.jpg", content.getId(), System.currentTimeMillis());
+            for (SpiderBaiduImageItem dataItem : dataItems) {
+                // 转存到OSS
+                String fileName = String.format("supply/spider/image/%s_%d.jpg", content.getId(), System.currentTimeMillis());
                 String fileUrl = AliOssFileTool.downloadAndSaveInOSS(fileName, dataItem.getUrl(), "image/jpeg");
                 if (StringUtils.isBlank(fileUrl)) {
                     log.warn("图片转存OSS失败,URL: {}", dataItem.getUrl());
@@ -85,6 +92,12 @@ public class ContentMediaSearchJob {
         return ReturnT.SUCCESS;
     }
 
+    private Long getSpiderContentMediaCount(Long contentId, String mediaType) {
+        SpiderContentMediaExample example = new SpiderContentMediaExample();
+        example.createCriteria().andContentIdEqualTo(contentId).andMediaTypeEqualTo(mediaType);
+        return spiderContentMediaMapper.countByExample(example);
+    }
+
     @XxlJob("contentMediaImageCheckJob")
     public ReturnT<String> contentMediaImageCheckJob(String param) {
         Long startTime = DateUtils.getTodayStart();
@@ -129,24 +142,40 @@ public class ContentMediaSearchJob {
             return ReturnT.SUCCESS;
         }
         for (SpiderContent content : contentList) {
-            List<ContentSearchResponse.DataItem> dataItems = spiderApiService.searchContentVideo(content.getTitle());
+            // 检查是否已经存在视频
+            Long mediaCount = getSpiderContentMediaCount(content.getId(), "video");
+            if (mediaCount > 0) {
+                continue;
+            }
+            List<SpiderHKVideoDetailItem> dataItems = spiderApiService.searchContentVideo(content.getTitle());
             if (CollectionUtil.isEmpty(dataItems)) {
                 continue;
             }
             List<SpiderContentMedia> saveList = new ArrayList<>();
             Long now = System.currentTimeMillis();
-            for (ContentSearchResponse.DataItem dataItem : dataItems) {
-                // todo 时长过滤
-                String fileName = String.format("supply/video/%s_%d.mp4", content.getId(), System.currentTimeMillis());
-                String fileUrl = AliOssFileTool.downloadAndSaveInOSS(fileName, dataItem.getUrl(), "video/mp4");
+            for (SpiderHKVideoDetailItem dataItem : dataItems) {
+                if (CollectionUtil.isEmpty(dataItem.getVideoURLList())) {
+                    continue;
+                }
+                SpiderHKVideoDetailItem.VideoURLList videoURLList = dataItem.getVideoURLList().get(0);
+                // 时长过滤
+                if (videoURLList.getVideoDuration() > 300 || videoURLList.getVideoDuration() < 10) {
+                    continue;
+                }
+                if (StringUtils.isBlank(videoURLList.getVideoURL())) {
+                    continue;
+                }
+                String fileName = String.format("supply/spider/video/%s_%d.mp4", content.getId(), System.currentTimeMillis());
+                String fileUrl = AliOssFileTool.downloadAndSaveInOSS(fileName, videoURLList.getVideoURL(), "video/mp4");
                 if (StringUtils.isBlank(fileUrl)) {
-                    log.warn("视频转存OSS失败,URL: {}", dataItem.getUrl());
+                    log.warn("视频转存OSS失败,URL: {}", videoURLList.getVideoURL());
                     continue;
                 }
                 SpiderContentMedia media = new SpiderContentMedia();
                 media.setContentId(content.getId());
                 media.setMediaType("video");
-                media.setUrl(dataItem.getUrl());
+                media.setUrl(videoURLList.getVideoURL());
+                media.setDuration((int) videoURLList.getVideoDuration() * 1000);
                 media.setTitle(dataItem.getTitle());
                 media.setOssKey(fileUrl);
                 media.setStatus(SpiderContentMediaStatusEnum.PASSED.getCode());

+ 51 - 11
core/src/main/java/com/tzld/supply/job/VideoGenerateJob.java

@@ -19,7 +19,6 @@ import com.tzld.supply.model.param.FFmpeg.VideoTimeCutParam;
 import com.tzld.supply.model.param.FFmpeg.VideoUrlsParam;
 import com.tzld.supply.model.po.supply.spider.*;
 import com.tzld.supply.util.AliOssFileTool;
-import com.tzld.supply.util.CdnUtil;
 import com.tzld.supply.util.DateUtils;
 import com.tzld.supply.util.DistributedIdGenerator;
 import com.xxl.job.core.biz.model.ReturnT;
@@ -167,20 +166,14 @@ public class VideoGenerateJob {
             for (SpiderContentMedia image : imageList) {
                 // 现仅一种效果,后更改为多种效果随机
                 String outputVideoName = String.format("%s_%d.mp4", produceVideo.getContentId(), System.currentTimeMillis());
-                String command = String.format("ffmpeg -loop 1 -i %s -t 3 -r 25 -filter_complex " +
-                                "\"[0:v]scale=720:1280:force_original_aspect_ratio=increase,crop=720:1280,gblur=sigma=5[bg];" +
-                                "[0:v]scale=720:1280:force_original_aspect_ratio=decrease,pad=720:1280:(ow-iw)/2:(oh-ih)/2:color=black@0[fg];" +
-                                "[fg]zoompan=z='min(1.00+0.000266*on,1.02)':d=1:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s=720x1280[fg_anim];" +
-                                "[bg][fg_anim]overlay=0:0:format=auto\" " +
-                                "-c:v libx264 -pix_fmt yuv420p -movflags +faststart \"%s\"",
-                        CdnUtil.getOssHttpUrl(image.getOssKey()), outputVideoName);
+                String command = randomImageToVideoAnimation(image.getOssKey(), outputVideoName);
                 CommandParam commandParam = CommandParam.builder()
                         .ffmpegCommand(command)
                         .outputFile(outputVideoName)
                         .outputType("video")
                         .build();
                 String commandResponseData = ffmpegApiService.command(commandParam);
-                String fileName = String.format("supply/material/video/%s", outputVideoName);
+                String fileName = String.format("supply/produce/material/video/%s", outputVideoName);
                 String imageToVideoOssUrl = AliOssFileTool.downloadAndSaveInOSS(fileName, commandResponseData, "video/mp4");
 
                 // 获取视频信息
@@ -191,6 +184,53 @@ public class VideoGenerateJob {
         }
     }
 
+    private String randomImageToVideoAnimation(String imageUrl, String outputVideoName) {
+        String command;
+        switch (new Random().nextInt(2)) {
+            // 放大
+            case 0:
+                command = String.format("ffmpeg -loop 1 -i %s -filter_complex " +
+                                "\"[0:v]scale=720:1280:force_original_aspect_ratio=increase,boxblur=40:40,crop=720:1280[bg];" +
+                                "[0:v]scale=720:1280:force_original_aspect_ratio=decrease,setsar=1[fg];" +
+                                "[bg][fg]overlay=(W-w)/2:(H-h)/2,format=yuv420p[base];" +
+                                "[base]zoompan=z='min(zoom+0.003,1.3)':d=75:x='(iw/2)-(iw/zoom/2)':y='(ih/2)-(ih/zoom/2)':s=720x1280:fps=25[zoom]\" " +
+                                "-map \"[zoom]\" -t 3 -y \"%s\"",
+                        imageUrl, outputVideoName);
+                break;
+            // 平移
+            case 1:
+                // 生成随机方向
+                String[][] directions = {
+                        {"0", "0", "(W-w)", "(H-h)"},   // 左上 -> 右下
+                        {"(W-w)", "(H-h)", "0", "0"},   // 右下 -> 左上
+                        {"0", "(H-h)", "(W-w)", "0"},   // 左下 -> 右上
+                        {"(W-w)", "0", "0", "(H-h)"}    // 右上 -> 左下
+                };
+                Random rand = new Random();
+                int index = rand.nextInt(directions.length);
+                String[] dir = directions[index];
+                String xExpr = String.format("x='%s + (%s-%s)*t/%d'", dir[0], dir[2], dir[0], 3);
+                String yExpr = String.format("y='%s + (%s-%s)*t/%d'", dir[1], dir[3], dir[1], 3);
+                // 构造 FFmpeg 命令
+                command = String.format("ffmpeg -y -loop 1 -i %s -filter_complex" +
+                        "\"[0:v]scale=720:1280:force_original_aspect_ratio=increase,boxblur=40:40,crop=720:1280[bg];" +
+                        "[0:v]scale=720:1280:force_original_aspect_ratio=decrease,setsar=1[fg];" +
+                        "[bg][fg]overlay=" + xExpr + ":" + yExpr + ",format=yuv420p\"" +
+                        "-t3 %s", imageUrl, outputVideoName);
+                break;
+            // 背景模糊
+            default:
+                command = String.format("ffmpeg -loop 1 -i %s -t 3 -r 25 -filter_complex " +
+                                "\"[0:v]scale=720:1280:force_original_aspect_ratio=increase,crop=720:1280,gblur=sigma=5[bg];" +
+                                "[0:v]scale=720:1280:force_original_aspect_ratio=decrease,pad=720:1280:(ow-iw)/2:(oh-ih)/2:color=black@0[fg];" +
+                                "[fg]zoompan=z='min(1.00+0.000266*on,1.02)':d=1:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s=720x1280[fg_anim];" +
+                                "[bg][fg_anim]overlay=0:0:format=auto\" " +
+                                "-c:v libx264 -pix_fmt yuv420p -movflags +faststart \"%s\"",
+                        imageUrl, outputVideoName);
+        }
+        return command;
+    }
+
     private MediaInfo getMediaInfo(String videoUrl) {
         String mediaInfo = ffmpegApiService.videoInfo(new VideoInfoParam(videoUrl));
         return JSONObject.parseObject(mediaInfo, MediaInfo.class);
@@ -234,7 +274,7 @@ public class VideoGenerateJob {
                             .build();
                     lastEndTime = endTime;
                     String videoCutUrl = ffmpegApiService.timeCutVideo(timeCutParam);
-                    String fileName = String.format("supply/material/video/%s_%d.mp4", produceVideo.getContentId(), System.currentTimeMillis());
+                    String fileName = String.format("supply/produce/material/video/%s_%d.mp4", produceVideo.getContentId(), System.currentTimeMillis());
                     String videoCutOssUrl = AliOssFileTool.downloadAndSaveInOSS(fileName, videoCutUrl, "video/mp4");
                     // 获取视频信息
                     MediaInfo mediaInfo = getMediaInfo(videoCutOssUrl);
@@ -334,7 +374,7 @@ public class VideoGenerateJob {
                     .outputType("video")
                     .build();
             String commandResponseData = ffmpegApiService.command(commandParam);
-            String fileName = String.format("supply/video/%s", videoName);
+            String fileName = String.format("supply/produce/video/%s", videoName);
             String finalVideoUrl = AliOssFileTool.downloadAndSaveInOSS(fileName, commandResponseData, "video/mp4");
             // 更新视频地址
             produceVideo.setUrl(finalVideoUrl);

+ 10 - 0
core/src/main/java/com/tzld/supply/model/entity/SpiderBaiduImageItem.java

@@ -0,0 +1,10 @@
+package com.tzld.supply.model.entity;
+
+import lombok.Data;
+
+@Data
+public class SpiderBaiduImageItem {
+    private String title;
+    private String url;
+    private String source;
+}

+ 14 - 0
core/src/main/java/com/tzld/supply/model/entity/SpiderContentSearchResponse.java

@@ -0,0 +1,14 @@
+package com.tzld.supply.model.entity;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class SpiderContentSearchResponse<T> {
+
+    private long code;
+    private String msg;
+    private List<T> data;
+
+}

+ 54 - 0
core/src/main/java/com/tzld/supply/model/entity/SpiderHKVideoDetailItem.java

@@ -0,0 +1,54 @@
+package com.tzld.supply.model.entity;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class SpiderHKVideoDetailItem {
+    private long channel;
+    private String channelContentID;
+    private String contentLink;
+    private Object wxSn;
+    private String title;
+    private String contentType;
+    private Object bodyText;
+    private Object location;
+    private Object sourceURL;
+    private Object miniProgram;
+    private Object topicList;
+    private List<ImageURLList> imageURLList;
+    private List<VideoURLList> videoURLList;
+    private Object bgmData;
+    private Object adInfo;
+    private Object voiceData;
+    private String channelAccountID;
+    private String channelAccountName;
+    private String avatar;
+    private Object itemIndex;
+    private Object viewCount;
+    private Object playCount;
+    private long likeCount;
+    private Object collectCount;
+    private long commentCount;
+    private Object shareCount;
+    private Object lookingCount;
+    private long publishTimestamp;
+    private Object modifyTimestamp;
+    private long updateTimestamp;
+
+
+    @Data
+    public static class ImageURLList {
+        private long imageType;
+        private String imageURL;
+    }
+
+    @Data
+    public static class VideoURLList {
+        private String videoURL;
+        private long videoDuration;
+    }
+}
+
+

+ 16 - 0
core/src/main/java/com/tzld/supply/model/entity/SpiderHKVideoSearchItem.java

@@ -0,0 +1,16 @@
+package com.tzld.supply.model.entity;
+
+import lombok.Data;
+
+@Data
+public class SpiderHKVideoSearchItem {
+    private String author;
+    private String authorID;
+    private String coverSrc;
+    private String duration;
+    private String publishTimeText;
+    private String readNum;
+    private String title;
+    private String url;
+    private String vid;
+}