import { AV_PROJECT_SCHEMA_VERSION, type AvProjectV1, type BrowserFileIdentity, type CreateMediaAssetOptions, type CreateProjectOptions, type MediaAssetReference, } from './project.types'; export function createEmptyProject( options: CreateProjectOptions = {} ): AvProjectV1 { const timestamp = options.timestamp ?? new Date().toISOString(); if (!isIsoDate(timestamp)) { throw new TypeError('Project timestamp must be a valid ISO-8601 date.'); } return { schemaVersion: AV_PROJECT_SCHEMA_VERSION, id: options.id ?? createProjectId(), name: options.name?.trim() || 'Untitled project', createdAt: timestamp, updatedAt: timestamp, assets: [], timeline: [], output: { presetId: options.output?.presetId ?? 'mp4-h264-balanced', fileName: options.output?.fileName ?? 'output.mp4', container: options.output?.container ?? 'mp4', videoEnabled: options.output?.videoEnabled ?? true, audioEnabled: options.output?.audioEnabled ?? true, ...(options.output?.width === undefined ? {} : { width: options.output.width }), ...(options.output?.height === undefined ? {} : { height: options.output.height }), ...(options.output?.frameRate === undefined ? {} : { frameRate: options.output.frameRate }), sampleRate: options.output?.sampleRate ?? 48_000, channelLayout: options.output?.channelLayout ?? 'stereo', missingAudioPolicy: options.output?.missingAudioPolicy ?? 'insert-silence', }, metadata: { custom: {}, }, chapters: [], subtitles: [], }; } export function createMediaAssetReference( file: BrowserFileIdentity, options: CreateMediaAssetOptions = {} ): MediaAssetReference { if (!file.name.trim()) { throw new TypeError('A source file must have a name.'); } if (!Number.isSafeInteger(file.size) || file.size < 0) { throw new TypeError('A source file size must be a non-negative integer.'); } if (!Number.isSafeInteger(file.lastModified) || file.lastModified < 0) { throw new TypeError( 'A source last-modified time must be a non-negative integer.' ); } return { id: options.id ?? createId('asset'), originalName: file.name, size: file.size, lastModified: file.lastModified, mimeType: file.type, sourceStatus: options.sourceStatus ?? 'attached', ...(options.probe === undefined ? {} : { probe: options.probe }), ...(options.hash === undefined ? {} : { hash: options.hash }), }; } function createProjectId(): string { return createId('project'); } function createId(prefix: string): string { if ( typeof globalThis.crypto !== 'undefined' && typeof globalThis.crypto.randomUUID === 'function' ) { return globalThis.crypto.randomUUID(); } return `${prefix}-${Date.now().toString(36)}-${Math.random() .toString(36) .slice(2, 10)}`; } function isIsoDate(value: string): boolean { return ( /^\d{4}-\d{2}-\d{2}T/.test(value) && Number.isFinite(Date.parse(value)) ); }