870d723eaf
生成视频持久化到 OSS 并回传 objectKey 与 assetObjectId 生成音频透传 OSS 对象身份并支持保存到素材库 普通素材上传支持 MP3 和 MP4 直传 OSS 后确认资产对象 素材库和画布恢复音频视频媒体类型与图层播放 更新画板音视频素材保存相关文档与测试
167 lines
4.5 KiB
TypeScript
167 lines
4.5 KiB
TypeScript
export function isImageFile(file: File) {
|
|
return file.type.startsWith('image/');
|
|
}
|
|
|
|
export const EDITOR_ASSET_UPLOAD_ACCEPT =
|
|
'image/*,.mp3,.mp4,audio/mpeg,audio/mp3,video/mp4';
|
|
|
|
export const SEEDANCE_REFERENCE_ACCEPT = {
|
|
image:
|
|
'.jpg,.jpeg,.png,.webp,.bmp,.tiff,.tif,.gif,.heic,.heif,image/jpeg,image/png,image/webp,image/bmp,image/tiff,image/gif,image/heic,image/heif',
|
|
video: '.mp4,.mov,video/mp4,video/quicktime',
|
|
audio: '.wav,.mp3,audio/wav,audio/x-wav,audio/mpeg,audio/mp3',
|
|
} as const;
|
|
|
|
export function getEditorUploadAccept(target: string) {
|
|
if (target === 'video-reference-video') {
|
|
return SEEDANCE_REFERENCE_ACCEPT.video;
|
|
}
|
|
if (target === 'video-reference-audio') {
|
|
return SEEDANCE_REFERENCE_ACCEPT.audio;
|
|
}
|
|
if (target === 'video-reference-image') {
|
|
return SEEDANCE_REFERENCE_ACCEPT.image;
|
|
}
|
|
return EDITOR_ASSET_UPLOAD_ACCEPT;
|
|
}
|
|
|
|
export const SEEDANCE_REFERENCE_FILE_LIMITS = {
|
|
imageBytes: 30 * 1024 * 1024,
|
|
videoBytes: 50 * 1024 * 1024,
|
|
audioBytes: 15 * 1024 * 1024,
|
|
mediaMinDurationSeconds: 2,
|
|
mediaMaxDurationSeconds: 15,
|
|
mediaTotalDurationSeconds: 15,
|
|
} as const;
|
|
|
|
const SEEDANCE_IMAGE_EXTENSIONS = new Set([
|
|
'jpg',
|
|
'jpeg',
|
|
'png',
|
|
'webp',
|
|
'bmp',
|
|
'tiff',
|
|
'tif',
|
|
'gif',
|
|
'heic',
|
|
'heif',
|
|
]);
|
|
const SEEDANCE_VIDEO_EXTENSIONS = new Set(['mp4', 'mov']);
|
|
const SEEDANCE_AUDIO_EXTENSIONS = new Set(['wav', 'mp3']);
|
|
const EDITOR_ASSET_VIDEO_EXTENSIONS = new Set(['mp4']);
|
|
const EDITOR_ASSET_AUDIO_EXTENSIONS = new Set(['mp3']);
|
|
|
|
function getFileExtension(file: File) {
|
|
return file.name.split('.').pop()?.toLowerCase() ?? '';
|
|
}
|
|
|
|
function hasAllowedExtension(file: File, extensions: Set<string>) {
|
|
return extensions.has(getFileExtension(file));
|
|
}
|
|
|
|
export function isSeedanceReferenceImageFile(file: File) {
|
|
return (
|
|
(file.type.startsWith('image/') || file.type === '') &&
|
|
hasAllowedExtension(file, SEEDANCE_IMAGE_EXTENSIONS)
|
|
);
|
|
}
|
|
|
|
export function isSeedanceReferenceVideoFile(file: File) {
|
|
return (
|
|
(file.type === 'video/mp4' ||
|
|
file.type === 'video/quicktime' ||
|
|
file.type === '') &&
|
|
hasAllowedExtension(file, SEEDANCE_VIDEO_EXTENSIONS)
|
|
);
|
|
}
|
|
|
|
export function isSeedanceReferenceAudioFile(file: File) {
|
|
return (
|
|
(file.type === 'audio/wav' ||
|
|
file.type === 'audio/x-wav' ||
|
|
file.type === 'audio/mpeg' ||
|
|
file.type === 'audio/mp3' ||
|
|
file.type === '') &&
|
|
hasAllowedExtension(file, SEEDANCE_AUDIO_EXTENSIONS)
|
|
);
|
|
}
|
|
|
|
export function isEditorAssetVideoFile(file: File) {
|
|
return (
|
|
(file.type === 'video/mp4' || file.type === '') &&
|
|
hasAllowedExtension(file, EDITOR_ASSET_VIDEO_EXTENSIONS)
|
|
);
|
|
}
|
|
|
|
export function isEditorAssetAudioFile(file: File) {
|
|
return (
|
|
(file.type === 'audio/mpeg' || file.type === 'audio/mp3' || file.type === '') &&
|
|
hasAllowedExtension(file, EDITOR_ASSET_AUDIO_EXTENSIONS)
|
|
);
|
|
}
|
|
|
|
export function resolveEditorAssetUploadMediaType(file: File) {
|
|
if (isEditorAssetVideoFile(file)) {
|
|
return 'video' as const;
|
|
}
|
|
if (isEditorAssetAudioFile(file)) {
|
|
return 'audio' as const;
|
|
}
|
|
if (isImageFile(file)) {
|
|
return 'image' as const;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function readFileAsDataUrl(file: File) {
|
|
return new Promise<string>((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.onload = () => {
|
|
if (typeof reader.result === 'string') {
|
|
resolve(reader.result);
|
|
return;
|
|
}
|
|
reject(new Error('文件读取失败'));
|
|
};
|
|
reader.onerror = () => reject(reader.error ?? new Error('文件读取失败'));
|
|
reader.readAsDataURL(file);
|
|
});
|
|
}
|
|
|
|
export function readImageFileAsDataUrl(file: File) {
|
|
return readFileAsDataUrl(file);
|
|
}
|
|
|
|
export function probeMediaDurationSeconds(
|
|
file: File,
|
|
mediaType: 'video' | 'audio',
|
|
) {
|
|
return new Promise<number>((resolve, reject) => {
|
|
if (typeof document === 'undefined' || typeof URL === 'undefined') {
|
|
resolve(Number.NaN);
|
|
return;
|
|
}
|
|
const element =
|
|
mediaType === 'video'
|
|
? document.createElement('video')
|
|
: document.createElement('audio');
|
|
const objectUrl = URL.createObjectURL(file);
|
|
const cleanup = () => {
|
|
URL.revokeObjectURL(objectUrl);
|
|
element.removeAttribute('src');
|
|
element.load();
|
|
};
|
|
element.preload = 'metadata';
|
|
element.onloadedmetadata = () => {
|
|
const duration = element.duration;
|
|
cleanup();
|
|
resolve(duration);
|
|
};
|
|
element.onerror = () => {
|
|
cleanup();
|
|
reject(new Error('读取媒体时长失败'));
|
|
};
|
|
element.src = objectUrl;
|
|
});
|
|
}
|