du
2025-04-07 c66d8ad131d3643913b7ceb0ce5fd88766abda8c
Merge remote-tracking branch 'origin/master'
已修改4个文件
已添加1个文件
385 ■■■■■ 文件已修改
easegen-front/src/views/chooseTemplate/index.vue 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
easegen-front/src/views/digitalcourse/digitalhumans/DigitalHumansForm.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
easegen-front/src/views/digitalcourse/digitalhumans/LookDigitalHumansForm.vue 271 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
easegen-front/src/views/digitalcourse/digitalhumans/index.vue 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceUtil.java 75 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
easegen-front/src/views/chooseTemplate/index.vue
@@ -1566,14 +1566,15 @@
            warningStrArr.push(
              `场景<span style="color: red; font-weight: bold;">${i + 1}</span>无有效的口播内容`
            )
          } else {
            //判断去除标签后的内容长度是否超过2000字
            if (plainText.length > 2000) {
              warningStrArr.push(
                `场景<span style="color: red; font-weight: bold;">${i + 1}</span>口播内容超过2000字,请减少或拆分场景`
              )
            }
          }
          // else {
          //   //判断去除标签后的内容长度是否超过2000字
          //   if (plainText.length > 2000) {
          //     warningStrArr.push(
          //       `场景<span style="color: red; font-weight: bold;">${i + 1}</span>口播内容超过2000字,请减少或拆分场景`
          //     )
          //   }
          // }
        }
      }
easegen-front/src/views/digitalcourse/digitalhumans/DigitalHumansForm.vue
@@ -47,13 +47,6 @@
      <el-form-item v-if="formData.useModel == 2" :label="t('digitalhumans.video')" prop="videoUrl">
        <!-- åŽŸæœ¬è‡ªå¸¦çš„è§†é¢‘ä¸Šä¼  -->
        <UploadFile v-if="!(formData.videoUrl || formData.fixVideoUrl)" v-model="formData.videoUrl" :fileType="['mp4','mov']" :limit="1" @on-success="handleFileSuccess('videoUrl', $event)"/>
        <!-- åŽæœŸæ·»åŠ çš„åŽ»é™¤ç»¿å¹•çš„ -->
        <!-- <div v-if="!(formData.videoUrl || formData.fixVideoUrl)" >
          <CES2 @start="StartCes" @Thnd="End" msg="Welcome to Your Vue.js App"/>
          <div style="color: red;" v-show="isUploading"  >
              å½“前正在处理并上传中,请耐心等待...
          </div>
        </div> -->
        <!-- åŽŸè§†é¢‘æ’­æ”¾å™¨ -->
        <!-- <video-player v-if="formData.videoUrl || formData.fixVideoUrl" :property="videoProperty"/> -->
         <!-- æ–°çš„视频播放器 -->
@@ -105,7 +98,6 @@
import {VideoPlayerProperty} from "@/components/DiyEditor/components/mobile/VideoPlayer/config";
import { useUpload } from '@/components/UploadFile/src/useUpload'
import { el } from 'element-plus/es/locale';
import CES2 from './CES2.vue'
import { any } from 'vue-types';
const { t } = useI18n() // å›½é™…化
easegen-front/src/views/digitalcourse/digitalhumans/LookDigitalHumansForm.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,271 @@
<template>
    <Dialog :title="dialogTitle" v-model="dialogVisible">
      <el-form
        ref="formRef"
        :model="formData"
        :rules="formRules"
        label-width="120px"
        v-loading="formLoading"
      >
        <el-form-item :label="t('digitalhumans.name')" prop="name">
          <el-input v-model="formData.name" :placeholder="t('common.inputText') + t('digitalhumans.name')" />
        </el-form-item>
        <el-form-item :label="t('digitalhumans.code')" prop="code" v-if="false" > // å½“前数字人视频的编码类型
          <el-input v-model="formData.code" :placeholder="t('common.inputText') + t('digitalhumans.code')" />
        </el-form-item>
        <el-form-item :label="t('digitalhumans.gender')" prop="gender">
          <el-select v-model="formData.gender" :placeholder="t('common.selectText')+t('digitalhumans.gender')">
            <el-option
              v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
              :key="dict.value"
              :label="dict.label"
              :value="dict.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item :label="t('digitalhumans.useModel')" prop="useModel" v-if="false" > //数字人模式选择框
          <el-select v-model="formData.useModel" :placeholder="t('common.selectText')+t('digitalhumans.useModel')">
            <el-option
              v-for="dict in getIntDictOptions(DICT_TYPE.USE_MODEL)"
              :key="dict.value"
              :label="dict.label"
              :value="Number(dict.value)"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="背景样式" prop="isTransparent">
          <el-select v-model="formData.isTransparent" placeholder="请选择是否去除背景">
            <el-option value="1" label="透明背景"/>
            <el-option value="2" label="绿幕背景"/>
          </el-select>
        </el-form-item>
        <el-form-item v-if="formData.useModel == 1" :label="t('digitalhumans.picture')" prop="pictureUrl">
          <UploadImg v-if="formData" v-model="formData.fixPictureUrl" />
          <UploadImg v-else v-model="formData.pictureUrl" />
        </el-form-item>
        <!-- <el-form-item v-if="formData.useModel == 2" :label="t('digitalhumans.video')" prop="videoUrl"> -->
        <el-form-item v-if="false" :label="t('digitalhumans.video')" prop="videoUrl">
          <!-- åŽŸæœ¬è‡ªå¸¦çš„è§†é¢‘ä¸Šä¼  -->
          <UploadFile v-if="!(formData.videoUrl || formData.fixVideoUrl)" v-model="formData.videoUrl" :fileType="['mp4','mov']" :limit="1" @on-success="handleFileSuccess('videoUrl', $event)"/>
          <!-- åŽŸè§†é¢‘æ’­æ”¾å™¨ -->
          <!-- <video-player v-if="formData.videoUrl || formData.fixVideoUrl" :property="videoProperty"/> -->
           <!-- æ–°çš„视频播放器 -->
          <VideoPlayerMov  v-if="formData.videoUrl || formData.fixVideoUrl" :property="videoProperty"/>
        </el-form-item>
  <!--      <el-form-item :label="抠图标识" prop="matting">
          <el-select v-model="formData.matting" :placeholder="请选择抠图标识">
            <el-option
              v-for="dict in getIntDictOptions(DICT_TYPE.DIGITALCOURSE_DIGITALHUMAN_MATTING)"
              :key="dict.value"
              :label="dict.label"
              :value="dict.value"
            />
          </el-select>
        </el-form-item>-->
        <el-form-item :label="t('digitalhumans.posture')" prop="posture">
          <el-select v-model="formData.posture" :placeholder="t('common.selectText') + t('digitalhumans.posture')">
            <el-option
              v-for="dict in getIntDictOptions(DICT_TYPE.DIGITALCOURSE_DIGITALHUMAN_POSTURE)"
              :key="dict.value"
              :label="dict.label"
              :value="dict.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item :label="t('digitalhumans.type')" prop="type">
          <el-select v-model="formData.type" :placeholder="t('common.selectText') + t('digitalhumans.type')">
            <el-option
              v-for="dict in getIntDictOptions(DICT_TYPE.DIGITALCOURSE_DIGITALHUMAN_TYPE)"
              :key="dict.value"
              :label="dict.label"
              :value="dict.value"
            />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="submitForm" type="primary" :disabled="formLoading" :loading="isUploading" >{{t('common.ok')}}</el-button>
        <el-button @click="dialogVisible = false">{{t('common.cancel')}}</el-button>
      </template>
    </Dialog>
  </template>
  <script setup lang="ts">
  import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
  import * as DigitalHumansApi from '@/api/digitalcourse/digitalhumans'
  import VideoPlayer from "@/components/DiyEditor/components/mobile/VideoPlayer/index.vue";
  import VideoPlayerMov from "@/components/DiyEditor/components/mobile/VideoPlayer_mov/index.vue";
  import {DiyComponent} from "@/components/DiyEditor/util";
  import {VideoPlayerProperty} from "@/components/DiyEditor/components/mobile/VideoPlayer/config";
  import { useUpload } from '@/components/UploadFile/src/useUpload'
  import { el } from 'element-plus/es/locale';
  import { any } from 'vue-types';
  const { t } = useI18n() // å›½é™…化
  const message = useMessage() // æ¶ˆæ¯å¼¹çª—
  const { uploadUrl, httpRequest } = useUpload() //上传方法
  const dialogVisible = ref(false) // å¼¹çª—的是否展示
  const dialogTitle = ref('') // å¼¹çª—的标题
  const formLoading = ref(false) // è¡¨å•的加载中:1)修改时的数据加载;2)提交的按钮禁用
  const formType = ref('') // è¡¨å•的类型:create - æ–°å¢žï¼›update - ä¿®æ”¹
  const formData = ref({
    id: undefined,
    expireStatus: undefined,
    finishTime: undefined,
    gender: undefined,
    matting: undefined,
    name: undefined,
    code: undefined,
    pictureUrl: undefined,
    posture: undefined,
    snapshotHeight: undefined,
    snapshotUrl: undefined,
    snapshotWidth: undefined,
    type: undefined,
    useGeneralModel: undefined,
    useModel: undefined,
    status: undefined,
    isTransparent: undefined,
  })
  // å½“前是否正在上传视频
  const isUploading = ref(false)
  const videoProperty = {
    videoUrl: '',
    posterUrl: '',
    autoplay: false,
    style: {
      bgType: 'color',
      bgColor: '#fff',
      marginBottom: 8,
      height: 300
    }
  } as DiyComponent<VideoPlayerProperty>
  watch(()=> formData.value.videoUrl,(newVal,oldValue)=>{
    if (newVal && newVal.length > 0){
      videoProperty.videoUrl = formData.value.fixVideoUrl || newVal
    }
  })
  const formRules = reactive({
    gender: [{ required: true, message: '性别不能为空', trigger: 'change' }],
    name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
    pictureUrl: [{ required: true, message: '图片URL不能为空', trigger: 'blur' }],
    posture: [{ required: true, message: '姿势不能为空', trigger: 'change' }],
    snapshotHeight: [{ required: true, message: '快照高度不能为空', trigger: 'blur' }],
    snapshotUrl: [{ required: true, message: '快照URL不能为空', trigger: 'blur' }],
    snapshotWidth: [{ required: true, message: '快照宽度不能为空', trigger: 'blur' }],
    type: [{ required: true, message: '类型不能为空', trigger: 'change' }],
    useGeneralModel: [{ required: true, message: '使用通用模型不能为空', trigger: 'change' }],
    isTransparent: [{ required: true, message: '是否去除背景不能为空', trigger: 'change' }],
    status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
    videoUrl: [{ required: true, message: '视频不能为空', trigger: 'blur' }]
  })
  const formRef = ref() // è¡¨å• Ref
  const StartCes = () => {
    console.log( " ----- å¼€å§‹ ----- " )
    isUploading.value = true
  }
  const End = (res)=>{
    const FileObject = {
      file:res
    }
    httpRequest( FileObject ).then( response =>{
      console.log(response)
      formData.value.videoUrl = response.data;
    } ).finally( res => {
      isUploading.value = false
    } )
  }
  /** æ‰“开弹窗 */
  const open = async (type: string, id?: number) => {
    dialogVisible.value = true
    dialogTitle.value = t('action.' + type)
    formType.value = type
    resetForm()
    // ä¿®æ”¹æ—¶ï¼Œè®¾ç½®æ•°æ®
    if (id) {
      formLoading.value = true
      try {
        formData.value = await DigitalHumansApi.getDigitalHumans(id)
      } finally {
        formLoading.value = false
      }
    }else{
      InitHumMODEL()
    }
  }
  defineExpose({ open }) // æä¾› open æ–¹æ³•,用于打开弹窗
  /** æäº¤è¡¨å• */
  const emit = defineEmits(['success']) // å®šä¹‰ success äº‹ä»¶ï¼Œç”¨äºŽæ“ä½œæˆåŠŸåŽçš„å›žè°ƒ
  const submitForm = async () => {
    // æ ¡éªŒè¡¨å•
    await formRef.value.validate()
    // æäº¤è¯·æ±‚
    formLoading.value = true
    try {
      const data = formData.value as unknown as DigitalHumansApi.DigitalHumansVO
      if (formType.value === 'create') {
        await DigitalHumansApi.createDigitalHumans(data)
        message.success(t('common.createSuccess'))
      } else {
        await DigitalHumansApi.updateDigitalHumans(data)
        message.success(t('common.updateSuccess'))
      }
      dialogVisible.value = false
      // å‘送操作成功的事件
      emit('success')
    } finally {
      formLoading.value = false
    }
  }
  /** é‡ç½®è¡¨å• */
  const resetForm = () => {
    formData.value = {
      id: undefined,
      expireStatus: undefined,
      finishTime: undefined,
      gender: undefined,
      matting: undefined,
      name: undefined,
      code: undefined,
      pictureUrl: undefined,
      posture: undefined,
      snapshotHeight: undefined,
      snapshotUrl: undefined,
      snapshotWidth: undefined,
      type: undefined,
      useGeneralModel: undefined,
      useModel: undefined,
      status: undefined,
    }
    formRef.value?.resetFields()
  }
  const handleFileSuccess = (fileType,response) => {
    if (fileType === 'videoUrl') {
      formData.value.videoUrl = response.data;
    }
  };
  // ä¿®æ”¹é»˜è®¤çš„æ•°å­—人视频模式为视频
  const InitHumMODEL = ()=>{
    let ModelList = getIntDictOptions( DICT_TYPE.USE_MODEL )
    for (let index = 0; index < ModelList.length; index++) {
      const element = ModelList[index];
      if( element.label === "视频" ){
        formData.value.useModel = Number( element.value )
      }
    }
  }
  </script>
easegen-front/src/views/digitalcourse/digitalhumans/index.vue
@@ -173,7 +173,7 @@
          <el-button
            link
            type="primary"
            @click="openForm('detail', scope.row.id)"
            @click="OpenLookformRef('detail', scope.row.id)"
            v-hasPermi="['digitalcourse:digital-humans:delete']"
          >
            {{t('digitalhumans.view')}}
@@ -190,8 +190,10 @@
    />
  </ContentWrap>
  <!-- è¡¨å•弹窗:添加/修改 -->
  <!-- è¡¨å•弹窗:添加 -->
  <DigitalHumansForm ref="formRef" @success="getList" />
  <!-- è¡¨å•弹窗:修改 -->
  <LookDigitalHumansForm ref="LookformRef" @success="getList" />
  <!-- å¤„理 -->
  <AuditForm ref="auditFormRef" @success="getList" />
</template>
@@ -203,6 +205,7 @@
import download from '@/utils/download'
import * as DigitalHumansApi from '@/api/digitalcourse/digitalhumans'
import DigitalHumansForm from './DigitalHumansForm.vue'
import LookDigitalHumansForm from './LookDigitalHumansForm.vue'
import AuditForm from './AuditForm.vue'
import { useUserStoreWithOut } from '@/store/modules/user'
const userStore = useUserStoreWithOut() // ç”¨æˆ·ä¿¡æ¯ç¼“å­˜
@@ -260,13 +263,20 @@
  handleQuery()
}
/** æ·»åŠ /修改操作 */
/** æ·»åŠ æ“ä½œ */
const formRef = ref()
const auditFormRef = ref()
const openForm = (type: string, id?: number) => {
  formRef.value.open(type, id)
}
/* ä¿®æ”¹æ“ä½œ */
const LookformRef = ref()
const OpenLookformRef = ( type: string, id?:number ) => {
  LookformRef.value.open( type, id )
}
const openAuditForm = (type: string, id?: number) => {
  auditFormRef.value.open(type, id)
}
yudao-module-digitalcourse/yudao-module-digitalcourse-biz/src/main/java/cn/iocoder/yudao/module/digitalcourse/service/coursemedia/CourseMediaServiceUtil.java
@@ -26,16 +26,15 @@
import org.springframework.validation.annotation.Validated;
import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -97,7 +96,7 @@
        AuditionVO auditionVO = new AuditionVO();
        auditionVO.setHumanId(String.valueOf(digitalHumansDO.getId()));
        for (AppCourseScenesMegerReqVO scene : scenes) {
            //TODO å…ˆåˆ¤æ–­æ˜¯å¦æœ‰å¤‡æ³¨å†…容
            auditionVO.setText(scene.getBackground().getPptRemark());
            String audition = serviceImpl.audition(auditionVO);
            // æå–音频文件名(路径的最后一部分)
@@ -107,32 +106,58 @@
            // æå–视频文件名(路径的最后一部分)
            String substring1 = configApi.getConfigValueByKey("easegen.url") + digitalHumansDO.getFixVideoUrl().substring(digitalHumansDO.getFixVideoUrl().lastIndexOf("/"));
            String fileName1 = digitalHumansDO.getFixVideoUrl().substring(digitalHumansDO.getFixVideoUrl().lastIndexOf('/') + 1);
            String newFileName1 = "D:/heygem_data/face2face/temp/" + fileName1;
            String newFileName1 = "D:/heygem_data/face2face/temp/"+fileName1;
            //获取时间戳
            Date date = new Date();
            long timestamp = date.getTime();
            String newFileName2 = "D:/heygem_data/face2face/temp/"+timestamp+".mp4";
            //获取PPT内容
            String cover = scene.getComponents().get(1).getCover();
            String cover = scene.getComponents().get(1).getSrc();
            cover = configApi.getConfigValueByKey("easegen.url") + scene.getComponents().get(1).getSrc().substring(scene.getComponents().get(1).getSrc().lastIndexOf("/"));
            //获取背景
            String cover1 = scene.getBackground().getCover();
            cover1 = configApi.getConfigValueByKey("easegen.url") + scene.getBackground().getCover().substring(scene.getBackground().getCover().lastIndexOf("/"));
            // åŽ»æŽ‰æ‰©å±•å
            int dotIndex = fileName1.lastIndexOf('.');
            String substring2 = fileName1.substring(0, dotIndex);
            // åˆæˆppt背景,视频,模板
            //ffmpeg -i 2.png -i 1.mp4 -filter_complex "[0:v]scale=w=ceil(iw/2)*2:h=ceil(ih/2)*2[bg];[1:v]scale=iw/2:ih/2[v1];[bg][v1]overlay=x=0:y=H-h" output.mp4
            ProcessBuilder builder = new ProcessBuilder(
                    "ffmpeg", "-i", cover1,"-i",cover,"-i",substring1,"-filter_complex","[0:v]scale=",scene.getBackground().getWidth().toString(),":",scene.getBackground().getHeight().toString(),"[bg];[1:v]scale=",scene.getComponents().get(1).getWidth().toString(),":",scene.getComponents().get(1).getHeight().toString(),"[v1];[bg][v1]overlay=x=",scene.getComponents().get(1).getMarginLeft().toString(),":y=",scene.getComponents().get(1).getTop().toString(),"[img];[2:v]scale=",scene.getComponents().get(0).getWidth().toString(),":",scene.getComponents().get(0).getHeight().toString(),"[v2];[img][v2]overlay=x=",scene.getComponents().get(0).getMarginLeft().toString(),":y=",scene.getComponents().get(0).getTop().toString(),newFileName1
                    "ffmpeg",
                    "-i", cover1,
                    "-i", cover,
                    "-i", substring1,
                    "-filter_complex",
                    "[0:v]scale=" + Math.round(scene.getBackground().getWidth()) + ":" + Math.round(scene.getBackground().getHeight()) + "[bg];" +
                            "[1:v]scale=" + Math.round(scene.getComponents().get(1).getWidth()) + ":" + Math.round(scene.getComponents().get(1).getHeight()) + "[v1];" +
                            "[bg][v1]overlay=x=" + Math.round(scene.getComponents().get(1).getMarginLeft()) + ":y=" + Math.round(scene.getComponents().get(1).getTop()) + "[img];" +
                            "[2:v]scale=" + Math.round(scene.getComponents().get(0).getWidth()) + ":" + Math.round(scene.getComponents().get(0).getHeight()) + "[v2];" +
                            "[img][v2]overlay=x=" + Math.round(scene.getComponents().get(0).getMarginLeft()) + ":y=" + Math.round(scene.getComponents().get(0).getTop()),
                    newFileName2
            );
            System.out.println(newFileName2);
            builder.redirectErrorStream(true);
            Process process = null;
            try {
                process = builder.start();
                // è¯»å– FFmpeg è¾“出(可选)
                BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                System.out.println(builder.command());
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            // è¯»å– FFmpeg è¾“出(可选)
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            try {
                Files.copy(Path.of(substring), Path.of(newFileName), StandardCopyOption.REPLACE_EXISTING);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            //最大重试次数
            int maxRetries = 3;
            // å½“前重试次数
@@ -145,8 +170,8 @@
                    HashMap<String, Object> objectObjectHashMap = new HashMap<>();
                    //音频路径
                    objectObjectHashMap.put("audio_url", fileName);
                    //视频路径(无声)
                    objectObjectHashMap.put("video_url", fileName1);
                    //视频路径
                    objectObjectHashMap.put("video_url",timestamp+".mp4");
                    //唯一key(用于查询)
                    String code = RandomUtil.randomString(32);
                    //固定值
@@ -191,10 +216,10 @@
                    String result = getResult(code);
                    result = "D:/heygem_data/face2face/temp" + result;
                    videoUrls.add(result);
                    System.out.println("驱动视频名"+result);
                    // å¦‚果成功,更新状态为1(成功)
                    courseMediaDO.setStatus(1);
                    courseMediaMapper.updateById(courseMediaDO);
                    success = true;
                } catch (Exception e) {
                    retryCount++;
                    if (retryCount >= maxRetries) {
@@ -213,7 +238,9 @@
                        break;
                    }
                }
                success = true;
            }
        }
        String fileListPath = "D:/heygem_data/face2face/temp/filelist.txt";
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileListPath))) {
@@ -224,13 +251,24 @@
        } catch (IOException e) {
            e.printStackTrace();
        }
        //去掉updateReqVO.getName()中的空格和特殊字符
        String newFileName = updateReqVO.getName().replaceAll("[\\s\\p{Punct}]", "");
        ProcessBuilder builder = new ProcessBuilder(
                "ffmpeg", "-f", "concat", "-safe","0", "-i", "DD:/heygem_data/face2face/temp/filelist.txt", "-c", "copy", "D:/heygem_data/face2face/temp/生成视频.mp4"
                "ffmpeg", "-f", "concat", "-safe","0", "-i",fileListPath , "-c", "copy", "D:/heygem_data/face2face/temp/"+"111111.mp4"
        );
        builder.redirectErrorStream(true);
        Process process = null;
        try {
            process = builder.start();
            // è¯»å– FFmpeg è¾“出(可选)
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            System.out.println(builder.command());
            System.out.println("最终视频已生成");
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
@@ -238,6 +276,13 @@
    }
    // å››èˆäº”入方法
    private static int round(double value) {
        return BigDecimal.valueOf(value)
                .setScale(0, RoundingMode.HALF_UP)
                .intValue();
    }
    public String getResult(String taskCode) {
        // ä½¿ç”¨ do-while å¾ªçŽ¯è½®è¯¢ä»»åŠ¡çŠ¶æ€
        // å®šä¹‰å˜é‡å­˜å‚¨è¿”回结果