[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-96081":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":15,"stars7d":15,"stars30d":16,"stars90d":15,"forks30d":15,"starsTrendScore":15,"compositeScore":17,"rankGlobal":10,"rankLanguage":10,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":19,"hasPages":19,"topics":21,"createdAt":10,"pushedAt":10,"updatedAt":22,"readmeContent":23,"aiSummary":24,"trendingCount":15,"starSnapshotCount":15,"syncStatus":25,"lastSyncTime":26,"discoverSource":27},96081,"itsgiving","gazijarin\u002Fitsgiving","gazijarin","Express yourself in meetings (with memes, of course).","",null,"Python",709,81,1,0,488,59.74,"MIT License",false,"main",[],"2026-09-20 04:01:32","# It's giving...\n\n\u003Ctable>\n  \u003Ctr>\n    \u003Ctd>\u003Cimg src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002Faa5ed48f-70c2-4022-ac7f-87a4c3066a24\" width=\"100%\">\u003C\u002Ftd>\n    \u003Ctd>\u003Cimg src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002Fc764c5eb-c17a-47f2-b4b0-49153c8cb3c0\" width=\"100%\">\u003C\u002Ftd>\n  \u003C\u002Ftr>\n\u003C\u002Ftable>\n\nPull a face at your webcam. It works out *which* face, and drops the matching\nmeme over your head, scaled to follow you around the frame. You can extend and\nadd more memes to your heart's desire.\n\nPoint Zoom at its virtual camera and the whole call sees it.\n\n```bash\npython its_giving.py              # preview + virtual camera\npython its_giving.py --no-vcam    # preview only\n```\n\nFourteen reactions: time out, heart hands, hands over face, crashing out,\ndancing, nose pinch, flirty, hand up, tongue out, gasp, disgust, talking to the\nwall, side-eye, and spinning.\n\nThere's a second file, `its_giving_v2.py`, which is the same thing with the\nexpression thresholds calibrated to *your* face instead of to a number I\nguessed. \n\n---\n\n## Setup\n\n```bash\npython3.12 -m venv venv\nsource venv\u002Fbin\u002Factivate           # Windows: venv\\Scripts\\activate\npip install -r requirements.txt\n```\n\nPython 3.11 or 3.12. Three MediaPipe models (~15 MB) download themselves on\nfirst run.\n\n**Don't unpin the dependencies.** MediaPipe 0.10.30+ (including 1.0.x) ships\nmacOS wheels that abort the moment they open a detector, so it's held at\n0.10.21. That build needs NumPy 1.x, and OpenCV 5 needs NumPy 2 — and 0.10.21\nasks for an *unpinned* `opencv-contrib-python`, which quietly drags OpenCV 5 and\ntherefore NumPy 2 back in. That's why the OpenCV pins are in there even though\nnothing in the code cares. Unpin one and you have to unpin all three.\n\n---\n\n## Running it\n\n```bash\npython its_giving_v2.py --calibrate   # once, seven seconds\npython its_giving_v2.py\n```\n\n| key | does |\n|---|---|\n| `q` | quit |\n| `d` | toggle the HUD |\n| `c` | recalibrate |\n| `1`–`9` `0` `-` `=` `[` `]` | force a reaction on screen for 2 seconds |\n\n---\n\n## Using it in meetings\n\nThe virtual camera is on by default, and Zoom, Meet, Teams, Discord and OBS all\ntreat it as a normal webcam.\n\n**1. Install a backend** (once):\n\n| OS | do this |\n|---|---|\n| macOS | install [OBS Studio](https:\u002F\u002Fobsproject.com), open it once, quit it |\n| Windows | install OBS Studio, or run its virtual-camera installer |\n| Linux | `sudo apt install v4l2loopback-dkms` then `sudo modprobe v4l2loopback` |\n\n**2. Run it.** It prints the device it's publishing to:\n\n```\nVirtual camera: 'OBS Virtual Camera'  \u003C- pick this camera in Zoom \u002F Meet\n```\n\n**3. Pick that device** in your meeting app — Zoom: Settings → Video → Camera.\nMeet, Teams and Discord all have the same setting under Video.\n\n**Start this before your meeting app.** Most of them scan for cameras once at\nlaunch and won't notice a device that appeared later.\n\nA few things worth knowing before you turn it on in front of colleagues. It\nfires on its own. Everyone sees whatever it decides, so try it on a call with\nsomeone who likes you first. \n\n---\n\n## The reactions\n\n| pose | do this |\n|---|---|\n| `time_out` | referee's T — one hand flat on top, one vertical underneath |\n| `heart` | two hands, index tips together, thumb tips together |\n| `cover_nose` | both hands over your nose and mouth |\n| `crashing_out` | both hands to your head, mouth open |\n| `dance` | both hands up behind your head, mouth closed |\n| `nose_closed` | pinch your nose shut |\n| `flirty` | one index fingertip on your lips |\n| `hand_up` | one open palm up beside your head |\n| `tongue_out` | tongue out, mouth open |\n| `open_mouth` | jaw drops |\n| `disgusted` | scrunch your nose, or brows down and frown |\n| `talking_to_wall` | hands in frame, gesturing away |\n| `suspicious` | turn your head and squint |\n| `spin` | leave the frame entirely |\n\nAssets live in `assets\u002F`, named after the pose — `heart.jpeg`, `spin.gif`.\nSwap in your own by dropping a file with the right name; JPEG, PNG and animated\nGIF all work, alpha channels composite properly, and GIF frame timings are read\nfrom the file. A missing asset gets you a red placeholder, not a crash.\n\n---\n## Making it yours\n\n### Swapping a meme (~30 seconds)\n\nDrop a file in `assets\u002F` named after the pose — `heart.png` replaces the heart\nreaction. JPEG, PNG and animated GIF all work; transparency composites properly\nand GIF timings are read from the file. A `something_` prefix is ignored, so\n`2019_heart.jpeg` still counts. Press that pose's test key to check it sits\nright on your head.\n\n### Adding a pose\n\n**1.** Drop `assets\u002Fthinking.png` in place.\n\n**2.** Add the name to `POSES`. The list is checked top to bottom and the first\nmatch wins, so put it above anything it might be mistaken for.\n\n**3.** Add a branch to `decide()`:\n\n```python\n    for h in hands:\n        if near(h.palm, face.chin, 0.5) and not h.open:\n            return \"thinking\", d\n```\n\nYou have `face` (`.nose` `.chin` `.mouth` `.w` `.h`, `.b(\"jawOpen\")` for any\nblendshape), `hands` (`.palm` `.thumb` `.index`, `.open`), `body`\n(`.elbows_up`), `m` for expressions in sigma, and `near(a, b, k)` for \"within k\nface widths\" — which is what keeps it working at any distance from the camera.\n\n**4.** Give it an `ARM` count if it's twitchy, then tune it against the HUD.\nGetting it to fire is easy; the work is *stopping* doing it, doing everything\nnearby that might be confused with it, and watching the number stay low.\n\nIf your pose needs an expression channel that isn't measured yet, add it to `Z`,\n`FLOOR` and `measure()`, then put it in `draw_hud()` - you can't tune a number\nyou can't see.\n\n### Two gotchas\n\n`TEST_KEYS` has one key per pose, matched by position. Adding a fifteenth pose\nis fine (it just gets no test key), but removing one without removing a key\ncrashes when that key is pressed.\n\nIf a new pose never fires, check the `POSES` order before you touch any\nthreshold. Something earlier matching first is the usual cause, and lowering\n`Z` can't fix it.\n\n---\n\n## How it works\n\n```\ncamera frame\n     |\n 1.  MediaPipe    face: 478 landmarks + 52 blendshapes\n                  hands: 2 x 21 points\n                  body: shoulders, elbows, wrists\n     |\n 2.  Measures     face-relative geometry, tongue colour, hand speed\n     |\n 3.  Baseline     expressions re-expressed in sigma above YOUR neutral face\n     |\n 4.  decide()     one ordered pass -- first pose that matches wins\n     |\n 5.  arm \u002F hold   must persist N frames to fire, lingers 10 frames after\n     |\n  overlay         scaled to your face, alpha-composited, GIFs animated\n```\n\n### Normalising away the camera\n\nLandmarks come out as pixel coordinates, which depend on how far you're sitting\nfrom the lens. So nothing is compared in pixels: every distance is divided by\nthe width of your face box first. `near(hand.index, face.mouth, 0.22)` means\n\"within 22% of a face width\", and that means the same thing at 40 cm and at a\nmetre and a half. Hand speed gets the same treatment — face-widths per frame.\n\nHead turn is the nose's position between the two edges of your face: 0 facing\nthe camera, about 0.4 in full profile. Already a ratio, so already scale-free.\n\n### Why fixed thresholds don't work, and what to do instead\n\nThis is the interesting part.\n\nMediaPipe's blendshape values are **not zero when your face is at rest**, and\nthe offset is very personal. Some faces idle at `jawOpen` 0.02; others sit at\n0.19 doing nothing. If your mouth naturally turns up you can read `mouthSmile`\n0.3 while thinking about absolutely nothing.\n\nSo `jawOpen > 0.5` is not one threshold — it's a different threshold for every\nface that meets it. Too eager for some, physically unreachable for others. Any\nconstant you pick is a compromise between people, and no individual user is the\naverage of those people.\n\nv2 fixes this by measuring your own neutral first. Seven seconds of a bored face\nrecords the **mean and the standard deviation** of all 52 channels, and from\nthen on every expression is scored as:\n\n```\nz = (what the channel reads now - your resting mean) \u002F your resting wobble\n```\n\n\"6 sigma above your neutral jaw\" means the same thing on every face. \"Above 0.5\"\ndoesn't. Same two gestures, on a face that idles low and sits still versus one\nthat idles high and fidgets:\n\n```\n                        still face        loose face\nresting                 z  +0.1           z  +0.1        both quiet\ngasp                    z +38.7           z  +8.7        both fire\nnose scrunch            z +19.3           z  +5.5        both fire\n```\n\n---\n\n## What's where\n\n```\nits_giving_v2.py   the calibrated version — the one to use\nits_giving.py      v1: same poses, fixed thresholds\ncalibration.json   your neutral face (made by --calibrate, gitignored)\nrequirements.txt   pinned on purpose — read the comments before changing them\nassets\u002F            the memes, named after their pose\nmodels\u002F            MediaPipe .task files (downloaded on first run)\n```\n\nInside the file: `POSES` \u002F `Z` \u002F `FLOOR` \u002F `ARM` is the tuning block,\n`Baseline` and `run_calibration()` are the seven-second sit-still, `measure()`\nturns a face into sigma-above-your-neutral, and `decide()` is the ordered pose\nchecks.\n\nWant to change **what sets off what**? `decide()`.\nWant to change **how easily it goes off**? `Z`, `FLOOR` and `ARM`.\n","这是一款基于计算机视觉的实时视频会议表情增强工具，通过摄像头捕捉用户面部动作，自动识别14种预设表情并叠加对应动态表情包（meme），支持虚拟摄像头输出供Zoom、Teams、Meet等会议软件直接调用。项目采用MediaPipe进行轻量级人脸关键点检测与表情分类，内置自适应校准机制（v2版本），可适配不同用户的面部特征。适用于远程会议中非正式互动、团队破冰、线上教学趣味表达等需要增强视觉反馈的轻量协作场景。",2,"2026-09-09 02:30:11","CREATED_QUERY"]