<script setup lang="ts"> import { onMounted, reactive, ref, nextTick } from "vue"; import { Sunny, UploadFilled } from "@element-plus/icons-vue"; import { ElMessage, genFileId, type UploadInstance, type UploadProps, type UploadRawFile } from "element-plus"; import text2videoService from "@/api/service/text2videoService"; import utils from "@/utils/utils"; import { useManyValues } from './compositions/useManyValues' const debug = ref(import.meta.env.MODE === 'production' ? false : true); const loading = ref(false); const dialogVisible = ref(false); const dialogData = ref(""); const default_data = useManyValues(); const form = reactive({ screen: default_data.screen, if_need_subtitle: default_data.if_need_subtitle, chatgpt_prompt: "", chatgpt_answer: "", chatgpt_answer_roles: <Wm.RolesItem[]>[], all_roles: "", adapt_result_json: <Wm.ScriptsItem[]>[], task_id: "", final_video: "", }); const sd_prompt_prefix = default_data.sd_prompt_prefix; const sd_negative_prompt_prefix = default_data.sd_negative_prompt_prefix; const wen_an_llm = default_data.llms.tyqw_online; const role_llm = default_data.llms.tyqw_online; const role_keywords_llm = default_data.llms.qwen_local; const tuili_llm = default_data.llms.qwen_local; const tuili_keyword_llm = default_data.llms.tyqw_online; const fanyi_llm = default_data.llms.qwen_local; const voice_rate = ref(-10) const voice_volume = ref(0) const voice = ref("zh-CN-YunjianNeural") const bgm = ref("解忧曲") const bgm_volume = ref(0.3) const pwdCheckDialogVisible = ref(false); const pwdCheckValue = ref("") const sub_font_color = ref("#FFFF00") const sub_font_size = ref(25) const sub_position = ref(0.4) onMounted(() => { // 初始化示例数据 onChangeScreen(form.screen); // 初始化密码框 if (debug.value == true) { pwdCheckDialogVisible.value = false; } else { pwdCheckDialogVisible.value = true; } }); const delay = (ms: any) => new Promise(res => setTimeout(res, ms)); const onSubmitGpt = () => { text2videoService .submitLLM(form.chatgpt_prompt, wen_an_llm.api) .then((result: string) => { console.log(form.chatgpt_prompt); console.log(result); form.chatgpt_answer = result; }) .catch((error: any) => { // console.error(error); ElMessage({ message: error, type: "error", }); }); }; const onAdaptRoles = async () => { if (!form.chatgpt_answer || form.chatgpt_answer.length == 0) { ElMessage({ message: "文案不能为空", type: "error", }); return; } // 推理角色 form.chatgpt_answer_roles = []; const adapt_restrict = `请理解这个故事:“${form.chatgpt_answer}”,给出这个故事中的所有角色,多个角色以逗号分隔。\n要求:只返回角色名称即可,不要添加其他的内容。`; let roles = await text2videoService.submitLLM(adapt_restrict, role_llm.api); form.all_roles = roles.replace(/。|(|)/g, '').replace(/、/g, ','); console.log(form.all_roles); const roles_arr = form.all_roles.split(/[,,]/); console.log(roles_arr); // 推理属性 const attribute_options = default_data.role_attribute_options.map(option => option.value); for (const one_role of roles_arr) { // const adapt_attribute_restrict = `根据这个故事:“${form.chatgpt_answer}”,你认为这个角色:“${one_role}”是“${String(attribute_options)}”中的哪一种?请返回选择的结果。\n要求:只返回选择的结果即可,不要添加其他的内容。`; // const adapt_attribute_restrict = `你认为这个角色:“${one_role}”是“${String(attribute_options)}”中的哪一种?请返回选择的结果。\n要求:只返回选择的结果即可,不要添加其他的内容。`; const adapt_attribute_restrict = `故事全文:“${form.chatgpt_answer}”,请理解故事全文,然后判断:“${one_role}”是“${String(attribute_options)}”中的哪一种?请返回选择的结果。\n要求:只返回选择的结果即可,不要添加其他的内容。`; let attribute = await text2videoService.submitLLM(adapt_attribute_restrict, role_keywords_llm.api); form.chatgpt_answer_roles.push({ "角色": one_role.trim(), "角色关键词": "", "角色英文关键词": "", "属性": attribute, }); } console.log(form.chatgpt_answer_roles) }; const onAdaptRolesKeywords = async () => { if (!form.chatgpt_answer_roles || form.chatgpt_answer_roles.length == 0) { ElMessage({ message: "总角色不能为空", type: "error", }); return; } for (const one_role of form.chatgpt_answer_roles) { if (!one_role.属性) { ElMessage({ message: `请选择 ${one_role.角色} 的属性`, type: "error", }); return; } } loading.value = true; // 推理角色关键词 try { async function processRoles() { for (const one_role of form.chatgpt_answer_roles) { await delay(100); let adapt_keyword_restrict = `请理解这个故事:“${form.chatgpt_answer}”,给出其中这个角色“${one_role.角色}”的关键词,以逗号分隔。包括但不限于:`; let temp_restrict = ""; if (one_role.属性.includes("人")) { temp_restrict = `性别(可以发挥想象进行补充,但一定要有), 年龄(可以发挥想象进行补充,但一定要有), 肤色(可以发挥想象进行补充,但一定要有), 衣服(可以发挥想象进行补充,但一定要有), 发型(可以发挥想象进行补充,但一定要有), 发色(可以发挥想象进行补充,但一定要有), 脸色(可以发挥想象进行补充,但一定要有), 五官特点(可以发挥想象进行补充,但一定要有)`; } else { let temp_restrict_pre = ""; if (one_role.属性.includes("动物")) { temp_restrict_pre = `哪一种动物(可以发挥想象进行补充,但一定要有),` } temp_restrict = `${temp_restrict_pre} 体型(可以发挥想象进行补充,但一定要有), 颜色(可以发挥想象进行补充,但一定要有), 四肢(可以发挥想象进行补充,但一定要有), 五官特点(可以发挥想象进行补充,但一定要有), 身体(皮毛,羽毛,鳞片,肤色等,可以发挥想象进行补充,没有就不提供)`; } adapt_keyword_restrict = adapt_keyword_restrict + temp_restrict + "。回答限制在30个字左右。"; let keywords = await text2videoService.submitLLM(adapt_keyword_restrict, role_keywords_llm.api); keywords = keywords.replace(/。/g, '').replace(/、/g, ','); one_role.角色关键词 = keywords; await delay(100); const adapt_attribute_restrict_en = `你现在扮演专业的英语翻译的角色。请将这段文字“${keywords}”翻译为英语。\n要求:只返回英语即可,不要返回其他内容。`; let keywords_en = await text2videoService.submitLLM(adapt_attribute_restrict_en, role_keywords_llm.api); keywords_en = utils.filterChineseAndPunctuation(keywords_en.replace(/"/g, '')); one_role.角色英文关键词 = keywords_en; } } try { await processRoles(); console.log(form.chatgpt_answer_roles) } catch (error) { ElMessage({ message: String(error), type: "error" }); } } catch (error) { ElMessage({ message: String(error), type: "error", }); } finally { // 最终关闭loading(无论成功或失败) loading.value = false; } }; const onAdapt = async () => { if (!form.chatgpt_answer || form.chatgpt_answer.length == 0) { ElMessage({ message: "文案不能为空", type: "error", }); return; } form.task_id = utils.genDateTimeStr(); console.log(form.task_id) // 按标点拆分成分镜 const sentences = utils.splitText(form.chatgpt_answer); console.log(sentences.length) // 分镜 form.adapt_result_json = [] for (let i = 0; i < sentences.length; i++) { form.adapt_result_json.push({ "编号": (i + 1).toString(), "场景描述": sentences[i].trim(), "场景关键词": "", "角色": "", "角色关键词": "", "画面描述词": "", "本镜配图": "src/assets/waiting.png", "local_image_path": "", }); } console.log(form.adapt_result_json) } const onAdaptScene = async () => { if (!form.adapt_result_json || form.adapt_result_json.length == 0) { ElMessage({ message: "分镜不能为空", type: "error", }); return; } loading.value = true; async function processScenes() { for (const item of form.adapt_result_json) { await onAdaptOneScene(item); } } try { await processScenes(); ElMessage({ message: "all scene ok", type: "success" }); console.log(form.adapt_result_json); } catch (error) { ElMessage({ message: String(error), type: "error" }); } finally { loading.value = false; // 最终关闭loading(无论成功或失败) } }; const onAdaptOneScene = async (item: any) => { if (!item.场景描述) { ElMessage({ message: `分镜 ${item.编号} 场景描述不能为空`, type: "error", }); return; } // 推理场景 try { const adapt_restrict = `你现在扮演专业的英语翻译的角色。请将这段文字“${item.场景描述}”翻译为英语。\n要求:只返回英语即可,不要返回其他内容。`; const keywords = await text2videoService.submitLLM(adapt_restrict, tuili_llm.api); // console.log(keywords) item.场景关键词 = utils.filterChineseAndPunctuation(keywords); } catch (error) { ElMessage({ message: String(error), type: "error", }); } }; const onAdaptSceneRoles = async () => { if (!form.adapt_result_json || form.adapt_result_json.length == 0) { ElMessage({ message: "分镜不能为空", type: "error", }); return; } if (!form.all_roles) { ElMessage({ message: `总角色不能为空`, type: "error", }); return; } loading.value = true; async function processScenes() { for (const item of form.adapt_result_json) { await onAdaptOneSceneRoles(item); } } try { await processScenes(); console.log(form.adapt_result_json); } catch (error) { ElMessage({ message: String(error), type: "error" }); } finally { loading.value = false; // 最终关闭loading(无论成功或失败) } }; const onAdaptOneSceneRoles = async (item: any) => { if (!item.场景描述) { ElMessage({ message: `分镜 ${item.编号} 场景描述不能为空`, type: "error", }); return; } if (!form.all_roles) { ElMessage({ message: `总角色不能为空`, type: "error", }); return; } // 推理角色 try { if (form.chatgpt_answer_roles.length === 0) { // 总角色为空 item.角色 = ''; item.角色关键词 = ''; } else { // 总角色不为空 // const adapt_role_restrict = `请理解这个故事:“${form.chatgpt_answer}”,针对其中的这个场景:“${item.场景描述}”,从所有角色:“${form.all_roles}”中选择本场景的角色,多个角色以逗号分隔。`; const adapt_role_restrict = `整个故事(“${form.chatgpt_answer}),\n\n本章节(${item.场景描述}),\n\n角色列表(${form.all_roles}),\n\n你是程序员,请返回给本章节出现的角色,注意,不要发挥想象,必须从角色列表中选出本章节出现过的角色,如果多个用逗号隔开。`; const item_roles = await text2videoService.submitLLM(adapt_role_restrict, tuili_keyword_llm.api); // console.log(adapt_role_restrict) // console.log(item_roles) item.角色 = item_roles.trim(); let role_kws = "" const item_roles_arr = item_roles.split(/[,,、]/); item_roles_arr.forEach( one_item_role => { let temp_role_kws = "" // 人工匹配角色关键词,先找想同的 for (const i of form.chatgpt_answer_roles) { if (i["角色"].trim() == one_item_role.trim()) { temp_role_kws = `[${i["角色英文关键词"]}]`; // 找到就ok break; } } // 如果找不到相同的,则模糊匹配 if (! temp_role_kws) { for (const i of form.chatgpt_answer_roles) { if (i["角色"].includes(one_item_role.trim()) || one_item_role.includes(i["角色"].trim())) { temp_role_kws = `[${i["角色英文关键词"]}]`; // 匹配到一个就ok break; } } } role_kws = `${role_kws}${temp_role_kws}`; }) item.角色关键词 = role_kws; } } catch (error) { ElMessage({ message: String(error), type: "error", }); } }; const onDraw = async () => { if (!form.adapt_result_json || form.adapt_result_json.length == 0) { ElMessage({ message: "分镜不能为空", type: "error", }); return; } let is_all_ok = true; for (const item of form.adapt_result_json) { if (!item.场景关键词) { ElMessage({ message: `分镜 ${item.编号} 场景关键词不能为空`, type: "error", }); is_all_ok = false; break; } }; if (is_all_ok) { for (const item of form.adapt_result_json) { onDrawOne(item); } } }; const onDrawOne = async (item: any) => { if (!item.场景关键词) { ElMessage({ message: "场景关键词不能为空", type: "error", }); return; } // 翻译+画图 if (!form.task_id) { form.task_id = utils.genDateTimeStr(); console.log(form.task_id) } try { item.本镜配图 = "src/assets/loading.gif"; // let temp_prompt = "" // if (item.场景描述) {temp_prompt = temp_prompt + `场景描述为:${item.场景描述}\n`}; // if (item.场景关键词) {temp_prompt = temp_prompt + `场景关键词为:${item.场景关键词}\n`}; // if (item.角色) {temp_prompt = temp_prompt + `场景中的角色有:${item.角色}\n`}; // if (item.角色关键词) {temp_prompt = temp_prompt + `角色关键词为:${item.角色关键词}\n`}; // const sd_describe = await text2videoService.submitLLM( // `${temp_prompt} // 指令: // 请理解以上内容,并返回一段英文的描述。`, fanyi_llm.api // ); // item.画面描述词 = sd_describe; item.画面描述词 = item.场景关键词 + "," + item.角色关键词; const sd_prompt = item.画面描述词 + "," + sd_prompt_prefix; let width = "960"; let height = "540"; if (form.screen == "竖屏") { width = "540"; height = "960"; } // console.log(sd_prompt); // console.log(sd_negative_prompt_prefix); const sampler_index = "DPM++ SDE Karras"; const seed = "-1"; const steps = "6"; const cfg_scale = "2"; const sd_img = await text2videoService.submitSD(form.task_id, item.编号, sd_prompt, sd_negative_prompt_prefix, width, height, sampler_index, seed, steps, cfg_scale); item.本镜配图 = sd_img.domain_image_path+"?v="+utils.genDateTimeStr(); item.local_image_path = sd_img.local_image_path; } catch (error) { ElMessage({ message: String(error), type: "error", }); item.本镜配图 = "" } }; const onGenVideo = () => { if (!form.adapt_result_json || form.adapt_result_json.length == 0 ) { ElMessage({ message: "必要信息不能为空,请重新执行", type: "error", }); return; } let is_all_ok = true; form.adapt_result_json.map(item => { if (item.编号 == "" || item.场景描述 == "" || item.local_image_path == "") { ElMessage({ message: `分镜 ${item.编号} 的必要信息为空,请重新执行`, type: "error", }); is_all_ok = false; } }); if (!is_all_ok) return; console.log(form.adapt_result_json); const video_param_detail = form.adapt_result_json.map(item => { return { idx: item.编号, text: item.场景描述, img_path: item.local_image_path }; }); let para_rate = `${voice_rate.value}%`; let para_volume = `${voice_volume.value}%`; if(voice_rate.value >= 0){para_rate = `+${para_rate}`} if(voice_volume.value >= 0){para_volume = `+${para_volume}`} const video_param = { task_id: form.task_id, if_need_subtitle: form.if_need_subtitle, lang: "zh", task_info: video_param_detail, rate: para_rate, volume: para_volume, voice: voice.value, bgm: bgm.value, bgm_volume: bgm_volume.value, sub_font_size: String(sub_font_size.value), sub_font_color: sub_font_color.value, sub_position: String(1 - sub_position.value), } text2videoService .submitGenVideo(video_param) .then((result: string) => { console.log(result); form.final_video = ""; form.final_video = result+"?v="+utils.genDateTimeStr(); }) .catch((error: any) => { // console.error(error); ElMessage({ message: error, type: "error", }); }); }; const clean_demo = () => { form.chatgpt_prompt = ""; form.chatgpt_answer = ""; form.chatgpt_answer_roles = <Wm.RolesItem[]>[]; form.adapt_result_json = <Wm.ScriptsItem[]>[]; form.task_id = ""; form.final_video = ""; } const clean_roles = () => { form.chatgpt_answer_roles = <Wm.RolesItem[]>[]; } const clean_scenes = () => { form.adapt_result_json = <Wm.ScriptsItem[]>[]; } const onChangeScreen = (val: string) => { if (debug.value == true) { if (val == "横屏") { form.task_id = default_data.horizontal_data.task_id; form.chatgpt_prompt = default_data.horizontal_data.chatgpt_prompt; form.chatgpt_answer = default_data.horizontal_data.chatgpt_answer; form.chatgpt_answer_roles = default_data.horizontal_data.chatgpt_answer_roles; form.adapt_result_json = default_data.horizontal_data.adapt_result_json; form.final_video = default_data.horizontal_data.final_video; } else { form.task_id = default_data.vertical_data.task_id; form.chatgpt_prompt = default_data.vertical_data.chatgpt_prompt; form.chatgpt_answer = default_data.vertical_data.chatgpt_answer; form.chatgpt_answer_roles = default_data.vertical_data.chatgpt_answer_roles; form.adapt_result_json = default_data.vertical_data.adapt_result_json; form.final_video = default_data.vertical_data.final_video; } } } const showsdprompt = (item: any) => { // alert(item.画面描述词) // dialogData.value = `${item.画面描述词},${sd_prompt_prefix}===== 反向提示词 =====${sd_negative_prompt_prefix}`; dialogData.value = `${item.场景关键词},${item.角色关键词},${sd_prompt_prefix}===== 反向提示词 =====${sd_negative_prompt_prefix}`; dialogVisible.value = true; // 打开对话框 } const upload = ref<UploadInstance>() const actionUrl = ref( import.meta.env.MODE === 'production' ? '/file' : import.meta.env.VITE_APP_BASE_API + '/file' ) const handleUploadSuccess = (val: Wm.UploadResult) => { if (val.code == 0){ // console.log(val) const id = parseInt(val.message) - 1; form.adapt_result_json[id].本镜配图 = val.data[0].url+"?v="+utils.genDateTimeStr(); form.adapt_result_json[id].local_image_path = val.data[0].path; ElMessage({ message: '上传成功', type: 'success' }) } else { ElMessage({ message: '上传失败', type: 'error' }) } } const handleExceed: UploadProps['onExceed'] = (files) => { upload.value!.clearFiles() const file = files[0] as UploadRawFile file.uid = genFileId() upload.value!.handleStart(file) upload.value!.submit() } const onPwdCheckDialog = () => { text2videoService .submitPwdCheck(pwdCheckValue.value) .then((result: string) => { if (result == "success") { pwdCheckDialogVisible.value = false; } else { ElMessage({ message: result, type: "error", }); } }) .catch((error: any) => { ElMessage({ message: error, type: "error", }); }); } const onDeleteOne = (item: any) => { try { let delete_no = item.编号; // 删除记录 form.adapt_result_json = form.adapt_result_json.filter(item => item.编号 !== delete_no); // 重新对记录进行编号 form.adapt_result_json = form.adapt_result_json.map((item, index) => { return {...item, 编号: (index + 1).toString()}; }); } catch (error) { ElMessage({ message: String(error), type: "error", }); } }; </script> <template> <main class="home-container"> <!-- 标题 --> <el-divider content-position="left">text2video</el-divider> <el-form :model="form" label-width="114px" v-loading="loading"> <el-form-item> <div> <el-radio-group v-model="form.screen" @change="onChangeScreen"> <el-radio label="横屏" size="large" border/> <el-radio label="竖屏" size="large" border/> </el-radio-group> </div> </el-form-item> <el-form-item> <el-button type="success" @click="clean_demo">清除所有数据</el-button> </el-form-item> <!-- Prompt到文案 --> <el-form-item label="Prompt"> <el-input v-model="form.chatgpt_prompt" :autosize="true" type="textarea" /> </el-form-item> <el-form-item> <el-button type="primary" @click="onSubmitGpt">生成文案({{wen_an_llm.name}})</el-button> </el-form-item> <el-form-item label="文案"> <el-input v-model="form.chatgpt_answer" :autosize="true" type="textarea" /> </el-form-item> <!-- 角色 --> <el-form-item> <el-button type="primary" @click="onAdaptRoles">推理角色({{role_llm.name}})</el-button> <el-button type="primary" @click="onAdaptRolesKeywords">推理角色关键词({{role_keywords_llm.name}})</el-button> <el-button plain @click="clean_roles">清空总角色列表</el-button> </el-form-item> <el-form-item label="角色"> <el-table :data="form.chatgpt_answer_roles" border style="width: 100%; z-index: calc(var(--el-table-index) -1)"> <el-table-column prop="角色" label="角色" width="300"> <template v-slot="scope"> <el-input v-model="scope.row.角色" :autosize="true" type="textarea"></el-input> </template> </el-table-column> <el-table-column prop="属性" label="属性" width="300"> <template v-slot="scope"> <el-select v-model="scope.row.属性" filterable allow-create :reserve-keyword="false"> <el-option v-for="item in default_data.role_attribute_options" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </template> </el-table-column> <el-table-column prop="角色关键词" label="角色关键词"> <template v-slot="scope"> <el-input v-model="scope.row.角色关键词" :autosize="true" type="textarea"></el-input> </template> </el-table-column> <el-table-column prop="角色英文关键词" label="角色英文关键词"> <template v-slot="scope"> <el-input v-model="scope.row.角色英文关键词" :autosize="true" type="textarea"></el-input> </template> </el-table-column> </el-table> </el-form-item> <!-- 分镜 --> <el-form-item> <el-button type="primary" @click="onAdapt">分镜</el-button> <el-button type="primary" @click="onAdaptScene">推理场景({{tuili_llm.name}})</el-button> <el-button type="primary" @click="onAdaptSceneRoles">推理场景中的角色({{tuili_keyword_llm.name}})</el-button> <el-button type="primary" @click="onDraw">绘图</el-button> <el-button plain @click="clean_scenes">清空分镜列表</el-button> </el-form-item> <el-form-item label="分镜"> <el-table :data="form.adapt_result_json" border style="width: 100%; z-index: calc(var(--el-table-index) -1)"> <el-table-column prop="编号" label="编号" width="60" /> <el-table-column prop="场景描述" label="场景描述"> <template v-slot="scope"> <el-input v-model="scope.row.场景描述" :autosize="true" type="textarea"></el-input> </template> </el-table-column> <el-table-column prop="场景关键词" label="场景英文描述"> <template v-slot="scope"> <el-input v-model="scope.row.场景关键词" :autosize="true" type="textarea"></el-input> </template> </el-table-column> <el-table-column prop="角色" label="角色"> <template v-slot="scope"> <el-input v-model="scope.row.角色" :autosize="true" type="textarea"></el-input> </template> </el-table-column> <el-table-column prop="角色关键词" label="角色关键词"> <template v-slot="scope"> <el-input v-model="scope.row.角色关键词" :autosize="true" type="textarea"></el-input> </template> </el-table-column> <el-table-column prop="本镜配图" label="本镜配图" width="300"> <template v-slot="scope"> <div> <el-image :src="scope.row.本镜配图" :zoom-rate="1.2" :max-scale="1.5" :min-scale="0.5" :preview-src-list="[scope.row.本镜配图]" fit="cover" :hide-on-click-modal="true" /> </div> </template> </el-table-column> <el-table-column width="120" label="操作" align="center"> <!-- <template v-slot:header> <el-button type="danger" size="default" @click="">批量绘制所有图片</el-button> </template> --> <template v-slot="scope"> <div style="margin: 10px 0"><el-button type="primary" size="small" @click="onAdaptOneScene(scope.row)">推理场景</el-button></div> <div style="margin: 10px 0"><el-button type="primary" size="small" @click="onAdaptOneSceneRoles(scope.row)">推理角色</el-button></div> <div style="margin: 10px 0"><el-button type="primary" size="small" @click="onDrawOne(scope.row)">绘图</el-button></div> <el-upload class="upload-demo" ref="upload" list-type="picture" :show-file-list="false" :limit="1" :action="actionUrl" :on-success="handleUploadSuccess" :on-exceed="handleExceed" :data="{item_id: scope.row.编号}" > <el-button type="primary" size="small">上传图片</el-button> </el-upload> <div style="margin: 10px 0"><el-button plain size="small" @click="showsdprompt(scope.row)">debug</el-button></div> <el-dialog v-model=dialogVisible width="80%" > <p>{{ dialogData }}</p> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="dialogVisible = false">ok</el-button> </div> </template> </el-dialog> <!-- <div style="margin: 10px 0"><el-button type="danger" size="small" @click="onDeleteOne(scope.row)">删除本镜</el-button></div> --> </template> </el-table-column> </el-table> </el-form-item> <!-- 生成视频 --> <el-form-item label="设置"> <span style="margin: 0 20px">TTS语速:</span> <el-slider v-model="voice_rate" show-input :min="-50" :max="50" :marks="default_data.marks" style="width: 900px" /> </el-form-item> <el-form-item> <span style="margin: 0 20px">TTS音量:</span> <el-slider v-model="voice_volume" show-input :min="-80" :max="80" :marks="default_data.marks" style="width: 900px" /> </el-form-item> <el-form-item> <span style="margin: 20px 20px 0 20px">TTS语音:</span> <el-select v-model="voice" placeholder="Select" style="width: 400px; margin-top: 20px;"> <el-option v-for="item in default_data.voices" :key="item.value" :label="item.value" :value="item.value" > <span style="float: left">{{ item.value }}</span> <span style=" float: right; color: var(--el-text-color-secondary); font-size: 13px; " >{{ item.label }}</span> </el-option> </el-select> <audio :src="'src/assets/edge-tts-voices/' + voice + '.mp3'" controls style="height: 30px; margin: 20px 0 0 10px;"></audio> </el-form-item> <el-form-item> <span style="margin: 0 20px">背景音乐:</span> <el-select v-model="bgm" placeholder="无" style="width: 400px;"> <el-option v-for="item in default_data.bgm" :key="item.value" :label="item.value" :value="item.value" > <span style="float: left">{{ item.label }}</span> <span style=" float: right; color: var(--el-text-color-secondary); font-size: 13px; " >{{ item.value }}</span> </el-option> </el-select> <audio :src="'src/assets/bgm/' + bgm + '.mp3'" controls style="height: 30px; margin-left:10px;"></audio> </el-form-item> <el-form-item> <span style="margin: 0 20px">背景音量:</span> <el-slider v-model="bgm_volume" show-input :step="0.1" :min="0" :max="2" :marks="default_data.bgm_volume_marks" style="width: 600px" /> </el-form-item> <el-form-item> <span style="margin: 20px 20px">字幕:</span> <el-switch v-model="form.if_need_subtitle" active-value="true" inactive-value="false"/> <div v-if="JSON.parse(form.if_need_subtitle.toLowerCase())"> <span style="margin-left:30px;">字体颜色:</span> <el-color-picker v-model="sub_font_color"/> <span style="margin-left:30px;">字体大小:</span> <el-input-number v-model="sub_font_size" :min="1" :max="50" controls-position="right" /> <span style="margin-left:30px;">在屏幕上的位置:</span> <el-slider v-model="sub_position" :step="0.1" :min="0" :max="1" show-input vertical height="100px" /> </div> </el-form-item> <el-form-item> <el-button type="primary" @click="onGenVideo">生成视频</el-button> </el-form-item> <el-form-item> <video :src="form.final_video" controls></video> </el-form-item> </el-form> <!-- 授权密码框 --> <el-dialog v-model=pwdCheckDialogVisible title="请输入密码" width="20%" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="false" > <el-form :model="form"> <el-form-item label="密码"> <el-input v-model="pwdCheckValue" autocomplete="off" type="password" show-password @keyup.enter="onPwdCheckDialog()" /> </el-form-item> </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="onPwdCheckDialog()">ok</el-button> </div> </template> </el-dialog> </main> </template> <style lang="scss" scoped> .home-container { width: 100%; } </style> <style lang="scss"> .home-container { .el-table .el-table__cell { z-index: calc(var(--el-table-index) -1); } } </style>