Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 

122 lignes
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]()