'use strict'; let face = null, hands = null, vision = null; // expressão (sorriso) para o contágio emocional: modelo opcional, roda 1 quadro a cada 4 let expressao = null, semExpressao = false, quadro = 0, sorriso = null; let lastTime = 0; async function prepare(c) { if (!vision) { const module = await import('/vendor/mediapipe/vision_bundle.mjs'); const files = await module.FilesetResolver.forVisionTasks('/vendor/mediapipe/wasm'); vision = {module,files}; } if (c.rosto && !face) face = await vision.module.FaceDetector.createFromOptions(vision.files, { baseOptions:{modelAssetPath:'/vendor/mediapipe/face.tflite',delegate:'CPU'},runningMode:'VIDEO',minDetectionConfidence:.6}); if (c.rosto && !expressao && !semExpressao) { try { expressao = await vision.module.FaceLandmarker.createFromOptions(vision.files, { baseOptions:{modelAssetPath:'/vendor/mediapipe/expressao.task',delegate:'CPU'},runningMode:'VIDEO',numFaces:1, outputFaceBlendshapes:true}); } catch (e) { semExpressao = true; } // sem o modelo: segue só com a posição do rosto } if (c.maos && !hands) hands = await vision.module.HandLandmarker.createFromOptions(vision.files, { baseOptions:{modelAssetPath:'/vendor/mediapipe/hands.task',delegate:'CPU'},runningMode:'VIDEO',numHands:2, minHandDetectionConfidence:.6,minHandPresenceConfidence:.6,minTrackingConfidence:.5}); } self.onmessage = async ({data}) => { const bitmap = data.bitmap; try { await prepare(data.config); if (!bitmap) { self.postMessage({tipo:'pronto'}); return; } const began = performance.now(); const stamp = Math.max(data.timestamp,lastTime+1); lastTime=stamp; const faces = data.config.rosto ? face.detectForVideo(bitmap,stamp).detections.map(d=>({ x:d.boundingBox.originX/bitmap.width,y:d.boundingBox.originY/bitmap.height, w:d.boundingBox.width/bitmap.width,h:d.boundingBox.height/bitmap.height, score:d.categories[0]?.score || 0,pontos:d.keypoints.map(p=>({x:p.x,y:p.y})) })) : []; if (!faces.length) sorriso = null; else if (expressao && quadro++ % 4 === 0) { const b = (expressao.detectForVideo(bitmap,stamp).faceBlendshapes || [])[0]; if (b) { const s = {}; for (const c of b.categories) s[c.categoryName] = c.score; sorriso = ((s.mouthSmileLeft || 0) + (s.mouthSmileRight || 0)) / 2; } } const result = data.config.maos ? hands.detectForVideo(bitmap,stamp) : {landmarks:[]}; self.postMessage({tipo:'resultado',faces,hands:result.landmarks.map(h=>h.map(p=>({x:p.x,y:p.y}))), sorriso,ms:performance.now()-began,timestamp:data.timestamp}); } catch(e) { self.postMessage({tipo:'erro',erro:e.message || String(e)}); } finally { if (bitmap) bitmap.close(); } };