The funscript format, as a spec
A funscript is a UTF-8 JSON file, ending in .funscript, that lists times and positions so a connected device can move in time with a video or audio file. This page writes the format down as a reference. It describes what scripts and players already do, and adds recommendations where practice varies; it does not invent a new format.
The words must, should and may carry their usual meaning in specifications. “Writer” means any tool or person producing a script; “player” means software that reads one and drives a device.
Pairing with media
Section titled “Pairing with media”- A script pairs with one media file and shares its name:
evening.mp4andevening.funscript. - Players should pair files in the same folder by name, ignoring the extensions.
- Extra axes use additional files (see multi-axis).
The file
Section titled “The file”The file is one JSON object. Only actions is required.
{ "version": "1.0", "inverted": false, "range": 100, "metadata": { "title": "Evening", "creator": "Your name", "license": "CC BY 4.0" }, "actions": [ { "at": 0, "pos": 10 }, { "at": 400, "pos": 90 }, { "at": 650, "pos": 30 }, { "at": 1000, "pos": 85 }, { "at": 1500, "pos": 10 } ]}| Field | Type | Required | Meaning |
|---|---|---|---|
actions |
array of action | yes | The points to play, in time order |
actions[].at |
number, ms, ≥ 0 | yes | Time from the start of the media |
actions[].pos |
number, 0–100 | yes | Position: 0 is the bottom of the stroke, 100 the top |
version |
string | no | Format version, usually "1.0" |
inverted |
boolean | no | When true, 0 is the top and 100 the bottom |
range |
number, 0–100 | no | The stroke range the script was written for |
metadata |
object | no | Editor details: title, creator, description, license, tags, duration and others |
Unknown fields must be ignored by players and should be preserved by editors.
Writers should
Section titled “Writers should”- write
atandposas whole numbers; - sort
actionsbyat, with no two points at the same time; - keep
poswithin 0–100; - leave
invertedfalse andrangeat 100 unless the script truly needs them.
Players should
Section titled “Players should”- accept unsorted actions by sorting them, and keep only the last point when two share a time;
- clip positions outside 0–100 to the nearest end;
- skip points that lack a number for
atorpos, and tell the user; - honour
invertedby mapping every position to100 - pos.
How a player reads it
Section titled “How a player reads it”Between two consecutive points the device moves in a straight line. Speed is distance over time: from position 10 at 0 ms to position 90 at 400 ms is 80 units in 0.4 s, or 200 units per second. Before the first point a player holds the first position; after the last point, the last.
Positions are relative to the range the user sets for the device. A player may let the user narrow that range (ANANGA calls this the user’s fit) and should map 0–100 onto it.
Device limits
Section titled “Device limits”Real devices have a top speed and need a moment between commands. ANANGA recommends these defaults, and uses them in its player and library:
| Limit | Default | What a player should do |
|---|---|---|
| Fastest move | 500 units per second | Shorten the move so it stays under the limit, keeping its timing |
| Smallest gap between points | 80 ms | Merge points that come closer, keeping the extremes |
Slower devices need lower limits. A player should not silently skip moves, because skipped strokes break sync with the media.
Multi-axis scripts
Section titled “Multi-axis scripts”Devices such as the OSR2 and SR6 also rotate and tilt. Each extra axis lives in its own file, named after the main script with the axis before .funscript. The names come from the TCode protocol these devices use.
| Axis | TCode | Motion | File name |
|---|---|---|---|
| Stroke | L0 | Up and down, the main axis | evening.funscript |
| Surge | L1 | Forward and back | evening.surge.funscript |
| Sway | L2 | Left and right | evening.sway.funscript |
| Twist | R0 | Rotation | evening.twist.funscript |
| Roll | R1 | Tilt left and right | evening.roll.funscript |
| Pitch | R2 | Tilt forward and back | evening.pitch.funscript |
Players should also accept the channel code as the name (evening.R0.funscript). TCode defines further channels for vibration (V0), valve (A0) and suction (A1) on devices that have them. Some editors store several axes in one file under an axes array of { "id": "R0", "actions": [...] }; players may read it.
Checking a script
Section titled “Checking a script”- In the browser: the checker on ananga.ai runs locally and uploads nothing.
- In code:
@ananga-ai/funscriptor the JSON Schema.
Sources
Section titled “Sources”- Funscript format, buttplug.io specification
- Funscript, funscript.wiki
- Multi-axis guide, FunscriptForge documentation