Files
Genarrative/src/components/image-editor/ImageCanvasFileModel.ts
T
kdletters 870d723eaf 修复画板音视频素材保存
生成视频持久化到 OSS 并回传 objectKey 与 assetObjectId

生成音频透传 OSS 对象身份并支持保存到素材库

普通素材上传支持 MP3 和 MP4 直传 OSS 后确认资产对象

素材库和画布恢复音频视频媒体类型与图层播放

更新画板音视频素材保存相关文档与测试
2026-06-19 15:11:42 +08:00

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;
});
}