feat: integrate authored nightdrive soundtrack
This commit is contained in:
@@ -5,11 +5,12 @@ import { readFile, readdir, stat } from 'node:fs/promises';
|
||||
import { relative, resolve, sep } from 'node:path';
|
||||
import sharp from 'sharp';
|
||||
import { fileTypeFromBuffer } from 'file-type';
|
||||
import { probeVideo } from './lib/video-frames.mjs';
|
||||
import { probeVideo, runMediaCommand } from './lib/video-frames.mjs';
|
||||
import {
|
||||
classifyArtifact,
|
||||
evaluateBudgetTotals,
|
||||
evaluateMediaAssetBudget,
|
||||
normalizeDetectedMediaType,
|
||||
resolveBudgetGateMode,
|
||||
validateRuntimeBudgetTraceReport,
|
||||
} from './lib/bundle-budgets.mjs';
|
||||
@@ -34,7 +35,7 @@ const listFiles = async (directory) => {
|
||||
return files;
|
||||
};
|
||||
|
||||
const compressedSize = (path, bytes) => (/\.(?:avif|br|gif|jpe?g|mp4|png|webm|webp|woff2?)$/iu.test(path)
|
||||
const compressedSize = (path, bytes) => (/\.(?:avif|br|gif|jpe?g|mp3|mp4|opus|png|webm|webp|woff2?)$/iu.test(path)
|
||||
? bytes.byteLength
|
||||
: brotliCompressSync(bytes, { params: { [zlibConstants.BROTLI_PARAM_QUALITY]: 11 } }).byteLength);
|
||||
|
||||
@@ -42,6 +43,21 @@ const readJson = async (path, label) => {
|
||||
try { return JSON.parse(await readFile(path, 'utf8')); } catch { throw new Error(`${label} must be valid JSON.`); }
|
||||
};
|
||||
|
||||
const probeAudio = async (path) => {
|
||||
const { stdout } = await runMediaCommand('ffprobe', [
|
||||
'-v', 'error', '-show_entries', 'format=duration:stream=codec_type', '-of', 'json', path,
|
||||
], 'ASSET-AUDIO-PROBE-001', 20_000);
|
||||
let result;
|
||||
try { result = JSON.parse(stdout); } catch { throw new Error('ASSET-AUDIO-PROBE-001'); }
|
||||
const streams = Array.isArray(result.streams) ? result.streams : [];
|
||||
const duration = Number(result.format?.duration);
|
||||
if (streams.length !== 1 || streams[0]?.codec_type !== 'audio'
|
||||
|| !Number.isFinite(duration) || duration <= 0 || duration > 300) {
|
||||
throw new Error('ASSET-AUDIO-PROBE-001');
|
||||
}
|
||||
return { duration };
|
||||
};
|
||||
|
||||
const manifest = await readJson(resolve('content/public/asset-manifest.json'), 'Public asset manifest');
|
||||
const content = await readJson(resolve('content/public/portfolio-content.json'), 'Portfolio content');
|
||||
if (!Array.isArray(manifest?.assets)) throw new Error('Public asset manifest is invalid.');
|
||||
@@ -59,6 +75,12 @@ for (const group of content.toolkit ?? []) for (const technology of group.techno
|
||||
if (technology.assetId) assetClassById.set(technology.assetId, 'project-still');
|
||||
}
|
||||
if (content.metadata?.socialPreviewAssetId) assetClassById.set(content.metadata.socialPreviewAssetId, 'project-still');
|
||||
if (content.soundtrack) {
|
||||
for (const region of [content.soundtrack.intro, content.soundtrack.main]) {
|
||||
if (region?.opusAssetId) assetClassById.set(region.opusAssetId, 'soundtrack-audio');
|
||||
if (region?.mp3AssetId) assetClassById.set(region.mp3AssetId, 'soundtrack-audio');
|
||||
}
|
||||
}
|
||||
|
||||
const manifestByPath = new Map(manifest.assets.map((asset) => [`assets/${asset.fileName}`, asset]));
|
||||
const records = [];
|
||||
@@ -74,7 +96,8 @@ for (const absolute of await listFiles(root)) {
|
||||
if (digest !== manifestAsset.sha256) throw new Error(`Published asset hash mismatch: ${path}`);
|
||||
const mediaType = manifestAsset.mediaType;
|
||||
const detected = await fileTypeFromBuffer(bytes);
|
||||
const detectedMediaType = detected?.mime ?? (/^\s*<svg[\s>]/u.test(bytes.toString('utf8', 0, Math.min(bytes.length, 256))) ? 'image/svg+xml' : null);
|
||||
const detectedMediaType = normalizeDetectedMediaType(detected?.mime)
|
||||
?? (/^\s*<svg[\s>]/u.test(bytes.toString('utf8', 0, Math.min(bytes.length, 256))) ? 'image/svg+xml' : null);
|
||||
if (detectedMediaType !== mediaType) throw new Error(`Manifest media type disagrees with deterministic probing: ${manifestAsset.id}`);
|
||||
let probe;
|
||||
if (mediaType?.startsWith('video/')) {
|
||||
@@ -85,6 +108,10 @@ for (const absolute of await listFiles(root)) {
|
||||
const image = await sharp(absolute, { failOn: 'error', limitInputPixels: 50_000_000 }).metadata();
|
||||
probe = { mediaType, width: image.width, height: image.height, durationMs: null };
|
||||
bucket = assetClassById.get(manifestAsset.id) === 'chart' ? 'chart' : 'project-still';
|
||||
} else if (mediaType?.startsWith('audio/')) {
|
||||
const audio = await probeAudio(absolute);
|
||||
probe = { mediaType, width: null, height: null, durationMs: Math.round(audio.duration * 1_000) };
|
||||
bucket = 'soundtrack-audio';
|
||||
} else {
|
||||
throw new Error(`Manifest asset has unsupported media type: ${manifestAsset.id}`);
|
||||
}
|
||||
@@ -92,11 +119,11 @@ for (const absolute of await listFiles(root)) {
|
||||
manifestAsset,
|
||||
probe,
|
||||
compressedSize(path, bytes),
|
||||
bucket === 'chart' ? 'chart' : 'project-still',
|
||||
bucket === 'chart' ? 'chart' : bucket === 'soundtrack-audio' ? 'soundtrack-audio' : 'project-still',
|
||||
).violations);
|
||||
} else {
|
||||
bucket = classifyArtifact(path, source);
|
||||
if (['project-video', 'project-still', 'chart'].includes(bucket)) {
|
||||
if (['project-video', 'project-still', 'chart', 'soundtrack-audio'].includes(bucket)) {
|
||||
throw new Error(`Project media must be present in the public asset manifest: ${path}`);
|
||||
}
|
||||
}
|
||||
@@ -114,6 +141,7 @@ const totals = {
|
||||
deferredProjectVisuals: sum('project-still') + sum('chart'),
|
||||
highTierExtras: sum('high-tier-extras'),
|
||||
deferredNonVideo: sum('first-live-scene') + sum('high-tier-extras') + sum('project-still') + sum('chart'),
|
||||
deferredAudio: sum('soundtrack-audio'),
|
||||
};
|
||||
|
||||
const entry = records.find((record) => record.bucket === 'initial-application-js');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export const PERFORMANCE_BUDGETS: Readonly<Record<string, number>>;
|
||||
export function normalizeDetectedMediaType(mediaType: string | null | undefined): string | null;
|
||||
export function resolveBudgetGateMode(options: {
|
||||
transferOnly: boolean;
|
||||
runtimeTracePath: string | null;
|
||||
@@ -11,7 +12,7 @@ export function evaluateMediaAssetBudget(
|
||||
asset: Readonly<Record<string, unknown>>,
|
||||
probe: Readonly<Record<string, unknown>> | null,
|
||||
transferBytes: number,
|
||||
assetClass?: 'chart' | 'project-still',
|
||||
assetClass?: 'chart' | 'project-still' | 'soundtrack-audio',
|
||||
): { violations: Array<{ budget: string; actual: number; threshold: number; id?: string }> };
|
||||
export function evaluateRuntimeBudgetEvidence(evidence: Readonly<Record<string, unknown>>): {
|
||||
tiers: Array<{ tier: string; textureBytes: number; geometryBytes: number; renderTargetBytes: number; runtimeAllowanceBytes: number; totalBytes: number }>;
|
||||
|
||||
@@ -13,6 +13,7 @@ export const PERFORMANCE_BUDGETS = Object.freeze({
|
||||
deferredProjectVisuals: 2.5 * 1024 * 1024,
|
||||
highTierExtras: 750 * 1024,
|
||||
deferredNonVideo: 3.5 * 1024 * 1024,
|
||||
deferredAudio: 13 * 1024 * 1024,
|
||||
imageSceneConcurrency: 2,
|
||||
videoConcurrency: 1,
|
||||
mobileTextureDimension: 2048,
|
||||
@@ -20,6 +21,10 @@ export const PERFORMANCE_BUDGETS = Object.freeze({
|
||||
highTierGpuEstimate: 192 * 1024 * 1024,
|
||||
});
|
||||
|
||||
export const normalizeDetectedMediaType = (mediaType) => (
|
||||
typeof mediaType === 'string' ? mediaType.split(';', 1)[0].trim() : null
|
||||
);
|
||||
|
||||
export const resolveBudgetGateMode = ({ transferOnly, runtimeTracePath }) => {
|
||||
if (transferOnly && runtimeTracePath !== null) throw new Error('Transfer-only mode cannot be combined with runtime evidence.');
|
||||
if (transferOnly) return { kind: 'transfer-only' };
|
||||
@@ -39,6 +44,7 @@ export const classifyArtifact = (path, source = '') => {
|
||||
if (/^assets\/project-[^/]+\.(?:webm|mp4)$/u.test(path)) return 'project-video';
|
||||
if (/^assets\/project-[^/]*chart[^/]*\.(?:avif|webp|png|jpe?g)$/u.test(path)) return 'chart';
|
||||
if (/^assets\/project-[^/]+\.(?:avif|webp|png|jpe?g|svg)$/u.test(path)) return 'project-still';
|
||||
if (/^assets\/nightdrive-(?:intro|main)\.(?:opus|mp3)$/u.test(path)) return 'soundtrack-audio';
|
||||
if (/^(?:robots\.txt|sitemap\.xml|_headers\.example|_redirects\.example)$/u.test(path)) return 'release-metadata';
|
||||
throw new Error(`Artifact is unclassified: ${path}`);
|
||||
};
|
||||
@@ -51,6 +57,7 @@ const thresholdByTotal = Object.freeze({
|
||||
firstLiveScene: PERFORMANCE_BUDGETS.firstLiveScene,
|
||||
deferredProjectVisuals: PERFORMANCE_BUDGETS.deferredProjectVisuals,
|
||||
deferredNonVideo: PERFORMANCE_BUDGETS.deferredNonVideo,
|
||||
deferredAudio: PERFORMANCE_BUDGETS.deferredAudio,
|
||||
highTierExtras: PERFORMANCE_BUDGETS.highTierExtras,
|
||||
});
|
||||
|
||||
@@ -63,13 +70,34 @@ export const evaluateBudgetTotals = (totals) => ({
|
||||
const violation = (budget, actual, threshold, id) => ({ budget, actual, threshold, ...(id === undefined ? {} : { id }) });
|
||||
const finitePositiveInteger = (value) => Number.isInteger(value) && value > 0;
|
||||
|
||||
export const evaluateMediaAssetBudget = (asset, probe, transferBytes, assetClass = undefined) => {
|
||||
export const evaluateMediaAssetBudget = (
|
||||
asset,
|
||||
probe,
|
||||
transferBytes,
|
||||
assetClass = /** @type {'chart' | 'project-still' | 'soundtrack-audio' | undefined} */ (undefined),
|
||||
) => {
|
||||
if (probe === null || typeof probe !== 'object') throw new Error(`Media probe evidence is required for ${asset?.id ?? 'unknown asset'}.`);
|
||||
if (assetClass === 'soundtrack-audio' && !asset?.mediaType?.startsWith('audio/')) {
|
||||
throw new Error(`Soundtrack classification requires audio media for ${asset?.id ?? 'unknown asset'}.`);
|
||||
}
|
||||
if (asset?.mediaType !== probe.mediaType) {
|
||||
throw new Error(`Manifest and probe evidence disagree for ${asset?.id ?? 'unknown asset'}.`);
|
||||
}
|
||||
if (asset.mediaType.startsWith('audio/')) {
|
||||
if (asset.width !== null || asset.height !== null || probe.width !== null || probe.height !== null) {
|
||||
throw new Error(`Audio dimensions must be null for ${asset.id}.`);
|
||||
}
|
||||
if (!finitePositiveInteger(asset.durationMs) || !finitePositiveInteger(probe.durationMs)
|
||||
|| Math.abs(asset.durationMs - probe.durationMs) > 100) {
|
||||
throw new Error(`Verified duration is required for ${asset.id}.`);
|
||||
}
|
||||
return { violations: [] };
|
||||
}
|
||||
if (!finitePositiveInteger(asset?.width) || !finitePositiveInteger(asset?.height)
|
||||
|| !finitePositiveInteger(probe.width) || !finitePositiveInteger(probe.height)) {
|
||||
throw new Error(`Verified dimensions are required for ${asset?.id ?? 'unknown asset'}.`);
|
||||
}
|
||||
if (asset.width !== probe.width || asset.height !== probe.height || asset.mediaType !== probe.mediaType) {
|
||||
if (asset.width !== probe.width || asset.height !== probe.height) {
|
||||
throw new Error(`Manifest and probe evidence disagree for ${asset.id}.`);
|
||||
}
|
||||
const violations = [];
|
||||
|
||||
@@ -82,7 +82,7 @@ const readManifest = async (repositoryRoot) => {
|
||||
}
|
||||
if (ids.has(id)) throw new Error(`Duplicate manifest asset ID: ${id}`);
|
||||
ids.add(id);
|
||||
if (typeof fileName !== 'string' || !/^[a-z0-9][a-z0-9-]*\.(avif|jpg|png|svg|webp|mp4|webm)$/u.test(fileName)) {
|
||||
if (typeof fileName !== 'string' || !/^[a-z0-9][a-z0-9-]*\.(avif|jpg|png|svg|webp|mp4|webm|opus|mp3)$/u.test(fileName)) {
|
||||
throw new Error(`Manifest filename must be a generated safe filename: ${id}`);
|
||||
}
|
||||
const foldedFileName = fileName.toLocaleLowerCase('en-US');
|
||||
@@ -128,6 +128,18 @@ const assetReferences = (content) => {
|
||||
throw new Error('Portfolio content must contain metadata.');
|
||||
}
|
||||
if (content.metadata.socialPreviewAssetId !== undefined) references.push(content.metadata.socialPreviewAssetId);
|
||||
if (content.soundtrack !== undefined) {
|
||||
if (typeof content.soundtrack !== 'object' || content.soundtrack === null || Array.isArray(content.soundtrack)) {
|
||||
throw new Error('Portfolio soundtrack must be an object.');
|
||||
}
|
||||
for (const regionName of ['intro', 'main']) {
|
||||
const region = content.soundtrack[regionName];
|
||||
if (typeof region !== 'object' || region === null || Array.isArray(region)) {
|
||||
throw new Error(`Portfolio soundtrack ${regionName} must be an object.`);
|
||||
}
|
||||
references.push(region.opusAssetId, region.mp3AssetId);
|
||||
}
|
||||
}
|
||||
return references;
|
||||
};
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ export function verifyNoJavaScriptDocument(html) {
|
||||
function verifySecurityHeaders(response, expectedDigest) {
|
||||
requireHeader(response, 'x-portfolio-manifest-digest', expectedDigest);
|
||||
const csp = requireHeader(response, 'content-security-policy', /default-src 'self'/u);
|
||||
for (const directive of ["connect-src 'none'", "object-src 'none'", "base-uri 'none'", "frame-ancestors 'none'", "form-action 'self'", 'upgrade-insecure-requests']) {
|
||||
for (const directive of ["connect-src 'self'", "object-src 'none'", "base-uri 'none'", "frame-ancestors 'none'", "form-action 'self'", 'upgrade-insecure-requests']) {
|
||||
if (!csp.includes(directive)) throw new Error(`Content-Security-Policy lacks ${directive}.`);
|
||||
}
|
||||
if (/unsafe-inline|unsafe-eval|\*/u.test(csp)) throw new Error('Content-Security-Policy contains an unsafe source expression.');
|
||||
|
||||
@@ -321,7 +321,7 @@ const assertMetadata = (html, inputs) => {
|
||||
const assertSecurityHeaders = (headers, inputs, serializedJsonLd) => {
|
||||
const required = [
|
||||
"default-src 'self'", "script-src 'self'", "style-src 'self'", "img-src 'self' data:", "media-src 'self'",
|
||||
"font-src 'self'", "connect-src 'none'", "object-src 'none'", "base-uri 'none'", "frame-ancestors 'none'",
|
||||
"font-src 'self'", "connect-src 'self'", "object-src 'none'", "base-uri 'none'", "frame-ancestors 'none'",
|
||||
"form-action 'self'", 'upgrade-insecure-requests', 'X-Content-Type-Options: nosniff',
|
||||
'Referrer-Policy: no-referrer', 'X-Frame-Options: DENY',
|
||||
'Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), accelerometer=(), gyroscope=(), magnetometer=()',
|
||||
|
||||
Reference in New Issue
Block a user