Commit 0d8701a5 by haojie

洗稿

parent 8b03221a
......@@ -91,6 +91,10 @@
.upload-success-box {
.file-name {
margin-top: 4px;
white-space: nowrap;
max-width: 70px;
overflow: hidden;
text-overflow: ellipsis;
}
}
.uploading-title {
......
import { defineComponent, reactive, ref, watch } from 'vue';
import { defineComponent, onMounted, reactive, ref, watch } from 'vue';
import './index.less';
import { Button as TButton, Upload as TUpload, Progress as TProgress, UploadFile } from 'tdesign-vue-next';
import { getUserCookie } from '@/utils/api/userApi';
......@@ -12,6 +12,10 @@ import { alyOssUpload } from '@/utils/tool';
export default defineComponent({
props: {
modelValue: Array,
value: {
type: Array,
default: [],
},
audioTotolTime: {
type: [Number, null],
default: null,
......@@ -53,6 +57,28 @@ export default defineComponent({
}, 100);
};
// 更新父组件的list
const submitList = (list: any[], change: boolean = true) => {
if (list.length) {
Curfile.status = 2;
} else {
Curfile.status = 0;
}
emit('update:modelValue', list);
// 再提交一份原始list
if (change) {
emit('change', list, fileList.value);
}
};
onMounted(() => {
//
if (props.modelValue.length) {
Curfile.status = 2;
fileList.value = props.modelValue;
}
});
// 是否需要计算文件总时长,音频或视频
const computedTotalTime = () => {
return typeof props.audioTotolTime === 'number';
......@@ -145,8 +171,11 @@ export default defineComponent({
totalSize += item.file.size;
}
}
// 更新绑定的值
emit('update:modelValue', list);
submitList(list, false);
if (list.length === fileList.value.length) {
// 全部上传成功才提交change事件
emit('change', list, fileList.value);
}
// 文件总大小
emit('update:totalSize', totalSize);
};
......@@ -185,7 +214,26 @@ export default defineComponent({
// 状态成功
if (!v.length) {
Curfile.status = 0;
fileList.value = [];
} else {
// // 要删除的下标
// let deleteIndex = [];
// // 数组对比,没有的就删除
// for (let i = 0; i < fileList.value.length; i++) {
// let item = fileList.value[i];
// let index = v.findIndex((url: string) => item == url);
// if (index === -1) {
// // 没找到,删除
// deleteIndex.push(i);
// }
// }
// console.log(deleteIndex);
// deleteIndex.forEach((index: any) => {
// fileList.value.splice(index, 1);
// });
}
} else {
Curfile.status = 0;
}
},
);
......@@ -199,8 +247,7 @@ export default defineComponent({
const reStart = () => {
// 重置
Curfile.status = 0;
emit('update:modelValue', []);
emit('change', []);
submitList([]);
};
const requestSuccessMethod = async (file: UploadFile | UploadFile[]) => {
return ExtranetUpload(file);
......@@ -227,19 +274,10 @@ export default defineComponent({
totalTime += item.time;
}
});
emit('update:modelValue', list);
submitList(list);
emit('update:audioTotolTime', totalTime);
};
watch(
() => props.modelValue,
(v) => {
if (!v) {
Curfile.status = 0;
}
},
);
// 未上传
const notUploadHtml = () => {
return (
......
......@@ -6,9 +6,11 @@
}"
>
<div class="script-template-tool" v-if="showTool">
<div class="edit-box" @click="onEdit">
<img :src="imgs.edit" alt="" />
</div>
<template v-if="showEdit">
<div class="edit-box" @click="onEdit">
<img :src="imgs.edit" alt="" />
</div>
</template>
<div class="delete-box" @click="onDelete">
<img :src="imgs.delete" alt="" />
</div>
......@@ -24,10 +26,12 @@ const props = withDefaults(
defineProps<{
height?: string;
showTool?: boolean;
showEdit?: boolean;
}>(),
{
height: '175px',
showTool: true,
showEdit: true,
},
);
const emit = defineEmits(['edit', 'delete']);
......@@ -59,6 +63,7 @@ const onDelete = () => {
right: 12px;
top: 12px;
.da();
z-index: 100;
.edit-box,
.delete-box {
cursor: pointer;
......
<template>
<div
class="custom-loading-two"
:class="{
'custom-loading-two-mask': mask,
}"
:style="{
position: position,
}"
......@@ -22,9 +25,11 @@
const props = withDefaults(
defineProps<{
position?: string;
mask?: boolean;
}>(),
{
position: 'absolute',
mask: false,
},
);
</script>
......@@ -35,136 +40,144 @@ const props = withDefaults(
left: 50%;
transform: translate(-50%, -50%);
z-index: 100;
}
.loading,
.loading > div {
position: relative;
box-sizing: border-box;
}
.loading,
.loading > div {
position: relative;
box-sizing: border-box;
}
.loading {
display: block;
font-size: 0;
color: #888fa1;
}
.loading {
display: block;
font-size: 0;
color: #888fa1;
}
.loading.la-dark {
color: #333;
}
.loading.la-dark {
color: #333;
}
.loading > div {
display: inline-block;
float: none;
background-color: currentColor;
border: 0 solid currentColor;
}
.loading > div {
display: inline-block;
float: none;
background-color: currentColor;
border: 0 solid currentColor;
}
.loading {
width: 58px;
height: 58px;
}
.loading {
width: 58px;
height: 58px;
}
.loading > div {
position: absolute;
width: 4px;
height: 14px;
margin: 2px;
margin-top: -5px;
margin-left: -1px;
border-radius: 4px;
animation: line-spin-clockwise-fade 1s infinite ease-in-out;
}
.loading > div {
position: absolute;
width: 4px;
height: 14px;
margin: 2px;
margin-top: -5px;
margin-left: -1px;
border-radius: 4px;
animation: line-spin-clockwise-fade 1s infinite ease-in-out;
}
.loading > div:nth-child(1) {
top: 15%;
left: 50%;
transform: rotate(0deg);
animation-delay: -0.875s;
}
.loading > div:nth-child(1) {
top: 15%;
left: 50%;
transform: rotate(0deg);
animation-delay: -0.875s;
}
.loading > div:nth-child(2) {
top: 25.2512626585%;
left: 74.7487373415%;
transform: rotate(45deg);
animation-delay: -0.75s;
}
.loading > div:nth-child(2) {
top: 25.2512626585%;
left: 74.7487373415%;
transform: rotate(45deg);
animation-delay: -0.75s;
}
.loading > div:nth-child(3) {
top: 50%;
left: 85%;
transform: rotate(90deg);
animation-delay: -0.625s;
}
.loading > div:nth-child(3) {
top: 50%;
left: 85%;
transform: rotate(90deg);
animation-delay: -0.625s;
}
.loading > div:nth-child(4) {
top: 74.7487373415%;
left: 74.7487373415%;
transform: rotate(135deg);
animation-delay: -0.5s;
}
.loading > div:nth-child(4) {
top: 74.7487373415%;
left: 74.7487373415%;
transform: rotate(135deg);
animation-delay: -0.5s;
}
.loading > div:nth-child(5) {
top: 84.9999999974%;
left: 50.0000000004%;
transform: rotate(180deg);
animation-delay: -0.375s;
}
.loading > div:nth-child(5) {
top: 84.9999999974%;
left: 50.0000000004%;
transform: rotate(180deg);
animation-delay: -0.375s;
}
.loading > div:nth-child(6) {
top: 74.7487369862%;
left: 25.2512627193%;
transform: rotate(225deg);
animation-delay: -0.25s;
}
.loading > div:nth-child(6) {
top: 74.7487369862%;
left: 25.2512627193%;
transform: rotate(225deg);
animation-delay: -0.25s;
}
.loading > div:nth-child(7) {
top: 49.9999806189%;
left: 15.0000039834%;
transform: rotate(270deg);
animation-delay: -0.125s;
}
.loading > div:nth-child(7) {
top: 49.9999806189%;
left: 15.0000039834%;
transform: rotate(270deg);
animation-delay: -0.125s;
}
.loading > div:nth-child(8) {
top: 25.2506949798%;
left: 25.2513989292%;
transform: rotate(315deg);
animation-delay: 0s;
}
.loading > div:nth-child(8) {
top: 25.2506949798%;
left: 25.2513989292%;
transform: rotate(315deg);
animation-delay: 0s;
}
.loading.la-sm {
width: 16px;
height: 16px;
}
.loading.la-sm {
width: 16px;
height: 16px;
}
.loading.la-sm > div {
width: 1px;
height: 4px;
margin-top: -2px;
margin-left: 0;
}
.loading.la-sm > div {
width: 1px;
height: 4px;
margin-top: -2px;
margin-left: 0;
}
.loading.la-2x {
width: 64px;
height: 64px;
}
.loading.la-2x {
width: 64px;
height: 64px;
}
.loading.la-2x > div {
width: 4px;
height: 20px;
margin-top: -10px;
margin-left: -2px;
}
.loading.la-2x > div {
width: 4px;
height: 20px;
margin-top: -10px;
margin-left: -2px;
}
.loading.la-3x {
width: 96px;
height: 96px;
}
.loading.la-3x {
width: 96px;
height: 96px;
}
.loading.la-3x > div {
width: 6px;
height: 30px;
margin-top: -15px;
margin-left: -3px;
.loading.la-3x > div {
width: 6px;
height: 30px;
margin-top: -15px;
margin-left: -3px;
}
}
.custom-loading-two-mask {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(255, 255, 255, 0.1);
}
@keyframes line-spin-clockwise-fade {
......
import { defineComponent, reactive, ref, watch } from 'vue';
import { defineComponent, onMounted, reactive, ref, watch } from 'vue';
import './index.less';
import UploadTip from '@/assets/svg/upload/uploadTip2.svg';
import {
......@@ -36,22 +36,34 @@ export default defineComponent({
type: String,
default: '',
},
id: {
type: [Number, null],
default: 0,
},
showOldName: {
type: Boolean,
default: false,
},
oldName: {
type: String,
default: '',
},
},
emits: ['update:modelValue', 'change', 'file'],
setup(props, { emit }) {
const files = ref([]);
// 文件地址
const Curfile = reactive({
url: '',
url: props.modelValue,
status: 0,
// 当前上传模块提交的状态
uploadStatus: false,
oldName: '',
});
const actionUrl = ref('');
// 上传进度条
const percentage = ref(0);
// 定时器
let percentageInterval: any = null;
// 上传进度定时器
const openpercentage = () => {
// 开启一个定时器,模拟上传进度
......@@ -63,6 +75,27 @@ export default defineComponent({
percentage.value += 1;
}, 100);
};
// 修改上传状态
const changeUploadStatus = (type: 'success' | 'wait' | 'progress' = 'success') => {
if (type == 'success') {
Curfile.url = props.modelValue;
Curfile.status = 2;
} else if (type == 'progress') {
Curfile.status = 1;
} else {
Curfile.url = '';
Curfile.status = 0;
files.value = [];
}
};
// 提交url
const submitUrl = (url: string) => {
emit('update:modelValue', url);
emit('change', url, props.id, Curfile.oldName);
};
// 获取文件尺寸
const getFileSize = async (file: any) => {
return new Promise((resolve, reject) => {
......@@ -119,21 +152,16 @@ export default defineComponent({
MessagePlugin.success('上传成功');
// 将将完整url传给父组件
Curfile.url = url;
// 成功2
Curfile.status = 2;
emit('update:modelValue', Curfile.url);
emit('change', Curfile.url);
submitUrl(Curfile.url);
changeUploadStatus('success');
};
// 上传失败回调
const UploadErrorCallback = () => {
// 关闭定时器
window.clearInterval(percentageInterval);
Curfile.url = '';
// 失败0
Curfile.status = 0;
emit('update:modelValue', Curfile.url);
emit('change', Curfile.url);
submitUrl(Curfile.url);
MessagePlugin.warning('上传失败');
changeUploadStatus('wait');
};
// 外网上传-func
const ExtranetUpload = (file: any) => {
......@@ -141,6 +169,9 @@ export default defineComponent({
// 上传中状态
Curfile.status = 1;
return new Promise<RequestMethodResponse>((resolve) => {
if (props.showOldName) {
Curfile.oldName = file.name;
}
const uuid = v4();
let url = '';
const { config } = props;
......@@ -172,15 +203,12 @@ export default defineComponent({
// 外网url
const url = config.domain + config.dir + uuid + `.${fileName}`;
UploadSuccessCallback(uuid, url);
//
Curfile.uploadStatus = true;
resolve({
status: 'success',
response: { url: Curfile.url },
});
} else {
UploadErrorCallback();
Curfile.uploadStatus = false;
}
})
.catch((e) => {
......@@ -190,14 +218,24 @@ export default defineComponent({
});
};
onMounted(() => {
// 根据props的value判断上传状态
if (props.modelValue) {
changeUploadStatus('success');
} else {
changeUploadStatus('wait');
}
});
watch(
() => props.modelValue,
(v) => {
if (v) {
// 状态成功
Curfile.url = v;
// 成功2
Curfile.status = 2;
changeUploadStatus('success');
} else {
changeUploadStatus('wait');
}
},
);
......@@ -205,11 +243,8 @@ export default defineComponent({
const reStart = () => {
// 重置
files.value = [];
Curfile.url = '';
Curfile.status = 0;
Curfile.uploadStatus = false;
emit('update:modelValue', '');
emit('change', '');
submitUrl('');
changeUploadStatus('wait');
};
const requestSuccessMethod = async (file: UploadFile | UploadFile[]) => {
return ExtranetUpload(file);
......@@ -263,7 +298,7 @@ export default defineComponent({
<div class="icon">
{props.uploadInfo.successIcon ? <img src={props.uploadInfo.successIcon} alt="" /> : ''}
</div>
<div class="file-name"></div>
<div class="file-name">{Curfile.oldName ? Curfile.oldName : props.oldName}</div>
<Button class="reset-submit" theme="green" onClick={reStart}>
{props.uploadInfo.successButtonLabel ?? '重新上传'}
</Button>
......@@ -282,18 +317,20 @@ export default defineComponent({
return UploadSuccess();
}
};
watch(
() => props.modelValue,
(v) => {
if (!v) {
Curfile.status = 0;
}
},
);
return () => (
<div class="custom-real-upload">
<div class="real-upload-content">
<div class="custom-real-upload-component">{currentUploadStatus()}</div>
{/* <div class="custom-real-upload-component">{currentUploadStatus()}</div> */}
<div class="custom-real-upload-component" v-show={Curfile.status == 0}>
{notUploadHtml()}
</div>
<div class="custom-real-upload-component" v-show={Curfile.status == 1}>
{UploadingHtml()}
</div>
<div class="custom-real-upload-component" v-show={Curfile.status == 2}>
{UploadSuccess()}
</div>
</div>
</div>
);
......
import { show_message } from '@/utils/tool';
import { liveContentRegenerate, liveContentRegenerateCallback } from '@/utils/api/userApi';
import { onBeforeUnmount, ref } from 'vue';
export default function () {
// 洗稿次数
const confuseNum = 1;
// 当前洗稿次数
const currentConfuseNum = ref(0);
// 定时器
const confuseInterval = ref(null);
// 洗稿回调列表
const confuseList = ref([]);
// 洗稿id
const currentConfuseId = ref('');
const openConfuseInterval = () => {
confuseInterval.value = window.setTimeout(() => {
currentStartConfuseBack(currentConfuseId.value);
}, 3000);
};
// 关闭定时器
const closeConfuseInterval = () => {
window.clearInterval(confuseInterval.value);
clearInterval(confuseInterval.value);
confuseInterval.value = null;
};
// 洗稿提交
const currentStartConfuse = async (data: any) => {
try {
//
let res: any = await liveContentRegenerate(data);
if (res.code == 0) {
// 打开定时器
openConfuseInterval();
}
} catch (e) {
console.log(e);
}
};
// 获取洗稿回调
const currentStartConfuseBack = async (id: any) => {
try {
let res: any = await liveContentRegenerateCallback({
task_id: id,
});
if (res.code == 0 && res.data.length) {
closeConfuseInterval();
confuseList.value = res.data;
}
} catch (e) {
console.log(e);
}
};
onBeforeUnmount(() => {
closeConfuseInterval();
});
return {
confuseList,
currentConfuseId,
currentStartConfuse,
};
}
......@@ -20,7 +20,7 @@ import { useRoute } from 'vue-router';
const route = useRoute();
const getKey = () => {
return route.fullPath;
return route.name;
};
</script>
......
......@@ -81,7 +81,7 @@ const confirm = () => {
const getList = async () => {
try {
loading.value = true;
let res: any = await getTonesList();
let res: any = await getTonesList(false);
res.soundColor.forEach((item: any) => {
item.play_status = false;
});
......
......@@ -42,7 +42,7 @@ const loading = ref(false);
const getList = async () => {
try {
loading.value = true;
let res: any = await getTonesList();
let res: any = await getTonesList(false);
res.soundColor.forEach((item: any) => {
item.play_status = false;
});
......
......@@ -3,6 +3,30 @@
<div class="all-select">
<Select :options="scriptTypeList" v-model="currentOption" @change="scriptTypeChange"></Select>
<CheckBox v-model="isDisorganize" @change="checkboxChange">GPT洗稿</CheckBox>
<template v-if="isDev()">
<Button
@click="
doCopy(`
大家好!欢迎来到今天的直播!我是你们的主持人,今天我将为大家带来一场精彩的直播节目。在这里,我们将分享一些有趣的内容,并回答你们的问题。
首先,让我们来聊一聊今天的主题。今天我们将重点聚焦在XX领域(根据直播主题填写),这是一个非常热门、有趣且前沿的领域。我们将了解最新的发展动态、分享一些实用的技巧,并回答你们提出的问题。
对于新加入直播的朋友,特别欢迎你们!如果你们有任何问题或者想要了解更多关于XX的信息,请随时在评论区留言,我将尽力回答你们的问题。
在这里,我们鼓励大家积极互动。请大家在评论区留下你们的想法、观点和问题。我会选择一些与主题相关的问题进行回答,在回答问题时我会尽量深入浅出,以确保每个人都能够理解。
也请大家相互尊重,遵守礼貌。如果有令人不舒服的言论或者不适当的内容,请及时举报,我们会及时处理。
接下来,让我们一起进入今天的正题吧!不管你是刚开始接触XX,还是已经有一定了解,请相信你们在这里能够获得更多知识、更多的收获。
再次感谢大家的到来和支持,让我们一起度过这个精彩的时刻!祝愿大家在本次直播中有所收获,也希望大家能够在评论区互相交流,共同进步。
谢谢大家!现在,让我们开始今天的直播吧!
`)
"
>测试文案</Button
>
</template>
<div
class="right-chose-tones"
:style="{
......@@ -119,8 +143,38 @@
<Textarea v-model="textareaValue" @change="textareaChange"></Textarea>
</template>
</div>
<div class="script-setting-upload flex1" v-show="currentOption === scriptTypePhonetics">
<CustomUpload v-model="mp3Url" :uploadInfo="uploadInfo" :config="ossConfig" @change="uploadChange"></CustomUpload>
<div class="script-setting-upload flex1 narrow-scrollbar" v-show="currentOption === scriptTypePhonetics">
<!-- edit -->
<template v-for="(item, index) in audioScriptList" :key="index">
<ScriptTemplate height="250px" :showEdit="false" @edit="uploadAudioEdit(index)" @delete="onDeleteAudio(index)">
<div class="script-template-body__audio-add">
<MultipleUpload
v-model="item.data"
:config="ossConfig"
label="选择音频"
@change="uploadEdit"
></MultipleUpload>
</div>
</ScriptTemplate>
</template>
<!-- create -->
<ScriptTemplate :showTool="false" height="250px">
<div class="script-template-body__audio-add">
<MultipleUpload
v-model="mp3UrlList"
:config="ossConfig"
label="选择音频"
@change="createUploadFile"
></MultipleUpload>
<!-- <CustomUpload
v-model="mp3Url"
:showOldName="true"
:uploadInfo="uploadInfo"
:config="ossConfig"
@change="createUploadFile"
></CustomUpload> -->
</div>
</ScriptTemplate>
</div>
<TextScriptDialog v-model="textScriptVisible" @submit="textScriptSubmit" :info="editTextInfo"></TextScriptDialog>
<ConfirmDialog
......@@ -128,11 +182,18 @@
title="确定要删除该声音吗?"
@confirm="confirmDeleteText"
></ConfirmDialog>
<ConfirmDialog
v-model="confirmDeleteAudioVisible"
title="确定要删除该声音吗?"
@confirm="confirmDeleteAudio"
></ConfirmDialog>
</div>
</template>
<script lang="tsx" setup>
import { computed, onMounted, reactive, ref, watch } from 'vue';
import Button from '@/components/Button.vue';
import MultipleUpload from '@/components/MultipleUpload';
import CheckBox from '@/components/CheckBox.vue';
import ConfirmDialog from '@/components/ConfirmDialog.vue';
import TextScriptDialog from './TextScriptDialog.vue';
......@@ -148,6 +209,8 @@ import { getUploadConfig, getTonesList } from '@/service/Common';
import { v4 } from 'uuid';
import { useStore } from 'vuex';
import { useRoute } from 'vue-router';
import useCopy from '@/hooks/useCopy';
const { doCopy } = useCopy();
const [commitInfo] = useLiveInfoSubmit();
const props = withDefaults(
......@@ -171,6 +234,8 @@ const createLiveInfo = computed(() => store.getters['live/getLiveInfo']);
// 文本脚本列表
const textScriptList = ref([]);
// 音频脚本列表
const audioScriptList = ref([]);
// 文本脚本el
const scriptSettingText = ref<HTMLDivElement>();
// 文本编辑时的行信息
......@@ -179,9 +244,16 @@ const editTextInfo = ref({});
const deleteTextId = ref();
// 确认删除弹窗
const confirmDeleteVisible = ref(false);
// 确认删除音频弹窗
const confirmDeleteAudioVisible = ref(false);
// 要删除的音频下标
const deleteAudioIndex = ref();
// 是否gpt洗稿
const isDisorganize = ref(false);
// upload组件的ref
const uploadRef = ref();
const lists = reactive({
tones: [],
soundColor: [],
......@@ -207,6 +279,8 @@ const textScriptVisible = ref(false);
const ossConfig = ref({});
// 上传的音频文件链接
const mp3Url = ref('');
// 多选文件列表
const mp3UrlList = ref([]);
const uploadInfo = {
label1: '选择音频',
label2: '或拖音频到此处上传',
......@@ -225,6 +299,51 @@ const textareaValue = ref('');
const currentOption = ref(scriptTypeText);
// 音频脚本上传后
const createUploadFile = (list: any[], oldList: any[]) => {
// 添加到数组中
audioScriptList.value.push({
data: JSON.parse(JSON.stringify(oldList)),
});
// 提交到store
uploadChange();
setTimeout(() => {
// 清空当前url
mp3UrlList.value = [];
}, 0);
};
// 音频脚本编辑后
const uploadEdit = (url: string, id: number) => {
uploadChange();
};
const confirmDeleteAudio = () => {
audioScriptList.value.splice(deleteAudioIndex.value, 1);
uploadChange();
};
// 删除音频
const onDeleteAudio = (index: number) => {
confirmDeleteAudioVisible.value = true;
deleteAudioIndex.value = index;
};
// 编辑按钮 音频脚本
const uploadAudioEdit = (index: number) => {
// 调用对应的上传事件
if (uploadRef.value && uploadRef.value.length) {
let element: HTMLDivElement = uploadRef.value[index].$el;
if (element) {
// 找到上传元素
let clickElement = element.getElementsByClassName('custom-upload-click-box');
if (clickElement && clickElement.length) {
clickElement[0].click();
}
}
}
};
// 洗稿checkbox变化
const checkboxChange = (value: boolean) => {
commitInfo({
......@@ -304,7 +423,6 @@ const updateTonesInfo = (tone_id: any, phonetic_timbres_id: any) => {
};
const updateInfo = (info: any) => {
console.log(info);
let type = '';
let type_content = '';
let phonetic_timbres_id = '';
......@@ -379,9 +497,9 @@ const openSoundColor = () => {
};
// 音频文件提交
const uploadChange = (value: string) => {
const uploadChange = () => {
commitInfo({
[createLiveKeys.phoneticsFile]: value,
[createLiveKeys.audioScriptList]: audioScriptList.value,
});
};
......@@ -648,8 +766,29 @@ onMounted(async () => {
}
}
.script-setting-upload {
.custom-real-upload {
overflow-y: auto;
overflow-x: hidden;
.script-template-body__audio-add {
height: 100%;
.custom-real-upload {
height: 100%;
background: transparent;
.custom-UploadSuccess-stauts {
.icon {
img {
width: 40px;
height: 40px;
}
}
.file-name {
margin-bottom: 12px;
}
}
}
.custom-multiple-upload {
height: 100%;
background: transparent;
}
}
}
}
......
......@@ -50,7 +50,7 @@
</div>
</div>
<ConfirmDialog v-model="confirmVisible" :title="'确定要生成该直播吗?'" @confirm="confirm"></ConfirmDialog>
<Loading v-show="loading"></Loading>
<Loading v-show="loading" :mask="true"></Loading>
</div>
</template>
......@@ -66,7 +66,7 @@ import ChoseDigitalPerson from './components/ChoseDigitalPerson.vue';
import HomeSvg from '@/assets/svg/createLive/home.svg';
import InteractSvg from '@/assets/svg/createLive/interact.svg';
import ScriptsSvg from '@/assets/svg/createLive/scripts.svg';
import { computed, onMounted, ref, onBeforeUnmount } from 'vue';
import { computed, onMounted, ref, onBeforeUnmount, onActivated } from 'vue';
import { getElBounding, show_message, DataType } from '@/utils/tool';
import { useStore } from 'vuex';
import { createLiveKeys, scriptTypeText, scriptTypePhonetics } from '@/service/CreateLive';
......@@ -78,7 +78,8 @@ import { createLiveRouteKey } from '@/constants/token';
import { callPyjsInWindow } from '@/utils/pyqt';
import { useLiveInfoSubmit } from '@/hooks/useStoreCommit';
import { processTextCallback } from './hooks/scripts';
const { openInterval } = processTextCallback();
const { loading, initNum, currentSetp, openInterval, filterFiled, backHome, submitSuccessed, submit, initCreateStore } =
processTextCallback();
const [commitInfo] = useLiveInfoSubmit();
const store = useStore();
......@@ -90,18 +91,11 @@ const liveImage = computed(() => store.getters['live/getLiveimage']);
const createLiveInfo = computed(() => store.getters['live/getLiveInfo']);
const liveName = computed(() => store.getters['live/getName']);
const loading = ref(false);
const confirmVisible = ref(false);
const publicTool = ref<HTMLElement>();
const toolHeight = ref(0);
const currentSetp = ref(1);
// 通知子组件初始化
const initNum = ref(1);
const videoPlay = ref<HTMLVideoElement>();
const videoCanplay = () => {
......@@ -147,8 +141,8 @@ const setpsList = [
{
key: createLiveKeys.scriptType,
value: scriptTypePhonetics,
require: [createLiveKeys.phoneticsFile],
message: '音频文件必填',
require: [createLiveKeys.audioScriptList],
message: '音频文件至少上传一个',
},
],
},
......@@ -225,13 +219,6 @@ const getEditInfo = async (id: any, type: string) => {
}
};
const backHome = () => {
router.push({
path: routerConfig.home.path,
name: routerConfig.home.name,
});
};
//
const currentModuleField = () => {
let index = setpsList.findIndex((item: any) => item.value === currentSetp.value);
......@@ -248,7 +235,6 @@ const currentModuleField = () => {
Object.keys(createLiveInfo.value).forEach((key: string) => {
if (it === key) {
let value = createLiveInfo.value[key];
console.log(value);
if (DataType(value) && !value.length) {
// 是数组
status = false;
......@@ -308,35 +294,6 @@ const onSave = () => {
}
};
// 过滤必要的字段
const filterFiled = () => {
let item = createLiveInfo.value;
// 过滤必须字段
let params: any = {};
params.name = liveName.value;
// 数字人id
params.digital_man_id = item[createLiveKeys.id];
// 脚本类型
params.type = item[createLiveKeys.scriptType];
// 脚本内容
if (item[createLiveKeys.scriptType] == scriptTypeText) {
// 文本
params.type_content = item[createLiveKeys.textScriptValue];
// 音色id
params.phonetic_timbres_id = item[createLiveKeys.textSoundColor];
} else {
// 音频
params.type_content = item[createLiveKeys.phoneticsFile];
// 音色id
params.phonetic_timbres_id = item[createLiveKeys.phoneticsSoundColor];
}
// 音调id
params.tone_id = item[createLiveKeys.textTones];
// 互动库
params.interaction_ids = item[createLiveKeys.interactiveLibrary];
return params;
};
// 保存为草稿
const onSaveDrafts = async () => {
let params = filterFiled();
......@@ -396,7 +353,6 @@ const confirm = async () => {
if (item[createLiveKeys.scriptType] == scriptTypeText) {
// 文本脚本
loading.value = true;
for (let i = 0; i < item[createLiveKeys.textScriptList].length; i++) {
let params = {
// 音色
......@@ -421,27 +377,14 @@ const confirm = async () => {
}
}
// 开启轮询
openInterval();
openInterval(false);
} else {
// 音频脚本
try {
loading.value = true;
let res: any = await createLiveTask(filterFiled());
if (res.code == 0) {
show_message('创建成功', 'success');
// 回首页
backHome();
// 清空name
store.commit('live/setName', '');
// 清空edit
store.commit('live/clearEditLive');
// 清空createLive
store.commit('live/clearCreateLive');
store.commit('live/initLiveInfo');
// 清空信息
// 通知三个模块清空自己的内容
initNum.value += 1;
// 回到模块1
currentSetp.value = 1;
let res = await submit();
if (res) {
submitSuccessed();
}
loading.value = false;
} catch (e) {
......@@ -467,6 +410,9 @@ onMounted(() => {
query: route.query,
});
});
onActivated(() => {
store.commit('live/setName', route.query.title ? route.query.title : '');
});
onBeforeUnmount(() => {
// 清空选择的数字人
......
......@@ -5,10 +5,14 @@ import {
updateLiveTask,
getGroupsInteraction,
updateDrafts,
liveContentRegenerate,
liveContentRegenerateCallback,
} from '@/utils/api/userApi';
import { typeTones, typeSoundColor } from '@/service/CreateLive';
import { show_message } from '@/utils/tool';
import { LIVE_AUDIT_STATUS } from './Live';
// 阿里云上传配置
export const getUploadConfig = async () => {
try {
let res: any = await getAlyOssConfig();
......@@ -43,7 +47,7 @@ export const getDigitalPeopleList = async () => {
};
// 获取我的音调
export const getTonesList = async () => {
export const getTonesList = async (mustSuccess: boolean = true) => {
let obj = {
tones: [],
soundColor: [],
......@@ -57,7 +61,14 @@ export const getTonesList = async () => {
item.c_categorie = item.extend?.voice;
});
obj.tones = res.data.filter((item: any) => item.type == typeTones);
obj.soundColor = res.data.filter((item: any) => item.type == typeSoundColor);
obj.soundColor = res.data.filter((item: any) => {
if (item.type == typeSoundColor) {
if (item.status != LIVE_AUDIT_STATUS.LIVE_AUDIT_STATUS_FINISH && mustSuccess) {
return;
}
return item;
}
});
}
return obj;
} catch (e) {
......
......@@ -8,6 +8,7 @@ export const createLiveKeys = {
textSoundColor: 'phonetic_timbres_id', // 文本音色
textScriptValue: 'type_content', // 文字脚本的文本
textScriptList: 'type_content_list', // 文字脚本列表
audioScriptList: 'type_content_audio_list', // 音频脚本文件列表
phoneticsSoundColor: 'phoneticsSoundColor', // 音频 音色
phoneticsFile: 'phoneticsFile', // 音频文件
commentMethod: 'commentMethod', // 评论方式
......
......@@ -7,13 +7,14 @@ const initParams = () => {
[createLiveKeys.scriptType]: '',
[createLiveKeys.textTones]: '',
[createLiveKeys.textSoundColor]: '',
[createLiveKeys.textScriptValue]: '',
[createLiveKeys.textScriptValue]: [],
[createLiveKeys.phoneticsSoundColor]: '',
[createLiveKeys.phoneticsFile]: '',
[createLiveKeys.commentMethod]: '',
[createLiveKeys.isDisorganize]: false,
[createLiveKeys.interactiveLibrary]: [],
[createLiveKeys.textScriptList]: [],
[createLiveKeys.audioScriptList]: [],
[createLiveKeys.scriptUuid]: '',
};
};
......@@ -39,7 +40,6 @@ const mutations = {
Object.keys(info).forEach((item: any) => {
state.createLive[item] = info[item];
});
console.log(state.createLive);
},
initLiveInfo(state: StateType) {
state.createLive = initParams();
......
......@@ -338,3 +338,34 @@ export const getLiveTtsCallback = (data: any) => {
},
});
};
// 洗稿提交
export const liveContentRegenerate = async (data: any) => {
const header = getHeader();
return request.post(`/api/live/content/regenerate`, data, {
headers: {
...header,
},
});
};
// 获取洗稿回调
export const liveContentRegenerateCallback = (data: any) => {
const header = getHeader();
return request.get(`/api/live/content/regenerate`, {
params: data,
headers: {
...header,
},
});
};
// 重新生成直播
export const liveTaskRegenerate = () => {
const header = getHeader();
return request.post(`/api/live/task/{id}/regenerate`, '', {
headers: {
...header,
},
});
};
// 将浮点数音频数据转换为16位PCM数据
const floatTo16BitPCM = (output, offset, input) => {
for (let i = 0; i < input.length; i++, offset += 2) {
const s = Math.max(-1, Math.min(1, input[i]));
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
}
};
// 在DataView中写入字符串
const writeString = (view, offset, string) => {
for (let i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
import { downloadBlobFile, show_message } from './tool';
import { v4 } from 'uuid';
export const createAudioContext = () => {
return new (window.AudioContext || window.webkitAudioContext)();
};
// 将AudioBuffer转换为WAVE格式的Blob
const bufferToWave = (buffer) => {
const arrayBuffer = new ArrayBuffer(44 + buffer.length * 2);
const view = new DataView(arrayBuffer);
// 添加WAVE文件头
writeString(view, 0, 'RIFF');
view.setUint32(4, 32 + buffer.length * 2, true);
writeString(view, 8, 'WAVE');
writeString(view, 12, 'fmt ');
view.setUint32(16, 16, true);
view.setUint16(20, 1, true);
view.setUint16(22, buffer.numberOfChannels, true);
view.setUint32(24, buffer.sampleRate, true);
view.setUint32(28, buffer.sampleRate * buffer.numberOfChannels * 2, true);
view.setUint16(32, buffer.numberOfChannels * 2, true);
view.setUint16(34, 16, true);
writeString(view, 36, 'data');
view.setUint32(40, buffer.length * 2, true);
// 将音频数据写入buffer
floatTo16BitPCM(view, 44, buffer);
// 将ArrayBuffer转换为Blob
return new Blob([view], { type: 'audio/wav' });
};
// 音频合并
export const audioMerge = async (audioUrls: string[]) => {
// 创建一个新的AudioContext对象
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const audioBuffers = [];
const loadAudioFile = async (url) => {
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
return audioContext.decodeAudioData(arrayBuffer);
};
const loadAllAudioFiles = async () => {
for (const audioUrl of audioUrls) {
const audioBuffer = await loadAudioFile(audioUrl);
audioBuffers.push(audioBuffer);
}
};
// 合并音频文件
const mergeAudioFiles = async () => {
// 创建一个新的AudioBuffer对象来保存合并后的音频
export async function decodeAudio(blob: Blob) {
const audioContext = createAudioContext();
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onloadend = function () {
const arrayBuffer = fileReader.result;
audioContext.decodeAudioData(
arrayBuffer,
(audioBuffer: any) => {
resolve(audioBuffer);
},
reject,
);
};
fileReader.onerror = reject;
fileReader.readAsArrayBuffer(blob);
});
}
// 合并音频文件
export async function audioMerge(filePaths) {
try {
// 创建一个新的音频上下文
const audioContext = createAudioContext();
// 存储每个音频文件的解码后的音频数据
const buffers = [];
// 使用 Promise 依次解码和添加音频数据到 buffers 数组
await Promise.all(
filePaths.map(async (filePath) => {
const response = await fetch(filePath);
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
return audioBuffer;
}),
).then((decodedBuffers) => {
buffers.push(...decodedBuffers); // 将解码后的音频缓冲区按顺序添加到数组中
});
// 计算合并后的音频数据的长度
const totalDuration = buffers.reduce((accumulator, current) => accumulator + current.duration, 0);
const sampleRate = audioContext.sampleRate;
const numberOfChannels = buffers[0].numberOfChannels;
const channels = buffers[0].numberOfChannels;
// 创建一个新的音频缓冲区
const mergedBuffer = audioContext.createBuffer(
audioBuffers[0].numberOfChannels,
audioBuffers[0].length,
audioBuffers[0].sampleRate,
numberOfChannels,
Math.round(sampleRate * totalDuration),
sampleRate,
);
// 将每个音频文件的数据复制到合并后的AudioBuffer中
for (let i = 0; i < audioBuffers.length; i++) {
const sourceBuffer = audioBuffers[i];
for (let channel = 0; channel < sourceBuffer.numberOfChannels; channel++) {
const sourceData = sourceBuffer.getChannelData(channel);
const mergedData = mergedBuffer.getChannelData(channel);
mergedData.set(sourceData, i * sourceBuffer.length);
let offset = 0;
// 合并音频数据
buffers.forEach((buffer, index) => {
for (let channel = 0; channel < channels; channel++) {
const sourceData = buffer.getChannelData(channel);
const targetData = mergedBuffer.getChannelData(channel);
targetData.set(sourceData, offset);
}
}
offset += Math.round(buffer.duration * sampleRate);
});
// 导出合并后的音频数据为 WAV 文件
const mergedData = exportBufferAsWav(mergedBuffer);
const blob = new Blob([mergedData], { type: 'audio/wav' });
return blob;
} catch (error) {
console.error('音频文件合并失败:', error);
}
}
// 将合并后的音频写入文件
let renderedBuffer = await audioContext.startRendering();
const offlineAudioContext = new OfflineAudioContext(
mergedBuffer.numberOfChannels,
renderedBuffer.length,
renderedBuffer.sampleRate,
);
const source = offlineAudioContext.createBufferSource();
source.buffer = mergedBuffer;
source.connect(offlineAudioContext.destination);
source.start();
let renderedBufferChild = await offlineAudioContext.startRendering();
return renderedBufferChild;
// 将合并后的音频保存为文件
const audioBlob = bufferToWave(renderedBufferChild);
// const audioUrl = URL.createObjectURL(audioBlob);
};
// 加载
await loadAllAudioFiles();
// 并合并音频文件
return mergeAudioFiles();
};
// 切割音频文件
export async function splitAudio(fileBlob: Blob, splitSize: number) {
if (fileBlob.size < splitSize) {
return [fileBlob];
}
// 切割音频并保存为Blob对象
export const splitAudio = async (audioBuffer, blockSize) => {
const numBlocks = Math.ceil(audioBuffer.length / blockSize);
const audioChunks = [];
try {
const MAX_CHUNK_SIZE = splitSize;
const newChunks = [];
for (let i = 0; i < numBlocks; i++) {
const startOffset = i * blockSize;
const endOffset = Math.min(startOffset + blockSize, audioBuffer.length);
const asyncReadFile = (file, offset) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
const bufferChunk = audioBuffer.slice(startOffset, endOffset);
audioChunks.push(bufferChunk);
reader.onload = (event) => {
const binaryData = event.target.result;
const chunkName = v4();
const binaryFile = new File([binaryData], chunkName, { type: file.type });
resolve(binaryFile);
};
reader.onerror = () => {
reject(new Error('File read error.'));
};
const chunk = file.slice(offset, offset + MAX_CHUNK_SIZE);
reader.readAsArrayBuffer(chunk);
});
};
for (const file of [fileBlob]) {
const fileSize = file.size;
let offset = 0;
while (offset < fileSize) {
const chunkSize = Math.min(MAX_CHUNK_SIZE, fileSize - offset);
const binaryFile = await asyncReadFile(file, offset);
newChunks.push(binaryFile);
offset += chunkSize;
}
}
return newChunks;
} catch (e) {
show_message('音频切割失败');
}
}
// 导出音频缓冲区为 WAV 文件
function exportBufferAsWav(audioBuffer) {
const numberOfChannels = audioBuffer.numberOfChannels;
const sampleRate = audioBuffer.sampleRate;
const length = audioBuffer.length;
const channels = [];
// 将音频缓冲区的数据转化为通道数组
for (let channel = 0; channel < numberOfChannels; channel++) {
channels.push(audioBuffer.getChannelData(channel));
}
// 将切割后的音频块进行合并,并转换为Blob对象
const mergedChunks = new Blob(audioChunks, { type: 'audio/wav' });
// 创建 WAV 文件头
const buffer = new ArrayBuffer(44 + length * 2);
const view = new DataView(buffer);
view.setUint32(0, 0x52494646, false); // RIFF 头部
view.setUint32(4, 36 + length * 2, true); // 文件大小
view.setUint32(8, 0x57415645, false); // WAVE 标记
view.setUint32(12, 0x666d7420, false); // fmt 头部
view.setUint32(16, 16, true); // fmt 大小
view.setUint16(20, 1, true); // 格式(1表示 PCM)
view.setUint16(22, numberOfChannels, true); // 通道数
view.setUint32(24, sampleRate, true); // 采样率
view.setUint32(28, sampleRate * 2 * numberOfChannels, true); // 数据速率
view.setUint16(32, numberOfChannels * 2, true); // 数据块大小
view.setUint16(34, 16, true); // 量化位数
view.setUint32(36, 0x64617461, false); // data 头部
view.setUint32(40, length * 2, true); // 数据大小
// 将音频数据写入 WAV 文件
for (let i = 0; i < length; i++) {
for (let channel = 0; channel < numberOfChannels; channel++) {
const sample = Math.max(-1, Math.min(1, channels[channel][i]));
const value = sample < 0 ? sample * 0x8000 : sample * 0x7fff;
view.setInt16(44 + i * numberOfChannels * 2 + channel * 2, value, true);
}
}
return mergedChunks;
};
return new Uint8Array(buffer);
}
......@@ -33,14 +33,6 @@ instance.interceptors.response.use(
const { data } = response;
if (data.code === 0) {
return data;
} else {
//@ts-ignore
if (response.config.needCode) {
return data;
} else {
show_message(data.msg || error_messaage, 'error');
return Promise.reject(data.msg);
}
}
},
(err) => {
......@@ -61,6 +53,7 @@ instance.interceptors.response.use(
// 格式不一致的
let res = err.toJSON();
show_message(res.message, 'error');
return;
}
},
);
......
import { MessagePlugin, RequestMethodResponse } from 'tdesign-vue-next';
import request from '@/utils/otherRequest';
import request from '@/utils/upLoadRequest';
/**
* 函数节流处理
......@@ -168,56 +168,98 @@ export const alyOssUpload = (
UploadErrorCallback: Function,
fileName: string,
) => {
return new Promise<RequestMethodResponse>((resolve) => {
let url = '';
url = 'https://' + config.host;
let files: any[] = [];
if (!DataType(file)) {
// 不是数组
files = [file];
} else {
files = file;
}
for (let i = 0; i < files.length; i++) {
let item = files[i];
const formData = new FormData();
formData.append('key', config.dir + fileName + `.${getFileSuffix(item)}`);
formData.append('policy', config.policy);
formData.append('OSSAccessKeyId', config.accessid);
formData.append('success_action_status', '200');
formData.append('callback', config.callback);
formData.append('signature', config.signature);
formData.append('file', item.raw);
request
.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data;charset=utf-8',
// Accept: '*/*',
},
})
.then((res: any) => {
// resolve 参数为关键代码
if (res === '' || res == 200) {
// 外网url
const url = config.domain + config.dir + fileName + `.${getFileSuffix(item)}`;
console.log(url);
UploadSuccessCallback(fileName, url);
resolve({
status: 'success',
response: { url: url },
return new Promise<RequestMethodResponse>((resolve, reject) => {
try {
let url = '';
url = 'https://' + config.host;
let files: any[] = [];
if (!DataType(file)) {
// 不是数组
files = [file];
} else {
files = file;
}
for (let i = 0; i < files.length; i++) {
let item = files[i];
const formData = new FormData();
formData.append('key', config.dir + fileName + `.${getFileSuffix(item)}`);
formData.append('policy', config.policy);
formData.append('OSSAccessKeyId', config.accessid);
formData.append('success_action_status', '200');
formData.append('callback', config.callback);
formData.append('signature', config.signature);
if (item.raw) {
// 存在row
formData.append('file', item.raw);
} else {
// 另一个格式
formData.append('file', item);
}
request
.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data;charset=utf-8',
// Accept: '*/*',
},
})
.then((res: any) => {
// resolve 参数为关键代码
if (res === '' || res == 200) {
// 外网url
const url = config.domain + config.dir + fileName + `.${getFileSuffix(item)}`;
UploadSuccessCallback(fileName, url);
resolve({
status: 'success',
response: { url: url },
});
} else {
UploadErrorCallback(fileName);
reject({
status: 'error',
response: '',
});
}
})
.catch((e) => {
console.log(e);
reject({
status: 'error',
response: '',
});
} else {
UploadErrorCallback(fileName);
}
})
.catch((e) => {
console.log(e);
});
});
}
} catch (e) {
console.log(e);
reject({
status: 'error',
response: '',
});
}
});
};
// 获取文件后缀
export const getFileSuffix = (file: File) => {
return file.name.split('.')[1];
export const getFileSuffix = (file: any) => {
if (file.name && file.name.indexOf('.') !== -1) {
return file.name.split('.')[1];
} else {
return file.type.split('/')[1];
}
};
// 二进制文件下载
export const downloadBlobFile = (blob: Blob, name: string = 'test') => {
// 创建下载链接
var url = URL.createObjectURL(blob);
// 创建下载链接元素
var link = document.createElement('a');
link.href = url;
link.download = name;
// 模拟点击事件下载文件
link.click();
// 清理 Blob URL
URL.revokeObjectURL(url);
};
import axios from 'axios';
import { MessagePlugin } from 'tdesign-vue-next';
const instance: any = axios.create({
timeout: 60 * 60 * 1000,
withCredentials: false,
});
instance.all = axios.all;
// 请求头
instance.interceptors.request.use((config: any) => {
return config;
});
instance.interceptors.response.use(
(response) => {
const { data, status } = response;
if (status == 201 || status == 200) {
return data;
}
if (data.code === 0) {
return data;
}
},
(err) => {
console.log(err);
if ('response' in err) {
return err.response;
}
},
);
export default instance;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment