You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

72 rivejä
4.3 KiB

  1. """Render one v2 sample with the existing server renderer and the old clip's audio."""
  2. from pathlib import Path
  3. import hashlib
  4. import json
  5. import subprocess
  6. import sys
  7. import threading
  8. import requests
  9. ROOT = Path(__file__).resolve().parents[3]
  10. API_ROOT = ROOT / 'ai_person/ai_person_api'
  11. sys.path.insert(0, str(API_ROOT))
  12. from app.config import get_settings
  13. from app.schemas.open_platform import GenerationCommand
  14. from app.services.open_generation_worker import OpenGenerationWorker
  15. def digest(path):
  16. return hashlib.sha256(path.read_bytes()).hexdigest()
  17. def main():
  18. original = ROOT / 'unreal_tran/ute2e/reports/bridge-digital-video-20260914/TASK-VIRTUAL-005-step-01.mp4'
  19. report = ROOT / 'unreal_tran/ute2e/reports/bridge-digital-video-v2-sample-20260915'
  20. report.mkdir(parents=True, exist_ok=True)
  21. output = report / 'TASK-VIRTUAL-005-step-01-v2.mp4'
  22. if output.exists():
  23. raise RuntimeError('Sample already exists; refusing to overwrite it')
  24. audio, captured = report / 'original-audio.wav', report / 'capture.webm'
  25. settings = get_settings()
  26. avatar = 'a1000000000000000000000000000005'
  27. session = requests.Session()
  28. session.trust_env = False
  29. manifest_response = session.get(str(settings.service_base_url).rstrip('/') + f'/api/v1/avatars/{avatar}/assets/manifest.json', timeout=30)
  30. manifest_response.raise_for_status()
  31. expected = json.loads((ROOT / 'ai_person/ai_person_service/builtin_avatars/instructors-v2.json').read_text('utf-8'))['instructors'][0]
  32. assert hashlib.sha256(manifest_response.content).hexdigest() == expected['bundle_sha256']['manifest.json'], 'Live avatar is not the validated v2 bundle'
  33. original_digest = digest(original)
  34. command = GenerationCommand(title='005:液压泵应急切换 · 第1步 · v2素材对比',
  35. text='第1步,确定故障液压泵。根据故障现象确定发生故障的液压泵。',
  36. avatarId='55', voiceId='49', speed=.85, width=480, height=640, subtitles=False)
  37. subprocess.run([settings.ffmpeg_binary, '-hide_banner', '-loglevel', 'error', '-y', '-i', str(original),
  38. '-vn', '-ac', '1', '-ar', '16000', '-c:a', 'pcm_s16le', str(audio)], check=True)
  39. print('Rendering validated instructor 1 v2 with the original narration audio', flush=True)
  40. worker = OpenGenerationWorker(None, settings, None, None)
  41. worker.capture(command, avatar, audio, None, captured, threading.Event())
  42. subprocess.run([settings.ffmpeg_binary, '-hide_banner', '-loglevel', 'error', '-y', '-i', str(captured),
  43. '-r', '25', '-c:v', 'libx264', '-preset', 'veryfast', '-crf', '20', '-pix_fmt', 'yuv420p',
  44. '-c:a', 'aac', '-b:a', '128k', '-movflags', '+faststart', str(output)], check=True)
  45. probe = subprocess.run([settings.ffprobe_binary, '-v', 'error', '-show_format', '-show_streams', '-of', 'json', str(output)], check=True, capture_output=True, text=True)
  46. metadata = json.loads(probe.stdout)
  47. assert any(stream['codec_type'] == 'video' and stream['codec_name'] == 'h264' for stream in metadata['streams'])
  48. assert any(stream['codec_type'] == 'audio' for stream in metadata['streams'])
  49. subprocess.run([settings.ffmpeg_binary, '-hide_banner', '-loglevel', 'error', '-i', str(output), '-f', 'null', '-'], check=True)
  50. for second in (.8, 2.0, 3.2):
  51. subprocess.run([settings.ffmpeg_binary, '-hide_banner', '-loglevel', 'error', '-y', '-ss', str(second),
  52. '-i', str(output), '-frames:v', '1', str(report / f'frame-{second}.jpg')], check=True)
  53. assert digest(original) == original_digest, 'Original comparison clip changed'
  54. result = {'avatarName': '教员1', 'materialVersion': 'v2', 'providerAvatarId': avatar,
  55. 'text': command.text, 'audioSource': str(original), 'audioSha256': digest(audio),
  56. 'originalSha256': original_digest, 'outputSha256': digest(output), 'output': str(output),
  57. 'renderEngine': 'OpenGenerationWorker.capture / open_render_capture.js (unchanged)',
  58. 'liveAvatarManifest': manifest_response.json(), 'metadata': metadata,
  59. 'originalPreserved': True, 'publishedTaskBindingsChanged': False}
  60. (report / 'result.json').write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding='utf-8')
  61. print(json.dumps({'output': str(output), 'duration': metadata['format']['duration'], 'originalPreserved': True}), flush=True)
  62. if __name__ == '__main__':
  63. main()