Files
Genarrative/src/components/image-editor/ImageCanvasFileModel.ts
T
JenkenB ac180ecad9 完善画板生成音乐与生成面板交互
新增画板底部生成音乐入口,支持游戏音效和游戏背景音乐生成面板。

接入编辑器音频生成前后端契约、VectorEngine 请求与 BFF 路由。

补齐音频结果图层、元数据、画布恢复和客户端提交流程。

统一生成类面板 Lovart 式参数浮层、参考素材入口与相关测试。

更新编辑器音乐生成设计文档和项目长期记忆。
2026-06-18 15:42:17 +08:00

135 lines
3.6 KiB
TypeScript

export function isImageFile(file: File) {
return file.type.startsWith('image/');
}
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 'image/*';
}
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']);
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 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;
});
}