Skip to content

Commit d9320a6

Browse files
committed
完善视频处理进度交互,增加批量任务能力,优化大量细节
1 parent 353b296 commit d9320a6

File tree

11 files changed

+304
-151
lines changed

11 files changed

+304
-151
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@
5858

5959
## 🗺️ 路线图
6060

61-
**当前正在积极开发中,即将发布第一个版本,喜欢可以点个 Star 支持一下**
61+
**喜欢可以点个 Star 支持一下哦**
6262

6363
下面是已实现和计划中的功能:
6464

6565
- [x] 文案生成,兼容通用的 OpenAI 接口格式
6666
- [x] 语音合成,支持EdgeTTS
6767
- [x] 视频剪辑,文案、视频、音频、字幕合成,自动混剪
68-
- [ ] 批量处理,支持一个批量任务,按预设自动持续合成视频
68+
- [x] 批量处理,支持一个批量任务,按预设自动持续合成视频
6969
- [ ] 字幕特效,支持多种字幕样式和特效
7070

7171

electron/lib/tools.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import fs from 'node:fs'
22
import path from 'node:path'
3+
import { app } from 'electron'
34

5+
// import packageJson from '~/package.json'
6+
7+
/**
8+
* 生成有序的唯一文件名,用于处理文件已存在的情况
9+
*/
410
export function generateUniqueFileName(filePath: string): string {
511
const dir = path.dirname(filePath)
612
const ext = path.extname(filePath)
@@ -14,3 +20,10 @@ export function generateUniqueFileName(filePath: string): string {
1420
}
1521
return newPath
1622
}
23+
24+
/**
25+
* 获取软件的临时文件存储路径
26+
*/
27+
export function getAppTempPath() {
28+
return path.join(app.getPath('temp'), app.name).replace(/\\/g, '/')
29+
}

electron/tts/index.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,37 @@
11
import fs from 'node:fs'
22
import path from 'node:path'
3-
import { app } from 'electron'
43
import { EdgeTTS } from '../lib/edge-tts'
54
import { parseBuffer } from 'music-metadata'
65
import {
76
EdgeTtsSynthesizeCommonParams,
87
EdgeTtsSynthesizeToFileParams,
98
EdgeTtsSynthesizeToFileResult,
109
} from './types'
10+
import { getAppTempPath } from '../lib/tools'
11+
import { app } from 'electron'
1112

1213
const edgeTts = new EdgeTTS()
1314
const setupTime = new Date().getTime()
1415

1516
export function getTempTtsVoiceFilePath() {
16-
return path.join(app.getPath('temp'), `temp-tts-voice-${setupTime}.mp3`).replace(/\\/g, '/')
17+
return path.join(getAppTempPath(), `temp-tts-voice-${setupTime}.mp3`).replace(/\\/g, '/')
1718
}
1819

20+
export function clearCurrentTtsFiles() {
21+
const voicePath = getTempTtsVoiceFilePath()
22+
if (fs.existsSync(voicePath)) {
23+
fs.unlinkSync(voicePath)
24+
}
25+
const srtPath = path.join(path.dirname(voicePath), path.basename(voicePath, '.mp3') + '.srt')
26+
if (fs.existsSync(srtPath)) {
27+
fs.unlinkSync(srtPath)
28+
}
29+
}
30+
31+
app.on('before-quit', () => {
32+
clearCurrentTtsFiles()
33+
})
34+
1935
export function edgeTtsGetVoiceList() {
2036
return edgeTts.getVoices()
2137
}

images/ScreenShot.png

-237 KB
Loading

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "short-video-factory",
33
"description": "短视频工厂,一键生成产品营销与泛内容短视频,AI批量自动剪辑",
4-
"version": "0.6.6",
4+
"version": "0.7.0",
55
"author": {
66
"name": "YILS",
77
"developer": "YILS",

src/store/app.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export const useAppStore = defineStore(
6262
outputFileName: '',
6363
outputFileExt: '.mp4',
6464
})
65+
const autoBatch = ref(false)
6566
const renderStatus = ref(RenderStatus.None)
6667
const updateRenderConfig = (newConfig: typeof renderConfig.value) => {
6768
renderConfig.value = newConfig
@@ -89,14 +90,15 @@ export const useAppStore = defineStore(
8990
tryListeningText,
9091

9192
renderConfig,
93+
autoBatch,
9294
renderStatus,
9395
updateRenderConfig,
9496
updateRenderStatus,
9597
}
9698
},
9799
{
98100
persist: {
99-
omit: ['genderList', 'speedList'],
101+
omit: ['genderList', 'speedList', 'autoBatch', 'renderStatus'],
100102
},
101103
},
102104
)

src/views/Home/components/text-generate.vue

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,14 @@
2121
>
2222
生成
2323
</v-btn>
24-
<v-btn v-else prepend-icon="mdi-stop" color="red" stacked @click="handleStopGenerate">
24+
<v-btn
25+
v-else
26+
prepend-icon="mdi-stop"
27+
color="red"
28+
stacked
29+
:disabled="disabled"
30+
@click="handleStopGenerate"
31+
>
2532
停止
2633
</v-btn>
2734

@@ -107,9 +114,9 @@ defineProps<{
107114
const outputText = ref('')
108115
const isGenerating = ref(false)
109116
const abortController = ref<AbortController | null>(null)
110-
const handleGenerate = async () => {
117+
const handleGenerate = async (oprions?: { noToast?: boolean }) => {
111118
if (!appStore.prompt) {
112-
toast.warning('提示词不能为空')
119+
!oprions?.noToast && toast.warning('提示词不能为空')
113120
throw new Error('提示词不能为空')
114121
}
115122
@@ -134,17 +141,19 @@ const handleGenerate = async () => {
134141
for await (const textPart of result.textStream) {
135142
outputText.value += textPart
136143
}
144+
return outputText.value
137145
} catch (error) {
138146
console.log(`error`, error)
139147
// @ts-ignore
140148
if (error?.name !== 'AbortError' && error?.error?.name !== 'AbortError') {
141149
// @ts-ignore
142150
const errorMessage = error?.message || error?.error?.message
143-
toast.error(
144-
`生成失败,请检查大模型配置是否正确\n${errorMessage ? '错误信息:“' + errorMessage + '' : ''}`,
145-
)
151+
!oprions?.noToast &&
152+
toast.error(
153+
`生成失败,请检查大模型配置是否正确\n${errorMessage ? '错误信息:“' + errorMessage + '' : ''}`,
154+
)
155+
throw error
146156
}
147-
throw error
148157
} finally {
149158
abortController.value = null
150159
isGenerating.value = false
@@ -204,8 +213,12 @@ const handleTestConfig = async () => {
204213
const getCurrentOutputText = () => {
205214
return outputText.value
206215
}
216+
// 清空文案
217+
const clearOutputText = () => {
218+
outputText.value = ''
219+
}
207220
208-
defineExpose({ handleGenerate, getCurrentOutputText })
221+
defineExpose({ handleGenerate, handleStopGenerate, getCurrentOutputText, clearOutputText })
209222
</script>
210223

211224
<style lang="scss" scoped>

src/views/Home/components/tts-control.vue

Lines changed: 57 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,58 @@
11
<template>
22
<div>
3-
<v-sheet class="h-fit p-2" border rounded>
4-
<v-combobox
5-
v-model="appStore.language"
6-
density="comfortable"
7-
label="语言"
8-
:items="appStore.languageList"
9-
no-data-text="无数据"
10-
@update:model-value="clearVoice"
11-
></v-combobox>
12-
<v-select
13-
v-model="appStore.gender"
14-
density="comfortable"
15-
label="性别"
16-
:items="appStore.genderList"
17-
item-title="label"
18-
item-value="value"
19-
@update:model-value="clearVoice"
20-
></v-select>
21-
<v-select
22-
v-model="appStore.voice"
23-
density="comfortable"
24-
label="声音"
25-
:items="filteredVoicesList"
26-
item-title="FriendlyName"
27-
return-object
28-
no-data-text="请先选择语言和性别"
29-
></v-select>
30-
<v-select
31-
v-model="appStore.speed"
32-
density="comfortable"
33-
label="语速"
34-
:items="appStore.speedList"
35-
item-title="label"
36-
item-value="value"
37-
></v-select>
38-
<v-text-field
39-
v-model="appStore.tryListeningText"
40-
density="comfortable"
41-
label="试听文本"
42-
></v-text-field>
43-
<v-btn
44-
class="mb-2"
45-
prepend-icon="mdi-volume-high"
46-
block
47-
:loading="tryListeningLoading"
48-
@click="handleTryListening"
49-
>
50-
试听
51-
</v-btn>
52-
</v-sheet>
3+
<v-form :disabled="disabled">
4+
<v-sheet class="h-fit p-2" border rounded>
5+
<v-combobox
6+
v-model="appStore.language"
7+
density="comfortable"
8+
label="语言"
9+
:items="appStore.languageList"
10+
no-data-text="无数据"
11+
@update:model-value="clearVoice"
12+
></v-combobox>
13+
<v-select
14+
v-model="appStore.gender"
15+
density="comfortable"
16+
label="性别"
17+
:items="appStore.genderList"
18+
item-title="label"
19+
item-value="value"
20+
@update:model-value="clearVoice"
21+
></v-select>
22+
<v-select
23+
v-model="appStore.voice"
24+
density="comfortable"
25+
label="声音"
26+
:items="filteredVoicesList"
27+
item-title="FriendlyName"
28+
return-object
29+
no-data-text="请先选择语言和性别"
30+
></v-select>
31+
<v-select
32+
v-model="appStore.speed"
33+
density="comfortable"
34+
label="语速"
35+
:items="appStore.speedList"
36+
item-title="label"
37+
item-value="value"
38+
></v-select>
39+
<v-text-field
40+
v-model="appStore.tryListeningText"
41+
density="comfortable"
42+
label="试听文本"
43+
></v-text-field>
44+
<v-btn
45+
class="mb-2"
46+
prepend-icon="mdi-volume-high"
47+
block
48+
:loading="tryListeningLoading"
49+
:disabled="disabled"
50+
@click="handleTryListening"
51+
>
52+
试听
53+
</v-btn>
54+
</v-sheet>
55+
</v-form>
5356
</div>
5457
</template>
5558

@@ -61,6 +64,10 @@ import { useToast } from 'vue-toastification'
6164
const toast = useToast()
6265
const appStore = useAppStore()
6366
67+
defineProps<{
68+
disabled?: boolean
69+
}>()
70+
6471
const configValid = () => {
6572
if (!appStore.voice) {
6673
toast.warning('请选择一个声音')
@@ -140,7 +147,6 @@ const synthesizedSpeechToFile = async (option: { text: string; withCaption?: boo
140147
return result
141148
} catch (error) {
142149
console.log('语音合成失败', error)
143-
toast.error('语音合成失败,请检查网络')
144150
throw error
145151
}
146152
}

0 commit comments

Comments
 (0)