"""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]()