You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

67 lines
4.1 KiB

  1. // Verify project/build copies and browser delivery without requiring avatar services.
  2. import assert from 'node:assert/strict';
  3. import fs from 'node:fs';
  4. import { createHash } from 'node:crypto';
  5. import { fileURLToPath } from 'node:url';
  6. import { spawnSync } from 'node:child_process';
  7. import videos from '../../unreal_tran_web/src/features/virtual-training-scripts/narration-video-manifest.js';
  8. import speech from '../../unreal_tran_web/src/features/virtual-training-scripts/narration-manifest.js';
  9. const root = new URL('../../', import.meta.url);
  10. const report = new URL('ute2e/reports/bridge-digital-video-static-20260915/', root);
  11. const sha = data => createHash('sha256').update(data).digest('hex');
  12. const generated = JSON.parse(fs.readFileSync(new URL('generation.json', report), 'utf8'));
  13. const original = JSON.parse(fs.readFileSync(new URL('ute2e/reports/bridge-digital-video-20260914/generation.json', root), 'utf8'));
  14. const clips = videos.scripts.flatMap(script => script.steps);
  15. assert.equal(clips.length, 39);
  16. const preview = new URL('index.html', report);
  17. const previewLinks = [...fs.readFileSync(preview, 'utf8').matchAll(/<video[^>]+src="([^"]+)"/g)];
  18. assert.equal(previewLinks.length, 39);
  19. for (const [, link] of previewLinks) assert.ok(fs.existsSync(new URL(link, preview)), `Broken preview ${link}`);
  20. const origins = ['http://127.0.0.1:6180', process.env.LAN_URL || 'http://192.168.31.168:6180'];
  21. const checks = [];
  22. for (const origin of origins) {
  23. for (const clip of clips) {
  24. const response = await fetch(origin + clip.url, { signal: AbortSignal.timeout(30000) });
  25. assert.equal(response.status, 200, `${origin} ${clip.url}`);
  26. assert.match(response.headers.get('content-type'), /video\/mp4/);
  27. assert.equal(sha(Buffer.from(await response.arrayBuffer())), clip.sha256);
  28. }
  29. const range = await fetch(origin + clips[0].url, { headers: { Range: 'bytes=0-1023' }, signal: AbortSignal.timeout(30000) });
  30. assert.equal(range.status, 206);
  31. assert.equal((await range.arrayBuffer()).byteLength, 1024);
  32. checks.push({ origin, playableUrls: clips.length, exactFiles: true, rangeRequests: true });
  33. }
  34. const audioChecks = [];
  35. for (const clip of clips) {
  36. const file = fs.readFileSync(new URL(`unreal_tran_web/dist${clip.url}`, root));
  37. assert.equal(sha(file), clip.sha256, `Missing or stale build file ${clip.url}`);
  38. const volume = spawnSync(process.env.FFMPEG_BINARY || 'ffmpeg', ['-hide_banner', '-nostdin',
  39. '-i', fileURLToPath(new URL(`unreal_tran_web/public${clip.url}`, root)), '-map', '0:a:0',
  40. '-af', 'volumedetect', '-f', 'null', '-'], { encoding: 'utf8', windowsHide: true, timeout: 30000 });
  41. assert.equal(volume.status, 0);
  42. const mean = Number(volume.stderr.match(/mean_volume:\s*(-?\d+(?:\.\d+)?)\s*dB/)?.[1]);
  43. assert.ok(Number.isFinite(mean) && mean > -70, `Silent narration ${clip.url}`);
  44. audioChecks.push({ url: clip.url, meanVolumeDb: mean });
  45. }
  46. for (const source of original.clips) {
  47. const record = generated.clips.find(clip => clip.key === source.key);
  48. assert.equal(sha(fs.readFileSync(new URL(`ute2e/${source.previewFile}`, root))), record.sourceVideoSha256);
  49. }
  50. let preservedWavs = 0;
  51. for (const script of speech.scripts) for (const clip of script.steps) {
  52. for (const [url, hash] of [[clip.url, clip.stepSha256], [clip.safetyUrl, clip.safetySha256]]) {
  53. if (!url) continue;
  54. assert.equal(sha(fs.readFileSync(new URL(`unreal_tran_web/public${url}`, root))), hash);
  55. preservedWavs++;
  56. }
  57. }
  58. const result = { clips: clips.length, checks, buildFilesVerified: clips.length,
  59. audibleNarrations: audioChecks,
  60. originalVideosPreserved: original.clips.length, originalWavsPreserved: preservedWavs,
  61. totalDuration: Number(clips.reduce((sum, clip) => sum + clip.duration, 0).toFixed(2)),
  62. totalBytes: clips.reduce((sum, clip) => sum + fs.statSync(new URL(`unreal_tran_web/public${clip.url}`, root)).size, 0),
  63. taskDataMutated: false };
  64. fs.writeFileSync(new URL('delivery-validation.json', report), JSON.stringify(result, null, 2));
  65. console.log(JSON.stringify({ ...result, audibleNarrations: audioChecks.length, report: fileURLToPath(new URL('delivery-validation.json', report)) }));