diff --git a/.gitignore b/.gitignore index a145aeb..e73f483 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ artifacts/runs/ artifacts/deploy-smoke/ playwright-report/ test-results/ +reports/**/*.log +**/__pycache__/ diff --git a/reports/instructors-v2-20260914/ROLLBACK.md b/reports/instructors-v2-20260914/ROLLBACK.md new file mode 100644 index 0000000..d17b2ff --- /dev/null +++ b/reports/instructors-v2-20260914/ROLLBACK.md @@ -0,0 +1,17 @@ +# 三位教员 v2 更新与旧版备份 + +更新于 2026-09-15,报告目录沿用任务开始日期 20260914。 + +- 本机旧形象包:本目录 `backup-local/`,按三个原编号保存。 +- 本机旧封面:本目录 `backup-covers/`。 +- 本机新形象包:本目录 `generated/`;已复制到 `ai_person/ai_person_service/builtin_avatars/` 中相同编号目录。 +- 服务器旧形象包与元数据:`/home/digital-human-service/data/backups/instructors-v2-20260914/`,包括 `builtin/`、`runtime/`、`before-metadata.json`。 +- 服务器源码与已发布封面备份:`/home/digital-human-service/releases/instructors-v2-20260914/backup-host/`、`backup-covers/`。 +- 旧服务镜像:`digital-human-service:before-instructors-v2-20260914`。 +- 新服务镜像:`digital-human-service:instructors-v2-20260914`;`digital-human-service:cpu` 同样指向新镜像。 + +需要恢复时,分别恢复三个原编号下的源码包、运行包与封面,并将服务镜像标签切回旧镜像;元数据通过 `app.database.upsert_builtin_avatar` 按备份参数恢复。不要删除或重新创建平台形象记录。 + +本次沿用既有平台记录,没有调用全量“同步服务资源”:该接口还会重新导入服务中保留的旧样例。平台维度、帧数、时长仍与新资源一致,默认教员2和声音配置保持不变。临时生成的三个资源已通过服务 DELETE 接口清理,生成日志、任务号、源文件摘要与最终资源均保留在报告中。 + +核验了三个原入口的 Viewer、约8秒语音播放起止、嘴部驱动、运行包与镜像包哈希、原平台记录及默认配置;未把这些检查当成逐音节口型质量验收。已生成的39个讲解视频与原有WAV没有改动。 diff --git a/reports/instructors-v2-20260914/after-platform.json b/reports/instructors-v2-20260914/after-platform.json new file mode 100644 index 0000000..3292185 --- /dev/null +++ b/reports/instructors-v2-20260914/after-platform.json @@ -0,0 +1,128 @@ +{ + "code": 0, + "message": "success", + "data": { + "items": [ + { + "id": "a1000000000000000000000000000007", + "dataVersion": 9, + "name": "教员3", + "status": "ready", + "bundleReady": true, + "engineAuthorized": true, + "owner": "系统管理员", + "ownerId": "1", + "ownerName": "系统管理员", + "sourceType": "built_in", + "role": "装备维修教员", + "specialty": "装备维修教学", + "gender": "男", + "visibility": "internal", + "builtIn": true, + "default": false, + "voiceCapabilityId": "builtin-voice-male-gentle", + "voice": { + "capabilityId": "builtin-voice-male-gentle", + "speaker": 1, + "speed": 0.8, + "name": "温和男声" + }, + "frameCount": 502, + "width": 720, + "height": 960, + "duration": 20080, + "errorMessage": null, + "coverFileCode": null, + "coverUrl": "/avatars/builtin/equipment-instructor-3.png", + "previewFileCode": null, + "previewUrl": null, + "viewerUrl": "/human/viewer/a1000000000000000000000000000007", + "manifestUrl": "/human/api/v1/avatars/a1000000000000000000000000000007/assets/manifest.json", + "createdAt": "2026-08-29T13:42:19", + "updatedAt": "2026-08-29T17:09:14" + }, + { + "id": "a1000000000000000000000000000006", + "dataVersion": 10, + "name": "教员2", + "status": "ready", + "bundleReady": true, + "engineAuthorized": true, + "owner": "系统管理员", + "ownerId": "1", + "ownerName": "系统管理员", + "sourceType": "built_in", + "role": "装备维修教员", + "specialty": "装备维修教学", + "gender": "男", + "visibility": "internal", + "builtIn": true, + "default": true, + "voiceCapabilityId": "builtin-voice-male-gentle", + "voice": { + "capabilityId": "builtin-voice-male-gentle", + "speaker": 1, + "speed": 0.8, + "name": "温和男声" + }, + "frameCount": 502, + "width": 720, + "height": 960, + "duration": 20080, + "errorMessage": null, + "coverFileCode": null, + "coverUrl": "/avatars/builtin/equipment-instructor-2.png", + "previewFileCode": null, + "previewUrl": null, + "viewerUrl": "/human/viewer/a1000000000000000000000000000006", + "manifestUrl": "/human/api/v1/avatars/a1000000000000000000000000000006/assets/manifest.json", + "createdAt": "2026-08-29T13:42:17", + "updatedAt": "2026-08-29T17:09:14" + }, + { + "id": "a1000000000000000000000000000005", + "dataVersion": 8, + "name": "教员1", + "status": "ready", + "bundleReady": true, + "engineAuthorized": true, + "owner": "系统管理员", + "ownerId": "1", + "ownerName": "系统管理员", + "sourceType": "built_in", + "role": "装备维修教员", + "specialty": "装备维修教学", + "gender": "男", + "visibility": "internal", + "builtIn": true, + "default": false, + "voiceCapabilityId": "builtin-voice-male-gentle", + "voice": { + "capabilityId": "builtin-voice-male-gentle", + "speaker": 1, + "speed": 0.8, + "name": "温和男声" + }, + "frameCount": 502, + "width": 720, + "height": 960, + "duration": 20080, + "errorMessage": null, + "coverFileCode": null, + "coverUrl": "/avatars/builtin/equipment-instructor-1.png", + "previewFileCode": null, + "previewUrl": null, + "viewerUrl": "/human/viewer/a1000000000000000000000000000005", + "manifestUrl": "/human/api/v1/avatars/a1000000000000000000000000000005/assets/manifest.json", + "createdAt": "2026-08-29T13:42:15", + "updatedAt": "2026-08-29T17:09:14" + } + ], + "page": 1, + "pageSize": 200, + "total": 3, + "pages": 1 + }, + "timestamp": 1789402143513, + "requestId": "3b9595df92c9459999719604d49bbac7" +} \ No newline at end of file diff --git a/reports/instructors-v2-20260914/after-service.json b/reports/instructors-v2-20260914/after-service.json new file mode 100644 index 0000000..f94bf61 --- /dev/null +++ b/reports/instructors-v2-20260914/after-service.json @@ -0,0 +1,59 @@ +[ + { + "id": "a1000000000000000000000000000005", + "name": "教员1", + "status": "ready", + "source_filename": "built_in:equipment-instructor-1", + "resize": true, + "matting_mode": "off", + "device": "cpu", + "frame_count": 502, + "width": 720, + "height": 960, + "duration": 20.08, + "error": "", + "created_at": "2026-08-30T16:30:15.680802+00:00", + "updated_at": "2026-09-14T16:05:16.360768+00:00", + "thumbnail_url": "/api/v1/avatars/a1000000000000000000000000000005/assets/thumbnail.jpg", + "viewer_url": "/viewer/a1000000000000000000000000000005", + "manifest_url": "/api/v1/avatars/a1000000000000000000000000000005/assets/manifest.json" + }, + { + "id": "a1000000000000000000000000000006", + "name": "教员2", + "status": "ready", + "source_filename": "built_in:equipment-instructor-2", + "resize": true, + "matting_mode": "off", + "device": "cpu", + "frame_count": 502, + "width": 720, + "height": 960, + "duration": 20.08, + "error": "", + "created_at": "2026-08-30T16:30:15.688395+00:00", + "updated_at": "2026-09-14T16:05:16.388739+00:00", + "thumbnail_url": "/api/v1/avatars/a1000000000000000000000000000006/assets/thumbnail.jpg", + "viewer_url": "/viewer/a1000000000000000000000000000006", + "manifest_url": "/api/v1/avatars/a1000000000000000000000000000006/assets/manifest.json" + }, + { + "id": "a1000000000000000000000000000007", + "name": "教员3", + "status": "ready", + "source_filename": "built_in:equipment-instructor-3", + "resize": true, + "matting_mode": "off", + "device": "cpu", + "frame_count": 502, + "width": 720, + "height": 960, + "duration": 20.08, + "error": "", + "created_at": "2026-08-30T16:30:15.696651+00:00", + "updated_at": "2026-09-14T16:05:16.422542+00:00", + "thumbnail_url": "/api/v1/avatars/a1000000000000000000000000000007/assets/thumbnail.jpg", + "viewer_url": "/viewer/a1000000000000000000000000000007", + "manifest_url": "/api/v1/avatars/a1000000000000000000000000000007/assets/manifest.json" + } +] \ No newline at end of file diff --git a/reports/instructors-v2-20260914/backup-covers/equipment-instructor-1.png b/reports/instructors-v2-20260914/backup-covers/equipment-instructor-1.png new file mode 100644 index 0000000..c9372e1 Binary files /dev/null and b/reports/instructors-v2-20260914/backup-covers/equipment-instructor-1.png differ diff --git a/reports/instructors-v2-20260914/backup-covers/equipment-instructor-2.png b/reports/instructors-v2-20260914/backup-covers/equipment-instructor-2.png new file mode 100644 index 0000000..4576bbe Binary files /dev/null and b/reports/instructors-v2-20260914/backup-covers/equipment-instructor-2.png differ diff --git a/reports/instructors-v2-20260914/backup-covers/equipment-instructor-3.png b/reports/instructors-v2-20260914/backup-covers/equipment-instructor-3.png new file mode 100644 index 0000000..2e8aab3 Binary files /dev/null and b/reports/instructors-v2-20260914/backup-covers/equipment-instructor-3.png differ diff --git a/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000005/assets/01.mp4 b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000005/assets/01.mp4 new file mode 100644 index 0000000..557a092 Binary files /dev/null and b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000005/assets/01.mp4 differ diff --git a/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000005/assets/combined_data.json.gz b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000005/assets/combined_data.json.gz new file mode 100644 index 0000000..95a7b55 Binary files /dev/null and b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000005/assets/combined_data.json.gz differ diff --git a/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000005/assets/manifest.json b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000005/assets/manifest.json new file mode 100644 index 0000000..6910825 --- /dev/null +++ b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000005/assets/manifest.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "created_at": "2026-08-29T08:35:53.172587+00:00", + "device": "cpu", + "matting_mode": "off", + "resize": true, + "source_frame_count": 251, + "width": 720, + "height": 960, + "fps": 25.0, + "frame_count": 502, + "duration": 20.08, + "files": { + "01.mp4": 4291902, + "combined_data.json.gz": 154401, + "thumbnail.jpg": 34112 + } +} \ No newline at end of file diff --git a/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000005/assets/thumbnail.jpg b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000005/assets/thumbnail.jpg new file mode 100644 index 0000000..dd1cf75 Binary files /dev/null and b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000005/assets/thumbnail.jpg differ diff --git a/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000006/assets/01.mp4 b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000006/assets/01.mp4 new file mode 100644 index 0000000..0d832c6 Binary files /dev/null and b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000006/assets/01.mp4 differ diff --git a/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000006/assets/combined_data.json.gz b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000006/assets/combined_data.json.gz new file mode 100644 index 0000000..60cd2f5 Binary files /dev/null and b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000006/assets/combined_data.json.gz differ diff --git a/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000006/assets/manifest.json b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000006/assets/manifest.json new file mode 100644 index 0000000..a0b676b --- /dev/null +++ b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000006/assets/manifest.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "created_at": "2026-08-29T08:37:30.778752+00:00", + "device": "cpu", + "matting_mode": "off", + "resize": true, + "source_frame_count": 251, + "width": 720, + "height": 960, + "fps": 25.0, + "frame_count": 502, + "duration": 20.08, + "files": { + "01.mp4": 3568366, + "combined_data.json.gz": 179829, + "thumbnail.jpg": 33782 + } +} \ No newline at end of file diff --git a/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000006/assets/thumbnail.jpg b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000006/assets/thumbnail.jpg new file mode 100644 index 0000000..2078530 Binary files /dev/null and b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000006/assets/thumbnail.jpg differ diff --git a/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000007/assets/01.mp4 b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000007/assets/01.mp4 new file mode 100644 index 0000000..261fdc8 Binary files /dev/null and b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000007/assets/01.mp4 differ diff --git a/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000007/assets/combined_data.json.gz b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000007/assets/combined_data.json.gz new file mode 100644 index 0000000..6c7bbc7 Binary files /dev/null and b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000007/assets/combined_data.json.gz differ diff --git a/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000007/assets/manifest.json b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000007/assets/manifest.json new file mode 100644 index 0000000..f9117a6 --- /dev/null +++ b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000007/assets/manifest.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "created_at": "2026-08-29T08:39:03.216895+00:00", + "device": "cpu", + "matting_mode": "off", + "resize": true, + "source_frame_count": 251, + "width": 720, + "height": 960, + "fps": 25.0, + "frame_count": 502, + "duration": 20.08, + "files": { + "01.mp4": 4593812, + "combined_data.json.gz": 165007, + "thumbnail.jpg": 34187 + } +} \ No newline at end of file diff --git a/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000007/assets/thumbnail.jpg b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000007/assets/thumbnail.jpg new file mode 100644 index 0000000..bdef3b2 Binary files /dev/null and b/reports/instructors-v2-20260914/backup-local/a1000000000000000000000000000007/assets/thumbnail.jpg differ diff --git a/reports/instructors-v2-20260914/before-platform.json b/reports/instructors-v2-20260914/before-platform.json new file mode 100644 index 0000000..3bd3aba --- /dev/null +++ b/reports/instructors-v2-20260914/before-platform.json @@ -0,0 +1,128 @@ +{ + "code": 0, + "message": "success", + "data": { + "items": [ + { + "id": "a1000000000000000000000000000007", + "dataVersion": 9, + "name": "教员3", + "status": "ready", + "bundleReady": true, + "engineAuthorized": true, + "owner": "系统管理员", + "ownerId": "1", + "ownerName": "系统管理员", + "sourceType": "built_in", + "role": "装备维修教员", + "specialty": "装备维修教学", + "gender": "男", + "visibility": "internal", + "builtIn": true, + "default": false, + "voiceCapabilityId": "builtin-voice-male-gentle", + "voice": { + "capabilityId": "builtin-voice-male-gentle", + "speaker": 1, + "speed": 0.8, + "name": "温和男声" + }, + "frameCount": 502, + "width": 720, + "height": 960, + "duration": 20080, + "errorMessage": null, + "coverFileCode": null, + "coverUrl": "/avatars/builtin/equipment-instructor-3.png", + "previewFileCode": null, + "previewUrl": null, + "viewerUrl": "/human/viewer/a1000000000000000000000000000007", + "manifestUrl": "/human/api/v1/avatars/a1000000000000000000000000000007/assets/manifest.json", + "createdAt": "2026-08-29T13:42:19", + "updatedAt": "2026-08-29T17:09:14" + }, + { + "id": "a1000000000000000000000000000006", + "dataVersion": 10, + "name": "教员2", + "status": "ready", + "bundleReady": true, + "engineAuthorized": true, + "owner": "系统管理员", + "ownerId": "1", + "ownerName": "系统管理员", + "sourceType": "built_in", + "role": "装备维修教员", + "specialty": "装备维修教学", + "gender": "男", + "visibility": "internal", + "builtIn": true, + "default": true, + "voiceCapabilityId": "builtin-voice-male-gentle", + "voice": { + "capabilityId": "builtin-voice-male-gentle", + "speaker": 1, + "speed": 0.8, + "name": "温和男声" + }, + "frameCount": 502, + "width": 720, + "height": 960, + "duration": 20080, + "errorMessage": null, + "coverFileCode": null, + "coverUrl": "/avatars/builtin/equipment-instructor-2.png", + "previewFileCode": null, + "previewUrl": null, + "viewerUrl": "/human/viewer/a1000000000000000000000000000006", + "manifestUrl": "/human/api/v1/avatars/a1000000000000000000000000000006/assets/manifest.json", + "createdAt": "2026-08-29T13:42:17", + "updatedAt": "2026-08-29T17:09:14" + }, + { + "id": "a1000000000000000000000000000005", + "dataVersion": 8, + "name": "教员1", + "status": "ready", + "bundleReady": true, + "engineAuthorized": true, + "owner": "系统管理员", + "ownerId": "1", + "ownerName": "系统管理员", + "sourceType": "built_in", + "role": "装备维修教员", + "specialty": "装备维修教学", + "gender": "男", + "visibility": "internal", + "builtIn": true, + "default": false, + "voiceCapabilityId": "builtin-voice-male-gentle", + "voice": { + "capabilityId": "builtin-voice-male-gentle", + "speaker": 1, + "speed": 0.8, + "name": "温和男声" + }, + "frameCount": 502, + "width": 720, + "height": 960, + "duration": 20080, + "errorMessage": null, + "coverFileCode": null, + "coverUrl": "/avatars/builtin/equipment-instructor-1.png", + "previewFileCode": null, + "previewUrl": null, + "viewerUrl": "/human/viewer/a1000000000000000000000000000005", + "manifestUrl": "/human/api/v1/avatars/a1000000000000000000000000000005/assets/manifest.json", + "createdAt": "2026-08-29T13:42:15", + "updatedAt": "2026-08-29T17:09:14" + } + ], + "page": 1, + "pageSize": 200, + "total": 3, + "pages": 1 + }, + "timestamp": 1789401373462, + "requestId": "a1f1fb85dcc44405be3c0971fc59131c" +} \ No newline at end of file diff --git a/reports/instructors-v2-20260914/before-service.json b/reports/instructors-v2-20260914/before-service.json new file mode 100644 index 0000000..646bd54 --- /dev/null +++ b/reports/instructors-v2-20260914/before-service.json @@ -0,0 +1,59 @@ +[ + { + "id": "a1000000000000000000000000000005", + "name": "教员1", + "status": "ready", + "source_filename": "built_in:equipment-instructor-1", + "resize": true, + "matting_mode": "off", + "device": "cpu", + "frame_count": 502, + "width": 720, + "height": 960, + "duration": 20.08, + "error": "", + "created_at": "2026-08-30T16:30:15.680802+00:00", + "updated_at": "2026-09-01T14:18:39.459243+00:00", + "thumbnail_url": "/api/v1/avatars/a1000000000000000000000000000005/assets/thumbnail.jpg", + "viewer_url": "/viewer/a1000000000000000000000000000005", + "manifest_url": "/api/v1/avatars/a1000000000000000000000000000005/assets/manifest.json" + }, + { + "id": "a1000000000000000000000000000006", + "name": "教员2", + "status": "ready", + "source_filename": "built_in:equipment-instructor-2", + "resize": true, + "matting_mode": "off", + "device": "cpu", + "frame_count": 502, + "width": 720, + "height": 960, + "duration": 20.08, + "error": "", + "created_at": "2026-08-30T16:30:15.688395+00:00", + "updated_at": "2026-09-01T14:18:39.494195+00:00", + "thumbnail_url": "/api/v1/avatars/a1000000000000000000000000000006/assets/thumbnail.jpg", + "viewer_url": "/viewer/a1000000000000000000000000000006", + "manifest_url": "/api/v1/avatars/a1000000000000000000000000000006/assets/manifest.json" + }, + { + "id": "a1000000000000000000000000000007", + "name": "教员3", + "status": "ready", + "source_filename": "built_in:equipment-instructor-3", + "resize": true, + "matting_mode": "off", + "device": "cpu", + "frame_count": 502, + "width": 720, + "height": 960, + "duration": 20.08, + "error": "", + "created_at": "2026-08-30T16:30:15.696651+00:00", + "updated_at": "2026-09-01T14:18:39.543021+00:00", + "thumbnail_url": "/api/v1/avatars/a1000000000000000000000000000007/assets/thumbnail.jpg", + "viewer_url": "/viewer/a1000000000000000000000000000007", + "manifest_url": "/api/v1/avatars/a1000000000000000000000000000007/assets/manifest.json" + } +] \ No newline at end of file diff --git a/reports/instructors-v2-20260914/catalog-preview-1.png b/reports/instructors-v2-20260914/catalog-preview-1.png new file mode 100644 index 0000000..a44bf92 Binary files /dev/null and b/reports/instructors-v2-20260914/catalog-preview-1.png differ diff --git a/reports/instructors-v2-20260914/catalog-preview-2.png b/reports/instructors-v2-20260914/catalog-preview-2.png new file mode 100644 index 0000000..5e8745c Binary files /dev/null and b/reports/instructors-v2-20260914/catalog-preview-2.png differ diff --git a/reports/instructors-v2-20260914/catalog-preview-3.png b/reports/instructors-v2-20260914/catalog-preview-3.png new file mode 100644 index 0000000..7ae5bfd Binary files /dev/null and b/reports/instructors-v2-20260914/catalog-preview-3.png differ diff --git a/reports/instructors-v2-20260914/catalog-v2.png b/reports/instructors-v2-20260914/catalog-v2.png new file mode 100644 index 0000000..4e9fd46 Binary files /dev/null and b/reports/instructors-v2-20260914/catalog-v2.png differ diff --git a/reports/instructors-v2-20260914/catalog-validation.json b/reports/instructors-v2-20260914/catalog-validation.json new file mode 100644 index 0000000..854b5f4 --- /dev/null +++ b/reports/instructors-v2-20260914/catalog-validation.json @@ -0,0 +1,22 @@ +{ + "count": 3, + "defaultInstructor": 2, + "previews": [ + { + "number": 1, + "viewer": "/human/viewer/a1000000000000000000000000000005", + "ready": true + }, + { + "number": 2, + "viewer": "/human/viewer/a1000000000000000000000000000006", + "ready": true + }, + { + "number": 3, + "viewer": "/human/viewer/a1000000000000000000000000000007", + "ready": true + } + ], + "errors": [] +} \ No newline at end of file diff --git a/reports/instructors-v2-20260914/final-validation.json b/reports/instructors-v2-20260914/final-validation.json new file mode 100644 index 0000000..8b38c44 --- /dev/null +++ b/reports/instructors-v2-20260914/final-validation.json @@ -0,0 +1,16 @@ +{ + "identitiesPreserved": true, + "defaultInstructor": 2, + "voicesPreserved": true, + "platformCount": 3, + "runtimeAssetHashesMatch": true, + "imageAssetHashesMatch": true, + "localAndPublishedCoversUpdated": true, + "temporaryAvatarsRemoved": [ + "b11e725a21c44b8a8499ee76cfbc8737", + "73339bc579334a8abf2696d9c4944b87", + "15606bd2b60a4d11951c21907968c3dc" + ], + "oldLectureVideosAndAudioPreserved": true, + "scope": "Resource regeneration and runtime playback verified; phoneme-level lip sync quality not accepted by this check." +} \ No newline at end of file diff --git a/reports/instructors-v2-20260914/generated-1-idle.png b/reports/instructors-v2-20260914/generated-1-idle.png new file mode 100644 index 0000000..31ebe77 Binary files /dev/null and b/reports/instructors-v2-20260914/generated-1-idle.png differ diff --git a/reports/instructors-v2-20260914/generated-1-speaking.png b/reports/instructors-v2-20260914/generated-1-speaking.png new file mode 100644 index 0000000..e3af984 Binary files /dev/null and b/reports/instructors-v2-20260914/generated-1-speaking.png differ diff --git a/reports/instructors-v2-20260914/generated-2-idle.png b/reports/instructors-v2-20260914/generated-2-idle.png new file mode 100644 index 0000000..fa23da1 Binary files /dev/null and b/reports/instructors-v2-20260914/generated-2-idle.png differ diff --git a/reports/instructors-v2-20260914/generated-2-speaking.png b/reports/instructors-v2-20260914/generated-2-speaking.png new file mode 100644 index 0000000..45a45e9 Binary files /dev/null and b/reports/instructors-v2-20260914/generated-2-speaking.png differ diff --git a/reports/instructors-v2-20260914/generated-3-idle.png b/reports/instructors-v2-20260914/generated-3-idle.png new file mode 100644 index 0000000..697e24f Binary files /dev/null and b/reports/instructors-v2-20260914/generated-3-idle.png differ diff --git a/reports/instructors-v2-20260914/generated-3-speaking.png b/reports/instructors-v2-20260914/generated-3-speaking.png new file mode 100644 index 0000000..0483812 Binary files /dev/null and b/reports/instructors-v2-20260914/generated-3-speaking.png differ diff --git a/reports/instructors-v2-20260914/generated-validation.json b/reports/instructors-v2-20260914/generated-validation.json new file mode 100644 index 0000000..14f98b6 --- /dev/null +++ b/reports/instructors-v2-20260914/generated-validation.json @@ -0,0 +1,92 @@ +[ + { + "number": 1, + "stable_id": "a1000000000000000000000000000005", + "generated_id": "b11e725a21c44b8a8499ee76cfbc8737", + "manifest": { + "version": 1, + "created_at": "2026-09-14T15:57:38.109202+00:00", + "device": "cpu", + "matting_mode": "off", + "resize": true, + "source_frame_count": 251, + "width": 720, + "height": 960, + "fps": 25.0, + "frame_count": 502, + "duration": 20.08, + "files": { + "01.mp4": 3959109, + "combined_data.json.gz": 147484, + "thumbnail.jpg": 31865 + } + }, + "geometry_type": "dict", + "sha256": { + "01.mp4": "001912f1bfd2242b22adb473d3e94ce1c5781085c43ca1184db0660adc26c521", + "combined_data.json.gz": "7b00847d2ed6eacebe58fe5b43a8e27fb6efea252af743c2aebaaa1cfe430d79", + "thumbnail.jpg": "67a5e13059a48b06b0a6e7b80cd7d9b72d0bb6afc6aba449432a51190e6e7733", + "manifest.json": "8155a21af68b60ea024c7591c8ad454aa9884a2c7d22cb069c16bb192bc4e122" + } + }, + { + "number": 2, + "stable_id": "a1000000000000000000000000000006", + "generated_id": "73339bc579334a8abf2696d9c4944b87", + "manifest": { + "version": 1, + "created_at": "2026-09-14T15:58:35.155627+00:00", + "device": "cpu", + "matting_mode": "off", + "resize": true, + "source_frame_count": 251, + "width": 720, + "height": 960, + "fps": 25.0, + "frame_count": 502, + "duration": 20.08, + "files": { + "01.mp4": 3644994, + "combined_data.json.gz": 171386, + "thumbnail.jpg": 34436 + } + }, + "geometry_type": "dict", + "sha256": { + "01.mp4": "2f6b0d0302d6e87db20f71e2a76449a69834f66290795af3655348b920fb9197", + "combined_data.json.gz": "b347b72949430d72299a1d2333a6c215ec759c6091144569b5c21f9938e64c6b", + "thumbnail.jpg": "a8ee39c0f0e96ef8878f1218527513b3fb1b675fd46fcc3109197d1b65fdadc2", + "manifest.json": "a508cf8af21db8148509d6c64667d1dce3587a57fcce4a4d5e66ab1062a9d522" + } + }, + { + "number": 3, + "stable_id": "a1000000000000000000000000000007", + "generated_id": "15606bd2b60a4d11951c21907968c3dc", + "manifest": { + "version": 1, + "created_at": "2026-09-14T15:59:28.067630+00:00", + "device": "cpu", + "matting_mode": "off", + "resize": true, + "source_frame_count": 251, + "width": 720, + "height": 960, + "fps": 25.0, + "frame_count": 502, + "duration": 20.08, + "files": { + "01.mp4": 4111377, + "combined_data.json.gz": 156012, + "thumbnail.jpg": 33681 + } + }, + "geometry_type": "dict", + "sha256": { + "01.mp4": "e7a0df84b35faba46605d26f3d6ca83e682e964f4f63561d4d370d7a3ce528f1", + "combined_data.json.gz": "2c313f2a22ae0a8a2848ef6576fba1ac77491fc26c9f0f9dbff72f0660cd46cc", + "thumbnail.jpg": "fc9848fac6afba7ac4f7f9462abf82a4b97a08416fd7c23fdfed9a224f8dccfe", + "manifest.json": "7bf78941670b418f003b370ca804c0df89412902bb48996e59a1ec36bd5f7491" + } + } +] \ No newline at end of file diff --git a/reports/instructors-v2-20260914/generated-viewer-validation.json b/reports/instructors-v2-20260914/generated-viewer-validation.json new file mode 100644 index 0000000..4f38b59 --- /dev/null +++ b/reports/instructors-v2-20260914/generated-viewer-validation.json @@ -0,0 +1,104 @@ +[ + { + "number": 1, + "avatarId": "b11e725a21c44b8a8499ee76cfbc8737", + "driver": { + "frames": 231, + "active": 231, + "maxBlend": 53.525753021240234 + }, + "events": [ + { + "type": "digital-human.ready", + "avatarId": "b11e725a21c44b8a8499ee76cfbc8737" + }, + { + "type": "digital-human.speaking-start", + "avatarId": "b11e725a21c44b8a8499ee76cfbc8737", + "requestId": "v2-validation", + "duration": 8.256 + }, + { + "type": "digital-human.command-accepted", + "avatarId": "b11e725a21c44b8a8499ee76cfbc8737", + "requestId": "v2-validation", + "command": "digital-human.play-audio" + }, + { + "type": "digital-human.speaking-end", + "avatarId": "b11e725a21c44b8a8499ee76cfbc8737", + "requestId": "v2-validation", + "reason": "ended" + } + ], + "errors": [] + }, + { + "number": 2, + "avatarId": "73339bc579334a8abf2696d9c4944b87", + "driver": { + "frames": 221, + "active": 221, + "maxBlend": 53.525753021240234 + }, + "events": [ + { + "type": "digital-human.ready", + "avatarId": "73339bc579334a8abf2696d9c4944b87" + }, + { + "type": "digital-human.speaking-start", + "avatarId": "73339bc579334a8abf2696d9c4944b87", + "requestId": "v2-validation", + "duration": 8.256 + }, + { + "type": "digital-human.command-accepted", + "avatarId": "73339bc579334a8abf2696d9c4944b87", + "requestId": "v2-validation", + "command": "digital-human.play-audio" + }, + { + "type": "digital-human.speaking-end", + "avatarId": "73339bc579334a8abf2696d9c4944b87", + "requestId": "v2-validation", + "reason": "ended" + } + ], + "errors": [] + }, + { + "number": 3, + "avatarId": "15606bd2b60a4d11951c21907968c3dc", + "driver": { + "frames": 213, + "active": 213, + "maxBlend": 53.52582931518555 + }, + "events": [ + { + "type": "digital-human.ready", + "avatarId": "15606bd2b60a4d11951c21907968c3dc" + }, + { + "type": "digital-human.speaking-start", + "avatarId": "15606bd2b60a4d11951c21907968c3dc", + "requestId": "v2-validation", + "duration": 8.256 + }, + { + "type": "digital-human.command-accepted", + "avatarId": "15606bd2b60a4d11951c21907968c3dc", + "requestId": "v2-validation", + "command": "digital-human.play-audio" + }, + { + "type": "digital-human.speaking-end", + "avatarId": "15606bd2b60a4d11951c21907968c3dc", + "requestId": "v2-validation", + "reason": "ended" + } + ], + "errors": [] + } +] \ No newline at end of file diff --git a/reports/instructors-v2-20260914/generated/a1000000000000000000000000000005/assets/01.mp4 b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000005/assets/01.mp4 new file mode 100644 index 0000000..4a94ab2 Binary files /dev/null and b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000005/assets/01.mp4 differ diff --git a/reports/instructors-v2-20260914/generated/a1000000000000000000000000000005/assets/combined_data.json.gz b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000005/assets/combined_data.json.gz new file mode 100644 index 0000000..9a252ac Binary files /dev/null and b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000005/assets/combined_data.json.gz differ diff --git a/reports/instructors-v2-20260914/generated/a1000000000000000000000000000005/assets/manifest.json b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000005/assets/manifest.json new file mode 100644 index 0000000..b493687 --- /dev/null +++ b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000005/assets/manifest.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "created_at": "2026-09-14T15:57:38.109202+00:00", + "device": "cpu", + "matting_mode": "off", + "resize": true, + "source_frame_count": 251, + "width": 720, + "height": 960, + "fps": 25.0, + "frame_count": 502, + "duration": 20.08, + "files": { + "01.mp4": 3959109, + "combined_data.json.gz": 147484, + "thumbnail.jpg": 31865 + } +} \ No newline at end of file diff --git a/reports/instructors-v2-20260914/generated/a1000000000000000000000000000005/assets/thumbnail.jpg b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000005/assets/thumbnail.jpg new file mode 100644 index 0000000..b02c90c Binary files /dev/null and b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000005/assets/thumbnail.jpg differ diff --git a/reports/instructors-v2-20260914/generated/a1000000000000000000000000000006/assets/01.mp4 b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000006/assets/01.mp4 new file mode 100644 index 0000000..c6a160a Binary files /dev/null and b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000006/assets/01.mp4 differ diff --git a/reports/instructors-v2-20260914/generated/a1000000000000000000000000000006/assets/combined_data.json.gz b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000006/assets/combined_data.json.gz new file mode 100644 index 0000000..f5d7eb6 Binary files /dev/null and b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000006/assets/combined_data.json.gz differ diff --git a/reports/instructors-v2-20260914/generated/a1000000000000000000000000000006/assets/manifest.json b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000006/assets/manifest.json new file mode 100644 index 0000000..21d1e2a --- /dev/null +++ b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000006/assets/manifest.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "created_at": "2026-09-14T15:58:35.155627+00:00", + "device": "cpu", + "matting_mode": "off", + "resize": true, + "source_frame_count": 251, + "width": 720, + "height": 960, + "fps": 25.0, + "frame_count": 502, + "duration": 20.08, + "files": { + "01.mp4": 3644994, + "combined_data.json.gz": 171386, + "thumbnail.jpg": 34436 + } +} \ No newline at end of file diff --git a/reports/instructors-v2-20260914/generated/a1000000000000000000000000000006/assets/thumbnail.jpg b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000006/assets/thumbnail.jpg new file mode 100644 index 0000000..9987430 Binary files /dev/null and b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000006/assets/thumbnail.jpg differ diff --git a/reports/instructors-v2-20260914/generated/a1000000000000000000000000000007/assets/01.mp4 b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000007/assets/01.mp4 new file mode 100644 index 0000000..75a778d Binary files /dev/null and b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000007/assets/01.mp4 differ diff --git a/reports/instructors-v2-20260914/generated/a1000000000000000000000000000007/assets/combined_data.json.gz b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000007/assets/combined_data.json.gz new file mode 100644 index 0000000..b683b83 Binary files /dev/null and b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000007/assets/combined_data.json.gz differ diff --git a/reports/instructors-v2-20260914/generated/a1000000000000000000000000000007/assets/manifest.json b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000007/assets/manifest.json new file mode 100644 index 0000000..66608d0 --- /dev/null +++ b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000007/assets/manifest.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "created_at": "2026-09-14T15:59:28.067630+00:00", + "device": "cpu", + "matting_mode": "off", + "resize": true, + "source_frame_count": 251, + "width": 720, + "height": 960, + "fps": 25.0, + "frame_count": 502, + "duration": 20.08, + "files": { + "01.mp4": 4111377, + "combined_data.json.gz": 156012, + "thumbnail.jpg": 33681 + } +} \ No newline at end of file diff --git a/reports/instructors-v2-20260914/generated/a1000000000000000000000000000007/assets/thumbnail.jpg b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000007/assets/thumbnail.jpg new file mode 100644 index 0000000..c6d8b7b Binary files /dev/null and b/reports/instructors-v2-20260914/generated/a1000000000000000000000000000007/assets/thumbnail.jpg differ diff --git a/reports/instructors-v2-20260914/image-validation.json b/reports/instructors-v2-20260914/image-validation.json new file mode 100644 index 0000000..18b9fae --- /dev/null +++ b/reports/instructors-v2-20260914/image-validation.json @@ -0,0 +1,20 @@ +{ + "a1000000000000000000000000000005": { + "01.mp4": "001912f1bfd2242b22adb473d3e94ce1c5781085c43ca1184db0660adc26c521", + "combined_data.json.gz": "7b00847d2ed6eacebe58fe5b43a8e27fb6efea252af743c2aebaaa1cfe430d79", + "thumbnail.jpg": "67a5e13059a48b06b0a6e7b80cd7d9b72d0bb6afc6aba449432a51190e6e7733", + "manifest.json": "8155a21af68b60ea024c7591c8ad454aa9884a2c7d22cb069c16bb192bc4e122" + }, + "a1000000000000000000000000000006": { + "01.mp4": "2f6b0d0302d6e87db20f71e2a76449a69834f66290795af3655348b920fb9197", + "combined_data.json.gz": "b347b72949430d72299a1d2333a6c215ec759c6091144569b5c21f9938e64c6b", + "thumbnail.jpg": "a8ee39c0f0e96ef8878f1218527513b3fb1b675fd46fcc3109197d1b65fdadc2", + "manifest.json": "a508cf8af21db8148509d6c64667d1dce3587a57fcce4a4d5e66ab1062a9d522" + }, + "a1000000000000000000000000000007": { + "01.mp4": "e7a0df84b35faba46605d26f3d6ca83e682e964f4f63561d4d370d7a3ce528f1", + "combined_data.json.gz": "2c313f2a22ae0a8a2848ef6576fba1ac77491fc26c9f0f9dbff72f0660cd46cc", + "thumbnail.jpg": "fc9848fac6afba7ac4f7f9462abf82a4b97a08416fd7c23fdfed9a224f8dccfe", + "manifest.json": "7bf78941670b418f003b370ca804c0df89412902bb48996e59a1ec36bd5f7491" + } +} \ No newline at end of file diff --git a/reports/instructors-v2-20260914/index.html b/reports/instructors-v2-20260914/index.html new file mode 100644 index 0000000..5caffc7 --- /dev/null +++ b/reports/instructors-v2-20260914/index.html @@ -0,0 +1,25 @@ + +三位教员 v2 资源更新与预览 +

三位教员已更新为 v2 素材

2026-09-15 · 已替换共享生成服务、镜像、源码资源包与封面。原有三条形象记录、默认教员2、声音配置保持不变。

打开数字人系统 · 查看列表截图 · 核验记录 · 旧版备份位置

+
本次核验范围

三位原编号均已完成实时加载、语音开始/结束、嘴部驱动与资源哈希核验,未出现运行错误。本次检查不代表“嘴型与每个音节完全吻合”已验收。

约10秒的原始视频会生成正放/倒放循环底片,时长20.08秒。本报告中的实时形象预览展示无声底片;语音驱动测试的截图及记录已保留。之前生成的讲解 MP4 和 WAV 均保留,没有自动重新生成。

本次用于驱动验证的讲解音频(单独试听,不联动上方预览):

+
加载中进入数字人系统
+ \ No newline at end of file diff --git a/reports/instructors-v2-20260914/input-1.jpg b/reports/instructors-v2-20260914/input-1.jpg new file mode 100644 index 0000000..967eb4f Binary files /dev/null and b/reports/instructors-v2-20260914/input-1.jpg differ diff --git a/reports/instructors-v2-20260914/input-2.jpg b/reports/instructors-v2-20260914/input-2.jpg new file mode 100644 index 0000000..94112ee Binary files /dev/null and b/reports/instructors-v2-20260914/input-2.jpg differ diff --git a/reports/instructors-v2-20260914/input-3.jpg b/reports/instructors-v2-20260914/input-3.jpg new file mode 100644 index 0000000..eb93aa4 Binary files /dev/null and b/reports/instructors-v2-20260914/input-3.jpg differ diff --git a/reports/instructors-v2-20260914/job-status.json b/reports/instructors-v2-20260914/job-status.json new file mode 100644 index 0000000..03fbe1b --- /dev/null +++ b/reports/instructors-v2-20260914/job-status.json @@ -0,0 +1,38 @@ +[ + { + "number": 1, + "id": "68b508bb56e049b4bf9320e146b4a9c8", + "avatar_id": "b11e725a21c44b8a8499ee76cfbc8737", + "status": "ready", + "stage": "ready", + "progress": 100, + "error": "", + "created_at": "2026-09-14T15:56:38.987426+00:00", + "updated_at": "2026-09-14T15:57:38.776292+00:00", + "avatar_url": "/api/v1/avatars/b11e725a21c44b8a8499ee76cfbc8737" + }, + { + "number": 2, + "id": "9aa04bbf6a26452fb3f63155927eed2c", + "avatar_id": "73339bc579334a8abf2696d9c4944b87", + "status": "ready", + "stage": "ready", + "progress": 100, + "error": "", + "created_at": "2026-09-14T15:56:53.845011+00:00", + "updated_at": "2026-09-14T15:58:35.782507+00:00", + "avatar_url": "/api/v1/avatars/73339bc579334a8abf2696d9c4944b87" + }, + { + "number": 3, + "id": "700077f642a6417c96bb339e8cd32118", + "avatar_id": "15606bd2b60a4d11951c21907968c3dc", + "status": "processing", + "stage": "preparing_video", + "progress": 5, + "error": "", + "created_at": "2026-09-14T15:56:57.886909+00:00", + "updated_at": "2026-09-14T15:58:35.836594+00:00", + "avatar_url": "/api/v1/avatars/15606bd2b60a4d11951c21907968c3dc" + } +] \ No newline at end of file diff --git a/reports/instructors-v2-20260914/jobs.json b/reports/instructors-v2-20260914/jobs.json new file mode 100644 index 0000000..93f336e --- /dev/null +++ b/reports/instructors-v2-20260914/jobs.json @@ -0,0 +1,35 @@ +[ + { + "number": 1, + "stable_id": "a1000000000000000000000000000005", + "source": "教员1_v2.mp4", + "source_sha256": "095ad7b86c86500c37e61b223b04e7577c579b8bbc5d5ed02477ad686a08f698", + "job_id": "68b508bb56e049b4bf9320e146b4a9c8", + "avatar_id": "b11e725a21c44b8a8499ee76cfbc8737", + "status": "queued", + "upload_bytes": 6590546, + "job_url": "/api/v1/jobs/68b508bb56e049b4bf9320e146b4a9c8" + }, + { + "number": 2, + "stable_id": "a1000000000000000000000000000006", + "source": "教员2_v2.mp4", + "source_sha256": "f5ce0b2edf3998d0702e13c2a64161bc3bb16779112f434bdf23e5d4b2bf03de", + "job_id": "9aa04bbf6a26452fb3f63155927eed2c", + "avatar_id": "73339bc579334a8abf2696d9c4944b87", + "status": "queued", + "upload_bytes": 6142900, + "job_url": "/api/v1/jobs/9aa04bbf6a26452fb3f63155927eed2c" + }, + { + "number": 3, + "stable_id": "a1000000000000000000000000000007", + "source": "教员3_v2.mp4", + "source_sha256": "bd848e1d46e0f170ebb441bf856e7f5b93b4229f74852b963a9239cb79ac80d0", + "job_id": "700077f642a6417c96bb339e8cd32118", + "avatar_id": "15606bd2b60a4d11951c21907968c3dc", + "status": "queued", + "upload_bytes": 1483614, + "job_url": "/api/v1/jobs/700077f642a6417c96bb339e8cd32118" + } +] \ No newline at end of file diff --git a/reports/instructors-v2-20260914/promotion-runtime.json b/reports/instructors-v2-20260914/promotion-runtime.json new file mode 100644 index 0000000..a446129 --- /dev/null +++ b/reports/instructors-v2-20260914/promotion-runtime.json @@ -0,0 +1,8 @@ +{ + "backup": "/app/data/backups/instructors-v2-20260914", + "updated": [ + "a1000000000000000000000000000005", + "a1000000000000000000000000000006", + "a1000000000000000000000000000007" + ] +} \ No newline at end of file diff --git a/reports/instructors-v2-20260914/promotion-start.json b/reports/instructors-v2-20260914/promotion-start.json new file mode 100644 index 0000000..48f6fcf --- /dev/null +++ b/reports/instructors-v2-20260914/promotion-start.json @@ -0,0 +1,4 @@ +{ + "baseImage": "sha256:2ac13cf158c3a1a50150dfcf7c98c8795a448a5b5353f2a6f2b28fdeacc774a6", + "remoteRelease": "/home/digital-human-service/releases/instructors-v2-20260914" +} \ No newline at end of file diff --git a/reports/instructors-v2-20260914/promotion.json b/reports/instructors-v2-20260914/promotion.json new file mode 100644 index 0000000..da60bbc --- /dev/null +++ b/reports/instructors-v2-20260914/promotion.json @@ -0,0 +1,11 @@ +{ + "backup": "/app/data/backups/instructors-v2-20260914", + "updated": [ + "a1000000000000000000000000000005", + "a1000000000000000000000000000006", + "a1000000000000000000000000000007" + ], + "oldImage": "sha256:2ac13cf158c3a1a50150dfcf7c98c8795a448a5b5353f2a6f2b28fdeacc774a6", + "newImage": "sha256:213f8a4458db5051dcb81f21ea62c85a26e6e647aa9ae156da8ba38277be4ff7", + "release": "/home/digital-human-service/releases/instructors-v2-20260914" +} \ No newline at end of file diff --git a/reports/instructors-v2-20260914/report.png b/reports/instructors-v2-20260914/report.png new file mode 100644 index 0000000..bccece3 Binary files /dev/null and b/reports/instructors-v2-20260914/report.png differ diff --git a/reports/instructors-v2-20260914/sample.wav b/reports/instructors-v2-20260914/sample.wav new file mode 100644 index 0000000..64accea Binary files /dev/null and b/reports/instructors-v2-20260914/sample.wav differ diff --git a/reports/instructors-v2-20260914/stable-1-idle.png b/reports/instructors-v2-20260914/stable-1-idle.png new file mode 100644 index 0000000..12437c2 Binary files /dev/null and b/reports/instructors-v2-20260914/stable-1-idle.png differ diff --git a/reports/instructors-v2-20260914/stable-1-speaking.png b/reports/instructors-v2-20260914/stable-1-speaking.png new file mode 100644 index 0000000..1f8e517 Binary files /dev/null and b/reports/instructors-v2-20260914/stable-1-speaking.png differ diff --git a/reports/instructors-v2-20260914/stable-2-idle.png b/reports/instructors-v2-20260914/stable-2-idle.png new file mode 100644 index 0000000..1599179 Binary files /dev/null and b/reports/instructors-v2-20260914/stable-2-idle.png differ diff --git a/reports/instructors-v2-20260914/stable-2-speaking.png b/reports/instructors-v2-20260914/stable-2-speaking.png new file mode 100644 index 0000000..64add0b Binary files /dev/null and b/reports/instructors-v2-20260914/stable-2-speaking.png differ diff --git a/reports/instructors-v2-20260914/stable-3-idle.png b/reports/instructors-v2-20260914/stable-3-idle.png new file mode 100644 index 0000000..c0f8fc4 Binary files /dev/null and b/reports/instructors-v2-20260914/stable-3-idle.png differ diff --git a/reports/instructors-v2-20260914/stable-3-speaking.png b/reports/instructors-v2-20260914/stable-3-speaking.png new file mode 100644 index 0000000..c7ce4bb Binary files /dev/null and b/reports/instructors-v2-20260914/stable-3-speaking.png differ diff --git a/reports/instructors-v2-20260914/stable-viewer-validation.json b/reports/instructors-v2-20260914/stable-viewer-validation.json new file mode 100644 index 0000000..8851597 --- /dev/null +++ b/reports/instructors-v2-20260914/stable-viewer-validation.json @@ -0,0 +1,104 @@ +[ + { + "number": 1, + "avatarId": "a1000000000000000000000000000005", + "driver": { + "frames": 223, + "active": 223, + "maxBlend": 53.52578353881836 + }, + "events": [ + { + "type": "digital-human.ready", + "avatarId": "a1000000000000000000000000000005" + }, + { + "type": "digital-human.speaking-start", + "avatarId": "a1000000000000000000000000000005", + "requestId": "v2-validation", + "duration": 8.256 + }, + { + "type": "digital-human.command-accepted", + "avatarId": "a1000000000000000000000000000005", + "requestId": "v2-validation", + "command": "digital-human.play-audio" + }, + { + "type": "digital-human.speaking-end", + "avatarId": "a1000000000000000000000000000005", + "requestId": "v2-validation", + "reason": "ended" + } + ], + "errors": [] + }, + { + "number": 2, + "avatarId": "a1000000000000000000000000000006", + "driver": { + "frames": 225, + "active": 225, + "maxBlend": 53.5256462097168 + }, + "events": [ + { + "type": "digital-human.ready", + "avatarId": "a1000000000000000000000000000006" + }, + { + "type": "digital-human.speaking-start", + "avatarId": "a1000000000000000000000000000006", + "requestId": "v2-validation", + "duration": 8.256 + }, + { + "type": "digital-human.command-accepted", + "avatarId": "a1000000000000000000000000000006", + "requestId": "v2-validation", + "command": "digital-human.play-audio" + }, + { + "type": "digital-human.speaking-end", + "avatarId": "a1000000000000000000000000000006", + "requestId": "v2-validation", + "reason": "ended" + } + ], + "errors": [] + }, + { + "number": 3, + "avatarId": "a1000000000000000000000000000007", + "driver": { + "frames": 226, + "active": 226, + "maxBlend": 53.52562713623047 + }, + "events": [ + { + "type": "digital-human.ready", + "avatarId": "a1000000000000000000000000000007" + }, + { + "type": "digital-human.speaking-start", + "avatarId": "a1000000000000000000000000000007", + "requestId": "v2-validation", + "duration": 8.256 + }, + { + "type": "digital-human.command-accepted", + "avatarId": "a1000000000000000000000000000007", + "requestId": "v2-validation", + "command": "digital-human.play-audio" + }, + { + "type": "digital-human.speaking-end", + "avatarId": "a1000000000000000000000000000007", + "requestId": "v2-validation", + "reason": "ended" + } + ], + "errors": [] + } +] \ No newline at end of file diff --git a/tools/finalize_instructors_v2.py b/tools/finalize_instructors_v2.py new file mode 100644 index 0000000..52c2e3f --- /dev/null +++ b/tools/finalize_instructors_v2.py @@ -0,0 +1,91 @@ +"""Verify promoted identities and clean up only this run's temporary avatars.""" +import hashlib +import json +import shlex + +from promote_instructors_v2 import connect, run, RELEASE +from retrain_instructors_v2 import ROOT, REPORT, IDS, FILES, API, SERVICE, HTTP, api_session, save + + +def main(): + jobs = json.loads((REPORT / 'jobs.json').read_text('utf-8')) + checks = json.loads((REPORT / 'generated-validation.json').read_text('utf-8')) + playback = json.loads((REPORT / 'stable-viewer-validation.json').read_text('utf-8')) + catalog = json.loads((REPORT / 'catalog-validation.json').read_text('utf-8')) + assert len(playback) == catalog['count'] == 3 + assert all(not item['errors'] and item['driver']['active'] > 30 for item in playback) + session = api_session() + response = session.get(API + '/api/v1/avatars', params={'pageSize': 200}, timeout=30) + response.raise_for_status() + after = response.json() + save('after-platform.json', after) + before = json.loads((REPORT / 'before-platform.json').read_text('utf-8')) + # No full catalog sync: it would re-import previously soft-deleted legacy entries. + assert before['data']['total'] == after['data']['total'] == 3 + before_items = {item['id']: item for item in before['data']['items']} + after_items = {item['id']: item for item in after['data']['items']} + assert set(before_items) == set(after_items) == set(IDS) + preserved_fields = ('id', 'name', 'status', 'default', 'voice', 'voiceCapabilityId', 'ownerId', 'visibility', 'viewerUrl') + for ident in IDS: + assert all(before_items[ident][field] == after_items[ident][field] for field in preserved_fields) + service_after = [] + for check in checks: + ident = check['stable_id'] + metadata = HTTP.get(SERVICE + '/api/v1/avatars/' + ident, timeout=30) + metadata.raise_for_status() + service_after.append(metadata.json()) + assert metadata.json()['status'] == 'ready' + for filename in FILES: + response = HTTP.get(SERVICE + f'/api/v1/avatars/{ident}/assets/{filename}', timeout=45) + response.raise_for_status() + assert hashlib.sha256(response.content).hexdigest() == check['sha256'][filename] + save('after-service.json', service_after) + + client = connect() + try: + expected = {row['stable_id']: row['sha256'] for row in checks} + code = 'from pathlib import Path;import json,hashlib;expected=' + repr(expected) + ";print(json.dumps({ident:{name:hashlib.sha256((Path('/app/builtin_avatars')/ident/'assets'/name).read_bytes()).hexdigest() for name in files} for ident,files in expected.items()}))" + actual = json.loads(run(client, 'docker run --rm --network none --entrypoint python digital-human-service:instructors-v2-20260914 -c ' + shlex.quote(code))) + assert actual == expected + save('image-validation.json', actual) + # The list uses source-delivered PNG covers, so replace those exact assets too. + run(client, f'mkdir -p {RELEASE}/backup-covers/source {RELEASE}/backup-covers/published') + with client.open_sftp() as sftp: + for number in (1, 2, 3): + name = f'equipment-instructor-{number}.png' + local = ROOT / 'ai_person/ai_person_web/public/avatars/builtin' / name + for group, base in ( + ('source', '/home/ai_person/ai_person_web/public/avatars/builtin'), + ('published', '/home/ai_person/html_ap/avatars/builtin'), + ): + target = base + '/' + name + backup = RELEASE + '/backup-covers/' + group + '/' + name + run(client, f'test -f {target} && test ! -e {backup} && cp {target} {backup}') + sftp.put(str(local), target + '.v2-stage') + sftp.posix_rename(target + '.v2-stage', target) + with sftp.open(target, 'rb') as stream: + assert hashlib.sha256(stream.read()).digest() == hashlib.sha256(local.read_bytes()).digest() + # Keep service-side generation logs in the rollback/audit report before deleting temporary jobs. + with client.open_sftp() as sftp: + for job in jobs: + sftp.get('/home/digital-human-service/data/jobs/' + job['job_id'] + '.log', str(REPORT / f"generation-{job['number']}.log")) + finally: + client.close() + removed = [] + for job in jobs: + assert job['avatar_id'] not in IDS + response = HTTP.delete(SERVICE + '/api/v1/avatars/' + job['avatar_id'], timeout=45) + assert response.status_code == 204, response.status_code + removed.append(job['avatar_id']) + save('final-validation.json', { + 'identitiesPreserved': True, 'defaultInstructor': 2, 'voicesPreserved': True, + 'platformCount': 3, 'runtimeAssetHashesMatch': True, 'imageAssetHashesMatch': True, + 'localAndPublishedCoversUpdated': True, 'temporaryAvatarsRemoved': removed, + 'oldLectureVideosAndAudioPreserved': True, + 'scope': 'Resource regeneration and runtime playback verified; phoneme-level lip sync quality not accepted by this check.', + }) + print('Verified: three original identities, default instructor, voices, runtime/image assets and covers. Temporary generation entries removed.') + + +if __name__ == '__main__': + main() diff --git a/tools/promote_instructors_v2.py b/tools/promote_instructors_v2.py new file mode 100644 index 0000000..5b55756 --- /dev/null +++ b/tools/promote_instructors_v2.py @@ -0,0 +1,124 @@ +"""Promote the three validated v2 bundles without replacing platform identities.""" +import importlib.util +import json +import shlex +import shutil +import subprocess +from pathlib import Path + +import paramiko +from retrain_instructors_v2 import ROOT, REPORT, BUNDLES, IDS, FILES, api_session, API, HTTP, SERVICE, save + +RELEASE = '/home/digital-human-service/releases/instructors-v2-20260914' +HOST_SOURCE = '/home/ai_person/ai_person_service/builtin_avatars' +CONTAINER = 'digital-human-service' + + +def connect(): + spec = importlib.util.spec_from_file_location('connection', ROOT / 'ai_person/ai_person_api/.run/test_file_service_tunnel.py') + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + host, port, user, password = module._read_connection() + client = paramiko.SSHClient() + client.load_system_host_keys() + client.set_missing_host_key_policy(paramiko.RejectPolicy()) + client.connect(host, port=port, username=user, password=password, look_for_keys=False, allow_agent=False) + return client + + +def run(client, command): + _, stdout, stderr = client.exec_command(command, timeout=300) + output, error = stdout.read().decode('utf-8', 'replace'), stderr.read().decode('utf-8', 'replace') + if stdout.channel.recv_exit_status() != 0: + raise RuntimeError(output + error) + return output.strip() + + +def remote_promote(client, jobs, checks): + # Use the service's own metadata operation. No SQL is issued against the platform database. + script = r''' +import hashlib,json,shutil +from pathlib import Path +from app.config import settings +from app import database +rows=PAYLOAD +backup=settings.data_root/'backups'/'instructors-v2-20260914' +if backup.exists():raise RuntimeError('Backup exists; refuse a second promotion') +source=settings.project_root/'builtin_avatars' +catalog={x['id']:x for x in json.loads((source/'catalog.json').read_text())} +for row in rows: + assert row['stable_id'] in catalog + assets=settings.avatars_root/row['avatar_id']/'assets' + assert database.get_avatar(row['avatar_id'])['status']=='ready' + for name,digest in row['sha256'].items():assert hashlib.sha256((assets/name).read_bytes()).hexdigest()==digest +backup.mkdir(parents=True) +before=[] +for row in rows: + ident=row['stable_id'] + before.append(database.get_avatar(ident)) + shutil.copytree(source/ident,backup/'builtin'/ident) + shutil.copytree(settings.avatars_root/ident,backup/'runtime'/ident) +(backup/'before-metadata.json').write_text(json.dumps(before,ensure_ascii=False,indent=2)) +for row in rows: + ident=row['stable_id'];assets=settings.avatars_root/row['avatar_id']/'assets' + for root in (source,settings.avatars_root): + stage=root/(ident+'.v2-stage') + if stage.exists():raise RuntimeError('Unexpected stale staging directory') + shutil.copytree(root/ident,stage) + for name in row['sha256']:shutil.copy2(assets/name,stage/'assets'/name) + previous=root/(ident+'.v1-previous') + if previous.exists():raise RuntimeError('Unexpected previous directory') + (root/ident).rename(previous) + try:stage.rename(root/ident) + except BaseException:previous.rename(root/ident);raise + # Keep the renamed directories until all replacement checks finish. + manifest=json.loads((assets/'manifest.json').read_text()) + spec=catalog[ident] + database.upsert_builtin_avatar(avatar_id=ident,name=spec['name'],source_filename='built_in:'+spec['code'], + frame_count=manifest['frame_count'],width=manifest['width'],height=manifest['height'],duration=manifest['duration']) +print(json.dumps({'backup':str(backup),'updated':[row['stable_id'] for row in rows]})) +'''.replace('PAYLOAD', repr([{**job, 'sha256': check['sha256']} for job, check in zip(jobs, checks)])) + return run(client, 'docker exec -w /app ' + CONTAINER + ' python -c ' + shlex.quote(script)) + + +def main(): + if (REPORT / 'promotion.json').exists(): + raise RuntimeError('Already promoted; do not run twice') + jobs = json.loads((REPORT / 'jobs.json').read_text('utf-8')) + checks = json.loads((REPORT / 'generated-validation.json').read_text('utf-8')) + playback = json.loads((REPORT / 'generated-viewer-validation.json').read_text('utf-8')) + assert len(jobs) == len(checks) == len(playback) == 3 + assert all(not row['errors'] and row['driver']['active'] > 30 for row in playback) + client = connect() + try: + base_image = run(client, "docker inspect digital-human-service --format '{{.Image}}'") + run(client, f'mkdir -p {RELEASE}/backup-host {RELEASE}/builtin_avatars') + for ident in IDS: + run(client, f'test -d {HOST_SOURCE}/{ident} && cp -a {HOST_SOURCE}/{ident} {RELEASE}/backup-host/{ident}') + save('promotion-start.json', {'baseImage': base_image, 'remoteRelease': RELEASE}) + result = json.loads(remote_promote(client, jobs, checks)) + save('promotion-runtime.json', result) + for ident in IDS: + run(client, f'docker cp {CONTAINER}:/app/builtin_avatars/{ident} {RELEASE}/builtin_avatars/{ident}') + for filename in FILES: + run(client, f'cp {RELEASE}/builtin_avatars/{ident}/assets/{filename} {HOST_SOURCE}/{ident}/assets/{filename}') + shutil.copy2(REPORT / 'generated' / ident / 'assets' / filename, BUNDLES / ident / 'assets' / filename) + # Persist bundles in the deployment image as well as the running container. + # The base is the exact current image; no application source is upgraded. + dockerfile = f'FROM {base_image}\nCOPY builtin_avatars/ /app/builtin_avatars/\nLABEL ai-person.avatar-material-version="instructors-v2-20260914"\n' + with client.open_sftp() as sftp: + with sftp.open(RELEASE + '/Dockerfile', 'w') as stream: + stream.write(dockerfile) + run(client, f'docker tag {base_image} digital-human-service:before-instructors-v2-20260914') + output = run(client, f'docker build --network=none -t digital-human-service:instructors-v2-20260914 {RELEASE}') + (REPORT / 'image-build.log').write_text(output, encoding='utf-8') + run(client, 'docker tag digital-human-service:instructors-v2-20260914 digital-human-service:cpu') + image_id = run(client, "docker image inspect digital-human-service:cpu --format '{{.Id}}'") + save('promotion.json', {**result, 'oldImage': base_image, 'newImage': image_id, 'release': RELEASE}) + print(json.dumps({'updated': IDS, 'newImage': image_id, 'backup': result['backup']}), flush=True) + finally: + client.close() + + +if __name__ == '__main__': + main() diff --git a/tools/report_instructors_v2.py b/tools/report_instructors_v2.py new file mode 100644 index 0000000..5e90edf --- /dev/null +++ b/tools/report_instructors_v2.py @@ -0,0 +1,30 @@ +import json +from retrain_instructors_v2 import ROOT, REPORT + +jobs = json.loads((REPORT / 'jobs.json').read_text('utf-8')) +audio = (ROOT / 'unreal_tran/ute2e/reports/bridge-lipsync-diagnosis-20260914/sample.wav').read_bytes() +(REPORT / 'sample.wav').write_bytes(audio) +cards = [] +for row in jobs: + n, ident = row['number'], row['stable_id'] + source = (ROOT / '数字人素材' / row['source']).as_uri() + cards.append(f'''

教员{n} v2{' · 默认形象' if n == 2 else ''}

+ 教员{n}的实时语音驱动截图 + +

原始 v2 素材 · 新形象底片(无声)

+

720 × 960 · 25 FPS · 502 帧
原编号:{ident}

''') +html = ''' +三位教员 v2 资源更新与预览 +

三位教员已更新为 v2 素材

2026-09-15 · 已替换共享生成服务、镜像、源码资源包与封面。原有三条形象记录、默认教员2、声音配置保持不变。

打开数字人系统 · 查看列表截图 · 核验记录 · 旧版备份位置

+
CARDS
本次核验范围

三位原编号均已完成实时加载、语音开始/结束、嘴部驱动与资源哈希核验,未出现运行错误。本次检查不代表“嘴型与每个音节完全吻合”已验收。

约10秒的原始视频会生成正放/倒放循环底片,时长20.08秒。本报告中的实时形象预览展示无声底片;语音驱动测试的截图及记录已保留。之前生成的讲解 MP4 和 WAV 均保留,没有自动重新生成。

本次用于驱动验证的讲解音频(单独试听,不联动上方预览):

+
加载中进入数字人系统
+'''.replace('CARDS', ''.join(cards)) +(REPORT / 'index.html').write_text(html, encoding='utf-8') +print('Report written:', REPORT / 'index.html') diff --git a/tools/retrain_instructors_v2.py b/tools/retrain_instructors_v2.py new file mode 100644 index 0000000..3975614 --- /dev/null +++ b/tools/retrain_instructors_v2.py @@ -0,0 +1,121 @@ +"""Reproducible v2 instructor generation; reports contain no credentials.""" +from pathlib import Path +import argparse +import gzip +import hashlib +import json +import re +import shutil +import requests + +ROOT = Path(__file__).resolve().parents[3] +REPORT = ROOT / 'ai_person/ape2e/reports/instructors-v2-20260914' +BUNDLES = ROOT / 'ai_person/ai_person_service/builtin_avatars' +SERVICE = 'http://1.14.103.234:8123' +API = 'http://127.0.0.1:8001' +FILES = ('01.mp4', 'combined_data.json.gz', 'thumbnail.jpg', 'manifest.json') +IDS = [f'a100000000000000000000000000000{i}' for i in (5, 6, 7)] +HTTP = requests.Session() +HTTP.trust_env = False + + +def save(name, value): + REPORT.mkdir(parents=True, exist_ok=True) + (REPORT / name).write_text(json.dumps(value, ensure_ascii=False, indent=2), encoding='utf-8') + + +def api_session(): + values = {} + for line in (ROOT / 'ai_person/账号.MD').read_text(encoding='utf-8-sig').splitlines(): + match = re.match(r'^\s*([^::]+)\s*[::]\s*(.*?)\s*$', line) + if match: + values[match[1].strip()] = match[2].strip() + session = requests.Session() + session.trust_env = False + response = session.post(API + '/api/auth/v1/auth/login', json={ + 'roleCode': 'admin', 'username': values.get('账号') or values.get('用户名'), + 'password': values['密码'], 'rememberMe': False, + }, timeout=30) + response.raise_for_status() + data = response.json()['data'] + token = data.get('accessToken') or data['tokens']['accessToken'] + session.headers['Authorization'] = 'Bearer ' + token + return session + + +def get_json(url): + response = HTTP.get(url, timeout=30) + response.raise_for_status() + return response.json() + + +def snapshot(): + if (REPORT / 'before-platform.json').exists(): + raise RuntimeError('Backup already exists; refusing to overwrite') + session = api_session() + response = session.get(API + '/api/v1/avatars', params={'pageSize': 200}, timeout=30) + response.raise_for_status() + save('before-platform.json', response.json()) + save('before-service.json', [get_json(SERVICE + '/api/v1/avatars/' + ident) for ident in IDS]) + for ident in IDS: + shutil.copytree(BUNDLES / ident, REPORT / 'backup-local' / ident) + print('Backed up platform catalog, service metadata and all three local bundles') + + +def submit(): + if not (REPORT / 'before-platform.json').exists(): + raise RuntimeError('Run snapshot first') + state = json.loads((REPORT / 'jobs.json').read_text(encoding='utf-8')) if (REPORT / 'jobs.json').exists() else [] + for number, ident in enumerate(IDS, 1): + if any(item['stable_id'] == ident for item in state): + continue + source = ROOT / '数字人素材' / f'教员{number}_v2.mp4' + with source.open('rb') as stream: + response = HTTP.post(SERVICE + '/api/v1/avatars', + files={'video': (source.name, stream, 'video/mp4')}, + data={'name': f'教员{number} v2 重训 20260914', 'resize': 'true', 'matting': 'off'}, timeout=180) + response.raise_for_status() + item = {'number': number, 'stable_id': ident, 'source': source.name, + 'source_sha256': hashlib.sha256(source.read_bytes()).hexdigest(), **response.json()} + state.append(item) + save('jobs.json', state) + print(json.dumps(item, ensure_ascii=False), flush=True) + + +def status(): + values = [] + for item in json.loads((REPORT / 'jobs.json').read_text(encoding='utf-8')): + job = get_json(SERVICE + item['job_url']) + values.append({'number': item['number'], **job}) + save('job-status.json', values) + print(json.dumps(values, ensure_ascii=False, indent=2)) + + +def download(): + values = [] + for item in json.loads((REPORT / 'jobs.json').read_text(encoding='utf-8')): + avatar = get_json(SERVICE + '/api/v1/avatars/' + item['avatar_id']) + if avatar['status'] != 'ready': + raise RuntimeError(f"Instructor {item['number']} not ready") + target = REPORT / 'generated' / item['stable_id'] / 'assets' + target.mkdir(parents=True, exist_ok=True) + for filename in FILES: + response = HTTP.get(SERVICE + f"/api/v1/avatars/{item['avatar_id']}/assets/{filename}", timeout=120) + response.raise_for_status() + (target / filename).write_bytes(response.content) + manifest = json.loads((target / 'manifest.json').read_text(encoding='utf-8')) + for filename, length in manifest['files'].items(): + assert (target / filename).stat().st_size == length, filename + with gzip.open(target / 'combined_data.json.gz', 'rt', encoding='utf-8') as stream: + geometry = json.load(stream) + values.append({'number': item['number'], 'stable_id': item['stable_id'], 'generated_id': item['avatar_id'], + 'manifest': manifest, 'geometry_type': type(geometry).__name__, + 'sha256': {name: hashlib.sha256((target / name).read_bytes()).hexdigest() for name in FILES}}) + save('generated-validation.json', values) + print(json.dumps(values, ensure_ascii=False, indent=2)) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('phase', choices=['snapshot', 'submit', 'status', 'download']) + globals()[parser.parse_args().phase]() diff --git a/tools/verify_instructors_v2.mjs b/tools/verify_instructors_v2.mjs new file mode 100644 index 0000000..b2b6f3e --- /dev/null +++ b/tools/verify_instructors_v2.mjs @@ -0,0 +1,57 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { chromium } from '@playwright/test'; + +const root = path.resolve('../..'); +const sourceReport = path.resolve('reports/instructors-v2-20260914'); +const report = path.resolve(process.env.REPORT_DIR || sourceReport); +fs.mkdirSync(report,{recursive:true}); +const stable = process.argv.includes('--stable'); +const jobs = JSON.parse(fs.readFileSync(`${sourceReport}/jobs.json`, 'utf8')); +const origin = process.env.BASE_URL || 'http://127.0.0.1:8003'; +const audio = fs.readFileSync(`${root}/unreal_tran/ute2e/reports/bridge-lipsync-diagnosis-20260914/sample.wav`).toString('base64'); +const browser = await chromium.launch({ channel: 'msedge', headless: true, + args: ['--autoplay-policy=no-user-gesture-required', '--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] }); +const results = []; +try { + for (const job of jobs) { + const id = stable ? job.stable_id : job.avatar_id; + const context = await browser.newContext({ viewport: { width: 560, height: 760 } }); + await context.route(`${origin}/__instructor-v2-check`, route => route.fulfill({ contentType: 'text/html', body: + `

教员${job.number} · v2 语音驱动预览

` })); + const page = await context.newPage(); + const errors = []; + page.on('pageerror', error => errors.push(error.message)); + await page.goto(`${origin}/__instructor-v2-check`); + const frame = page.frames().find(frame => frame.url().includes('/human/viewer/')); + await frame.waitForFunction(() => window.DH_RUNTIME_READY, {}, { timeout: 120000 }); + await frame.evaluate(() => { + window.v2Check = { frames: 0, active: 0, maxBlend: 0 }; + const original = Module._updateBlendShape; + Module._updateBlendShape = function(pointer, count) { + const result = original(pointer, count); + const max = Math.max(...Array.from(new Float32Array(Module.HEAPU8.buffer, pointer, 12)).map(Math.abs)); + window.v2Check.frames++; + window.v2Check.maxBlend = Math.max(window.v2Check.maxBlend, max); + if (max > .001) window.v2Check.active++; + return result; + }; + }); + await page.screenshot({ path: `${report}/${stable ? 'stable' : 'generated'}-${job.number}-idle.png` }); + await page.evaluate(audioBase64 => document.querySelector('iframe').contentWindow.postMessage({ + type: 'digital-human.play-audio', requestId: 'v2-validation', audioBase64, + }, location.origin), audio); + await page.waitForFunction(() => window.events.some(event => event.type === 'digital-human.speaking-start'), {}, { timeout: 15000 }); + await frame.waitForFunction(() => window.v2Check.active > 30, {}, { timeout: 15000 }); + await page.screenshot({ path: `${report}/${stable ? 'stable' : 'generated'}-${job.number}-speaking.png` }); + await page.waitForFunction(() => window.events.some(event => event.type === 'digital-human.speaking-end'), {}, { timeout: 20000 }); + const events = await page.evaluate(() => window.events); + const driver = await frame.evaluate(() => window.v2Check); + if (errors.length || events.some(event => event.type === 'digital-human.error')) throw new Error(JSON.stringify({ errors, events })); + const result = { number: job.number, avatarId: id, driver, events, errors }; + results.push(result); + console.log(JSON.stringify(result)); + fs.writeFileSync(`${report}/${stable ? 'stable' : 'generated'}-viewer-validation.json`, JSON.stringify(results, null, 2)); + await context.close(); + } +} finally { await browser.close(); } diff --git a/tools/verify_instructors_v2_catalog.mjs b/tools/verify_instructors_v2_catalog.mjs new file mode 100644 index 0000000..24e36ba --- /dev/null +++ b/tools/verify_instructors_v2_catalog.mjs @@ -0,0 +1,53 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import assert from 'node:assert/strict'; +import { chromium } from '@playwright/test'; + +const report = path.resolve('reports/instructors-v2-20260914'); +const origin = 'http://127.0.0.1:8003'; +const values = new Map(fs.readFileSync('../账号.MD', 'utf8').split(/\r?\n/).flatMap(line => { + const match = line.match(/^\s*([^::]+)\s*[::]\s*(.*?)\s*$/); + return match ? [[match[1].trim(), match[2].trim()]] : []; +})); +const browser = await chromium.launch({ channel: 'msedge', headless: true, + args: ['--autoplay-policy=no-user-gesture-required', '--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] }); +try { + const context = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); + const page = await context.newPage(); + const errors = []; + page.on('pageerror', error => errors.push(error.message)); + await page.goto(origin + '/login'); + await page.locator('[data-role-code="admin"]').click(); + await page.locator('input[name="username"]').fill(values.get('账号') || values.get('用户名')); + await page.locator('input[name="password"]').fill(values.get('密码')); + await page.getByRole('button', { name: /^(?:登录|登录系统|正在验证身份)$/ }).click(); + await page.waitForURL(url => !url.pathname.startsWith('/login'), { timeout: 30000 }); + await page.goto(origin + '/assets/avatars'); + await page.locator('.avatar-card').first().waitFor(); + assert.equal(await page.locator('.avatar-card').count(), 3); + await page.waitForFunction(() => [...document.querySelectorAll('.avatar-cover img')].every(image => image.complete && image.naturalWidth > 0)); + await page.screenshot({ path: report + '/catalog-v2.png', fullPage: true }); + const cards = await page.locator('.avatar-card').allTextContents(); + assert.ok(cards.find(text => text.includes('教员2'))?.includes('默认数字人')); + const previews = []; + for (const number of [1, 2, 3]) { + const card = page.locator('.avatar-card').filter({ has: page.getByRole('heading', { name: `教员${number}`, exact: true }) }); + await card.getByRole('button', { name: '预览', exact: true }).click(); + const dialog = page.locator('.el-dialog:visible'); + await dialog.waitFor(); + const iframe = dialog.locator('iframe'); + await iframe.waitFor(); + const source = await iframe.getAttribute('src'); + assert.ok(source.includes(`a100000000000000000000000000000${number + 4}`)); + const frame = await (await iframe.elementHandle()).contentFrame(); + await frame.waitForFunction(() => window.DH_RUNTIME_READY, {}, { timeout: 120000 }); + await page.screenshot({ path: `${report}/catalog-preview-${number}.png`, fullPage: true }); + previews.push({ number, viewer: source, ready: true }); + await dialog.locator('.el-dialog__headerbtn').click(); + await dialog.waitFor({ state: 'hidden' }); + } + assert.deepEqual(errors, []); + const result = { count: 3, defaultInstructor: 2, previews, errors }; + fs.writeFileSync(`${report}/catalog-validation.json`, JSON.stringify(result, null, 2)); + console.log(JSON.stringify(result)); +} finally { await browser.close(); }