feat: save modified subtitle
This commit is contained in:
@@ -37,9 +37,9 @@ defineShortcuts({
|
||||
},
|
||||
},
|
||||
'meta_s': {
|
||||
handler: () => {
|
||||
handler: async () => {
|
||||
if (isDropdownOpen.value && isDownloadable.value) {
|
||||
startDownload(props.course.subtitle_url, `眩生花微课_${ props.course.title }_${ props.course.task_id }.srt`)
|
||||
await startDownload(await fetchCourseSubtitleUrl(props.course), `眩生花微课_${ props.course.title }_${ props.course.task_id }.srt`)
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -220,7 +220,7 @@ const copyTaskId = (extraMessage?: string) => {
|
||||
disabled: !isDownloadable,
|
||||
click: () => isPreviewModalOpen = true,
|
||||
}, {
|
||||
label: '查看字幕',
|
||||
label: '编辑字幕',
|
||||
icon: 'i-solar-subtitles-linear',
|
||||
shortcuts: [metaSymbol, 'D'],
|
||||
disabled: !isDownloadable,
|
||||
@@ -233,8 +233,8 @@ const copyTaskId = (extraMessage?: string) => {
|
||||
icon: 'i-tabler-file-download',
|
||||
shortcuts: [metaSymbol, 'S'],
|
||||
disabled: !isDownloadable,
|
||||
click: () => {
|
||||
startDownload(course.subtitle_url, `眩生花微课_${ props.course.title }_${ props.course.task_id }.srt`)
|
||||
click: async () => {
|
||||
await startDownload(await fetchCourseSubtitleUrl(course), `眩生花微课_${ props.course.title }_${ props.course.task_id }.srt`)
|
||||
}
|
||||
}], [{
|
||||
label: '删除记录',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { PropType } from 'vue'
|
||||
import { encode } from '@monosky/base64'
|
||||
|
||||
interface Subtitle {
|
||||
start: string;
|
||||
@@ -17,18 +18,22 @@ const props = defineProps({
|
||||
|
||||
const dayjs = useDayjs()
|
||||
const toast = useToast()
|
||||
const loginState = useLoginState()
|
||||
|
||||
const isDrawerActive = ref(false)
|
||||
const isLoading = ref(true)
|
||||
const isSaving = ref(false)
|
||||
const rawSrt = ref<string | null>(null)
|
||||
const subtitles = ref<Subtitle[]>([])
|
||||
const modified = ref(false)
|
||||
|
||||
const videoElement = ref<HTMLVideoElement | null>(null)
|
||||
|
||||
const loadSrt = async () => {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const response = await fetch(props.course.subtitle_url)
|
||||
// const response = await fetch(props.course.subtitle_url)
|
||||
const response = await fetch(await fetchCourseSubtitleUrl(props.course))
|
||||
const text = await response.text()
|
||||
rawSrt.value = text
|
||||
parseSrt(text)
|
||||
@@ -129,6 +134,30 @@ const onSubtitleInputClick = (subtitle: Subtitle) => {
|
||||
videoElement.value.pause()
|
||||
}
|
||||
|
||||
const saveNewSubtitle = () => {
|
||||
isSaving.value = true
|
||||
const encodedSubtitle = encode(generateSrt())
|
||||
useFetchWrapped<
|
||||
req.gen.CourseSubtitleCreate & AuthedRequest,
|
||||
BaseResponse<resp.gen.CourseSubtitleCreate>
|
||||
>('App.Digital_VideoSubtitle.CreateFile', {
|
||||
token: loginState.token!,
|
||||
user_id: loginState.user.id,
|
||||
sub_type: 1,
|
||||
sub_content: encodedSubtitle,
|
||||
task_id: props.course?.task_id,
|
||||
}).then(_ => {
|
||||
modified.value = false
|
||||
toast.add({
|
||||
color: 'green',
|
||||
title: '字幕已保存',
|
||||
description: '修改后的字幕文件已保存',
|
||||
})
|
||||
}).finally(() => {
|
||||
isSaving.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (rawSrt.value) {
|
||||
parseSrt(rawSrt.value)
|
||||
@@ -240,6 +269,7 @@ defineExpose({
|
||||
:autofocus="false"
|
||||
:color="subtitle.active ? 'primary' : undefined"
|
||||
@click="onSubtitleInputClick(subtitle)"
|
||||
@input="() => { if(!modified) modified = true }"
|
||||
>
|
||||
<template #trailing>
|
||||
<Icon v-if="subtitle.active" name="tabler:keyframe-align-vertical-filled"/>
|
||||
@@ -252,11 +282,12 @@ defineExpose({
|
||||
|
||||
<template #footer>
|
||||
<!-- TODO: 24/07/02 Modified subtitles upload -->
|
||||
<UButton @click="() => {
|
||||
console.log(generateSrt())
|
||||
}">
|
||||
Generate
|
||||
<div class="flex justify-end items-center gap-2">
|
||||
<span v-if="modified" class="text-sm text-yellow-500 font-medium">已更改但未保存</span>
|
||||
<UButton :disabled="!modified" :loading="isSaving" @click="saveNewSubtitle">
|
||||
保存{{ isSaving ? '中' : '' }}
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
</USlideover>
|
||||
|
||||
24
composables/fetchCourseSubtitleUrl.ts
Normal file
24
composables/fetchCourseSubtitleUrl.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export const fetchCourseSubtitleUrl = async (course: resp.gen.CourseGenItem) => {
|
||||
const loginState = useLoginState()
|
||||
|
||||
const subtitleRecord = await useFetchWrapped<
|
||||
{
|
||||
page?: number
|
||||
perpage?: number
|
||||
task_id: string
|
||||
} & AuthedRequest,
|
||||
BaseResponse<PagedData<resp.gen.CourseSubtitleCreate>>
|
||||
>('App.Digital_VideoSubtitle.GetList', {
|
||||
token: loginState.token!,
|
||||
user_id: loginState.user.id,
|
||||
task_id: course.task_id,
|
||||
page: 1,
|
||||
perpage: 1,
|
||||
})
|
||||
|
||||
if (subtitleRecord.data.items.length !== 1) {
|
||||
return course.subtitle_url
|
||||
}
|
||||
|
||||
return subtitleRecord.data.items[0].url
|
||||
}
|
||||
@@ -27,7 +27,7 @@ export default defineNuxtConfig({
|
||||
icons: ['tabler', 'solar', 'line-md', 'svg-spinners'],
|
||||
},
|
||||
colorMode: {
|
||||
preference: 'dark',
|
||||
preference: 'light',
|
||||
},
|
||||
dayjs: {
|
||||
locales: ['zh', 'en'],
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"@iconify-json/solar": "^1.1.9",
|
||||
"@iconify-json/svg-spinners": "^1.1.2",
|
||||
"@iconify-json/tabler": "^1.1.105",
|
||||
"@monosky/base64": "^0.0.3",
|
||||
"@nuxt/image": "^1.7.0",
|
||||
"@nuxt/ui": "^2.14.1",
|
||||
"@uniiem/object-trim": "^0.2.0",
|
||||
|
||||
21
typings/types.d.ts
vendored
21
typings/types.d.ts
vendored
@@ -85,6 +85,18 @@ namespace req {
|
||||
to_user_id: number
|
||||
task_id: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sub_type 0为绿幕生成,1为PPT生成
|
||||
* @param sub_content BASE64后的ass字幕字符串
|
||||
* @param sub_ver optional 字幕版本
|
||||
*/
|
||||
interface CourseSubtitleCreate {
|
||||
sub_type: 0 | 1
|
||||
task_id: string
|
||||
sub_content: string
|
||||
sub_ver?: number
|
||||
}
|
||||
}
|
||||
|
||||
interface AssistantTemplateList {
|
||||
@@ -152,6 +164,15 @@ namespace resp {
|
||||
interface CourseGenDelete {
|
||||
code: 0 | 1
|
||||
}
|
||||
|
||||
/**
|
||||
* @param video_sub_id 字幕记录 ID
|
||||
* @param url 已上传的ass文件URL(文件存放于OSS)
|
||||
*/
|
||||
interface CourseSubtitleCreate {
|
||||
video_sub_id: number
|
||||
url: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
36
yarn.lock
36
yarn.lock
@@ -1541,6 +1541,11 @@
|
||||
semver "^7.3.5"
|
||||
tar "^6.1.11"
|
||||
|
||||
"@monosky/base64@^0.0.3":
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@monosky/base64/-/base64-0.0.3.tgz#32f512911df482df9f5bce208c9eff3a13a94589"
|
||||
integrity sha512-PNNOPniUyjY725FvPjfRZ++Bbufjy3sy2YH7HdvpmwexuZSSdJ1UDkZx03gnWAbhM3hIhBTWbF49/9xMSbNRgg==
|
||||
|
||||
"@netlify/functions@^2.8.0":
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@netlify/functions/-/functions-2.8.0.tgz#245f4be789891159d95391c171263ded8ee1ee3a"
|
||||
@@ -7647,16 +7652,7 @@ streamx@^2.15.0, streamx@^2.18.0:
|
||||
optionalDependencies:
|
||||
bare-events "^2.2.0"
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@@ -7743,14 +7739,7 @@ stringify-object@^3.3.0:
|
||||
is-obj "^1.0.1"
|
||||
is-regexp "^1.0.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@@ -8838,16 +8827,7 @@ workbox-window@7.1.0, workbox-window@^7.1.0:
|
||||
"@types/trusted-types" "^2.0.2"
|
||||
workbox-core "7.1.0"
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
|
||||
Reference in New Issue
Block a user