Browse Source

增加对口型视频添加

黎海 2 months ago
parent
commit
cdb186b497

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

@@ -1,5 +1,5 @@
1
 import React, { useState } from 'react';
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
 import { toast } from 'react-toastify';
3
 import { toast } from 'react-toastify';
4
 import './style.css';
4
 import './style.css';
5
 
5
 
@@ -12,6 +12,12 @@ const AudioUpload = ({ onAudioSelected }) => {
12
   const handleAudioFileChange = (e) => {
12
   const handleAudioFileChange = (e) => {
13
     const selectedFile = e.target.files[0];
13
     const selectedFile = e.target.files[0];
14
     if (selectedFile) {
14
     if (selectedFile) {
15
+      // 检查文件类型
16
+      if (!selectedFile.type.startsWith('audio/')) {
17
+        toast.error('请选择有效的音频文件');
18
+        return;
19
+      }
20
+      
15
       setAudioFile(selectedFile);
21
       setAudioFile(selectedFile);
16
       setAudioFileName(selectedFile.name);
22
       setAudioFileName(selectedFile.name);
17
       
23
       
@@ -19,6 +25,8 @@ const AudioUpload = ({ onAudioSelected }) => {
19
       if (typeof onAudioSelected === 'function') {
25
       if (typeof onAudioSelected === 'function') {
20
         onAudioSelected(selectedFile);
26
         onAudioSelected(selectedFile);
21
       }
27
       }
28
+      
29
+      toast.success('音频文件已选择');
22
     }
30
     }
23
   };
31
   };
24
 
32
 
@@ -27,6 +35,9 @@ const AudioUpload = ({ onAudioSelected }) => {
27
       <Card>
35
       <Card>
28
         <Card.Header as="h5">上传音频(必选)</Card.Header>
36
         <Card.Header as="h5">上传音频(必选)</Card.Header>
29
         <Card.Body>
37
         <Card.Body>
38
+          <Alert variant="info">
39
+            请上传项目需要的音频文件。音频和视频文件都是创建项目所必需的。
40
+          </Alert>
30
           <Form>
41
           <Form>
31
             <Form.Group controlId="audioFile" className="mb-3">
42
             <Form.Group controlId="audioFile" className="mb-3">
32
               <Form.Label>选择音频文件</Form.Label>
43
               <Form.Label>选择音频文件</Form.Label>
@@ -54,6 +65,11 @@ const AudioUpload = ({ onAudioSelected }) => {
54
                   请上传音频文件,这是必选项
65
                   请上传音频文件,这是必选项
55
                 </Form.Text>
66
                 </Form.Text>
56
               )}
67
               )}
68
+              {audioFile && (
69
+                <Form.Text className="text-success">
70
+                  音频文件已选择 ✓
71
+                </Form.Text>
72
+              )}
57
             </Form.Group>
73
             </Form.Group>
58
           </Form>
74
           </Form>
59
         </Card.Body>
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
 import { toast } from 'react-toastify';
3
 import { toast } from 'react-toastify';
4
 import './style.css';
4
 import './style.css';
5
 import { bookService, bookInfoService } from '../../db';
5
 import { bookService, bookInfoService } from '../../db';
6
-import { processProjectAudio } from '../../utils/audioProcessor'; // 导入音频处理工具
6
+// 移除音频处理器导入,因为不再需要分割音频
7
+// import { processProjectAudio } from '../../utils/audioProcessor';
7
 // 替换Node.js模块为浏览器兼容版本
8
 // 替换Node.js模块为浏览器兼容版本
8
 // const fs = require('fs');
9
 // const fs = require('fs');
9
 // const path = require('path');
10
 // const path = require('path');
@@ -12,7 +13,7 @@ import path from 'path-browserify';
12
 import os from 'os-browserify/browser';
13
 import os from 'os-browserify/browser';
13
 // fs模块在浏览器中不可用,需要通过IPC调用主进程
14
 // fs模块在浏览器中不可用,需要通过IPC调用主进程
14
 
15
 
15
-const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
16
+const SubtitleUpload = ({ onProjectCreated, projectAudioFile, projectVideoFile }) => {
16
   const [file, setFile] = useState(null);
17
   const [file, setFile] = useState(null);
17
   const [isLoading, setIsLoading] = useState(false);
18
   const [isLoading, setIsLoading] = useState(false);
18
   const [subtitles, setSubtitles] = useState([]);
19
   const [subtitles, setSubtitles] = useState([]);
@@ -22,6 +23,7 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
22
   const [isCreating, setIsCreating] = useState(false);
23
   const [isCreating, setIsCreating] = useState(false);
23
   const [processingAudio, setProcessingAudio] = useState(false);
24
   const [processingAudio, setProcessingAudio] = useState(false);
24
   const [audioProgress, setAudioProgress] = useState(0);
25
   const [audioProgress, setAudioProgress] = useState(0);
26
+  const [processingVideo, setProcessingVideo] = useState(false);
25
 
27
 
26
   // 处理文件选择
28
   // 处理文件选择
27
   const handleFileChange = (e) => {
29
   const handleFileChange = (e) => {
@@ -133,6 +135,11 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
133
       toast.error('请先上传音频文件(必选)');
135
       toast.error('请先上传音频文件(必选)');
134
       return;
136
       return;
135
     }
137
     }
138
+
139
+    if (!projectVideoFile) {
140
+      toast.error('请先上传视频文件(必选)');
141
+      return;
142
+    }
136
     
143
     
137
     setShowCreateModal(true);
144
     setShowCreateModal(true);
138
   };
145
   };
@@ -206,7 +213,7 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
206
           return {
213
           return {
207
             success: true,
214
             success: true,
208
             path: result.filePath,
215
             path: result.filePath,
209
-            relativePath: path.join('audio', fileName)
216
+            absolutePath: result.filePath
210
           };
217
           };
211
         } else {
218
         } else {
212
           throw new Error(result.error);
219
           throw new Error(result.error);
@@ -216,10 +223,11 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
216
         console.warn('Electron IPC不可用,无法保存文件到本地');
223
         console.warn('Electron IPC不可用,无法保存文件到本地');
217
         
224
         
218
         // 模拟成功返回,用于开发环境测试
225
         // 模拟成功返回,用于开发环境测试
226
+        const fullPath = path.join(audioDir, fileName);
219
         return {
227
         return {
220
           success: true,
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
     } catch (error) {
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
   const handleCreateProject = async () => {
297
   const handleCreateProject = async () => {
236
     if (!projectTitle.trim()) {
298
     if (!projectTitle.trim()) {
@@ -243,16 +305,13 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
243
       return;
305
       return;
244
     }
306
     }
245
 
307
 
308
+    if (!projectVideoFile) {
309
+      toast.error('请上传视频文件(必选)');
310
+      return;
311
+    }
312
+
246
     setIsCreating(true);
313
     setIsCreating(true);
247
     try {
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
       const processedSubtitles = subtitles.map(sub => {
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
       const bookInfoPromises = processedSubtitles.map(sub => {
344
       const bookInfoPromises = processedSubtitles.map(sub => {
277
         return bookInfoService.createBookInfo({
345
         return bookInfoService.createBookInfo({
278
           book_id: bookId,
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
       setProcessingAudio(true);
358
       setProcessingAudio(true);
291
-      toast.info('正在处理音频文件,请稍候...');
359
+      toast.info('正在保存音频文件,请稍候...');
292
       
360
       
293
       // 保存完整音频文件
361
       // 保存完整音频文件
294
       const savedAudio = await saveAudioFile(projectAudioFile, bookId);
362
       const savedAudio = await saveAudioFile(projectAudioFile, bookId);
295
       
363
       
296
       if (savedAudio.success) {
364
       if (savedAudio.success) {
297
-        // 更新项目记录中的音频路径为对路径
365
+        // 更新项目记录中的音频路径为对路径
298
         await bookService.updateBook(bookId, {
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
       } else {
370
       } else {
328
         toast.warning(`保存音频文件失败: ${savedAudio.error}`);
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
       toast.success('项目创建成功!');
391
       toast.success('项目创建成功!');
332
       setShowCreateModal(false);
392
       setShowCreateModal(false);
333
       
393
       
@@ -337,6 +397,7 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
337
       setFileName('');
397
       setFileName('');
338
       setSubtitles([]);
398
       setSubtitles([]);
339
       setProcessingAudio(false);
399
       setProcessingAudio(false);
400
+      setProcessingVideo(false);
340
       
401
       
341
       // 调用回调函数
402
       // 调用回调函数
342
       if (typeof onProjectCreated === 'function') {
403
       if (typeof onProjectCreated === 'function') {
@@ -348,6 +409,7 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
348
     } finally {
409
     } finally {
349
       setIsCreating(false);
410
       setIsCreating(false);
350
       setProcessingAudio(false);
411
       setProcessingAudio(false);
412
+      setProcessingVideo(false);
351
     }
413
     }
352
   };
414
   };
353
 
415
 
@@ -427,7 +489,7 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
427
                   variant="success" 
489
                   variant="success" 
428
                   size="lg" 
490
                   size="lg" 
429
                   onClick={openCreateProjectModal}
491
                   onClick={openCreateProjectModal}
430
-                  disabled={!projectAudioFile}
492
+                  disabled={!projectAudioFile || !projectVideoFile}
431
                 >
493
                 >
432
                   创建项目
494
                   创建项目
433
                 </Button>
495
                 </Button>
@@ -436,6 +498,11 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
436
                     请先上传音频文件(必选)
498
                     请先上传音频文件(必选)
437
                   </div>
499
                   </div>
438
                 )}
500
                 )}
501
+                {!projectVideoFile && (
502
+                  <div className="text-danger mt-2">
503
+                    请先上传视频文件(必选)
504
+                  </div>
505
+                )}
439
               </div>
506
               </div>
440
             </>
507
             </>
441
           )}
508
           )}
@@ -443,8 +510,8 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
443
       </Card>
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
           <Modal.Title>创建新项目</Modal.Title>
515
           <Modal.Title>创建新项目</Modal.Title>
449
         </Modal.Header>
516
         </Modal.Header>
450
         <Modal.Body>
517
         <Modal.Body>
@@ -456,7 +523,7 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
456
                 placeholder="请输入项目标题"
523
                 placeholder="请输入项目标题"
457
                 value={projectTitle}
524
                 value={projectTitle}
458
                 onChange={(e) => setProjectTitle(e.target.value)}
525
                 onChange={(e) => setProjectTitle(e.target.value)}
459
-                disabled={isCreating || processingAudio}
526
+                disabled={isCreating || processingAudio || processingVideo}
460
               />
527
               />
461
             </Form.Group>
528
             </Form.Group>
462
             <Form.Group className="mt-3">
529
             <Form.Group className="mt-3">
@@ -478,6 +545,15 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
478
               </InputGroup>
545
               </InputGroup>
479
             </Form.Group>
546
             </Form.Group>
480
             <Form.Group className="mt-3">
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
               <Form.Label>字幕条数</Form.Label>
557
               <Form.Label>字幕条数</Form.Label>
482
               <Form.Control
558
               <Form.Control
483
                 readOnly
559
                 readOnly
@@ -485,10 +561,10 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
485
               />
561
               />
486
             </Form.Group>
562
             </Form.Group>
487
             
563
             
488
-            {processingAudio && (
564
+            {(processingAudio || processingVideo) && (
489
               <div className="mt-3">
565
               <div className="mt-3">
490
                 <Alert variant="info">
566
                 <Alert variant="info">
491
-                  正在处理音频文件,请稍候...这可能需要一些时间。
567
+                  正在保存{processingAudio ? '音频' : '视频'}文件,请稍候...
492
                 </Alert>
568
                 </Alert>
493
               </div>
569
               </div>
494
             )}
570
             )}
@@ -498,16 +574,16 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
498
           <Button 
574
           <Button 
499
             variant="secondary" 
575
             variant="secondary" 
500
             onClick={() => setShowCreateModal(false)}
576
             onClick={() => setShowCreateModal(false)}
501
-            disabled={isCreating || processingAudio}
577
+            disabled={isCreating || processingAudio || processingVideo}
502
           >
578
           >
503
             取消
579
             取消
504
           </Button>
580
           </Button>
505
           <Button 
581
           <Button 
506
             variant="primary" 
582
             variant="primary" 
507
             onClick={handleCreateProject}
583
             onClick={handleCreateProject}
508
-            disabled={isCreating || processingAudio}
584
+            disabled={isCreating || processingAudio || processingVideo}
509
           >
585
           >
510
-            {isCreating || processingAudio ? (
586
+            {isCreating || processingAudio || processingVideo ? (
511
               <>
587
               <>
512
                 <Spinner
588
                 <Spinner
513
                   as="span"
589
                   as="span"
@@ -516,7 +592,10 @@ const SubtitleUpload = ({ onProjectCreated, projectAudioFile }) => {
516
                   role="status"
592
                   role="status"
517
                   aria-hidden="true"
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
 .modal-footer {
258
 .modal-footer {
259
   background-color: #f8f9fa;
259
   background-color: #f8f9fa;
260
   border-top: 1px solid #dee2e6;
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
 import VideoDownload from '../../components/videoDownload';
6
 import VideoDownload from '../../components/videoDownload';
7
 import SubtitleUpload from '../../components/subtitleUpload';
7
 import SubtitleUpload from '../../components/subtitleUpload';
8
 import AudioUpload from '../../components/audioUpload';
8
 import AudioUpload from '../../components/audioUpload';
9
+import VideoUpload from '../../components/videoUpload';
9
 import { bookService, bookInfoService, getDbPath } from '../../db';
10
 import { bookService, bookInfoService, getDbPath } from '../../db';
10
 import CozeApiSettings from '../../components/CozeApiSettings';
11
 import CozeApiSettings from '../../components/CozeApiSettings';
11
 import { hasValidToken } from '../../utils/cozeConfig';
12
 import { hasValidToken } from '../../utils/cozeConfig';
@@ -20,6 +21,7 @@ const Home = forwardRef((props, ref) => {
20
   const [showAudioPlayer, setShowAudioPlayer] = useState(false);
21
   const [showAudioPlayer, setShowAudioPlayer] = useState(false);
21
   const [currentAudio, setCurrentAudio] = useState(null);
22
   const [currentAudio, setCurrentAudio] = useState(null);
22
   const [projectAudioFile, setProjectAudioFile] = useState(null);
23
   const [projectAudioFile, setProjectAudioFile] = useState(null);
24
+  const [projectVideoFile, setProjectVideoFile] = useState(null);
23
   const history = useHistory();
25
   const history = useHistory();
24
 
26
 
25
   const handleAddProject = () => {
27
   const handleAddProject = () => {
@@ -377,6 +379,7 @@ const Home = forwardRef((props, ref) => {
377
                             <th>创建时间</th>
379
                             <th>创建时间</th>
378
                             <th>字幕文件</th>
380
                             <th>字幕文件</th>
379
                             <th>音频文件</th>
381
                             <th>音频文件</th>
382
+                            <th>视频文件</th>
380
                             <th>操作</th>
383
                             <th>操作</th>
381
                           </tr>
384
                           </tr>
382
                         </thead>
385
                         </thead>
@@ -394,7 +397,26 @@ const Home = forwardRef((props, ref) => {
394
                               </td>
397
                               </td>
395
                               <td>
398
                               <td>
396
                                 {project.audio_path ? (
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
                                   <span className="text-muted">无</span>
421
                                   <span className="text-muted">无</span>
400
                                 )}
422
                                 )}
@@ -470,11 +492,12 @@ const Home = forwardRef((props, ref) => {
470
 
492
 
471
               <AudioUpload onAudioSelected={(audioFile) => setProjectAudioFile(audioFile)} />
493
               <AudioUpload onAudioSelected={(audioFile) => setProjectAudioFile(audioFile)} />
472
               
494
               
473
-              {/* <VideoDownload /> */}
495
+              <VideoUpload onVideoSelected={(videoFile) => setProjectVideoFile(videoFile)} />
474
 
496
 
475
               <SubtitleUpload 
497
               <SubtitleUpload 
476
                 onProjectCreated={backToHome} 
498
                 onProjectCreated={backToHome} 
477
-                projectAudioFile={projectAudioFile} 
499
+                projectAudioFile={projectAudioFile}
500
+                projectVideoFile={projectVideoFile}
478
               />
501
               />
479
 
502
 
480
             </>
503
             </>