import { CreateVideoCommand } from '../core/commands/assetCommands'; import type { UploadedVideo, VideoStore } from '../core/store/VideoStore'; import { type ImportKind, type ImportOutcome, importStored } from './assetImport'; import type { EditorState } from './state'; /** What the file dialog offers and a drop accepts; ffmpeg on the server has the last word. */ export const VIDEO_ACCEPT = '.mp4,.mov,.m4v,.webm,.mkv,.avi'; const VIDEO_EXTENSION = /\.(mp4|mov|m4v|webm|mkv|avi)$/i; export function isVideoFileName(name: string): boolean { return VIDEO_EXTENSION.test(name); } export interface VideoImportDeps { state: EditorState; videos: VideoStore; } /** * A video becomes an asset only once the worker has made it (#402): the asset copies its size, * length and frame rate, which are not known before. */ const VIDEO_KIND: ImportKind = { type: 'video', noun: 'video', fallbackName: 'Video', store: (deps) => deps.videos, prepare: (file) => isVideoFileName(file.name) ? Promise.resolve({ blob: file, type: 'application/octet-stream' }) : Promise.reject(new Error('Not a video file – use MP4, MOV, WebM, MKV or AVI.')), create: (name, video) => new CreateVideoCommand(name, video.file, { width: video.width ?? 0, height: video.height ?? 0, duration: video.duration ?? 0, fps: video.fps ?? 0, audio: video.audio ?? false, }), }; export function importStoredVideo(video: UploadedVideo, deps: VideoImportDeps): ImportOutcome { return importStored(VIDEO_KIND, video, deps); } /** * Sends the files up one after the other and says how it went; what is done already is made an * asset at once. The rest is converted on the server – `waiting` gets their names, for the picker * to watch and adopt when they are ready. */ export async function uploadVideos( files: readonly File[], deps: VideoImportDeps, waiting: (files: string[]) => void, ): Promise { const pending: string[] = []; const failed: string[] = []; for (const file of files) { try { const { blob, type } = await VIDEO_KIND.prepare(file); const uploaded = await deps.videos.upload(blob, type, file.name); if (uploaded.status === 'ready') importStoredVideo(uploaded, deps); else pending.push(uploaded.file); } catch (error) { failed.push(`"${file.name}": ${error instanceof Error ? error.message : String(error)}`); } } if (pending.length > 0) waiting(pending); if (failed.length > 0) deps.state.setNotice(`Could not upload ${failed.join(', ')}`, 'error'); else if (pending.length > 0) deps.state.setNotice( pending.length === 1 ? 'Converting the video on the server…' : `Converting ${pending.length} videos on the server…`, ); }