feat: integrate authored nightdrive soundtrack
This commit is contained in:
@@ -59,6 +59,11 @@ const navigationItemSchema = z.object({
|
||||
label: visibleString,
|
||||
}).strict();
|
||||
|
||||
const soundtrackAssetSourcesSchema = z.object({
|
||||
opusAssetId: opaqueId,
|
||||
mp3AssetId: opaqueId,
|
||||
}).strict();
|
||||
|
||||
const mediaPresentationBase = {
|
||||
assetId: opaqueId,
|
||||
alt: visibleString,
|
||||
@@ -194,6 +199,10 @@ const portfolioContentSchema = z.object({
|
||||
description: visibleString,
|
||||
socialPreviewAssetId: opaqueId.optional(),
|
||||
}).strict(),
|
||||
soundtrack: z.object({
|
||||
intro: soundtrackAssetSourcesSchema,
|
||||
main: soundtrackAssetSourcesSchema,
|
||||
}).strict().optional(),
|
||||
sceneDescription: visibleString,
|
||||
}).strict().superRefine((content, context) => {
|
||||
const navigationIds = content.navigation.map((item) => item.id);
|
||||
@@ -220,7 +229,11 @@ const portfolioContentSchema = z.object({
|
||||
}
|
||||
});
|
||||
|
||||
const mediaTypes = ['image/avif', 'image/jpeg', 'image/png', 'image/svg+xml', 'image/webp', 'video/mp4', 'video/webm'] as const;
|
||||
const mediaTypes = [
|
||||
'image/avif', 'image/jpeg', 'image/png', 'image/svg+xml', 'image/webp',
|
||||
'video/mp4', 'video/webm',
|
||||
'audio/ogg', 'audio/mpeg',
|
||||
] as const;
|
||||
const fileExtensionByMediaType: Record<(typeof mediaTypes)[number], string> = {
|
||||
'image/avif': 'avif',
|
||||
'image/jpeg': 'jpg',
|
||||
@@ -229,10 +242,12 @@ const fileExtensionByMediaType: Record<(typeof mediaTypes)[number], string> = {
|
||||
'image/webp': 'webp',
|
||||
'video/mp4': 'mp4',
|
||||
'video/webm': 'webm',
|
||||
'audio/ogg': 'opus',
|
||||
'audio/mpeg': 'mp3',
|
||||
};
|
||||
const publicAssetSchema = z.object({
|
||||
id: opaqueId,
|
||||
fileName: z.string().regex(/^[a-z0-9][a-z0-9-]*\.(avif|jpg|png|svg|webp|mp4|webm)$/, 'Asset filenames must be generated safe filenames.'),
|
||||
fileName: z.string().regex(/^[a-z0-9][a-z0-9-]*\.(avif|jpg|png|svg|webp|mp4|webm|opus|mp3)$/, 'Asset filenames must be generated safe filenames.'),
|
||||
sha256: z.string().regex(/^[a-f0-9]{64}$/, 'Asset sha256 must be lowercase hexadecimal.'),
|
||||
mediaType: z.enum(mediaTypes),
|
||||
width: z.number().int().positive().nullable(),
|
||||
@@ -246,12 +261,17 @@ const publicAssetSchema = z.object({
|
||||
context.addIssue({ code: 'custom', message: 'Asset filename extension must match media type.', path: ['fileName'] });
|
||||
}
|
||||
const image = asset.mediaType.startsWith('image/');
|
||||
const video = asset.mediaType.startsWith('video/');
|
||||
const audio = asset.mediaType.startsWith('audio/');
|
||||
if (image && (asset.width === null || asset.height === null || asset.durationMs !== null)) {
|
||||
context.addIssue({ code: 'custom', message: 'Image assets require dimensions and no duration.', path: ['width'] });
|
||||
}
|
||||
if (!image && (asset.width === null || asset.height === null || asset.durationMs === null)) {
|
||||
if (video && (asset.width === null || asset.height === null || asset.durationMs === null)) {
|
||||
context.addIssue({ code: 'custom', message: 'Video assets require dimensions and duration.', path: ['durationMs'] });
|
||||
}
|
||||
if (audio && (asset.width !== null || asset.height !== null || asset.durationMs === null)) {
|
||||
context.addIssue({ code: 'custom', message: 'Audio assets require null dimensions and a positive duration.', path: ['durationMs'] });
|
||||
}
|
||||
});
|
||||
const publicAssetManifestSchema = z.object({ assets: z.array(publicAssetSchema) }).strict().superRefine((manifest, context) => {
|
||||
const ids = manifest.assets.map((asset) => asset.id);
|
||||
@@ -296,6 +316,12 @@ export const parseProductionInputSnapshot = (input: unknown): ProductionInputs =
|
||||
...content.toolkit.flatMap((group) => group.technologies.flatMap((technology) =>
|
||||
technology.assetId === undefined ? [] : [technology.assetId])),
|
||||
...(content.metadata.socialPreviewAssetId === undefined ? [] : [content.metadata.socialPreviewAssetId]),
|
||||
...(content.soundtrack === undefined ? [] : [
|
||||
content.soundtrack.intro.opusAssetId,
|
||||
content.soundtrack.intro.mp3AssetId,
|
||||
content.soundtrack.main.opusAssetId,
|
||||
content.soundtrack.main.mp3AssetId,
|
||||
]),
|
||||
];
|
||||
const seenReferences = new Set<string>();
|
||||
for (const assetId of referencedAssetIds) {
|
||||
|
||||
@@ -133,6 +133,16 @@ export interface MetadataContent {
|
||||
socialPreviewAssetId?: string;
|
||||
}
|
||||
|
||||
export interface SoundtrackAssetSources {
|
||||
opusAssetId: string;
|
||||
mp3AssetId: string;
|
||||
}
|
||||
|
||||
export interface SoundtrackContent {
|
||||
intro: SoundtrackAssetSources;
|
||||
main: SoundtrackAssetSources;
|
||||
}
|
||||
|
||||
export interface PortfolioContent {
|
||||
identity: {
|
||||
name: 'Luca Pîslaru';
|
||||
@@ -148,6 +158,7 @@ export interface PortfolioContent {
|
||||
toolkit: readonly ToolkitGroup[];
|
||||
contact: ContactContent;
|
||||
metadata: MetadataContent;
|
||||
soundtrack?: SoundtrackContent;
|
||||
sceneDescription: string;
|
||||
}
|
||||
|
||||
@@ -158,7 +169,9 @@ export type PublicAssetMediaType =
|
||||
| 'image/svg+xml'
|
||||
| 'image/webp'
|
||||
| 'video/mp4'
|
||||
| 'video/webm';
|
||||
| 'video/webm'
|
||||
| 'audio/ogg'
|
||||
| 'audio/mpeg';
|
||||
|
||||
export interface PublicAsset {
|
||||
id: string;
|
||||
|
||||
Reference in New Issue
Block a user