ソースを参照

增加对口型视频添加

黎海 2 ヶ月 前
コミット
cdb186b497
共有6 個のファイルを変更した304 個の追加59 個の削除を含む
  1. 17 1
      src/components/audioUpload/index.js
  2. 134 55
      src/components/subtitleUpload/index.js
  3. 81 0
      src/components/videoUpload/index.js
  4. 31 0
      src/components/videoUpload/style.css
  5. 15 0
      src/pages/home/home.css
  6. 26 3
      src/pages/home/home.js

+ 17 - 1
src/components/audioUpload/index.js

@@ -1,5 +1,5 @@
1 1
 import React, { useState } from 'react';
2
-import { Button, Form, Spinner, Card } from 'react-bootstrap';
2
+import { Button, Form, Spinner, Card, Alert } from 'react-bootstrap';
3 3
 import { toast } from 'react-toastify';
4 4
 import './style.css';
5 5
 
@@ -12,6 +12,12 @@ const AudioUpload = ({ onAudioSelected }) => {
12 12
   const handleAudioFileChange = (e) => {
13 13
     const selectedFile = e.target.files[0];
14 14
     if (selectedFile) {
15
+      // 检查文件类型
16
+      if (!selectedFile.type.startsWith('audio/')) {
17
+        toast.error('请选择有效的音频文件');
18
+        return;
19
+      }
20
+      
15 21
       setAudioFile(selectedFile);
16 22
       setAudioFileName(selectedFile.name);
17 23
       
@@ -19,6 +25,8 @@ const AudioUpload = ({ onAudioSelected }) => {
19 25
       if (typeof onAudioSelected === 'function') {
20 26
         onAudioSelected(selectedFile);
21 27
       }
28
+      
29
+      toast.success('音频文件已选择');
22 30
     }
23 31
   };
24 32
 
@@ -27,6 +35,9 @@ const AudioUpload = ({ onAudioSelected }) => {
27 35
       <Card>
28 36
         <Card.Header as="h5">上传音频(必选)</Card.Header>
29 37
         <Card.Body>
38
+          <Alert variant="info">
39
+            请上传项目需要的音频文件。音频和视频文件都是创建项目所必需的。
40
+          </Alert>
30 41
           <Form>
31 42
             <Form.Group controlId="audioFile" className="mb-3">
32 43
               <Form.Label>选择音频文件</Form.Label>
@@ -54,6 +65,11 @@ const AudioUpload = ({ onAudioSelected }) => {
54 65
                   请上传音频文件,这是必选项
55 66
                 </Form.Text>
56 67
               )}
68
+              {audioFile && (
69
+                <Form.Text className="text-success">
70
+                  音频文件已选择 ✓
71
+                </Form.Text>
72
+              )}
57 73
             </Form.Group>
58 74
           </Form>
59 75
         </Card.Body>

+ 134 - 55
src/components/subtitleUpload/index.js

@@ -3,7 +3,8 @@ import { Button, Form, Spinner, Card, ListGroup, Modal, InputGroup, Alert } from
3 3
 import { toast } from 'react-toastify';
4 4
 import './style.css';
5 5
 import { bookService, bookInfoService } from '../../db';
6
-import { processProjectAudio } from '../../utils/audioProcessor'; // 导入音频处理工具
6
+// 移除音频处理器导入,因为不再需要分割音频
7
+// import { processProjectAudio } from '../../utils/audioProcessor';
7 8
 // 替换Node.js模块为浏览器兼容版本
8 9
 // const fs = require('fs');
9 10
 // const path = require('path');
@@ -12,7 +13,7 @@ import path from 'path-browserify';
12 13
 import os from 'os-browserify/browser';
13 14
 // fs模块在浏览器中不可用,需要通过IPC调用主进程
14 15
 
15
-const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
16
+const SubtitleUpload = ({ onProjectCreated, projectAudioFile, projectVideoFile }) => {
16 17
   const [file, setFile] = useState(null);
17 18
   const [isLoading, setIsLoading] = useState(false);
18 19
   const [subtitles, setSubtitles] = useState([]);
@@ -22,6 +23,7 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
22 23
   const [isCreating, setIsCreating] = useState(false);
23 24
   const [processingAudio, setProcessingAudio] = useState(false);
24 25
   const [audioProgress, setAudioProgress] = useState(0);
26
+  const [processingVideo, setProcessingVideo] = useState(false);
25 27
 
26 28
   // 处理文件选择
27 29
   const handleFileChange = (e) => {
@@ -133,6 +135,11 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
133 135
       toast.error('请先上传音频文件(必选)');
134 136
       return;
135 137
     }
138
+
139
+    if (!projectVideoFile) {
140
+      toast.error('请先上传视频文件(必选)');
141
+      return;
142
+    }
136 143
     
137 144
     setShowCreateModal(true);
138 145
   };
@@ -206,7 +213,7 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
206 213
           return {
207 214
             success: true,
208 215
             path: result.filePath,
209
-            relativePath: path.join('audio', fileName)
216
+            absolutePath: result.filePath
210 217
           };
211 218
         } else {
212 219
           throw new Error(result.error);
@@ -216,10 +223,11 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
216 223
         console.warn('Electron IPC不可用,无法保存文件到本地');
217 224
         
218 225
         // 模拟成功返回,用于开发环境测试
226
+        const fullPath = path.join(audioDir, fileName);
219 227
         return {
220 228
           success: true,
221
-          path: path.join(audioDir, fileName),
222
-          relativePath: path.join('audio', fileName)
229
+          path: fullPath,
230
+          absolutePath: fullPath
223 231
         };
224 232
       }
225 233
     } catch (error) {
@@ -231,6 +239,60 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
231 239
     }
232 240
   };
233 241
 
242
+  // 保存视频文件到本地
243
+  const saveVideoFile = async (videoFile, projectId) => {
244
+    try {
245
+      const audioDir = await getAppDataPath();
246
+      if (!audioDir) {
247
+        throw new Error('无法获取视频存储路径');
248
+      }
249
+      
250
+      const fileName = `project_${projectId}_video.mp4`;
251
+      
252
+      // 读取文件内容
253
+      const buffer = await videoFile.arrayBuffer();
254
+      
255
+      // 检查electron对象是否存在
256
+      if (typeof window.electron !== 'undefined' && window.electron.ipcRenderer) {
257
+        // 通过IPC调用保存文件
258
+        const result = await window.electron.ipcRenderer.invoke('save-audio-file', {
259
+          buffer: Array.from(new Uint8Array(buffer)),
260
+          fileName: fileName,
261
+          directory: audioDir,
262
+          projectId: projectId,
263
+          isVideo: true
264
+        });
265
+        
266
+        if (result.success) {
267
+          return {
268
+            success: true,
269
+            path: result.filePath,
270
+            absolutePath: result.filePath
271
+          };
272
+        } else {
273
+          throw new Error(result.error);
274
+        }
275
+      } else {
276
+        // 处理非Electron环境(例如开发环境中的Web浏览器)
277
+        console.warn('Electron IPC不可用,无法保存文件到本地');
278
+        
279
+        // 模拟成功返回,用于开发环境测试
280
+        const fullPath = path.join(audioDir, fileName);
281
+        return {
282
+          success: true,
283
+          path: fullPath,
284
+          absolutePath: fullPath
285
+        };
286
+      }
287
+    } catch (error) {
288
+      console.error('保存视频文件失败:', error);
289
+      return {
290
+        success: false,
291
+        error: error.message
292
+      };
293
+    }
294
+  };
295
+
234 296
   // 创建项目
235 297
   const handleCreateProject = async () => {
236 298
     if (!projectTitle.trim()) {
@@ -243,16 +305,13 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
243 305
       return;
244 306
     }
245 307
 
308
+    if (!projectVideoFile) {
309
+      toast.error('请上传视频文件(必选)');
310
+      return;
311
+    }
312
+
246 313
     setIsCreating(true);
247 314
     try {
248
-      // 创建项目记录
249
-      const bookId = await bookService.createBook({
250
-        title: projectTitle,
251
-        subtitle_path: file ? file.name : null,
252
-        audio_path: projectAudioFile ? projectAudioFile.name : null,
253
-        created_at: new Date().toISOString()
254
-      });
255
-
256 315
       // 为每个字幕段计算持续时间
257 316
       const processedSubtitles = subtitles.map(sub => {
258 317
         // 解析开始和结束时间
@@ -272,7 +331,16 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
272 331
         };
273 332
       });
274 333
 
275
-      // 创建项目详细信息(先不包含音频路径)
334
+      // 创建项目记录
335
+      const bookId = await bookService.createBook({
336
+        title: projectTitle,
337
+        subtitle_path: file ? file.name : null,
338
+        audio_path: projectAudioFile ? projectAudioFile.name : null,
339
+        video_path: projectVideoFile ? projectVideoFile.name : null,
340
+        created_at: new Date().toISOString()
341
+      });
342
+
343
+      // 创建项目详细信息
276 344
       const bookInfoPromises = processedSubtitles.map(sub => {
277 345
         return bookInfoService.createBookInfo({
278 346
           book_id: bookId,
@@ -284,50 +352,42 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
284 352
         });
285 353
       });
286 354
 
287
-      const createdSegments = await Promise.all(bookInfoPromises);
355
+      await Promise.all(bookInfoPromises);
288 356
       
289
-      // 处理音频文件
357
+      // 处理音频文件 - 仅保存路径
290 358
       setProcessingAudio(true);
291
-      toast.info('正在处理音频文件,请稍候...');
359
+      toast.info('正在保存音频文件,请稍候...');
292 360
       
293 361
       // 保存完整音频文件
294 362
       const savedAudio = await saveAudioFile(projectAudioFile, bookId);
295 363
       
296 364
       if (savedAudio.success) {
297
-        // 更新项目记录中的音频路径为对路径
365
+        // 更新项目记录中的音频路径为对路径
298 366
         await bookService.updateBook(bookId, {
299
-          audio_path: savedAudio.relativePath
367
+          audio_path: savedAudio.absolutePath
300 368
         });
301
-        
302
-        // 获取所有已创建的分镜信息
303
-        const segments = await bookInfoService.getBookInfoByBookId(bookId);
304
-        
305
-        // 切割音频
306
-        const audioProcessResult = await processProjectAudio(
307
-          bookId,
308
-          savedAudio.path,
309
-          segments,
310
-          getAppDataPath()
311
-        );
312
-        
313
-        if (audioProcessResult.success) {
314
-          // 更新每个分镜的音频路径
315
-          const updatePromises = audioProcessResult.segments.map(segment => {
316
-            return bookInfoService.updateBookInfo(segment.segmentId, {
317
-              audio_path: segment.relativePath
318
-            });
319
-          });
320
-          
321
-          await Promise.all(updatePromises);
322
-          
323
-          toast.success(`音频处理完成,已为 ${audioProcessResult.processedSegments}/${audioProcessResult.totalSegments} 个分镜生成独立音频`);
324
-        } else {
325
-          toast.warning(`音频切割过程中出现一些问题: ${audioProcessResult.error}`);
326
-        }
369
+        toast.success('音频文件路径已保存');
327 370
       } else {
328 371
         toast.warning(`保存音频文件失败: ${savedAudio.error}`);
329 372
       }
330 373
 
374
+      // 处理视频文件 - 仅保存路径
375
+      setProcessingVideo(true);
376
+      toast.info('正在保存视频文件,请稍候...');
377
+      
378
+      // 保存视频文件
379
+      const savedVideo = await saveVideoFile(projectVideoFile, bookId);
380
+      
381
+      if (savedVideo.success) {
382
+        // 更新项目记录中的视频路径为绝对路径
383
+        await bookService.updateBook(bookId, {
384
+          video_path: savedVideo.absolutePath
385
+        });
386
+        toast.success('视频文件路径已保存');
387
+      } else {
388
+        toast.warning(`保存视频文件失败: ${savedVideo.error}`);
389
+      }
390
+
331 391
       toast.success('项目创建成功!');
332 392
       setShowCreateModal(false);
333 393
       
@@ -337,6 +397,7 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
337 397
       setFileName('');
338 398
       setSubtitles([]);
339 399
       setProcessingAudio(false);
400
+      setProcessingVideo(false);
340 401
       
341 402
       // 调用回调函数
342 403
       if (typeof onProjectCreated === 'function') {
@@ -348,6 +409,7 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
348 409
     } finally {
349 410
       setIsCreating(false);
350 411
       setProcessingAudio(false);
412
+      setProcessingVideo(false);
351 413
     }
352 414
   };
353 415
 
@@ -427,7 +489,7 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
427 489
                   variant="success" 
428 490
                   size="lg" 
429 491
                   onClick={openCreateProjectModal}
430
-                  disabled={!projectAudioFile}
492
+                  disabled={!projectAudioFile || !projectVideoFile}
431 493
                 >
432 494
                   创建项目
433 495
                 </Button>
@@ -436,6 +498,11 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
436 498
                     请先上传音频文件(必选)
437 499
                   </div>
438 500
                 )}
501
+                {!projectVideoFile && (
502
+                  <div className="text-danger mt-2">
503
+                    请先上传视频文件(必选)
504
+                  </div>
505
+                )}
439 506
               </div>
440 507
             </>
441 508
           )}
@@ -443,8 +510,8 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
443 510
       </Card>
444 511
 
445 512
       {/* 创建项目模态框 */}
446
-      <Modal show={showCreateModal} onHide={() => !isCreating && !processingAudio && setShowCreateModal(false)}>
447
-        <Modal.Header closeButton={!isCreating && !processingAudio}>
513
+      <Modal show={showCreateModal} onHide={() => !isCreating && !processingAudio && !processingVideo && setShowCreateModal(false)}>
514
+        <Modal.Header closeButton={!isCreating && !processingAudio && !processingVideo}>
448 515
           <Modal.Title>创建新项目</Modal.Title>
449 516
         </Modal.Header>
450 517
         <Modal.Body>
@@ -456,7 +523,7 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
456 523
                 placeholder="请输入项目标题"
457 524
                 value={projectTitle}
458 525
                 onChange={(e) => setProjectTitle(e.target.value)}
459
-                disabled={isCreating || processingAudio}
526
+                disabled={isCreating || processingAudio || processingVideo}
460 527
               />
461 528
             </Form.Group>
462 529
             <Form.Group className="mt-3">
@@ -478,6 +545,15 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
478 545
               </InputGroup>
479 546
             </Form.Group>
480 547
             <Form.Group className="mt-3">
548
+              <Form.Label>视频文件</Form.Label>
549
+              <InputGroup>
550
+                <Form.Control
551
+                  readOnly
552
+                  value={projectVideoFile ? projectVideoFile.name : '未选择视频文件'}
553
+                />
554
+              </InputGroup>
555
+            </Form.Group>
556
+            <Form.Group className="mt-3">
481 557
               <Form.Label>字幕条数</Form.Label>
482 558
               <Form.Control
483 559
                 readOnly
@@ -485,10 +561,10 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
485 561
               />
486 562
             </Form.Group>
487 563
             
488
-            {processingAudio && (
564
+            {(processingAudio || processingVideo) && (
489 565
               <div className="mt-3">
490 566
                 <Alert variant="info">
491
-                  正在处理音频文件,请稍候...这可能需要一些时间。
567
+                  正在保存{processingAudio ? '音频' : '视频'}文件,请稍候...
492 568
                 </Alert>
493 569
               </div>
494 570
             )}
@@ -498,16 +574,16 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
498 574
           <Button 
499 575
             variant="secondary" 
500 576
             onClick={() => setShowCreateModal(false)}
501
-            disabled={isCreating || processingAudio}
577
+            disabled={isCreating || processingAudio || processingVideo}
502 578
           >
503 579
             取消
504 580
           </Button>
505 581
           <Button 
506 582
             variant="primary" 
507 583
             onClick={handleCreateProject}
508
-            disabled={isCreating || processingAudio}
584
+            disabled={isCreating || processingAudio || processingVideo}
509 585
           >
510
-            {isCreating || processingAudio ? (
586
+            {isCreating || processingAudio || processingVideo ? (
511 587
               <>
512 588
                 <Spinner
513 589
                   as="span"
@@ -516,7 +592,10 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
516 592
                   role="status"
517 593
                   aria-hidden="true"
518 594
                 />
519
-                <span className="ml-2">{processingAudio ? '处理音频中...' : '创建中...'}</span>
595
+                <span className="ml-2">
596
+                  {processingAudio ? '保存音频中...' : 
597
+                   processingVideo ? '保存视频中...' : '创建中...'}
598
+                </span>
520 599
               </>
521 600
             ) : (
522 601
               '确认创建'

+ 81 - 0
src/components/videoUpload/index.js

@@ -0,0 +1,81 @@
1
+import React, { useState } from 'react';
2
+import { Button, Form, Spinner, Card, Alert } from 'react-bootstrap';
3
+import { toast } from 'react-toastify';
4
+import './style.css';
5
+
6
+const VideoUpload = ({ onVideoSelected }) => {
7
+  const [videoFile, setVideoFile] = useState(null);
8
+  const [loading, setLoading] = useState(false);
9
+  const [videoFileName, setVideoFileName] = useState('');
10
+
11
+  // 处理视频文件选择
12
+  const handleVideoFileChange = (e) => {
13
+    const selectedFile = e.target.files[0];
14
+    if (selectedFile) {
15
+      // 检查文件类型
16
+      if (!selectedFile.type.startsWith('video/')) {
17
+        toast.error('请选择有效的视频文件');
18
+        return;
19
+      }
20
+      
21
+      setVideoFile(selectedFile);
22
+      setVideoFileName(selectedFile.name);
23
+      
24
+      // 调用回调函数将视频文件传递给父组件
25
+      if (typeof onVideoSelected === 'function') {
26
+        onVideoSelected(selectedFile);
27
+      }
28
+      
29
+      toast.success('视频文件已选择');
30
+    }
31
+  };
32
+
33
+  return (
34
+    <div className="video-upload-container">
35
+      <Card>
36
+        <Card.Header as="h5">上传视频(必选)</Card.Header>
37
+        <Card.Body>
38
+          <Alert variant="info">
39
+            请上传项目需要的视频文件。音频和视频文件都是创建项目所必需的。
40
+          </Alert>
41
+          <Form>
42
+            <Form.Group controlId="videoFile" className="mb-3">
43
+              <Form.Label>选择视频文件</Form.Label>
44
+              <div className="custom-file-upload">
45
+                <input
46
+                  type="file"
47
+                  accept="video/*"
48
+                  onChange={handleVideoFileChange}
49
+                  id="video-file-input"
50
+                  className="d-none"
51
+                  required
52
+                />
53
+                <div className="file-upload-box">
54
+                  <Button 
55
+                    variant="outline-primary" 
56
+                    onClick={() => document.getElementById('video-file-input').click()}
57
+                  >
58
+                    选择视频
59
+                  </Button>
60
+                  <span className="ml-2">{videoFileName || '未选择视频文件'}</span>
61
+                </div>
62
+              </div>
63
+              {!videoFile && (
64
+                <Form.Text className="text-danger">
65
+                  请上传视频文件,这是必选项
66
+                </Form.Text>
67
+              )}
68
+              {videoFile && (
69
+                <Form.Text className="text-success">
70
+                  视频文件已选择 ✓
71
+                </Form.Text>
72
+              )}
73
+            </Form.Group>
74
+          </Form>
75
+        </Card.Body>
76
+      </Card>
77
+    </div>
78
+  );
79
+};
80
+
81
+export default VideoUpload; 

+ 31 - 0
src/components/videoUpload/style.css

@@ -0,0 +1,31 @@
1
+.video-upload-container {
2
+  margin-bottom: 2rem;
3
+}
4
+
5
+.custom-file-upload {
6
+  display: flex;
7
+  align-items: center;
8
+}
9
+
10
+.file-upload-box {
11
+  display: flex;
12
+  align-items: center;
13
+  width: 100%;
14
+}
15
+
16
+.file-upload-box span {
17
+  margin-left: 1rem;
18
+  flex-grow: 1;
19
+  overflow: hidden;
20
+  text-overflow: ellipsis;
21
+  white-space: nowrap;
22
+}
23
+
24
+.video-upload-container .card {
25
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
26
+}
27
+
28
+.video-upload-container .card-header {
29
+  background-color: #e7f5ff;
30
+  color: #0c63e4;
31
+} 

+ 15 - 0
src/pages/home/home.css

@@ -258,4 +258,19 @@
258 258
 .modal-footer {
259 259
   background-color: #f8f9fa;
260 260
   border-top: 1px solid #dee2e6;
261
+}
262
+
263
+/* 文件路径显示 */
264
+.file-path-badge {
265
+  white-space: nowrap;
266
+  overflow: hidden;
267
+  text-overflow: ellipsis;
268
+  max-width: 150px;
269
+}
270
+
271
+.file-path-badge .badge {
272
+  overflow: hidden;
273
+  text-overflow: ellipsis;
274
+  display: inline-block;
275
+  max-width: 100%;
261 276
 }

+ 26 - 3
src/pages/home/home.js

@@ -6,6 +6,7 @@ import { toast } from 'react-toastify';
6 6
 import VideoDownload from '../../components/videoDownload';
7 7
 import SubtitleUpload from '../../components/subtitleUpload';
8 8
 import AudioUpload from '../../components/audioUpload';
9
+import VideoUpload from '../../components/videoUpload';
9 10
 import { bookService, bookInfoService, getDbPath } from '../../db';
10 11
 import CozeApiSettings from '../../components/CozeApiSettings';
11 12
 import { hasValidToken } from '../../utils/cozeConfig';
@@ -20,6 +21,7 @@ const Home = forwardRef((props, ref) => {
20 21
   const [showAudioPlayer, setShowAudioPlayer] = useState(false);
21 22
   const [currentAudio, setCurrentAudio] = useState(null);
22 23
   const [projectAudioFile, setProjectAudioFile] = useState(null);
24
+  const [projectVideoFile, setProjectVideoFile] = useState(null);
23 25
   const history = useHistory();
24 26
 
25 27
   const handleAddProject = () => {
@@ -377,6 +379,7 @@ const Home = forwardRef((props, ref) => {
377 379
                             <th>创建时间</th>
378 380
                             <th>字幕文件</th>
379 381
                             <th>音频文件</th>
382
+                            <th>视频文件</th>
380 383
                             <th>操作</th>
381 384
                           </tr>
382 385
                         </thead>
@@ -394,7 +397,26 @@ const Home = forwardRef((props, ref) => {
394 397
                               </td>
395 398
                               <td>
396 399
                                 {project.audio_path ? (
397
-                                  <Badge variant="success">{project.audio_path}</Badge>
400
+                                  <div className="file-path-badge" title={project.audio_path}>
401
+                                    <Badge bg="success">
402
+                                      {project.audio_path.length > 15 
403
+                                        ? project.audio_path.substring(0, 6) + '...' + project.audio_path.substring(project.audio_path.length - 6) 
404
+                                        : project.audio_path}
405
+                                    </Badge>
406
+                                  </div>
407
+                                ) : (
408
+                                  <span className="text-muted">无</span>
409
+                                )}
410
+                              </td>
411
+                              <td>
412
+                                {project.video_path ? (
413
+                                  <div className="file-path-badge" title={project.video_path}>
414
+                                    <Badge bg="primary">
415
+                                      {project.video_path.length > 15 
416
+                                        ? project.video_path.substring(0, 6) + '...' + project.video_path.substring(project.video_path.length - 6) 
417
+                                        : project.video_path}
418
+                                    </Badge>
419
+                                  </div>
398 420
                                 ) : (
399 421
                                   <span className="text-muted">无</span>
400 422
                                 )}
@@ -470,11 +492,12 @@ const Home = forwardRef((props, ref) => {
470 492
 
471 493
               <AudioUpload onAudioSelected={(audioFile) => setProjectAudioFile(audioFile)} />
472 494
               
473
-              {/* <VideoDownload /> */}
495
+              <VideoUpload onVideoSelected={(videoFile) => setProjectVideoFile(videoFile)} />
474 496
 
475 497
               <SubtitleUpload 
476 498
                 onProjectCreated={backToHome} 
477
-                projectAudioFile={projectAudioFile} 
499
+                projectAudioFile={projectAudioFile}
500
+                projectVideoFile={projectVideoFile}
478 501
               />
479 502
 
480 503
             </>