102 lines
3.0 KiB
TypeScript
102 lines
3.0 KiB
TypeScript
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))
|
|
);
|
|
}
|