| | |
| | | <!-- 视频列表 --> |
| | | <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="t('myCourse.videoName')" align="center" prop="name" min-width="180" /> |
| | | <el-table-column :label="t('myCourse.courseName')" align="center" prop="courseName" min-width="180"> |
| | | <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('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="进度" align="center" prop="progressVideo" width="100"> |
| | | <template #default="scope"> |
| | | <span v-if="scope.row.status==2">100%</span> |
| | | <span v-else>{{ Math.max(1, Math.min(calculateProgress(scope.row.progressVideo), 99)) }}%</span> |
| | | </template> |
| | | </el-table-column> |
| | | <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"> |
| | | <el-table-column :label="t('myCourse.errorReason')" align="center" prop="errorReason" width="150"> |
| | | <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> |
| | | <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 |
| | |
| | | {{ 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"> |
| | | <el-table-column :label="t('table.action')" align="center" width="280" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button-group> |
| | | <template v-if="scope.row.status == 2"> |
| | | <el-button |
| | | type="text" |
| | | type="primary" |
| | | link |
| | | @click="openPreview(scope.row)" |
| | | plain |
| | | > |
| | | {{t('myCourse.preview')}} |
| | | </el-button> |
| | | <el-button |
| | | type="text" |
| | | type="primary" |
| | | link |
| | | @click="handleDownload(scope.row.previewUrl, scope.row.courseName + '_视频')" |
| | | v-if="scope.row.previewUrl!=null" |
| | | > |
| | | 下载 |
| | | </el-button> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | @click="openSubtitleDialog(scope.row.id)" |
| | | v-if="scope.row.status == 2" |
| | | > |
| | | 字幕 |
| | | </el-button> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | @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 |
| | | type="danger" |
| | | link |
| | | @click="handleDelete(scope.row.id)" |
| | | v-if="scope.row.status == 2 || scope.row.status==3 || scope.row.status==4" |
| | | > |
| | | {{ t('action.del') }} |
| | | </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 |
| | | @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> |
| | | </template> |
| | | </el-button-group> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | <el-dialog |
| | | v-model="subtitleDialogVisible" |
| | | title="字幕查看修改" |
| | | width="60%" |
| | | width="70%" |
| | | class="subtitle-dialog" |
| | | > |
| | | <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-tabs v-model="activeSubtitleTab"> |
| | | <el-tab-pane label="字幕设置" name="setting"> |
| | | <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="subtitleForm.subtitlesStatus === 1" |
| | | :disabled="subtitleForm.subtitlesStatus === 1 || subtitleForm.subtitlesAddStatus === 1" |
| | | > |
| | | {{ subtitleForm.subtitlesStatus === 1 ? '字幕生成中' : '生成字幕' }} |
| | | </el-button> |
| | | <el-button |
| | | type="primary" |
| | | @click="triggerFileUpload" |
| | | :disabled="subtitleForm.subtitlesStatus === 1 || subtitleForm.subtitlesAddStatus === 1" |
| | | > |
| | | 上传SRT文件 |
| | | <input |
| | | ref="fileInput" |
| | | type="file" |
| | | accept=".srt" |
| | | style="display: none" |
| | | @change="handleFileUpload" |
| | | /> |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="字幕内容" prop="content" :rules="[ |
| | | { required: true, 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" |
| | | <div style="width: 100%;" class="textarea-wrapper"> |
| | | <el-input |
| | | class="scroll-outside" |
| | | v-model="subtitleForm.content" |
| | | type="textarea" |
| | | :rows="20" |
| | | placeholder="字幕内容将显示在这里(SRT格式)" |
| | | resize="none" |
| | | :disabled="!subtitleForm.content" |
| | | @input="handleSubtitleChange" |
| | | /> |
| | | </el-button> |
| | | <el-button |
| | | type="primary" |
| | | @click="downloadSubtitles" |
| | | :loading="generating || polling" |
| | | > |
| | | 字幕视频合成 |
| | | </el-button> |
| | | <div class="button-group"> |
| | | <el-button |
| | | type="primary" |
| | | @click="saveSubtitles" |
| | | :loading="saving" |
| | | :disabled="!subtitleForm.content || !isSubtitleModified" |
| | | > |
| | | 保存字幕 |
| | | </el-button> |
| | | <el-button |
| | | type="primary" |
| | | @click="downloadSubtitles" |
| | | :loading="subtitleForm.subtitlesAddStatus === 1" |
| | | :disabled="!subtitleForm.content || subtitleForm.subtitlesStatus === 1" |
| | | > |
| | | {{ subtitleForm.subtitlesAddStatus === 1 ? '字幕视频合成中' : '字幕视频合成' }} |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </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-form> |
| | | </el-tab-pane> |
| | | <el-tab-pane label="预览与下载" name="preview"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <div class="preview-section"> |
| | | <h4>字幕视频</h4> |
| | | <div class="video-container"> |
| | | <video |
| | | v-if="subtitleForm.videoUrl" |
| | | :src="subtitleForm.videoUrl" |
| | | controls |
| | | class="preview-video" |
| | | ></video> |
| | | <div v-else class="no-video">暂无字幕视频</div> |
| | | </div> |
| | | <div class="button-group"> |
| | | <el-button |
| | | type="primary" |
| | | @click="handleDownload(subtitleForm.videoUrl, subtitleForm.courseName + '_字幕视频')" |
| | | :disabled="!subtitleForm.videoUrl" |
| | | > |
| | | 下载字幕视频 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | </el-dialog> |
| | | |
| | | <!-- 片头片尾设置弹框 --> |
| | | <el-dialog |
| | | v-model="headerFooterDialogVisible" |
| | | title="片头片尾设置" |
| | | width="50%" |
| | | width="70%" |
| | | > |
| | | <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-tabs v-model="activeTab"> |
| | | <el-tab-pane label="设置片头片尾" name="setting"> |
| | | <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-item> |
| | | <el-button type="primary" @click="applyHeaderFooter" :loading="applyingHeaderFooter">保存设置</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-tab-pane> |
| | | <el-tab-pane label="合成视频" name="merge"> |
| | | <el-form :model="formData1" label-width="120px"> |
| | | <el-form-item label="视频格式"> |
| | | <el-select v-model="formData1.isvideo" class="!w-240px"> |
| | | <el-option :disabled="formData1.value?.subtitlesAddStatus!=2" label="字幕视频" :value="1" /> |
| | | <el-option label="原视频" :value="2" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="片头视频"> |
| | | <div class="video-container"> |
| | | <video |
| | | v-if="formData1.value?.titles" |
| | | :src="formData1.value.titles" |
| | | controls |
| | | class="preview-video" |
| | | @error="handleVideoError('titles')" |
| | | ></video> |
| | | <div v-else class="no-video">暂无片头视频</div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="片尾视频"> |
| | | <div class="video-container"> |
| | | <video |
| | | v-if="formData1.value?.trailer" |
| | | :src="formData1.value.trailer" |
| | | controls |
| | | class="preview-video" |
| | | @error="handleVideoError('trailer')" |
| | | ></video> |
| | | <div v-else class="no-video">暂无片尾视频</div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item> |
| | | <el-button |
| | | type="primary" |
| | | @click="hecheng" |
| | | :loading="applyingHeaderFooter" |
| | | :disabled="!formData1.value?.titles || !formData1.value?.trailer" |
| | | > |
| | | 开始合成 |
| | | </el-button> |
| | | <span v-if="!formData1.value?.titles || !formData1.value?.trailer" class="ml-10px text-red-500"> |
| | | 请先设置片头和片尾视频 |
| | | </span> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-tab-pane> |
| | | <el-tab-pane label="预览与下载" name="preview"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="preview-section"> |
| | | <h4>视频</h4> |
| | | <div class="video-container"> |
| | | <video |
| | | v-if="formData1.value?.videoUrl || formData1.value?.previewUrl" |
| | | :src="formData1.value?.videoUrl || formData1.value?.previewUrl" |
| | | controls |
| | | class="preview-video" |
| | | ></video> |
| | | <div v-else class="no-video">暂无视频</div> |
| | | </div> |
| | | <div class="button-group"> |
| | | <el-button |
| | | type="primary" |
| | | @click="handleDownload(formData1.value?.videoUrl || formData1.value?.previewUrl, formData1.value?.courseName + '_视频')" |
| | | :disabled="!formData1.value?.videoUrl && !formData1.value?.previewUrl" |
| | | > |
| | | 下载视频 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="preview-section"> |
| | | <h4>片头片尾视频</h4> |
| | | <div class="video-container"> |
| | | <video |
| | | v-if="formData1.value?.compositeVideo" |
| | | :src="formData1.value.compositeVideo" |
| | | controls |
| | | class="preview-video" |
| | | ></video> |
| | | <div v-else class="no-video">暂无片头片尾视频</div> |
| | | </div> |
| | | <div class="button-group"> |
| | | <el-button |
| | | type="primary" |
| | | @click="handleDownload(formData1.value?.compositeVideo, formData1.value?.courseName + '_片头片尾视频')" |
| | | :disabled="!formData1.value?.compositeVideo" |
| | | > |
| | | 下载片头片尾视频 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | </el-dialog> |
| | | |
| | | <!-- 视频合成选择弹框 --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | title="视频合成中" |
| | | title="视频合成" |
| | | width="50%" |
| | | > |
| | | <el-form :model="formData1" label-width="120px"> |
| | | <el-form-item label="视频格式"> |
| | | <el-form-item label="视频格式"> |
| | | <el-select v-model="formData1.isvideo"> |
| | | <el-option :disabled="formData1.videoUrl==null" label="字幕视频" :value="1" /> |
| | | <el-option :disabled="formData1.value.subtitlesAddStatus!=2" label="字幕视频" :value="1" /> |
| | | <el-option label="原视频" :value="2" /> |
| | | </el-select> |
| | | </el-form-item> |
| | |
| | | import { config } from '@/config/axios/config' |
| | | import {createVideo, createVideoMeger, videoMeger} from "@/api/pptTemplate"; |
| | | import { ArrowDown } from '@element-plus/icons-vue' |
| | | import { ElMessageBox } from 'element-plus' |
| | | |
| | | const router = useRouter() |
| | | const message = useMessage() |
| | |
| | | const subtitleDialogVisible = ref(false) |
| | | const subtitleFormRef = ref() |
| | | const fileInput = ref<HTMLInputElement | null>(null) |
| | | const activeSubtitleTab = ref('setting') |
| | | const subtitleForm = reactive({ |
| | | videoId: null as number | null, |
| | | timeThreshold: '0.05', |
| | |
| | | subtitlesUrl: '', |
| | | videoUrl: '', |
| | | courseName: '', |
| | | subtitlesAddStatus: null |
| | | subtitlesAddStatus: null, |
| | | originalContent: '', |
| | | subtitlesStatus: null |
| | | }) |
| | | const generating = ref(false) |
| | | const saving = ref(false) |
| | | const isSubtitleModified = ref(false) |
| | | |
| | | // 片头片尾弹框相关 |
| | | const headerFooterDialogVisible = ref(false) |
| | | const activeTab = ref('setting') |
| | | const headerFooterForm = reactive({ |
| | | id: null as number | null, |
| | | titles: '', |
| | |
| | | return `${hrs > 0 ? `${hrs}时` : ''}${mins > 0 ? `${mins}分` : ''}${secs}秒` |
| | | } |
| | | |
| | | // 打开字幕弹框 |
| | | // 处理字幕内容变化 |
| | | const handleSubtitleChange = () => { |
| | | isSubtitleModified.value = subtitleForm.content !== subtitleForm.originalContent |
| | | } |
| | | |
| | | // 修改打开字幕弹窗的函数 |
| | | const openSubtitleDialog = async (videoId: number) => { |
| | | try { |
| | | subtitleDialogVisible.value = true |
| | | activeSubtitleTab.value = 'setting' |
| | | subtitleForm.videoId = videoId |
| | | |
| | | // 重置表单状态 |
| | | subtitleForm.content = '' |
| | | subtitleForm.originalContent = '' |
| | | subtitleForm.videoUrl = '' |
| | | subtitleForm.subtitlesUrl = '' |
| | | subtitleForm.subtitlesStatus = null |
| | | subtitleForm.subtitlesAddStatus = null |
| | | isSubtitleModified.value = false |
| | | generating.value = false |
| | | polling.value = false |
| | | |
| | | const videoDetail = await pptTemplateApi.myCourseDetail(videoId) |
| | | |
| | | if (!videoDetail) { |
| | | message.error('获取视频详情失败,请重试') |
| | | subtitleDialogVisible.value = false |
| | | return |
| | | } |
| | | |
| | | subtitleForm.subtitlesAddStatus = videoDetail.subtitlesAddStatus |
| | | subtitleForm.subtitlesStatus = videoDetail.subtitlesStatus |
| | | subtitleForm.courseName = videoDetail.courseName |
| | | |
| | | if (videoDetail.subtitlesAddStatus === 2) { |
| | |
| | | subtitleForm.videoUrl = '' |
| | | generating.value = true |
| | | polling.value = true |
| | | }else { |
| | | } else { |
| | | subtitleForm.videoUrl = videoDetail.videoUrl || '' |
| | | generating.value = false |
| | | polling.value = false |
| | |
| | | if (response.ok) { |
| | | const srtContent = await response.text() |
| | | subtitleForm.content = srtContent |
| | | subtitleForm.originalContent = srtContent |
| | | } else { |
| | | subtitleForm.content = videoDetail.subtitlesContent || '' |
| | | subtitleForm.originalContent = videoDetail.subtitlesContent || '' |
| | | } |
| | | } catch (error) { |
| | | console.error('获取字幕内容失败:', error) |
| | | subtitleForm.content = videoDetail.subtitlesContent || '' |
| | | subtitleForm.originalContent = videoDetail.subtitlesContent || '' |
| | | } |
| | | } else if (videoDetail.subtitlesContent) { |
| | | subtitleForm.content = videoDetail.subtitlesContent |
| | | subtitleForm.originalContent = videoDetail.subtitlesContent |
| | | } |
| | | } else if (videoDetail.subtitlesStatus === 3) { |
| | | generating.value = false |
| | | polling.value = false |
| | | subtitleForm.content = '' |
| | | subtitleForm.originalContent = '' |
| | | } else if (videoDetail.subtitlesStatus === 1) { |
| | | generating.value = true |
| | | polling.value = true |
| | | subtitleForm.content = '' |
| | | }else{ |
| | | subtitleForm.originalContent = '' |
| | | } else { |
| | | generating.value = false |
| | | polling.value = false |
| | | } |
| | | isSubtitleModified.value = false |
| | | } catch (error) { |
| | | console.error('获取视频详情失败:', error) |
| | | message.error('获取视频详情失败,请重试') |
| | |
| | | } |
| | | |
| | | generating.value = true |
| | | subtitleForm.subtitlesStatus = 1 // 设置字幕状态为生成中 |
| | | |
| | | const params = { |
| | | id: subtitleForm.videoId, |
| | |
| | | if (response.ok) { |
| | | const srtContent = await response.text() |
| | | subtitleForm.content = srtContent |
| | | subtitleForm.originalContent = srtContent |
| | | } |
| | | } catch (error) { |
| | | console.error('Error fetching SRT file:', error) |
| | | } |
| | | } else if (videoDetail.subtitlesContent) { |
| | | subtitleForm.content = videoDetail.subtitlesContent |
| | | subtitleForm.originalContent = videoDetail.subtitlesContent |
| | | } |
| | | message.success(subtitleForm.courseName+' '+'字幕生成成功') |
| | | subtitleForm.subtitlesStatus = 2 // 设置字幕状态为已完成 |
| | | stopPolling() |
| | | } else if (videoDetail.subtitlesStatus === 3) { |
| | | subtitleForm.subtitlesStatus = 3 // 设置字幕状态为失败 |
| | | stopPolling() |
| | | } else if (attempts >= maxAttempts) { |
| | | message.warning(subtitleForm.courseName+' '+'字幕生成超时,请稍后手动检查') |
| | |
| | | } |
| | | } |
| | | |
| | | // 保存字幕 |
| | | // 修改保存字幕函数 |
| | | const saveSubtitles = async () => { |
| | | try { |
| | | saving.value = true |
| | |
| | | |
| | | await pptTemplateApi.saveSubtitles(params) |
| | | message.success('字幕保存成功') |
| | | subtitleDialogVisible.value = false |
| | | |
| | | // 刷新列表 |
| | | getList() |
| | | subtitleForm.originalContent = subtitleForm.content |
| | | isSubtitleModified.value = false |
| | | } catch (error) { |
| | | console.error('保存字幕失败:', error) |
| | | message.error(`保存字幕失败: ${error.message || '未知错误'}`) |
| | |
| | | return srtContent |
| | | } |
| | | |
| | | // 字幕视频合成 |
| | | // 修改字幕视频合成函数 |
| | | const downloadSubtitles = async () => { |
| | | try { |
| | | if (!subtitleForm.content.trim()) { |
| | |
| | | return |
| | | } |
| | | |
| | | generating.value = true |
| | | if (isSubtitleModified.value) { |
| | | try { |
| | | await ElMessageBox.confirm( |
| | | '修改的字幕没有保存,确定要生成视频吗?', |
| | | '提示', |
| | | { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | } |
| | | ) |
| | | } catch { |
| | | return |
| | | } |
| | | } |
| | | |
| | | subtitleForm.subtitlesAddStatus = 1 // 设置字幕视频合成状态为进行中 |
| | | const obj = { |
| | | id: 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) { |
| | |
| | | console.error('字幕视频合成失败:', error) |
| | | message.error(`字幕视频合成失败: ${error.message || '未知错误'}`) |
| | | stopPolling() |
| | | } finally { |
| | | generating.value = false |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | // 处理片头片尾按钮点击 |
| | | const handleHeaderFooter =async (row) => { |
| | | console.log(row) |
| | | const handleHeaderFooter = async (row) => { |
| | | headerFooterForm.id = row.id |
| | | let details= await pptTemplateApi.myCourseDetail(row.id) |
| | | console.log(details) |
| | | let details = await pptTemplateApi.myCourseDetail(row.id) |
| | | headerFooterForm.titles = details.titles || '' |
| | | headerFooterForm.trailer = details.trailer || '' |
| | | formData1.value = details |
| | | headerFooterDialogVisible.value = true |
| | | activeTab.value = 'setting' |
| | | } |
| | | |
| | | // 应用片头片尾设置 |
| | | const applyHeaderFooter = async () => { |
| | | try { |
| | | console.log('应用片头片尾设置:', headerFooterForm) |
| | | const title = await pptTemplateApi.createVideo(headerFooterForm) |
| | | console.log('创建视频标题:', title) |
| | | if (title) { |
| | | message.success('片头片尾设置成功') |
| | | headerFooterDialogVisible.value = false |
| | | getList() |
| | | // 切换到合成视频标签页 |
| | | activeTab.value = 'merge' |
| | | // 更新formData1的值 |
| | | const details = await pptTemplateApi.myCourseDetail(headerFooterForm.id!) |
| | | formData1.value = details |
| | | } |
| | | } catch (error) { |
| | | console.error('片头片尾设置出错:', error) |
| | |
| | | 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 |
| | | 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 |
| | | message.success('视频合成任务已提交,请稍后查看') |
| | | 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 |
| | | } 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.videoUrl, |
| | | previewUrl: null |
| | | } |
| | | const res = await pptTemplateApi.createVideoMeger(obj) |
| | | if (res) { |
| | | message.success('视频合成成功') |
| | | applyingHeaderFooter.value = true |
| | | dialogVisible.value = false |
| | | message.success('视频合成任务已提交,请稍后查看') |
| | | getList() |
| | | } |
| | | } |
| | | // loading.value = true |
| | | // |
| | | } |
| | | catch (error) { |
| | | } catch (error) { |
| | | console.error(error) |
| | | message.error('视频合成失败') |
| | | } finally { |
| | | applyingHeaderFooter.value = false |
| | | } |
| | | } |
| | | |
| | | // 检查视频URL是否有效 |
| | | |
| | | |
| | | // 获取完整的视频URL |
| | | |
| | | |
| | | // 处理视频加载错误 |
| | | const handleVideoError = (type: 'titles' | 'trailer') => { |
| | | console.error(`${type}视频加载失败`) |
| | | if (type === 'titles') { |
| | | formData1.value.titles = '' |
| | | } else { |
| | | formData1.value.trailer = '' |
| | | } |
| | | } |
| | | |
| | |
| | | <style scoped> |
| | | .textarea-wrapper { |
| | | position: relative; |
| | | width: fit-content; |
| | | width: 100%; |
| | | } |
| | | |
| | | .scroll-outside { |
| | |
| | | .el-dropdown { |
| | | margin-left: 0; |
| | | } |
| | | |
| | | .button-group { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | :deep(.el-dialog__body) { |
| | | padding: 20px; |
| | | } |
| | | |
| | | :deep(.el-form-item__label) { |
| | | font-weight: 500; |
| | | } |
| | | |
| | | :deep(.el-button--primary.is-link) { |
| | | padding: 0 8px; |
| | | } |
| | | |
| | | :deep(.el-tabs__nav) { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | :deep(.el-tabs__item) { |
| | | font-size: 16px; |
| | | padding: 0 20px; |
| | | } |
| | | |
| | | :deep(.el-form-item) { |
| | | margin-bottom: 22px; |
| | | } |
| | | |
| | | :deep(.el-input.is-disabled .el-input__inner) { |
| | | background-color: #f5f7fa; |
| | | border-color: #e4e7ed; |
| | | color: #606266; |
| | | } |
| | | |
| | | .preview-section { |
| | | background: #f5f7fa; |
| | | padding: 20px; |
| | | border-radius: 4px; |
| | | height: 100%; |
| | | } |
| | | |
| | | .preview-section h4 { |
| | | margin: 0 0 15px 0; |
| | | color: #606266; |
| | | } |
| | | |
| | | .video-container { |
| | | width: 100%; |
| | | height: 200px; |
| | | background: #000; |
| | | border-radius: 4px; |
| | | overflow: hidden; |
| | | margin-bottom: 15px; |
| | | position: relative; |
| | | } |
| | | |
| | | .preview-video { |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: contain; |
| | | background: #000; |
| | | } |
| | | |
| | | .no-video { |
| | | width: 100%; |
| | | height: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: #909399; |
| | | font-size: 14px; |
| | | background: #f5f7fa; |
| | | } |
| | | |
| | | .button-group { |
| | | display: flex; |
| | | justify-content: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .ml-10px { |
| | | margin-left: 10px; |
| | | } |
| | | |
| | | .text-red-500 { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .subtitle-dialog :deep(.el-dialog__body) { |
| | | padding: 20px; |
| | | min-height: 500px; |
| | | } |
| | | |
| | | .preview-section { |
| | | background: #f5f7fa; |
| | | padding: 20px; |
| | | border-radius: 4px; |
| | | height: 100%; |
| | | min-height: 400px; |
| | | } |
| | | |
| | | .video-container { |
| | | width: 100%; |
| | | height: 300px; |
| | | background: #000; |
| | | border-radius: 4px; |
| | | overflow: hidden; |
| | | margin-bottom: 15px; |
| | | position: relative; |
| | | } |
| | | |
| | | .preview-video { |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: contain; |
| | | background: #000; |
| | | } |
| | | |
| | | .no-video { |
| | | width: 100%; |
| | | height: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: #909399; |
| | | font-size: 14px; |
| | | background: #f5f7fa; |
| | | } |
| | | </style> |