feat: 上传视频/图片处理

This commit is contained in:
rd
2025-08-01 11:49:15 +08:00
parent 1cb33bd3ad
commit c211693d1f
7 changed files with 444 additions and 169 deletions

View File

@ -113,22 +113,155 @@ export function genRandomId() {
export function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
export function getVideoDuration(file: any) {
// 修改函数以同时获取视频时长和首帧
export function getVideoInfo(file: File): Promise<{ duration: number; firstFrame: string }> {
return new Promise((resolve) => {
const video = document.createElement('video');
video.preload = 'metadata';
video.onloadedmetadata = function() {
window.URL.revokeObjectURL(video.src);
resolve(video.duration);
video.crossOrigin = 'anonymous'; // 避免跨域问题
video.muted = true; // 静音,避免意外播放声音
video.style.position = 'fixed'; // 确保视频元素在DOM中
video.style.top = '-1000px'; // 但不可见
document.body.appendChild(video); // 添加到DOM
let hasResolved = false;
// 先获取元数据(时长)
video.onloadedmetadata = function () {
// 视频时长
const duration = video.duration;
// 尝试将视频定位到非常小的时间点,确保有帧可捕获
if (duration > 0) {
video.currentTime = Math.min(0.1, duration / 2);
}
};
// 当视频定位完成后尝试捕获首帧
video.onseeked = function () {
if (hasResolved) return;
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
}
// 清理
window.URL.revokeObjectURL(video.src);
document.body.removeChild(video);
// 返回结果
hasResolved = true;
resolve({
duration: video.duration,
firstFrame: canvas.toDataURL('image/jpeg', 0.9) // 提高质量
});
};
// 作为备选方案监听loadeddata事件
video.onloadeddata = function () {
if (hasResolved) return;
// 尝试捕获帧
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
}
// 检查是否捕获到有效帧(非全黑)
const imageData = ctx?.getImageData(0, 0, canvas.width, canvas.height);
if (imageData) {
let isAllBlack = true;
for (let i = 0; i < imageData.data.length; i += 4) {
if (imageData.data[i] > 10 || imageData.data[i+1] > 10 || imageData.data[i+2] > 10) {
isAllBlack = false;
break;
}
}
if (!isAllBlack) {
// 清理
window.URL.revokeObjectURL(video.src);
document.body.removeChild(video);
// 返回结果
hasResolved = true;
resolve({
duration: video.duration,
firstFrame: canvas.toDataURL('image/jpeg', 0.9)
});
return;
}
}
// 如果是全黑帧尝试定位到0.1秒
if (video.duration > 0) {
video.currentTime = 0.1;
}
};
// 设置视频源以触发加载
video.src = URL.createObjectURL(file);
// 设置超时,防止长时间无响应
setTimeout(() => {
if (!hasResolved) {
document.body.removeChild(video);
resolve({
duration: 0,
firstFrame: ''
});
}
}, 5000); // 5秒超时
});
}
}
export const formatDuration = (seconds: number) => {
const hours = Math.floor(seconds / 3600);
const remainingSecondsAfterHours = seconds % 3600;
const minutes = Math.floor(remainingSecondsAfterHours / 60);
const remainingSeconds = Math.floor(remainingSecondsAfterHours % 60);
if (hours > 0) {
if (remainingSecondsAfterHours === 0) {
return `${hours}小时`;
}
if (remainingSeconds === 0) {
return `${hours}小时${minutes}`;
}
return `${hours}小时${minutes}${remainingSeconds}`;
} else if (minutes > 0) {
if (remainingSeconds === 0) {
return `${minutes}`;
}
return `${minutes}${remainingSeconds}`;
} else {
return `${remainingSeconds}`;
}
};
export const formatUploadSpeed = (bytesPerSecond: number): string => {
if (bytesPerSecond < 1024) {
return `${bytesPerSecond.toFixed(2)} B/s`;
} else if (bytesPerSecond < 1024 * 1024) {
return `${(bytesPerSecond / 1024).toFixed(2)} KB/s`;
} else {
return `${(bytesPerSecond / (1024 * 1024)).toFixed(2)} MB/s`;
}
};