8000 feat: improve task management, completed task will not re-start again · buxuku/SmartSub@24a9ca1 · GitHub
[go: up one dir, main page]

Skip to content

Commit 24a9ca1

Browse files
committed
feat: improve task management, completed task will not re-start again
1 parent 24642a3 commit 24a9ca1

File tree

11 files changed

+220
-107
lines changed

11 files changed

+220
-107
lines changed

main/helpers/fileProcessor.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
generateSubtitleWithBuiltinWhisper,
1010
} from './subtitleGenerator';
1111
import translate from '../translate';
12+
import type { IpcMainEvent } from 'electron';
13+
import type { ITaskFile, Provider } from '../../types';
1214

1315
/**
1416
* 处理任务错误
@@ -124,12 +126,13 @@ async function translateSubtitle(
124126
* 处理文件
125127
*/
126128
export async function processFile(
127-
event,
128-
file,
129-
formData,
130-
hasOpenAiWhisper,
131-
provider,
129+
event: IpcMainEvent,
130+
file: ITaskFile,
131+
hasOpenAiWhisper: boolean,
132+
provider: Provider,
132133
) {
134+
const { formData = {} } = file;
135+
133136
const {
134137
sourceLanguage,
135138
targetLanguage,
@@ -202,14 +205,14 @@ export async function processFile(
202205

203206
// 生成字幕
204207
logMessage(`generate subtitle ${srtFile}`, 'info');
205-
srtFile = await generateSubtitle(
208+
srtFile = (await generateSubtitle(
206209
event,
207210
file,
208211
tempAudioFile,
209212
srtFile,
210213
formData,
211214
hasOpenAiWhisper,
212-
);
215+
)) as string;
213216
} catch (error) {
214217
// 如果是提取音频或生成字幕过程中出错,已经在各自的函数中处理了错误状态
215218
// 这里只需要继续抛出错误,中断后续流程

main/helpers/ipcHandlers.ts

Lines changed: 68 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,45 @@ import { createMessageSender } from './messageHandler';
66
// 定义支持的文件扩展名常量
77
export const MEDIA_EXTENSIONS = [
88
// 视频格式
9-
'.mp4', '.avi', '.mov', '.mkv', '.flv', '.wmv', '.webm',
9+
'.mp4',
10+
'.avi',
11+
'.mov',
12+
'.mkv',
13+
'.flv',
14+
'.wmv',
15+
'.webm',
1016
// 音频格式
11-
'.mp3', '.wav', '.ogg', '.aac', '.wma', '.flac', '.m4a',
12-
'.aiff', '.ape', '.opus', '.ac3', '.amr', '.au', '.mid',
17+
'.mp3',
18+
'.wav',
19+
'.ogg',
20+
'.aac',
21+
'.wma',
22+
'.flac',
23+
'.m4a',
24+
'.aiff',
25+
'.ape',
26+
'.opus',
27+
'.ac3',
28+
'.amr',
29+
'.au',
30+
'.mid',
1331
// 其他常见视频格式
14-
'.3gp', '.asf', '.rm', '.rmvb', '.vob', '.ts', '.mts', '.m2ts',
32+
'.3gp',
33+
'.asf',
34+
'.rm',
35+
'.rmvb',
36+
'.vob',
37+
'.ts',
38+
'.mts',
39+
'.m2ts',
1540
];
1641

1742
export const SUBTITLE_EXTENSIONS = [
1843
// 字幕格式
19-
'.srt', '.vtt', '.ass', '.ssa',
44+
'.srt',
45+
'.vtt',
46+
'.ass',
47+
'.ssa',
2048
];
2149

2250
// 判断文件是否为媒体文件
@@ -32,23 +60,30 @@ export function isSubtitleFile(filePath: string): boolean {
3260
}
3361

3462
// 递归获取文件夹中的符合任务类型的文件
35-
async function getMediaFilesFromDirectory(directoryPath: string, taskType: string): Promise<string[]> {
63+
async function getMediaFilesFromDirectory(
64+
directoryPath: string,
65+
taskType: string,
66+
): Promise<string[]> {
3667
// 根据任务类型选择扩展名
37-
const supportedExtensions = taskType === 'translate'
38-
? SUBTITLE_EXTENSIONS
39-
: MEDIA_EXTENSIONS;
40-
68+
const supportedExtensions =
69+
taskType === 'translate' ? SUBTITLE_EXTENSIONS : MEDIA_EXTENSIONS;
70+
4171
const files: string[] = [];
42-
72+
4373
try {
44-
const entries = await fs.promises.readdir(directoryPath, { withFileTypes: true });
45-
74+
const entries = await fs.promises.readdir(directoryPath, {
75+
withFileTypes: true,
76+
});
77+
4678
for (const entry of entries) {
4779
const fullPath = path.join(directoryPath, entry.name);
48-
80+
4981
if (entry.isDirectory()) {
5082
// 递归处理子目录
51-
const subDirFiles = await getMediaFilesFromDirectory(fullPath, taskType);
83+
const subDirFiles = await getMediaFilesFromDirectory(
84+
fullPath,
85+
taskType,
86+
);
5287
files.push(...subDirFiles);
5388
} else if (entry.isFile()) {
5489
// 检查文件扩展名是否受支持
@@ -61,7 +96,7 @@ async function getMediaFilesFromDirectory(directoryPath: string, taskType: strin
6196
} catch (error) {
6297
console.error(`读取目录 ${directoryPath} 时出错:`, error);
6398
}
64-
99+
65100
return files;
66101
}
67102

@@ -74,12 +109,13 @@ export function setupIpcHandlers(mainWindow: BrowserWindow) {
74109
const { fileType } = data;
75110
console.log(fileType, 'fileType');
76111
const name = fileType === 'srt' ? 'Subtitle Files' : 'Media Files';
77-
112+
78113
// 使用已定义的常量获取扩展名
79-
const extensions = fileType === 'srt'
80-
? SUBTITLE_EXTENSIONS.map(ext => ext.substring(1)) // 移除前面的点
81-
: MEDIA_EXTENSIONS.map(ext => ext.substring(1));
82-
114+
const extensions =
115+
fileType === 'srt'
116+
? SUBTITLE_EXTENSIONS.map((ext) => ext.substring(1)) // 移除前面的点
117+
: MEDIA_EXTENSIONS.map((ext) => ext.substring(1));
118+
83119
const result = await dialog.showOpenDialog({
84120
properties: ['openFile', 'multiSelections'],
85121
filters: [
@@ -107,20 +143,25 @@ export function setupIpcHandlers(mainWindow: BrowserWindow) {
107143
ipcMain.handle('getDroppedFiles', async (event, { files, taskType }) => {
108144
// 处理文件和文件夹
109145
const allValidPaths: string[] = [];
110-
146+
111147
for (const filePath of files) {
112148
try {
113149
const stats = await fs.promises.stat(filePath);
114-
150+
115151
if (stats.isDirectory()) {
116152
// 如果是文件夹,递归获取所有符合任务类型的文件
117-
const filteredFiles = await getMediaFilesFromDirectory(filePath, taskType);
153+
const filteredFiles = await getMediaFilesFromDirectory(
154+
filePath,
155+
taskType,
156+
);
118157
allValidPaths.push(...filteredFiles);
119158
} else if (stats.isFile()) {
120159
// 如果是文件,根据任务类型过滤
121160
// 根据任务类型决定添加哪种文件
122-
if ((taskType === 'translate' && isSubtitleFile(filePath)) ||
123-
(taskType !== 'translate' && isMediaFile(filePath))) {
161+
if (
162+
(taskType === 'translate' && isSubtitleFile(filePath)) ||
163+
(taskType !== 'translate' && isMediaFile(filePath))
164+
) {
124165
allValidPaths.push(filePath);
125166
}
126167
}
@@ -129,7 +170,7 @@ export function setupIpcHandlers(mainWindow: BrowserWindow) {
129170
continue;
130171
}
131172
}
132-
173+
133174
return allValidPaths;
134175
});
135176

main/helpers/taskManager.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ipcMain } from 'electron';
2+
import type { ITaskFile } from '../../types';
23

3-
let taskList = [];
4+
let taskList: ITaskFile[] = [];
45

56
export function setupTaskManager() {
67
ipcMain.handle('getTasks', () => {

main/helpers/taskProcessor.ts

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import fse from 'fs-extra';
2-
import { ipcMain, BrowserWindow } from 'electron';
2+
import { ipcMain, BrowserWindow, type IpcMainEvent } from 'electron';
33
import { processFile } from './fileProcessor';
44
import { checkOpenAiWhisper, getPath } from './whisper';
55
import { logMessage, store } from './storeManager';
66
import path from 'path';
77
import { isAppleSilicon } from './utils';
8+
import type { ITaskFile } from '../../types';
89

9-
let processingQueue = [];
10+
let processingQueue: ITaskFile[] = [];
1011
let isProcessing = false;
1112
let isPaused = false;
1213
let shouldCancel = false;
@@ -15,19 +16,25 @@ let hasOpenAiWhisper = false;
1516
let activeTasksCount = 0;
1617

1718
export function setupTaskProcessor(mainWindow: BrowserWindow) {
18-
ipcMain.on('handleTask', async (event, { files, formData }) => {
19-
logMessage(`handleTask start`, 'info');
20-
logMessage(`formData: \n ${JSON.stringify(formData, null, 2)}`, 'info');
21-
processingQueue.push(...files.map((file) => ({ file, formData })));
22-
if (!isProcessing) {
23-
isProcessing = true;
24-
isPaused = false;
25-
shouldCancel = false;
26-
hasOpenAiWhisper = await checkOpenAiWhisper();
27-
maxConcurrentTasks = formData.maxConcurrentTasks || 3;
28-
processNextTasks(event);
29-
}
30-
});
19+
ipcMain.on(
20+
'handleTask',
21+
async (
22+
event,
23+
{ files, formData }: { files: ITaskFile[]; formData: any },
24+
) => {
25+
logMessage(`handleTask start`, 'info');
26+
logMessage(`formData: \n ${JSON.stringify(formData, null, 2)}`, 'info');
27+
processingQueue.push(...files);
28+
if (!isProcessing) {
29+
isProcessing = true;
30+
isPaused = false;
31+
shouldCancel = false;
32+
hasOpenAiWhisper = await checkOpenAiWhisper();
33+
maxConcurrentTasks = formData.maxConcurrentTasks || 3;
34+
processNextTasks(event);
35+
}
< 3E14 code>36+
},
37+
);
3138

3239
ipcMain.on('pauseTask', () => {
3340
isPaused = true;
@@ -67,7 +74,7 @@ export function setupTaskProcessor(mainWindow: BrowserWindow) {
6774
});
6875
}
6976

70-
async function processNextTasks(event) {
77+
async function processNextTasks(event: IpcMainEvent) {
7178
if (shouldCancel) {
7279
isProcessing = false;
7380
event.sender.send('taskComplete', 'cancelled');
@@ -98,16 +105,11 @@ async function processNextTasks(event) {
98105
activeTasksCount++;
99106
try {
100107
const provider = translationProviders.find(
101-
(p) => p.id === task.formData.translateProvider,
102-
);
103-
await processFile(
104-
event,
105-
task.file,
106-
task.formData,
107-
hasOpenAiWhisper,
108-
provider,
108+
(p) => p.id === task.formData?.translateProvider,
109109
);
110+
await processFile(event, task, hasOpenAiWhisper, provider);
110111
} catch (error) {
112+
console.error(error.stack || error);
111113
event.sender.send('message', error);
112114
} finally {
113115
activeTasksCount--;

renderer/components/TaskControls.tsx

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
1-
import { useEffect, useState } from 'react';
1+
import {
2+
useEffect,
3+
useState,
4+
type Dispatch,
5+
type FC,
6+
type SetStateAction,
7+
} from 'react';
28
import { Button } from './ui/button';
39
import { toast } from 'sonner';
4-
import { isSubtitleFile, needsCoreML } from 'lib/utils';
10+
import { getNewTaskFiles, needsCoreML } from 'lib/utils';
511
import { useTranslation } from 'next-i18next';
12+
import type { ITaskFile } from '../../types';
613

7-
const TaskControls = ({ files, formData }) => {
14+
const TaskControls: FC<{
15+
files: ITaskFile[];
16+
setFiles: Dispatch<SetStateAction<ITaskFile[]>>;
17+
formData: any;
18+
}> = ({ files, setFiles, formData }) => {
819
const [taskStatus, setTaskStatus] = useState('idle');
920
const { t } = useTranslation(['home', 'common']);
1021

@@ -28,30 +39,22 @@ const TaskControls = ({ files, formData }) => {
2839

2940
const handleTask = async () => {
3041
if (!files?.length) {
31-
toast(t('common:notification'), {
32-
description: t('home:noTask'),
33-
});
34-
return;
42+
return toast(t('common:notification'), { description: t('home:noTask') });
3543
}
36-
const isAllFilesProcessed = files.every((item) => {
37-
const basicProcessingDone = item.extractAudio && item.extractSubtitle;
38-
39-
if (formData.translateProvider === '-1') {
40-
return basicProcessingDone;
41-
}
42-
if (isSubtitleFile(item?.filePath)) {
43-
return item.translateSubtitle;
44-
}
4544

46-
return basicProcessingDone && item.translateSubtitle;
45+
// when start task button pressed, persist task config to ITaskFile
46+
const updatedFiles = files.map((f) => {
47+
return { formData, taskType: formData.taskType, ...f };
4748
});
49+
setFiles(updatedFiles);
4850

49-
if (isAllFilesProcessed) {
50-
toast(t('common:notification'), {
51+
const newTaskFiles = getNewTaskFiles(updatedFiles);
52+
if (!newTaskFiles.length) {
53+
return toast(t('common:notification'), {
5154
description: t('home:allFilesProcessed'),
5255
});
53-
return;
5456
}
57+
5558
// if(formData.model && needsCoreML(formData.model)){
5659
// const checkMlmodel = await window.ipc.invoke('checkMlmodel', formData.model);
5760
// if(!checkMlmodel){
@@ -61,8 +64,9 @@ const TaskControls = ({ files, formData }) => {
6164
// return;
6265
// }
6366
// }
67+
6468
setTaskStatus('running');
65-
window?.ipc?.send('handleTask', { files, formData });
69+
window?.ipc?.send('handleTask', { files: newTaskFiles, formData });
6670
};
6771
const handlePause = () => {
6872
window?.ipc?.send('pauseTask', null);

0 commit comments

Comments
 (0)
0