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.
 
 
 
 

72 lignes
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()