<template>
|
<!-- 搜索区域 -->
|
<ContentWrap>
|
<el-form
|
class="-mb-15px"
|
:model="queryParams"
|
ref="queryFormRef"
|
:inline="true"
|
label-width="68px"
|
>
|
<el-form-item :label="t('myCourse.videoName')" prop="name">
|
<el-input
|
v-model="queryParams.name"
|
:placeholder="t('common.inputText')+t('myCourse.videoName')"
|
clearable
|
@keyup.enter="handleQuery"
|
class="!w-240px"
|
/>
|
</el-form-item>
|
<el-form-item>
|
<el-button @click="handleQuery" type="primary">
|
<Icon icon="ep:search" class="mr-5px" />
|
{{t('table.search')}}
|
</el-button>
|
<el-button @click="resetQuery">
|
<Icon icon="ep:refresh" class="mr-5px" />
|
{{t('table.reset')}}
|
</el-button>
|
</el-form-item>
|
</el-form>
|
</ContentWrap>
|
|
<!-- 视频列表 -->
|
<ContentWrap>
|
<el-table v-loading="loading" :data="list" style="width: 100%">
|
<el-table-column :label="t('myCourse.videoCode')" align="center" prop="id" width="100" />
|
<el-table-column :label="t('myCourse.videoName')" align="center" prop="name" min-width="150" />
|
<el-table-column label="排队个数" align="center" prop="pos" width="100">
|
<template #default="scope">
|
<span v-if="scope.row.pos==0">视频正在合成...</span>
|
<span v-else>{{ scope.row.pos }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column label="进度" align="center" prop="progressVideo" width="100">
|
<template #default="scope">
|
<span v-if="scope.row.status==2">100%</span>
|
<span v-else>{{ calculateProgress(scope.row.progressVideo) }}%</span>
|
</template>
|
</el-table-column>
|
<el-table-column :label="t('myCourse.courseName')" align="center" prop="courseName" min-width="150">
|
<template #default="scope">
|
<el-link type="primary" @click="goDetail(scope.row.courseId)">{{ scope.row.courseName }}</el-link>
|
</template>
|
</el-table-column>
|
<el-table-column
|
:label="t('table.createTime')"
|
align="center"
|
prop="createTime"
|
width="160"
|
:formatter="dateFormatter"
|
/>
|
<el-table-column
|
:label="t('myCourse.finishTime')"
|
align="center"
|
prop="finishTime"
|
width="160"
|
:formatter="dateFormatter"
|
/>
|
<el-table-column :label="t('myCourse.SynthesisTime')" align="center" width="120">
|
<template #default="scope">
|
{{ calculateDuration(scope.row.createTime, scope.row.finishTime) }}
|
</template>
|
</el-table-column>
|
<el-table-column :label="t('myCourse.errorReason')" align="center" prop="errorReason" width="150">
|
<template #default="scope">
|
<el-tooltip :content="scope.row.errorReason || '--'" placement="top">
|
<span>
|
{{ scope.row.errorReason ? (scope.row.errorReason.length > 10 ? scope.row.errorReason.slice(0, 10) + '...' : scope.row.errorReason) : '--' }}
|
</span>
|
</el-tooltip>
|
</template>
|
</el-table-column>
|
<el-table-column :label="t('myCourse.status')" align="center" prop="status" width="120">
|
<template #default="scope">
|
<dict-tag v-if="scope.row.status==2 && scope.row.subtitlesAddStatus!=null" :type="DICT_TYPE.video_zi" :value="scope.row.subtitlesAddStatus" />
|
<dict-tag v-else :type="DICT_TYPE.VIDEO_STATUS" :value="scope.row.status" />
|
</template>
|
</el-table-column>
|
<el-table-column :label="t('table.action')" align="center" width="230" fixed="right">
|
<template #default="scope">
|
<el-button-group>
|
<template v-if="scope.row.status == 2">
|
<el-button
|
type="text"
|
@click="openPreview(scope.row)"
|
plain
|
>
|
{{t('myCourse.preview')}}
|
</el-button>
|
<el-button
|
type="text"
|
@click="handleHeaderFooter(scope.row)"
|
plain
|
>
|
片头片尾
|
</el-button>
|
<template v-if="scope.row.status == 2 || scope.row.status==3">
|
<el-button
|
type="text"
|
@click="handleDelete(scope.row.id)"
|
plain
|
>
|
{{ t('action.del') }}
|
</el-button>
|
</template>
|
<template v-if="scope.row.status == 3">
|
<el-button
|
type="text"
|
@click="reMegerMedia(scope.row.id)"
|
plain
|
>
|
{{t('myCourse.resynthesize')}}
|
</el-button>
|
</template>
|
<template v-if="scope.row.status == 2">
|
<el-button
|
type="text"
|
@click="openSubtitleDialog(scope.row.id)"
|
plain
|
>
|
字幕
|
</el-button>
|
</template>
|
</template>
|
<el-dropdown>
|
<el-button type="text" plain>
|
更多<el-icon class="el-icon--right"><arrow-down /></el-icon>
|
</el-button>
|
<template #dropdown>
|
<el-dropdown-menu>
|
<el-dropdown-item
|
v-if="scope.row.subtitlesAddStatus == 2"
|
@click="handleDownload(scope.row.videoUrl, scope.row.courseName + '_字幕合成视频')"
|
>
|
<Icon class="mr-2" />下载字幕合成视频
|
</el-dropdown-item>
|
<el-dropdown-item
|
v-if="scope.row.previewUrl!=null"
|
@click="handleDownload(scope.row.previewUrl, scope.row.courseName + '_视频')"
|
>
|
<Icon class="mr-2" />下载视频
|
</el-dropdown-item>
|
<el-dropdown-item
|
v-if="scope.row.compositeVideo!=null"
|
@click="handleDownload(scope.row.compositeVideo, scope.row.courseName + '_片头片尾视频')"
|
>
|
<Icon class="mr-2" />下载片头片尾视频
|
</el-dropdown-item>
|
<el-dropdown-item
|
v-if="scope.row.titles!=null && scope.row.trailer!=null"
|
@click="mergeHeaderFooter(scope.row.id)"
|
>
|
<Icon class="mr-2" />合成片头片尾视频
|
</el-dropdown-item>
|
</el-dropdown-menu>
|
</template>
|
</el-dropdown>
|
</el-button-group>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<!-- 分页 -->
|
<Pagination
|
:total="total"
|
v-model:page="queryParams.pageNo"
|
v-model:limit="queryParams.pageSize"
|
@pagination="getList"
|
/>
|
</ContentWrap>
|
|
<!-- 视频播放弹框 -->
|
<videoDialog ref="videoRef" />
|
|
<!-- 字幕生成弹框 -->
|
<el-dialog
|
v-model="subtitleDialogVisible"
|
title="字幕查看修改"
|
width="60%"
|
>
|
<el-form :model="subtitleForm" ref="subtitleFormRef">
|
<el-row :gutter="20">
|
<el-col :span="7">
|
<el-form-item label="断句时间阈值" prop="timeThreshold" :rules="[
|
{ required: true, message: '请输入断句时间阈值', trigger: 'blur' },
|
{ pattern: /^\d+(\.\d+)?$/, message: '请输入有效数字', trigger: 'blur' }
|
]">
|
<el-input v-model="subtitleForm.timeThreshold" placeholder="例如:0.05" clearable />
|
</el-form-item>
|
</el-col>
|
<el-col :span="7">
|
<el-form-item label="语言" prop="language" :rules="[
|
{ required: true, message: '请选择语言', trigger: 'change' }
|
]">
|
<el-select v-model="subtitleForm.language" placeholder="请选择语言" clearable>
|
<el-option label="中文" value="zh" />
|
<el-option label="英文" value="en" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="10">
|
<el-form-item>
|
<el-button
|
type="primary"
|
@click="generateSubtitles"
|
:loading="generating || polling"
|
>
|
生成字幕
|
</el-button>
|
<el-button
|
type="primary"
|
@click="triggerFileUpload"
|
>
|
上传SRT文件
|
<input
|
ref="fileInput"
|
type="file"
|
accept=".srt"
|
style="display: none"
|
@change="handleFileUpload"
|
/>
|
</el-button>
|
<el-button
|
type="primary"
|
@click="downloadSubtitles"
|
:loading="generating || polling"
|
>
|
字幕视频合成
|
</el-button>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-form-item label="字幕内容" prop="content" :rules="[
|
{ required: true, message: '请先生成或上传字幕内容', trigger: 'blur' }
|
]">
|
<div style="width: 100%;" class="textarea-wrapper">
|
<el-input
|
class="scroll-outside"
|
v-model="subtitleForm.content"
|
type="textarea"
|
:rows="20"
|
placeholder="字幕内容将显示在这里(SRT格式)"
|
resize="none"
|
/>
|
<el-button
|
style="margin-top: 20px;float: right;margin-left: 20px"
|
type="primary"
|
:disabled="!subtitleForm.subtitlesUrl"
|
@click="handleDownload(subtitleForm.subtitlesUrl,subtitleForm.courseName)"
|
>
|
{{t('myCourse.downloadSubtitles')}}
|
</el-button>
|
<el-button
|
style="margin-top: 20px;float: right"
|
type="primary"
|
@click="saveSubtitles"
|
:loading="saving"
|
:disabled="!subtitleForm.content"
|
>
|
保存字幕
|
</el-button>
|
</div>
|
</el-form-item>
|
<el-form-item label="预览视频" v-if="subtitleForm.subtitlesAddStatus==2">
|
<div style="width: 100%;">
|
<video width="100%" :src="subtitleForm.videoUrl" controls></video>
|
<el-button
|
style="margin-top: 20px;float: right"
|
type="primary"
|
@click="handleDownload(subtitleForm.videoUrl,subtitleForm.videoUrl)"
|
>
|
下载视频
|
</el-button>
|
</div>
|
</el-form-item>
|
</el-form>
|
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="subtitleDialogVisible = false">关 闭</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
|
<!-- 片头片尾设置弹框 -->
|
<el-dialog
|
v-model="headerFooterDialogVisible"
|
title="片头片尾设置"
|
width="50%"
|
>
|
<el-form :model="headerFooterForm" label-width="120px">
|
<el-form-item label="片头视频">
|
<UploadFile v-model="headerFooterForm.titles" :fileType="['mp4']" :limit="1" @on-success="handleFileSuccess('audition', $event)"/>
|
</el-form-item>
|
<el-form-item label="片尾视频">
|
<UploadFile v-model="headerFooterForm.trailer" :fileType="['mp4']" :limit="1" @on-success="handleFileSuccess1('audition', $event)"/>
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="headerFooterDialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="applyHeaderFooter" :loading="applyingHeaderFooter">应用</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
<el-dialog
|
v-model="dialogVisible"
|
title="视频合成中"
|
width="50%"
|
>
|
<el-form :model="formData1" label-width="120px">
|
<el-form-item label="视频格式">
|
<el-select v-model="formData1.isvideo">
|
<el-option :disabled="formData1.subtitlesAddStatus==2" label="字幕视频" :value="1" />
|
<el-option label="原视频" :value="2" />
|
</el-select>
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="dialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="hecheng" :loading="applyingHeaderFooter">合成</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</template>
|
|
<script lang="ts" setup>
|
import { DICT_TYPE } from '@/utils/dict'
|
import { dateFormatter } from '@/utils/formatTime'
|
import download from '@/utils/download'
|
import * as pptTemplateApi from '@/api/pptTemplate'
|
import { useRouter } from 'vue-router'
|
import videoDialog from "./videoDialog.vue"
|
import { getAccessToken, getTenantId } from "@/utils/auth"
|
import axios from 'axios'
|
import { config } from '@/config/axios/config'
|
import {createVideo, createVideoMeger, videoMeger} from "@/api/pptTemplate";
|
import { ArrowDown } from '@element-plus/icons-vue'
|
|
const router = useRouter()
|
const message = useMessage()
|
const { t } = useI18n()
|
const polling = ref(false)
|
let pollingTimer: number | null = null
|
//合成片头片尾视频
|
const dialogVisible = ref(false)
|
const formData1 = reactive({
|
isvideo: 2,
|
id: null as number | null
|
})
|
// 视频列表相关数据
|
const loading = ref(true)
|
const total = ref(0)
|
const list = ref([])
|
const queryParams = reactive({
|
pageNo: 1,
|
pageSize: 20,
|
name: undefined
|
})
|
const queryFormRef = ref()
|
|
// 视频预览相关
|
const videoRef = ref()
|
|
// 字幕弹框相关
|
const subtitleDialogVisible = ref(false)
|
const subtitleFormRef = ref()
|
const fileInput = ref<HTMLInputElement | null>(null)
|
const subtitleForm = reactive({
|
videoId: null as number | null,
|
timeThreshold: '0.05',
|
language: 'zh',
|
content: '',
|
subtitlesUrl: '',
|
videoUrl: '',
|
courseName: '',
|
subtitlesAddStatus: null
|
})
|
const generating = ref(false)
|
const saving = ref(false)
|
|
// 片头片尾弹框相关
|
const headerFooterDialogVisible = ref(false)
|
const headerFooterForm = reactive({
|
id: null as number | null,
|
titles: '',
|
trailer: '',
|
})
|
|
const applyingHeaderFooter = ref(false)
|
const handleFileSuccess = (fileType,response) => {
|
if (fileType === 'audition') {
|
headerFooterForm.titles = response.data
|
}
|
};
|
const handleFileSuccess1 = (fileType,response) => {
|
if (fileType === 'audition') {
|
headerFooterForm.trailer = response.data
|
}
|
};
|
// 获取视频列表
|
const getList = async () => {
|
loading.value = true
|
try {
|
const data = await pptTemplateApi.myCourseList(queryParams)
|
list.value = data.list
|
total.value = data.total
|
} finally {
|
loading.value = false
|
}
|
}
|
|
// 搜索视频
|
const handleQuery = () => {
|
queryParams.pageNo = 1
|
getList()
|
}
|
|
// 重置搜索
|
const resetQuery = () => {
|
queryFormRef.value.resetFields()
|
handleQuery()
|
}
|
|
// 预览视频
|
const openPreview = (row) => {
|
if(row){
|
videoRef.value.open(row.previewUrl, row.subtitlesVttUrl)
|
}
|
}
|
|
// 删除视频
|
const handleDelete = async (id: number) => {
|
try {
|
await message.delConfirm()
|
await pptTemplateApi.deleteMyCourse(id)
|
message.success(t('common.delSuccess'))
|
await getList()
|
} catch {}
|
}
|
|
// 下载文件
|
const handleDownload = (url, filename) => {
|
if (!url) {
|
message.warning("未找到资源文件!")
|
return
|
}
|
|
const link = document.createElement('a')
|
link.href = url
|
link.download = filename || 'download'
|
link.target = '_blank'
|
document.body.appendChild(link)
|
link.click()
|
document.body.removeChild(link)
|
}
|
|
// 跳转到课程详情
|
const goDetail = (id) => {
|
pptTemplateApi.coursesDetail(id).then((res) => {
|
if (!res) {
|
message.warning('关联课件视频或口播视频被删除')
|
return
|
}
|
if (res.pageMode === 2 || res.pageMode === 0) {
|
router.push({ path: '/chooseTemplate/index', query: { id } })
|
} else if (res.pageMode === 3) {
|
router.push({ path: '/chooseTemplate/speakvideo', query: { id } })
|
}
|
})
|
}
|
|
// 计算进度
|
const calculateProgress = (progressStr) => {
|
if (!progressStr || typeof progressStr !== 'string') return 0;
|
|
const parts = progressStr.split('/');
|
if (parts.length !== 2) return 0;
|
|
const completed = parseFloat(parts[0]);
|
const total = parseFloat(parts[1]);
|
|
if (isNaN(completed) || isNaN(total) || total === 0) return 0;
|
|
return Math.round((completed / total) * 100);
|
}
|
|
// 重新合成视频
|
const reMegerMedia = async (id: number) => {
|
try {
|
loading.value = true
|
const res = await pptTemplateApi.reMegerMedia({ id })
|
if (res) {
|
message.success("合成视频任务提交成功,请到我的视频中查看!")
|
}
|
} catch (error) {
|
console.error(error)
|
} finally {
|
getList()
|
loading.value = false
|
}
|
}
|
|
// 计算合成耗时
|
const calculateDuration = (createTime, finishTime) => {
|
if (!createTime || !finishTime) return '未完成'
|
|
const start = new Date(createTime).getTime()
|
const end = new Date(finishTime).getTime()
|
|
const duration = (end - start) / 1000
|
const hrs = Math.floor(duration / 3600)
|
const mins = Math.floor((duration % 3600) / 60)
|
const secs = Math.floor(duration % 60)
|
|
return `${hrs > 0 ? `${hrs}时` : ''}${mins > 0 ? `${mins}分` : ''}${secs}秒`
|
}
|
|
// 打开字幕弹框
|
const openSubtitleDialog = async (videoId: number) => {
|
try {
|
subtitleDialogVisible.value = true
|
subtitleForm.videoId = videoId
|
const videoDetail = await pptTemplateApi.myCourseDetail(videoId)
|
|
subtitleForm.subtitlesAddStatus = videoDetail.subtitlesAddStatus
|
subtitleForm.courseName = videoDetail.courseName
|
|
if (videoDetail.subtitlesAddStatus === 2) {
|
subtitleForm.videoUrl = videoDetail.videoUrl || ''
|
generating.value = false
|
polling.value = false
|
} else if (videoDetail.subtitlesAddStatus === 1) {
|
subtitleForm.videoUrl = ''
|
generating.value = true
|
polling.value = true
|
}else {
|
subtitleForm.videoUrl = videoDetail.videoUrl || ''
|
generating.value = false
|
polling.value = false
|
}
|
|
if (videoDetail.subtitlesStatus === 2) {
|
generating.value = false
|
polling.value = false
|
if (videoDetail.subtitlesUrl) {
|
subtitleForm.subtitlesUrl = videoDetail.subtitlesUrl
|
try {
|
const response = await fetch(videoDetail.subtitlesUrl)
|
if (response.ok) {
|
const srtContent = await response.text()
|
subtitleForm.content = srtContent
|
} else {
|
subtitleForm.content = videoDetail.subtitlesContent || ''
|
}
|
} catch (error) {
|
console.error('获取字幕内容失败:', error)
|
subtitleForm.content = videoDetail.subtitlesContent || ''
|
}
|
} else if (videoDetail.subtitlesContent) {
|
subtitleForm.content = videoDetail.subtitlesContent
|
}
|
} else if (videoDetail.subtitlesStatus === 3) {
|
generating.value = false
|
polling.value = false
|
subtitleForm.content = ''
|
} else if (videoDetail.subtitlesStatus === 1) {
|
generating.value = true
|
polling.value = true
|
subtitleForm.content = ''
|
}else{
|
generating.value = false
|
polling.value = false
|
}
|
} catch (error) {
|
console.error('获取视频详情失败:', error)
|
message.error('获取视频详情失败,请重试')
|
subtitleDialogVisible.value = false
|
}
|
}
|
|
// 触发文件上传
|
const triggerFileUpload = () => {
|
fileInput.value?.click()
|
}
|
|
// 处理文件上传
|
const handleFileUpload = async (event: Event) => {
|
const input = event.target as HTMLInputElement
|
if (!input.files?.length) return
|
|
const file = input.files[0]
|
if (!file.name.endsWith('.srt')) {
|
message.warning('请上传SRT格式的字幕文件')
|
return
|
}
|
|
try {
|
const content = await readFileAsText(file)
|
subtitleForm.content = content
|
message.success('字幕文件上传成功')
|
} catch (error) {
|
message.error('读取字幕文件失败')
|
console.error(error)
|
} finally {
|
input.value = ''
|
}
|
}
|
|
// 读取文件为文本
|
const readFileAsText = (file: File): Promise<string> => {
|
return new Promise((resolve, reject) => {
|
const reader = new FileReader()
|
reader.onload = (e) => resolve(e.target?.result as string)
|
reader.onerror = (e) => reject(e)
|
reader.readAsText(file)
|
})
|
}
|
|
// 生成字幕
|
const generateSubtitles = async () => {
|
try {
|
await subtitleFormRef.value.validateField(['timeThreshold', 'language'])
|
|
if (!subtitleForm.videoId) {
|
message.warning('视频ID不能为空')
|
return
|
}
|
|
generating.value = true
|
|
const params = {
|
id: subtitleForm.videoId,
|
sentenceGap: parseFloat(subtitleForm.timeThreshold),
|
lang: subtitleForm.language
|
}
|
|
await pptTemplateApi.generateSubtitles(params)
|
message.success(subtitleForm.courseName+' '+'字幕生成任务已开始')
|
|
const maxAttempts = 20000
|
const interval = 3000
|
let attempts = 0
|
|
const poll = async () => {
|
polling.value = true
|
attempts++
|
|
try {
|
const videoDetail = await pptTemplateApi.myCourseDetail(subtitleForm.videoId!)
|
|
if (videoDetail.subtitlesStatus === 2) {
|
if (videoDetail.subtitlesUrl) {
|
try {
|
const response = await fetch(videoDetail.subtitlesUrl)
|
if (response.ok) {
|
const srtContent = await response.text()
|
subtitleForm.content = srtContent
|
}
|
} catch (error) {
|
console.error('Error fetching SRT file:', error)
|
}
|
} else if (videoDetail.subtitlesContent) {
|
subtitleForm.content = videoDetail.subtitlesContent
|
}
|
message.success(subtitleForm.courseName+' '+'字幕生成成功')
|
stopPolling()
|
} else if (videoDetail.subtitlesStatus === 3) {
|
stopPolling()
|
} else if (attempts >= maxAttempts) {
|
message.warning(subtitleForm.courseName+' '+'字幕生成超时,请稍后手动检查')
|
stopPolling()
|
} else {
|
pollingTimer = window.setTimeout(poll, interval)
|
}
|
} catch (error) {
|
console.error('轮询出错:', error)
|
if (attempts >= maxAttempts) {
|
message.error('字幕状态检查超时')
|
stopPolling()
|
} else {
|
pollingTimer = window.setTimeout(poll, interval)
|
}
|
}
|
}
|
|
poll()
|
} catch (error) {
|
console.error(subtitleForm.courseName+' '+'生成字幕出错:', error)
|
stopPolling()
|
} finally {
|
generating.value = false
|
}
|
}
|
|
// 保存字幕
|
const saveSubtitles = async () => {
|
try {
|
saving.value = true
|
|
// 1. 将字幕内容转换为SRT格式
|
const srtContent = formatToSrt(subtitleForm.content)
|
|
// 2. 创建Blob对象表示SRT文件
|
const blob = new Blob([srtContent], { type: 'text/plain' })
|
const file = new File([blob], 'subtitles.srt', { type: 'text/plain' })
|
|
// 3. 创建FormData并添加文件
|
const formData = new FormData()
|
formData.append('file', file)
|
|
// 4. 上传文件
|
const uploadResponse = await axios({
|
url: config.base_url+'/infra/file/upload',
|
method: 'post',
|
data: formData,
|
headers: {
|
'Content-Type': 'multipart/form-data',
|
'Authorization': `Bearer ${getAccessToken()}`,
|
'tenant-id': getTenantId()
|
}
|
})
|
|
// 5. 调用保存字幕接口
|
const params = {
|
id: subtitleForm.videoId,
|
subtitlesUrl: uploadResponse.data.data,
|
}
|
|
await pptTemplateApi.saveSubtitles(params)
|
message.success('字幕保存成功')
|
subtitleDialogVisible.value = false
|
|
// 刷新列表
|
getList()
|
} catch (error) {
|
console.error('保存字幕失败:', error)
|
message.error(`保存字幕失败: ${error.message || '未知错误'}`)
|
} finally {
|
saving.value = false
|
}
|
}
|
|
// 将文本内容格式化为SRT格式
|
const formatToSrt = (content: string): string => {
|
if (content.trim().match(/^\d+\s+\d{2}:\d{2}:\d{2},\d{3}\s-->\s\d{2}:\d{2}:\d{2},\d{3}/)) {
|
return content
|
}
|
|
const lines = content.split('\n').filter(line => line.trim())
|
let srtContent = ''
|
|
lines.forEach((line, index) => {
|
srtContent += `${index + 1}\n`
|
srtContent += `00:00:${String(index).padStart(2, '0')},000 --> 00:00:${String(index + 1).padStart(2, '0')},000\n`
|
srtContent += `${line}\n\n`
|
})
|
|
return srtContent
|
}
|
|
// 字幕视频合成
|
const downloadSubtitles = async () => {
|
try {
|
if (!subtitleForm.content.trim()) {
|
message.warning('请先生成或上传字幕内容')
|
return
|
}
|
|
generating.value = true
|
const obj = {
|
id: subtitleForm.videoId
|
}
|
await pptTemplateApi.videoMeger(obj)
|
message.success(subtitleForm.courseName+' '+'字幕视频合成任务已开始')
|
|
const maxAttempts = 200000
|
const interval = 3000
|
let attempts = 0
|
|
const poll = async () => {
|
polling.value = true
|
attempts++
|
|
try {
|
const videoDetail = await pptTemplateApi.myCourseDetail(subtitleForm.videoId!)
|
|
if (videoDetail.subtitlesAddStatus === 2) {
|
message.success(subtitleForm.courseName+' '+'字幕视频合成成功')
|
if (videoDetail.previewUrl) {
|
subtitleForm.content = ''
|
stopPolling()
|
subtitleDialogVisible.value = false
|
getList()
|
}
|
} else if (videoDetail.subtitlesAddStatus === 3) {
|
message.error(subtitleForm.courseName+' '+`字幕视频合成失败: ${videoDetail.errorReason || '未知原因'}`)
|
stopPolling()
|
} else if (attempts >= maxAttempts) {
|
message.warning(subtitleForm.courseName+' '+'字幕视频合成超时,请稍后手动检查')
|
stopPolling()
|
} else {
|
pollingTimer = window.setTimeout(poll, interval)
|
}
|
} catch (error) {
|
console.error('轮询字幕视频合成状态出错:', error)
|
if (attempts >= maxAttempts) {
|
message.error('字幕视频合成状态检查超时')
|
stopPolling()
|
} else {
|
pollingTimer = window.setTimeout(poll, interval)
|
}
|
}
|
}
|
|
poll()
|
} catch (error) {
|
console.error('字幕视频合成失败:', error)
|
message.error(`字幕视频合成失败: ${error.message || '未知错误'}`)
|
stopPolling()
|
} finally {
|
generating.value = false
|
}
|
}
|
|
// 停止轮询
|
const stopPolling = () => {
|
if (pollingTimer) {
|
clearTimeout(pollingTimer)
|
pollingTimer = null
|
}
|
polling.value = false
|
}
|
|
// 处理片头片尾按钮点击
|
const handleHeaderFooter =async (row) => {
|
console.log(row)
|
headerFooterForm.id = row.id
|
let details= await pptTemplateApi.myCourseDetail(row.id)
|
console.log(details)
|
headerFooterForm.titles = details.titles || ''
|
headerFooterForm.trailer = details.trailer || ''
|
headerFooterDialogVisible.value = true
|
}
|
// 应用片头片尾设置
|
const applyHeaderFooter = async () => {
|
try {
|
console.log('应用片头片尾设置:', headerFooterForm)
|
const title = await pptTemplateApi.createVideo(headerFooterForm)
|
console.log('创建视频标题:', title)
|
if (title) {
|
message.success('片头片尾设置成功')
|
headerFooterDialogVisible.value = false
|
getList()
|
}
|
} catch (error) {
|
console.error('片头片尾设置出错:', error)
|
message.error('片头片尾设置出错')
|
} finally {
|
applyingHeaderFooter.value = false
|
}
|
}
|
//合成片头片尾视频
|
const mergeHeaderFooter = async (id: number) => {
|
try {
|
let details= await pptTemplateApi.myCourseDetail(id)
|
formData1.value=details
|
dialogVisible.value = true
|
console.log(formData1.value)
|
}
|
catch (error) {
|
console.error(error)
|
}
|
|
}
|
//合成片头片尾
|
const hecheng = async () => {
|
try {
|
console.log(formData1.value)
|
applyingHeaderFooter.value = true
|
let obj={}
|
if (formData1.isvideo=='2'){
|
obj={
|
id:formData1.value.id,
|
titles:formData1.value.titles,
|
trailer:formData1.value.trailer,
|
courseName:formData1.value.courseName,
|
videoUrl:null,
|
previewUrl:formData1.value.previewUrl
|
}
|
const res = await pptTemplateApi.createVideoMeger(obj)
|
if (res) {
|
message.success('视频合成成功')
|
applyingHeaderFooter.value = true
|
dialogVisible.value = false
|
getList()
|
}
|
}else if (formData1.isvideo=='1'){
|
obj={
|
id:formData1.value.id,
|
titles:formData1.value.titles,
|
trailer:formData1.value.trailer,
|
courseName:formData1.value.courseName,
|
videoUrl:formData1.value.courseName,
|
previewUrl:null
|
}
|
const res = await pptTemplateApi.createVideoMeger(obj)
|
if (res) {
|
message.success('视频合成成功')
|
applyingHeaderFooter.value = true
|
dialogVisible.value = false
|
getList()
|
}
|
}
|
// loading.value = true
|
//
|
}
|
catch (error) {
|
console.error(error)
|
}
|
}
|
|
// 清理定时器
|
onBeforeUnmount(() => {
|
stopPolling()
|
})
|
|
// 初始化
|
onMounted(() => {
|
getList()
|
})
|
</script>
|
|
<style scoped>
|
.textarea-wrapper {
|
position: relative;
|
width: fit-content;
|
}
|
|
.scroll-outside {
|
overflow: hidden;
|
}
|
|
.scroll-outside .el-textarea__inner {
|
overflow-y: auto;
|
padding-right: 0;
|
margin-right: 16px;
|
}
|
|
.scroll-outside .el-textarea__inner::-webkit-scrollbar {
|
width: 8px;
|
}
|
|
.scroll-outside .el-textarea__inner::-webkit-scrollbar-thumb {
|
background: #c0c4cc;
|
border-radius: 4px;
|
}
|
|
.el-button-group {
|
display: flex;
|
align-items: center;
|
gap: 4px;
|
}
|
|
.el-dropdown {
|
margin-left: 0;
|
}
|
</style>
|