選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 

122 行
5.2 KiB

  1. """Reproducible v2 instructor generation; reports contain no credentials."""
  2. from pathlib import Path
  3. import argparse
  4. import gzip
  5. import hashlib
  6. import json
  7. import re
  8. import shutil
  9. import requests
  10. ROOT = Path(__file__).resolve().parents[3]
  11. REPORT = ROOT / 'ai_person/ape2e/reports/instructors-v2-20260914'
  12. BUNDLES = ROOT / 'ai_person/ai_person_service/builtin_avatars'
  13. SERVICE = 'http://1.14.103.234:8123'
  14. API = 'http://127.0.0.1:8001'
  15. FILES = ('01.mp4', 'combined_data.json.gz', 'thumbnail.jpg', 'manifest.json')
  16. IDS = [f'a100000000000000000000000000000{i}' for i in (5, 6, 7)]
  17. HTTP = requests.Session()
  18. HTTP.trust_env = False
  19. def save(name, value):
  20. REPORT.mkdir(parents=True, exist_ok=True)
  21. (REPORT / name).write_text(json.dumps(value, ensure_ascii=False, indent=2), encoding='utf-8')
  22. def api_session():
  23. values = {}
  24. for line in (ROOT / 'ai_person/账号.MD').read_text(encoding='utf-8-sig').splitlines():
  25. match = re.match(r'^\s*([^::]+)\s*[::]\s*(.*?)\s*$', line)
  26. if match:
  27. values[match[1].strip()] = match[2].strip()
  28. session = requests.Session()
  29. session.trust_env = False
  30. response = session.post(API + '/api/auth/v1/auth/login', json={
  31. 'roleCode': 'admin', 'username': values.get('账号') or values.get('用户名'),
  32. 'password': values['密码'], 'rememberMe': False,
  33. }, timeout=30)
  34. response.raise_for_status()
  35. data = response.json()['data']
  36. token = data.get('accessToken') or data['tokens']['accessToken']
  37. session.headers['Authorization'] = 'Bearer ' + token
  38. return session
  39. def get_json(url):
  40. response = HTTP.get(url, timeout=30)
  41. response.raise_for_status()
  42. return response.json()
  43. def snapshot():
  44. if (REPORT / 'before-platform.json').exists():
  45. raise RuntimeError('Backup already exists; refusing to overwrite')
  46. session = api_session()
  47. response = session.get(API + '/api/v1/avatars', params={'pageSize': 200}, timeout=30)
  48. response.raise_for_status()
  49. save('before-platform.json', response.json())
  50. save('before-service.json', [get_json(SERVICE + '/api/v1/avatars/' + ident) for ident in IDS])
  51. for ident in IDS:
  52. shutil.copytree(BUNDLES / ident, REPORT / 'backup-local' / ident)
  53. print('Backed up platform catalog, service metadata and all three local bundles')
  54. def submit():
  55. if not (REPORT / 'before-platform.json').exists():
  56. raise RuntimeError('Run snapshot first')
  57. state = json.loads((REPORT / 'jobs.json').read_text(encoding='utf-8')) if (REPORT / 'jobs.json').exists() else []
  58. for number, ident in enumerate(IDS, 1):
  59. if any(item['stable_id'] == ident for item in state):
  60. continue
  61. source = ROOT / '数字人素材' / f'教员{number}_v2.mp4'
  62. with source.open('rb') as stream:
  63. response = HTTP.post(SERVICE + '/api/v1/avatars',
  64. files={'video': (source.name, stream, 'video/mp4')},
  65. data={'name': f'教员{number} v2 重训 20260914', 'resize': 'true', 'matting': 'off'}, timeout=180)
  66. response.raise_for_status()
  67. item = {'number': number, 'stable_id': ident, 'source': source.name,
  68. 'source_sha256': hashlib.sha256(source.read_bytes()).hexdigest(), **response.json()}
  69. state.append(item)
  70. save('jobs.json', state)
  71. print(json.dumps(item, ensure_ascii=False), flush=True)
  72. def status():
  73. values = []
  74. for item in json.loads((REPORT / 'jobs.json').read_text(encoding='utf-8')):
  75. job = get_json(SERVICE + item['job_url'])
  76. values.append({'number': item['number'], **job})
  77. save('job-status.json', values)
  78. print(json.dumps(values, ensure_ascii=False, indent=2))
  79. def download():
  80. values = []
  81. for item in json.loads((REPORT / 'jobs.json').read_text(encoding='utf-8')):
  82. avatar = get_json(SERVICE + '/api/v1/avatars/' + item['avatar_id'])
  83. if avatar['status'] != 'ready':
  84. raise RuntimeError(f"Instructor {item['number']} not ready")
  85. target = REPORT / 'generated' / item['stable_id'] / 'assets'
  86. target.mkdir(parents=True, exist_ok=True)
  87. for filename in FILES:
  88. response = HTTP.get(SERVICE + f"/api/v1/avatars/{item['avatar_id']}/assets/{filename}", timeout=120)
  89. response.raise_for_status()
  90. (target / filename).write_bytes(response.content)
  91. manifest = json.loads((target / 'manifest.json').read_text(encoding='utf-8'))
  92. for filename, length in manifest['files'].items():
  93. assert (target / filename).stat().st_size == length, filename
  94. with gzip.open(target / 'combined_data.json.gz', 'rt', encoding='utf-8') as stream:
  95. geometry = json.load(stream)
  96. values.append({'number': item['number'], 'stable_id': item['stable_id'], 'generated_id': item['avatar_id'],
  97. 'manifest': manifest, 'geometry_type': type(geometry).__name__,
  98. 'sha256': {name: hashlib.sha256((target / name).read_bytes()).hexdigest() for name in FILES}})
  99. save('generated-validation.json', values)
  100. print(json.dumps(values, ensure_ascii=False, indent=2))
  101. if __name__ == '__main__':
  102. parser = argparse.ArgumentParser()
  103. parser.add_argument('phase', choices=['snapshot', 'submit', 'status', 'download'])
  104. globals()[parser.parse_args().phase]()