返回 AiToEarn
VideoChoose.tsx
根目录 / project / aitoearn-electron / src / components / Choose / VideoChoose.tsx
1 /*
2 * @Author: nevin
3 * @Date: 2025-01-22 21:59:36
4 * @LastEditTime: 2025-01-23 15:40:37
5 * @LastEditors: nevin
6 * @Description: 选择视频
7 */
8 import { Button, message } from 'antd';
9 import { FC } from 'react';
10 import { getFilePathName } from '@/utils';
11 import { icpGetFileStream } from '@/icp/view';
12 import { saveCropperImage } from '@/views/publish/children/videoPage/components/VideoCoverSeting';
13 import { formatImg, IImgFile } from '@/components/Choose/ImgChoose';
14
15 interface VideoChooseProps {
16 // 单选就使用单选方法,多选就使用单选方法
17
18 // 单选返回
19 onChoose?: (videoFile: IVideoFile) => void;
20 // 多选返回方法
21 onMultipleChoose?: (videoFiles: IVideoFile[]) => void;
22 children?: React.ReactNode;
23 // 开始选择
24 onStartShoose?: () => void;
25 onChooseFail?: () => void;
26 }
27
28 export interface IVideoFile {
29 size: number;
30 file: Blob;
31 // 前端临时路径,注意不要存到数据库
32 videoUrl: string;
33 filename: string;
34 // 视频在硬盘上的路径
35 videoPath: string;
36 // 视频宽度
37 width: number;
38 // 视频高度
39 height: number;
40 // 视频下取整的时长,单位秒
41 duration: number;
42 // 视频首帧图片
43 cover: IImgFile;
44 }
45
46 // 根据视频在硬盘上的路径获取文件
47 export const getVideoFile = async (path: string): Promise<IVideoFile> => {
48 const file = await icpGetFileStream(path);
49 return await formatVideo(path, file);
50 };
51
52 function getVideoInfo(
53 videoUrl: string,
54 fileName: string,
55 ): Promise<{
56 width: number;
57 height: number;
58 // 下取整的时长
59 duration: number;
60 // 视频首帧
61 cover: IImgFile;
62 }> {
63 return new Promise((resolve) => {
64 // 创建一个临时的 video 元素
65 const video = document.createElement('video');
66
67 // 设置 video 元素的 src 属性
68 video.src = videoUrl;
69
70 // 当视频元数据加载完毕时执行回调
71 video.addEventListener('loadedmetadata', () => {
72 video.currentTime = 0;
73 });
74
75 video.addEventListener('seeked', function () {
76 // 获取视频的宽度和高度
77 const width = video.videoWidth;
78 const height = video.videoHeight;
79 // 获取视频的时长
80 const duration = video.duration;
81
82 // 获取视频首帧
83 const canvas = document.createElement('canvas');
84 canvas.width = width;
85 canvas.height = height;
86 const context = canvas.getContext('2d')!;
87 context.fillStyle = 'white';
88 context.fillRect(0, 0, width, height);
89 context.drawImage(video, 0, 0);
90 canvas.toBlob(async (blob) => {
91 const imgPath = await saveCropperImage(
92 `${fileName}.${blob!.type.split('/')[1]}`,
93 blob!,
94 );
95 const cover = await formatImg({
96 blob: blob!,
97 path: imgPath,
98 });
99 resolve({
100 width,
101 height,
102 duration: Math.floor(duration),
103 cover,
104 });
105 video.remove();
106 });
107 });
108 // 加载视频
109 video.load();
110 });
111 }
112
113 // 根据视频的Uint8Array和路径获取文件
114 export const formatVideo = async (
115 path: string,
116 file: Uint8Array,
117 ): Promise<IVideoFile> => {
118 const { filename, suffix } = getFilePathName(path);
119 const blob = new Blob([file], { type: `video/${suffix}` });
120 const videoUrl = URL.createObjectURL(blob);
121
122 const videoInfo = await getVideoInfo(videoUrl, filename);
123 return {
124 videoPath: path,
125 size: file.length,
126 filename,
127 file: blob,
128 videoUrl,
129 ...videoInfo,
130 };
131 };
132
133 const VideoChoose: FC<VideoChooseProps> = ({
134 onChoose,
135 onMultipleChoose,
136 children,
137 onStartShoose,
138 onChooseFail,
139 }) => {
140 /**
141 * 发送上传的事件
142 */
143 const handleUploadVideo = async () => {
144 try {
145 // if (onStartShoose) onStartShoose();
146 const result: {
147 path: string;
148 video: Uint8Array;
149 }[] = await window.ipcRenderer.invoke(
150 'ICP_VIEWS_CHOSE_VIDEO',
151 !!onMultipleChoose,
152 );
153 if (!result) {
154 // if (onChooseFail) onChooseFail();
155 return;
156 }
157 if (onStartShoose) onStartShoose();
158
159 const tasks: Promise<IVideoFile>[] = [];
160 for (const v of result) {
161 tasks.push(formatVideo(v.path, v.video));
162 }
163 const videoFiles = await Promise.all(tasks);
164
165 if (onMultipleChoose) {
166 onMultipleChoose(videoFiles);
167 } else if (onChoose) {
168 onChoose(videoFiles[0]);
169 }
170 } catch (error) {
171 message.error('选择视频失败');
172 console.error(error);
173 }
174 };
175
176 return (
177 <>
178 {children ? (
179 <div
180 className="videoChoose"
181 onClick={handleUploadVideo}
182 style={{ cursor: 'pointer' }}
183 >
184 {children}
185 </div>
186 ) : (
187 <Button type="primary" onClick={handleUploadVideo}>
188 选择视频
189 </Button>
190 )}
191 </>
192 );
193 };
194
195 export default VideoChoose;
196
196 lines Plain Text