diff --git a/yudao-module-tik/pom.xml b/yudao-module-tik/pom.xml index 4e98cfecdf..9d6a265bbc 100644 --- a/yudao-module-tik/pom.xml +++ b/yudao-module-tik/pom.xml @@ -20,6 +20,11 @@ + + com.aliyun + ice20201109 + 2.3.0 + com.mashape.unirest unirest-java diff --git a/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/InitConfig.java b/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/InitConfig.java new file mode 100644 index 0000000000..afdd915e38 --- /dev/null +++ b/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/InitConfig.java @@ -0,0 +1,11 @@ +package cn.iocoder.yudao.module.tik; + +import org.springframework.boot.CommandLineRunner; + +public class InitConfig implements CommandLineRunner { + + @Override + public void run(String... args) throws Exception { + + } +} diff --git a/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/media/BatchProduceAlignment.java b/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/media/BatchProduceAlignment.java new file mode 100644 index 0000000000..2988e53c30 --- /dev/null +++ b/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/media/BatchProduceAlignment.java @@ -0,0 +1,173 @@ +package cn.iocoder.yudao.module.tik.media; + + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.aliyun.ice20201109.Client; +import com.aliyun.ice20201109.models.*; +import com.aliyun.teaopenapi.models.Config; +import org.springframework.stereotype.Component; + +import java.util.*; + +// 成功视频 +// http://oushu-test-shanghai.oss-cn-shanghai.aliyuncs.com/ice_output/46b29eb5775f4f758846171ab79bfca7.mp4 + +/** + * 需要maven引入二方包依赖: + * + * com.aliyun + * ice20201109 + * 2.1.0 + * + * + * com.alibaba + * fastjson + * 1.2.9 + * + */ +public class BatchProduceAlignment { + + static final String regionId = "cn-hangzhou"; + static final String bucket = "muye-ai-chat"; + private Client iceClient; + + private final String accessKeyId = "LTAI5tPV9Ag3csf41GZjaLTA"; + private final String accessKeySecret = "kDqlGeJTKw6tJtFYiaY8vQTFuVIQDs"; + + + public static void main(String[] args) { + try { + BatchProduceAlignment batchProduce = new BatchProduceAlignment(); + batchProduce.initClient(); + // 文字素材 + String text = "人们懂得用五味杂陈形容人生,因为懂得味道是每个人心中固守的情怀。在这个时代,每一个人都经历了太多的苦痛和喜悦,人们总会将苦涩藏在心里,而把幸福变成食物,呈现在四季的餐桌之上"; + // 视频素材 + String[] videoArray = new String[]{ + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f1.mp4", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f2.mp4", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f3.mp4", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f4.mp4", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f5.mp4", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f6.mp4", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f7.mp4", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f8.mp4", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f9.mp4", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f10.mp4", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f11.mp4", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f12.mp4", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f13.mp4", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f14.mp4", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f15.mp4", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f16.mp4", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f17.mp4", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f18.mp4" + }; + // 背景音乐素材 + String[] bgMusicArray = new String[]{ + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/music/m1.wav", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/music/m2.wav", + "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/music/m3.wav" + }; + // 视频标题 + String title = "舌尖上的美食"; + // 生成的成片数 + int produceCount = 3; + List mediaList = batchProduce.batchProduceAlignment(title,text,videoArray,bgMusicArray,produceCount); + mediaList.forEach(System.out::println); + } catch (Exception e) { + System.out.println("Produce failed. Exception: " + e.toString()); + } + } + + + public void initClient() throws Exception { + // 阿里云账号AccessKey拥有所有API的访问权限,建议您使用RAM用户进行API访问或日常运维。 + // 本示例以将AccessKey ID和 AccessKey Secret保存在环境变量为例说明。配置方法请参见:https://help.aliyun.com/zh/sdk/developer-reference/v2-manage-access-credentials?spm=a2c4g.11186623.0.0.423350fbOTFdOB#2a38e5c14b4em + com.aliyun.credentials.Client credentialClient = new com.aliyun.credentials.Client(); + Config config = new Config(); + config.setCredential(credentialClient); + // 如需硬编码AccessKey ID和AccessKey Secret,代码如下,但强烈建议不要把AccessKey ID和AccessKey Secret保存到工程代码里,否则可能导致AccessKey泄露,威胁您账号下所有资源的安全。 + config.accessKeyId = accessKeyId; + config.accessKeySecret = accessKeySecret; + config.endpoint = "ice." + regionId + ".aliyuncs.com"; + config.regionId = regionId; + iceClient = new Client(config); + } + + public List batchProduceAlignment(String title,String text,String[] videoArray,String[] bgMusicArray,int produceCount) throws Exception { + + // 批量提交任务 + List jobIds = new ArrayList<>(); + for (int i = 0; i < produceCount; i++) { + String jobId = produceSingleVideo(title, text, videoArray, bgMusicArray); + jobIds.add(jobId); + } + while (true) { + Thread.sleep(3000); + boolean allFinished = true; + for (int i = 0; i < jobIds.size(); i++) { + String jobId = jobIds.get(i); + GetMediaProducingJobRequest req = new GetMediaProducingJobRequest(); + req.setJobId(jobId); + GetMediaProducingJobResponse response = iceClient.getMediaProducingJob(req); + GetMediaProducingJobResponseBody.GetMediaProducingJobResponseBodyMediaProducingJob mediaProducingJob = response.getBody().getMediaProducingJob(); + String status = mediaProducingJob.getStatus(); + System.out.println("jobId: " + mediaProducingJob.getJobId() + ", status:" + status); + if ("Failed".equalsIgnoreCase(status)) { + throw new Exception("Produce failed. jobid: " + mediaProducingJob.getJobId()); + } + if (!"Success".equalsIgnoreCase(status)) { + allFinished = false; + break; + } + } + if (allFinished) { + break; + } + } + return jobIds; + } + + public String produceSingleVideo(String title, String text, String[] videoArray, String[] bgMusicArray) throws Exception { + text = text.replace(",", "。"); + text = text.replace("\n", "。"); + String[] sentenceArray = text.split("。"); + + JSONArray videoClipArray = new JSONArray(); + JSONArray audioClipArray = new JSONArray(); + + List videoList = Arrays.asList(videoArray); + Collections.shuffle(videoList); + for (int i = 0; i < sentenceArray.length; i++) { + String sentence = sentenceArray[i]; + String clipId = "clip" + i; + String videoUrl = videoList.get(i); + String videoClip = "{\"MediaURL\":\""+videoUrl+"\",\"ReferenceClipId\":\""+clipId+"\",\"Effects\":[{\"Type\":\"Background\",\"SubType\":\"Blur\",\"Radius\":0.1}]}"; + videoClipArray.add(JSONObject.parseObject(videoClip)); + String audioClip = "{\"Type\":\"AI_TTS\",\"Content\":\"" + sentence + "\",\"Voice\":\"zhichu\",\"ClipId\":\""+clipId+"\",\"Effects\":[{\"Type\":\"AI_ASR\",\"Font\":\"Alibaba PuHuiTi\",\"Alignment\":\"TopCenter\",\"Y\":0.75,\"FontSize\":55,\"FontColor\":\"#ffffff\",\"AdaptMode\":\"AutoWrap\",\"TextWidth\":0.8,\"Outline\":2,\"OutlineColour\":\"#000000\"}]}"; + audioClipArray.add(JSONObject.parseObject(audioClip)); + } + + String subtitleTrack = "{\"SubtitleTrackClips\":[{\"Type\":\"Text\",\"Font\":\"HappyZcool-2016\",\"Content\":\""+title+"\",\"FontSize\":80,\"FontColor\":\"#ffffff\",\"Y\":0.15,\"Alignment\":\"TopCenter\",\"EffectColorStyle\":\"CS0004-000005\",\"FontFace\":{\"Bold\":true,\"Italic\":false,\"Underline\":false}}]}"; + + int bgMusicIndex = (int)(Math.random() * bgMusicArray.length); + String bgMusicUrl = bgMusicArray[bgMusicIndex]; + String timeline = "{\"VideoTracks\":[{\"VideoTrackClips\":"+videoClipArray.toJSONString()+"}],\"AudioTracks\":[{\"AudioTrackClips\":"+audioClipArray.toJSONString()+"},{\"AudioTrackClips\":[{\"MediaURL\":\""+bgMusicUrl+"\"}]}],\"SubtitleTracks\":[" + subtitleTrack + "]}"; + + // + String targetFileName = UUID.randomUUID().toString().replace("-", ""); + String outputMediaUrl = "http://" + bucket + ".oss-" + regionId + ".aliyuncs.com/ice_output/" + targetFileName + ".mp4"; + int width = 720; + int height = 1280; + String outputMediaConfig = "{\"MediaURL\":\"" + outputMediaUrl + "\",\"Width\":"+width+",\"Height\":"+height+"}"; + + SubmitMediaProducingJobRequest request = new SubmitMediaProducingJobRequest(); + request.setTimeline(timeline); + request.setOutputMediaConfig(outputMediaConfig); + SubmitMediaProducingJobResponse response = iceClient.submitMediaProducingJob(request); + //ystem.out.println("start job. jobid: " + response.getBody().getJobId() + ", outputMediaUrl: " + outputMediaUrl); + return response.getBody().getJobId() + " : " + outputMediaUrl; + } + +} \ No newline at end of file diff --git a/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/media/controller/BatchProduceAlignmentController.java b/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/media/controller/BatchProduceAlignmentController.java new file mode 100644 index 0000000000..2c18b895cb --- /dev/null +++ b/yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/media/controller/BatchProduceAlignmentController.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.tik.media.controller; + + +import cn.iocoder.yudao.framework.common.exception.ErrorCode; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.tik.media.BatchProduceAlignment; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "BatchProduceAlignment - 视频混剪", description = "视频混剪相关接口") +@RestController +@RequestMapping("/api/media") +@RequiredArgsConstructor +@Slf4j +public class BatchProduceAlignmentController { + + @PostMapping("/batchProduceAlignment") + @Operation(summary = "视频混剪", description = "视频混剪") + public Object batchProduceAlignment( + //视频标题 + @RequestParam String title, + //文字素材 + @RequestParam String text, + // 视频素材 + @RequestParam String[] videoArray, + // 背景音乐素材 + @RequestParam String[] bgMusicArray, + // 生成的成片数 + int produceCount) { + try { + BatchProduceAlignment batchProduce = new BatchProduceAlignment(); + batchProduce.initClient(); + List jobIds = batchProduce.batchProduceAlignment(title,text,videoArray,bgMusicArray,produceCount); + return CommonResult.success(jobIds); + } catch (Exception e) { + return CommonResult.error(new ErrorCode(500, "Produce failed. Exception: " + e)); + } + + } + +}