import fs from 'node:fs'; import assert from 'node:assert/strict'; import { login, call } from './role-permissions-live.mjs'; import narration from '../../unreal_tran_web/src/features/virtual-training-scripts/narration-manifest.js'; const dir = 'reports/bridge-digital-video-20260914'; fs.mkdirSync(dir, { recursive: true }); const manifestPath = `${dir}/generation.json`; const progress = fs.existsSync(manifestPath) ? JSON.parse(fs.readFileSync(manifestPath, 'utf8')) : { avatarId: '55', avatarName: '教员1', voiceId: '49', voiceName: '温和男声', clips: [] }; const save = () => fs.writeFileSync(manifestPath, JSON.stringify(progress, null, 2)); let session = await login('root'); const api = async (path, data, method) => { let r = await call(`tran/v1/${path}`, session.accessToken, data, method); if (r.status === 401) { session = await login('root'); r = await call(`tran/v1/${path}`, session.accessToken, data, method); } assert.equal(r.status, 200, `${path}: ${r.message}`); return r.data; }; const assignments = (await api('teaching/assignments?channel=VIRTUAL&assignmentKind=TRAINING&size=100')).records; const limit = process.argv.includes('--all') ? 39 : 1; let count = 0; for (const script of narration.scripts) { const task = assignments.find(t => t.code === script.code); assert.ok(task); for (const step of script.steps) { if (count++ >= limit) break; const key = `${script.id}:${script.version}:${step.stepCode}`; let clip = progress.clips.find(c => c.key === key); if (!clip) { const detail = await api(`content/projects/${task.contentProjectId}`); const generation = { title: `${script.title} · ${step.number} ${step.expectedTitle}`.slice(0, 120), text: step.text, avatarId: progress.avatarId, voiceId: progress.voiceId, backgroundId: null, speed: .85, width: 480, height: 640, subtitles: false }; const job = await api(`content/projects/${task.contentProjectId}/digital-human/jobs`, { version: detail.project.version, stepId: step.stepCode, requestKey: `bridge-video-20260914-${script.code}-${step.number}`, generation }); clip = { key, code: script.code, projectId: task.contentProjectId, assignmentId: task.id, stepCode: step.stepCode, number: step.number, title: generation.title, text: step.text, jobId: job.id, appId: job.appId, status: job.status }; progress.clips.push(clip); save(); } if (clip.status !== 'SUCCEEDED') { const deadline = Date.now() + 15 * 60000; while (Date.now() < deadline) { const job = await api(`content/projects/${clip.projectId}/digital-human/jobs/${clip.jobId}`); Object.assign(clip, { status: job.status, stage: job.stage, videoId: job.videoId, errorMessage: job.errorMessage }); save(); if (job.status === 'SUCCEEDED') break; if (['FAILED', 'CANCELLED'].includes(job.status)) throw new Error(`${clip.code} ${clip.number}: ${job.errorMessage}`); console.log(clip.code, clip.number, job.status, job.stage); await new Promise(resolve => setTimeout(resolve, 5000)); } assert.equal(clip.status, 'SUCCEEDED', 'Generation timeout'); } const output = `${dir}/${clip.code}-step-${String(clip.number).padStart(2, '0')}.mp4`; if (!fs.existsSync(output)) { const r = await fetch(`http://127.0.0.1:6180/api/tran/v1/content/digital-human/videos/${clip.videoId}/preview`, { headers: { Authorization: `Bearer ${session.accessToken}` }, signal: AbortSignal.timeout(180000) }); assert.equal(r.status, 200); fs.writeFileSync(output, Buffer.from(await r.arrayBuffer())); } clip.previewFile = output; save(); console.log('READY', clip.code, clip.number, clip.videoId); } }