Spaces:
Paused
Paused
| #!/usr/bin/env python3 | |
| """ | |
| Patch @met4citizen/talkinghead/dist/talkinghead.mjs to fix: | |
| "Cannot read properties of undefined (reading 'value')" | |
| Root cause: this.mtRandomized contains morph keys not in this.mtAvatar for | |
| the loaded GLB model. Line 2619: j = this.mtAvatar[i]; if (!j.needsUpdate) crashes. | |
| """ | |
| import os, sys, re | |
| PKG_DIR = os.path.join(os.getcwd(), 'node_modules', '@met4citizen', 'talkinghead') | |
| dist_files = ['dist/talkinghead.mjs', 'dist/talkinghead.js'] | |
| target_path = None | |
| for name in dist_files: | |
| p = os.path.join(PKG_DIR, name) | |
| if os.path.exists(p): | |
| target_path = p | |
| break | |
| if not target_path: | |
| print('ERROR: talkinghead file not found in ' + PKG_DIR) | |
| sys.exit(1) | |
| print('Patching: ' + target_path) | |
| with open(target_path, 'r') as f: | |
| code = f.read() | |
| if '__VT_PATCHED__' in code: | |
| print('Already patched, skipping.') | |
| sys.exit(0) | |
| # Fix 1: Null check for mtRandomized loop (line ~2619) | |
| old1 = "j = this.mtAvatar[i];\n if ( !j.needsUpdate ) {" | |
| new1 = "j = this.mtAvatar[i];\n if ( !j ) continue;\n __VT_PATCHED__=1;\n if ( !j.needsUpdate ) {" | |
| if old1 in code: | |
| code = code.replace(old1, new1) | |
| print('OK Fix1: added null check for mtRandomized loop') | |
| else: | |
| code = re.sub( | |
| r'(j = this\.mtAvatar\[i\];)\s*\n\s*if\s*\(\s*!j\.needsUpdate', | |
| r'\1\n if ( !j ) continue;\n __VT_PATCHED__=1;\n if ( !j.needsUpdate', | |
| code | |
| ) | |
| # Fix 2: Guard direct .value accesses on specific keys | |
| for key in ['bodyRotateY', 'eyeLookInLeft', 'eyeLookOutLeft', 'eyesLookDown', | |
| 'browDownLeft', 'browDownRight', 'eyeBlinkLeft', 'eyeBlinkRight', | |
| 'eyesLookUp', 'bodyRotateX', 'bodyRotateZ', 'headRotateX', | |
| 'headRotateY', 'headRotateZ']: | |
| full_access = 'this.mtAvatar[' + repr(key) + '].value' | |
| safe_access = "(this.mtAvatar[" + repr(key) + "] || {}).value || 0" | |
| code = code.replace('this.mtAvatar[' + repr(key) + '].value', safe_access) | |
| if '(this.mtAvatar[' + repr('bodyRotateY') + '] || {}).value' in code: | |
| print('OK Fix2: guarded direct .value accesses on known keys') | |
| # Fix 3: Wrap animate method in try/catch as safety net | |
| match = re.search( | |
| r'prototype\.animate\s*=\s*function\s*\(\s*t\s*\)\s*\{', | |
| code | |
| ) | |
| if match: | |
| brace_open = code.index('{', match.end() - 1) | |
| depth = 1 | |
| pos = brace_open + 1 | |
| while depth > 0 and pos < len(code): | |
| ch = code[pos] | |
| if ch == '{': depth += 1 | |
| elif ch == '}': depth -= 1 | |
| pos += 1 | |
| brace_close = pos - 1 | |
| indent = ' ' | |
| body = code[brace_open + 1:brace_close] | |
| if '__VT_CATCH__' not in body: | |
| new_body = ( | |
| indent + '__VT_CATCH__=1;\n' | |
| + indent + 'try {\n' | |
| + body + '\n' | |
| + indent + '} catch (e) {\n' | |
| + indent + indent + 'if (!(e && e.message && e.message.includes("Cannot read properties of undefined"))) {\n' | |
| + indent + indent + indent + 'console.warn("[patch_talkinghead] animate:", e?.message || e);\n' | |
| + indent + indent + indent + 'throw e;\n' | |
| + indent + indent + '}\n' | |
| + indent + '}\n' | |
| ) | |
| code = code[:brace_open + 1] + new_body + code[brace_close + 1:] | |
| print('OK Fix3: wrapped animate() in try/catch') | |
| else: | |
| print('WARN Fix3: could not wrap animate()') | |
| with open(target_path, 'w') as f: | |
| f.write(code) | |
| print('Done. Wrote ' + str(len(code)) + ' bytes') | |