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.

Peraravatar demo. Avatar performing mouth movement, blinking, and auto animations.
Demo
× Features ×
  • 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
× Setup ×

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 --version in the command prompt
  • Node.js (alternative)
    • Download the LTS installer from nodejs.org
    • Default install sets up PATH
    • Verify: node --version, npx --version
× How to use ×
  1. 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.
  2. Set your avatar images in the “Images” tab: assign images to the normal / blink / look-away (L/R) / mouth-level slots.
  3. Tweak in the “Motion” tab: adjust breathing, sway, blink interval, mouth threshold, etc. via sliders or numeric input in real time.
  4. Export your settings as JSON: from the Settings tab, save peraravatar-config.json somewhere.
  5. 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.

× Recommended image specs ×
  • 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
× Tab descriptions ×

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)
× Custom expression motions ×

Each expression uses one motion type from the list below:

MotionDescription
BounceBouncy up-and-down motion
Lean backLeans backward, then returns
TrembleSmall rapid tremor
SwaySlow side-to-side sway
NodNods
Shake headShakes side to side
HopSmall hops
IdleNo 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
× Audio pattern detection ×

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

  1. Press the ”🎤 Add recording” button in expression settings
  2. After a 3-second countdown, make the target sound for 2.5 seconds
  3. The system records the waveform
  4. 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
× OBS integration ×
Heads up:

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.

  1. Decide which URL the OBS Browser Source will load:

    • Run your own local HTTP server (recommended): fetch the source code and run server-python.bat or server-node.bathttp://localhost:8765/. See Setup for prerequisites.
    • GitHub Pages version: zero setup, but updates can ship without notice.
  2. 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.

  3. 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.

  4. 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=1 to the URL.
    • Width × Height: match your image aspect ratio (e.g. 600×900)
    • “Local file”: uncheck
  5. Import the JSON inside the OBS Browser Source: On first launch you’ll see the default avatar. Load the peraravatar-config.json you 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.

× Locking expressions during streaming ×

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.

  1. 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)

  2. In OBS hotkey settings, bind F1, F2 etc. to each scene switch

  3. During the stream, use the main scene (auto control) normally, and switch to a locked scene with F1 etc. when needed

× Default custom expressions ×

Three expressions are pre-registered out of the box:

ExpressionHotkeyMotionAudio trigger
SurprisedZLean backVolume above 90 sustained
LaughingXBounceNone
CryingCTrembleNone
× Hotkeys ×
KeyAction
Z, X, C, V…Custom expressions (auto-assigned in order, customizable)
HToggle panel
YForce look-away (random L/R)
EscapeCancel hotkey assignment
× URL parameters ×
ParameterAction
?custom=zLocks display to the expression assigned to key Z (panel auto-hidden)
?custom=笑いSpecify by label name
?custom=idSpecify by the expression’s internal ID
?panel=0Force-hide the panel
?panel=1Force-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)
× Data storage ×
  • 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
× Path-based image export (advanced) ×

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

  1. Upload images through the file picker as usual.
  2. Copy the same files (using their original filenames) into the avatar/ folder.
  3. Check “Export images as paths” in the Settings tab → click “Export JSON”.
  4. 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.
× Troubleshooting ×

Mic permission not granted

  • OBS Browser Source: launch flag --use-fake-ui-for-media-stream is 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 as file:// → 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=1 to the OBS Browser Source URL (use &panel=1 if other params are already there) and reload. ?panel=1 force-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=1 from the URL to return to normal operation.
× Credit ×

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.

× Contact ×