Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 

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