Peraravatar
A lightweight, browser-only PNGTuber
Mouth movement driven by mic volume, auto blinking, custom expression switching, OBS integration, and more — all the essentials of a PNGTuber bundled in a single file. No setup required, you can try it straight from the browser.
- Mouth movement: switches images based on mic volume (number of levels, thresholds, and images per level all customizable)
- Auto animations: breathing, blinking, head tilt, side-to-side sway, looking around
- Sleepy state: eyes close after a period of silence
- Loud-sound reaction: reacts to sudden volume spikes
- Custom expressions: unlimited count, with per-expression motion and trigger configuration
- 8 motion types: bounce / lean back / tremble / sway / nod / shake head / hop / idle
- 3 trigger types: hotkey / volume threshold / audio pattern
- Audio pattern recognition: automatically detects audio similar to a recorded waveform (laughter, crying, etc.)
- Background options: transparent / checkered / solid color
- OBS mode: hides the settings panel automatically on startup
- Storage: auto-saved to localStorage + IndexedDB, with JSON export/import
The online version (GitHub Pages) works for the basics, but for stable streaming use, running it through a local HTTP server is recommended. Due to getUserMedia restrictions, opening as file:// directly will not work.
1. Download the source code
From the GitHub repository, click the green “Code” button → “Download ZIP” and extract to a folder of your choice.
2. Start the local HTTP server
Run one of the bundled launch scripts from the root of the extracted folder:
server-python.bat(requires Python)server-node.bat(requires Node.js)
Either one is fine. The script opens a command prompt that needs to stay open (closing it stops the server).
3. Open it in a browser
Visit http://localhost:8765/. The first time, a mic permission dialog will appear.
Required software
- Python (simple, recommended)
- Download the Windows installer from python.org
- Check “Add Python to PATH” during install
- Verify:
python --versionin the command prompt
- Node.js (alternative)
- Download the LTS installer from nodejs.org
- Default install sets up PATH
- Verify:
node --version,npx --version
- Open in a browser: either the “Try it” button above (GitHub Pages version) or your own local HTTP server at
http://localhost:8765/. The first time you visit, a mic permission dialog appears. - Set your avatar images in the “Images” tab: assign images to the normal / blink / look-away (L/R) / mouth-level slots.
- Tweak in the “Motion” tab: adjust breathing, sway, blink interval, mouth threshold, etc. via sliders or numeric input in real time.
- Export your settings as JSON: from the Settings tab, save
peraravatar-config.jsonsomewhere. - Integrate with OBS: point an OBS Browser Source at
http://localhost:8765/and load the exported JSON inside it (see OBS integration for details).
Note: settings are isolated per URL domain / browser instance (GitHub Pages, localhost, and the OBS Browser Source each have their own localStorage). See Data storage for details.
- Size: 600×900 to 1080×1620 (vertical 2:3 ratio)
- Format: transparent PNG
- Per image: 500 KB or less recommended
- Aligning position and scale across all images avoids jitter when switching
Images tab
- Fixed slots: normal expression, blink (eyes closed), look-away (left), look-away (right)
- Add unlimited custom expressions (each with its own motion and trigger settings)
- Register an image for each mouth-movement level
Motion tab
- Adjust breathing, sway, blink interval, mouth threshold, etc. via sliders or numeric input in real time
- Time-based values are shown in seconds
- Configure the silence duration that triggers the sleepy state
Settings tab
- Background options (transparent / checkered / solid color)
- OBS mode (panel hidden on startup)
- Reset all settings
- Export / import settings as JSON (for backups)
Each expression uses one motion type from the list below:
| Motion | Description |
|---|---|
| Bounce | Bouncy up-and-down motion |
| Lean back | Leans backward, then returns |
| Tremble | Small rapid tremor |
| Sway | Slow side-to-side sway |
| Nod | Nods |
| Shake head | Shakes side to side |
| Hop | Small hops |
| Idle | No motion |
Three trigger types:
- Hotkey: assign keys like Z / X / C / V (route through scene switching when integrated with OBS)
- Volume threshold: fires when volume stays above the configured value for a set duration
- Audio pattern: detects audio similar to a recorded voice waveform
Record a specific audio waveform pattern like “laughter” or “crying” in advance, and the expression fires automatically when you produce a similar sound. This is waveform matching, not speech recognition — it judges by similarity of volume changes and peak count, not the words being spoken.
Recording procedure
- Press the ”🎤 Add recording” button in expression settings
- After a 3-second countdown, make the target sound for 2.5 seconds
- The system records the waveform
- Afterwards, when a similar pattern is detected, the expression shows for 1 second
Characteristics and notes
- Sensitivity is adjustable in the range
0.3 to 0.95 - Because waveform sampling takes 2.5 seconds, detection has roughly a 2.5 second lag
- When integrating with OBS, add about +1500ms audio delay in “Advanced Audio Properties” to sync with viewers
Tips to mitigate misfires
- Assign audio patterns to only one expression (limits the impact of false positives)
- Register multiple patterns to the same expression (laughter variations, etc.)
- For moments you definitely want to trigger, lock via hotkey or scene switch
Operating the settings UI inside an OBS Browser Source is not the intended workflow — in-page keyboard shortcuts don’t fire there, so once you hide the settings panel you can’t bring it back. Configure everything in a regular browser, export the JSON, and use the OBS side only to load it.
-
Decide which URL the OBS Browser Source will load:
- Run your own local HTTP server (recommended): fetch the source code and run
server-python.batorserver-node.bat→http://localhost:8765/. See Setup for prerequisites. - GitHub Pages version: zero setup, but updates can ship without notice.
- Run your own local HTTP server (recommended): fetch the source code and run
-
Configure in a regular browser and export the JSON: open
http://localhost:8765/(or the GitHub Pages version), finish your image swaps, mouth thresholds, custom expressions, etc., and then “Export JSON” from the Settings tab (peraravatar-config.json). The OBS Browser Source is a separate instance with its own localStorage, so you’ll need to load this JSON on the OBS side later. -
Add the mic-enable flags to OBS’s launch options: Right-click the OBS shortcut (desktop, etc.) → Properties → edit “Target” as follows.
"C:\Program Files\obs-studio\bin\64bit\obs64.exe" --use-fake-ui-for-media-stream --autoplay-policy=no-user-gesture-required
If “Start in” is empty, set it to
C:\Program Files\obs-studio\bin\64bit. Without these flags, the Browser Source won’t be granted mic permission. -
Add a Browser Source in OBS: add a Browser source and configure:
- Set the URL to your local HTTP server (recommended) or the GitHub Pages version. If the settings panel doesn’t appear, append
?panel=1to the URL. - Width × Height: match your image aspect ratio (e.g. 600×900)
- “Local file”: uncheck
- Set the URL to your local HTTP server (recommended) or the GitHub Pages version. If the settings panel doesn’t appear, append
-
Import the JSON inside the OBS Browser Source: On first launch you’ll see the default avatar. Load the
peraravatar-config.jsonyou prepared earlier from the Settings tab. From then on it’s saved to the OBS side’s localStorage, so you don’t have to re-import every time.
If you added ?panel=1 to the URL, remove it now.
When you’re focused on the game and can’t easily trigger by voice, or when you want a specific expression for sure, locking via scene switching is reliable.
-
Create an expression-lock scene per expression
Example:In a scene named “Surprised”, set the Browser Source URL to
http://localhost:8765/?custom=z(z is the key assigned to the “Surprised” expression) -
In OBS hotkey settings, bind
F1,F2etc. to each scene switch -
During the stream, use the main scene (auto control) normally, and switch to a locked scene with
F1etc. when needed
Three expressions are pre-registered out of the box:
| Expression | Hotkey | Motion | Audio trigger |
|---|---|---|---|
| Surprised | Z | Lean back | Volume above 90 sustained |
| Laughing | X | Bounce | None |
| Crying | C | Tremble | None |
| Key | Action |
|---|---|
Z, X, C, V… | Custom expressions (auto-assigned in order, customizable) |
H | Toggle panel |
Y | Force look-away (random L/R) |
Escape | Cancel hotkey assignment |
| Parameter | Action |
|---|---|
?custom=z | Locks display to the expression assigned to key Z (panel auto-hidden) |
?custom=笑い | Specify by label name |
?custom=id | Specify by the expression’s internal ID |
?panel=0 | Force-hide the panel |
?panel=1 | Force-show the panel (overrides the “OBS mode (hide panel on startup)” setting and ?custom=…; use as recovery when the panel is stuck hidden in an OBS Browser Source) |
- Lightweight settings (thresholds, animation values, etc.): saved to the browser’s
localStorage - Image data: saved to
IndexedDB. In theory limited only by the browser’s quota (a sizeable fraction of free disk space on Chromium). In practice, a few hundred MB total is the safe range — beyond that, JSON export (which has to base64-encode and hold everything in memory) starts to risk the browser stalling or crashing. - Isolated storage: per URL domain / browser instance (GitHub Pages, localhost, and the OBS Browser Source each have their own localStorage + IndexedDB)
- Migration: use the Settings tab’s JSON export / import to move everything in one go. Images are embedded inline as base64 dataURLs inside the JSON, so a single JSON file fully reproduces your setup (the trade-off is file size — anywhere from a few MB to a few hundred MB depending on how many images you use)
- Note: nothing is saved in incognito / private windows
Re-exporting the JSON every time you tweak an image gets tedious. Check “Export images as paths” in the Settings tab, and the exported JSON will reference images as avatar/<original filename> instead of embedding dataURLs. If you copy the same filenames into the avatar/ folder (shipped empty in the repo root) ahead of time, any environment loading that JSON can fetch the images from that path — and from then on, overwriting files in avatar/ is all you need to swap them.
Steps
- Upload images through the file picker as usual.
- Copy the same files (using their original filenames) into the
avatar/folder. - Check “Export images as paths” in the Settings tab → click “Export JSON”.
- Load the JSON from the OBS Browser Source as usual.
Caveats
- You need to be able to drop files into the
avatar/folder yourself, so this is local-server only. The GitHub Pages version won’t work. - Images uploaded by older versions may not have their filename recorded — those fall back to dataURLs in the JSON. If the file size doesn’t drop when you toggle the checkbox, re-upload the affected images.
Mic permission not granted
- OBS Browser Source: launch flag
--use-fake-ui-for-media-streamis required - Regular browser: click the lock icon in the address bar → grant mic permission
- If the debug area shows
protocol=file:, you’ve opened it asfile://→ re-open via the GitHub Pages version or a local HTTP server (http://localhost)
IndexedDB quota exceeded
- Delete some existing images, or export a JSON backup → reset to defaults
- Reduce image file sizes (shrink PNGs, convert to JPEG, etc.)
Settings disappear
- Full browser cache clear, or incognito mode — nothing is persisted there
- For migration, use JSON (images included, transferable as a single file)
Avatar not showing
- Check that an image is registered in the “Normal” slot
- Check the debug panel for error messages
Avatar clipped or has whitespace in OBS
- Match the Browser Source width/height to your image’s aspect ratio
- Use OBS scene transform (Ctrl+E) to fit to screen
Settings panel is hidden in the OBS Browser Source and can’t be reopened
Keyboard input doesn’t reach the page from inside an OBS Browser Source (including via the “Interact” mode), so once “OBS mode (hide panel on startup)” is ON, normal UI access can’t bring the settings tab back.
Recovery:
- Append
?panel=1to the OBS Browser Source URL (use&panel=1if other params are already there) and reload.?panel=1force-shows the panel regardless of the “OBS mode (hide panel on startup)” setting or?custom=…. - Once you’ve turned “OBS mode (hide panel on startup)” OFF in the Settings tab, remove
?panel=1from the URL to return to normal operation.
When using this tool for streaming or personal projects, credit is not required. But if you do credit me, it really helps motivate further development.
Standard format
Peraravatar by Ichigyun https://gyungyun.dev/works/peraravatar
For stream descriptions (short)
Avatar: Peraravatar (gyungyun.dev) / @zkmy_kuro
See the credit notes on the top page for general guidance.