assistanimate
v0.1.0
Published
Convert local MP4, AVI, and MOV videos to animated GIFs with FFmpeg.
Maintainers
Readme
assistanimate
A local video-to-GIF CLI for screen recordings, product demos, and documentation. Set the frame rate and dimensions, choose an output path, and get JSON results for scripts and coding agents.
The problem
A screen recording is useful for showing a feature or reproducing a bug, but sharing it often means opening a video editor or uploading it to a conversion website. Running FFmpeg directly gives you control, but even a basic conversion requires knowing its filters and output options.
assistanimate turns a local MP4, AVI, or MOV recording into an animated GIF with one command:
assistanimate convert recording.mp4 --fps 12 --resolution width:640 -o demo.gifThe conversion runs on your machine. Video files stay local, and the installed CLI needs only Node.js, FFmpeg, and ffprobe.
What you can do
Create demos for documentation
Turn a screen recording into a GIF for a README, issue, pull request, or product guide. Choose an exact filename with --output, or let the CLI create a timestamped name in your chosen directory.
assistanimate convert 'Feature walkthrough.mov' --fps 12 --resolution width:640 --output ./docs/images/walkthrough.gifControl dimensions and frame rate
Keep the source settings, choose a resolution preset, or fit the recording within a custom size. Width-only and height-only targets preserve the aspect ratio and avoid enlarging the selected dimension.
assistanimate convert recording.mp4 --fps 10 --resolution 800x600Automate conversions
Use --json to receive the output path, dimensions, frame rate, and file size. Commands return exit codes and structured errors that scripts and coding agents can handle without parsing terminal prose.
assistanimate convert recording.mp4 --output demo.gif --json
assistanimate probe demo.gif --jsonQuick start
Install FFmpeg first. On macOS with Homebrew:
brew install ffmpegInstall the CLI, check its dependencies, and convert a recording:
npm install -g assistanimate
assistanimate doctor
assistanimate convert recording.mp4 --fps 12 --resolution width:640 --output demo.gifReplace recording.mp4 with a real video path. This example creates demo.gif in the current directory, with a maximum width of 640 pixels and a target rate of 12 FPS.
Install
npm
npm install -g assistanimateBun
bun install -g assistanimateRun with npx
npx -y assistanimate convert recording.mp4 --fps 12 --resolution width:640All three options use the same Node executable. FFmpeg and ffprobe must be installed separately and available on PATH.
Requirements
| Requirement | Used for |
| --- | --- |
| Node.js 20 or newer | Runs the installed CLI. |
| ffmpeg | Converts video frames to GIF. |
| ffprobe | Reads input and output metadata. |
| Bun | Builds and tests a source checkout. Not required to run the npm package. |
Run assistanimate doctor to check both FFmpeg executables. If either is missing, the command explains which one and exits with status 1.
FFmpeg installation options are listed on the FFmpeg download page.
Features
- Converts MP4, AVI, and MOV files, including paths containing spaces.
- Preserves source FPS and dimensions when options are omitted.
- Accepts explicit frame rates, resolution presets, bounding boxes, and width-only or height-only targets.
- Creates parent directories for output paths.
- Generates sanitized, timestamped filenames when an exact output path is omitted.
- Refuses to replace an existing destination unless
--forceis supplied. - Validates the generated GIF before saving it to the destination.
- Cleans up partial output after conversion failures and interruptions.
- Provides JSON results, structured errors, and nonzero exit codes for automation.
- Runs locally without an API server, Python environment, account, or model provider.
CLI reference
assistanimate convert <video> [options] Convert one local video
assistanimate <video> [options] Shorthand for convert
assistanimate probe <file> [--json] Inspect FPS, dimensions, and size
assistanimate doctor [--json] Check FFmpeg and ffprobe
assistanimate --help Show usage
assistanimate --version Show the installed versionConversion options
| Option | Default | Behavior |
| --- | --- | --- |
| -o, --output <path> | Timestamped filename | Exact .gif output path. Parent directories are created. |
| --output-dir <dir> | Current directory | Destination for timestamped GIFs. |
| --output-name <name> | Source filename stem | Name to sanitize and place before the timestamp. |
| -f, --fps <number> | Source FPS | Finite numeric value greater than or equal to 1. |
| -r, --resolution <value> | original | Output scaling, as described below. |
| --force | Off | Replace an existing destination after conversion succeeds. |
| --json | Off | Print a compact JSON result. |
Use --output for an exact filename, or combine --output-dir and --output-name for a timestamped filename. These two approaches cannot be combined.
Unknown options, repeated options, and extra input files are rejected. probe and doctor accept --json, but reject conversion-only options.
Resolution values
| Value | Behavior |
| --- | --- |
| original | Preserve the source dimensions. |
| 1080p | Fit within 1920 × 1080. |
| 720p | Fit within 1280 × 720. |
| 480p | Fit within 854 × 480. |
| width:640 | Reduce width to at most 640 pixels. |
| height:480 | Reduce height to at most 480 pixels. |
| 800x600 | Fit within an 800 × 600 bounding box. |
Scaling preserves the aspect ratio. A bounding box sets maximum dimensions, so the resulting GIF may be smaller in one dimension. It does not crop or add padding.
Width-only and height-only targets do not upscale the selected dimension. FFmpeg rounds the other dimension to an even number. Presets and bounding boxes can upscale smaller recordings, matching the original converter.
Dimensions must be integers between 1 and 65535. Available memory and encoding time still limit practical output sizes.
Output naming
An exact output path creates the requested filename:
assistanimate convert recording.mp4 --output ./gifs/demo.gifWithout --output, the CLI uses the source filename and a local-time timestamp:
recording_YYYYMMDD_HHMMSS.gifChoose a different stem and directory with:
assistanimate convert recording.mp4 --output-dir ./gifs --output-name 'My demo'The result is a file such as gifs/My_demo_20260915_120000.gif. The CLI replaces spaces and unsupported filename characters with underscores and removes leading or trailing dots, underscores, and hyphens.
Relative paths are resolved from the directory where you run the command. The default output directory is your current directory, even when the input video is elsewhere.
Scripts and coding agents
Use an explicit output path and --json when another process needs to consume the result:
assistanimate convert recording.mp4 --output demo.gif --fps 12 --resolution width:640 --jsonA successful conversion writes one JSON object to stdout. For example:
{
"output_path": "/work/demos/demo.gif",
"filename": "demo.gif",
"final_fps": 12,
"encoded_fps": 12.5,
"resolution": { "width": 640, "height": 360 },
"size_bytes": 12717175
}| Field | Meaning |
| --- | --- |
| output_path | Absolute path to the completed GIF. |
| filename | Output filename, including .gif. |
| final_fps | Requested frame rate, or the source frame rate when omitted. |
| encoded_fps | Generated GIF's average frame rate reported by ffprobe. |
| resolution | Actual output width and height in pixels. |
| size_bytes | Completed file size in bytes. |
GIF frame delays use hundredths of a second, so final_fps and encoded_fps can differ. For example, an eight-hundredths delay corresponds to 12.5 FPS, even when the target was 12 FPS.
probe --json returns input_path, fps, width, height, and size_bytes. It can inspect generated GIFs and other local media that ffprobe can read, in addition to the three supported conversion formats.
doctor --json returns an ok boolean and a checks array with the availability and version or error for each executable. It writes this result to stdout even when a dependency is missing.
Errors and exit codes
Failed conversions and argument errors leave stdout empty and write diagnostics to stderr. With --json, the error has this shape:
{
"error": {
"code": "OUTPUT_EXISTS",
"message": "Output already exists: /work/demos/demo.gif. Use --force to replace it."
}
}Error codes are INVALID_INPUT, DEPENDENCY_MISSING, CONVERSION_FAILED, OUTPUT_EXISTS, and CANCELLED.
| Exit code | Meaning |
| --- | --- |
| 0 | Success. |
| 1 | Conversion, dependency, or output error. |
| 2 | Invalid arguments or input. |
| 130 | Conversion interrupted. |
Help and version output remain plain text, including when --json is supplied.
How conversion works
- Validate the options and local input file.
- Read the source FPS and dimensions with ffprobe.
- Apply the FPS and scale filters with FFmpeg, writing to a temporary directory beside the destination.
- Probe the generated GIF and read its size.
- Save the completed GIF to the requested path and remove the temporary directory.
Conversions time out after 300 seconds. Metadata probes and dependency checks time out after 20 seconds. An interruption stops the active encoder or probe before cleanup.
Without --force, saving uses a hard link so two conversions cannot silently overwrite the same destination. The output filesystem must support hard links. With --force, the CLI uses a same-filesystem rename to replace the destination after conversion succeeds. Failed conversions preserve the existing destination in both modes.
Current scope
The CLI converts one complete local video per invocation. It does not currently expose trimming, cropping, captions, target file sizes, palette controls, batch commands, or URL downloads. GIF output contains no audio and uses FFmpeg's default infinite looping.
Automation uses CLI commands and JSON output. This package does not include an MCP server, agent skill installer, or conversational interface.
FAQ
Why is my GIF still large?
Duration, dimensions, frame rate, and visual content all affect the result. assistanimate converts the full recording and has no target-size option. Try a lower frame rate and smaller dimensions:
assistanimate convert recording.mp4 --fps 8 --resolution width:480 --output smaller.gifUse a shorter source recording if you only need to show part of a workflow.
Why did I get 640 × 360 instead of 640 × 480?
Scaling preserves the source aspect ratio. A 16:9 recording scaled to a width of 640 becomes 640 × 360. Bounding-box options also preserve the aspect ratio.
Why did a preset enlarge my recording?
Presets fit the source into the chosen bounding box and can upscale. Use width:<n> or height:<n> to cap a dimension without enlarging it, or use original to keep the source dimensions.
Where did the GIF go?
The command prints its absolute output path. Without --output or --output-dir, it saves to the directory where you ran the command.
Can I overwrite an existing GIF?
Yes, with an explicit output path and --force:
assistanimate convert recording.mp4 --output demo.gif --forceThe existing file is replaced only after the new GIF has been generated and validated.
Can I use paths containing spaces or starting with a dash?
Quote paths containing spaces. Place a filename starting with - after --:
assistanimate convert 'My recording.mov' --output 'My demo.gif'
assistanimate convert --fps 12 -- -recording.mp4Why does doctor report a missing binary?
Both ffmpeg and ffprobe must be available to the process running the CLI. Install FFmpeg, ensure its executable directory is on PATH, and run assistanimate doctor again. A terminal, editor, and automation runner may inherit different environments.
Build from source
From the repository root:
bun install --frozen-lockfile
npm run build
node dist/cli.js doctor
node dist/cli.js convert recording.mp4 --fps 12 --resolution width:640You can also run the TypeScript entrypoint during development:
bun run dev convert recording.mp4 --fps 12 --resolution width:640To install a locally built package:
npm pack
npm install -g ./assistanimate-0.1.0.tgz
assistanimate --versionDevelopment and verification
| Command | Purpose |
| --- | --- |
| npm run typecheck | Check TypeScript source, scripts, and tests. |
| npm run lint | Run Oxlint with warnings treated as failures. |
| npm test | Run the unit tests. |
| npm run test:integration | Build and test the CLI with Node and real FFmpeg. |
| npm run test:all | Run all checks. |
| npm run prepack:inspect | Build and inspect the npm package contents. |
Integration tests generate MP4, AVI, and MOV fixtures in temporary directories and verify GIF dimensions and frame counts. They also cover output collisions, concurrent conversions, missing binaries, malformed inputs, encoder failures, and interruption cleanup. FFmpeg and ffprobe are required for these tests.
Bun bundles the TypeScript entrypoint into dist/cli.js. The npm package includes that executable, its package metadata, and this README. The development dependencies and test media are excluded. Installation does not run setup scripts or install FFmpeg.
Publishing
Verify the npm account and inspect the release package:
npm login --auth-type=web
npm whoami
npm publish --dry-runInspect the file list, then publish the release:
npm publish --access publicprepublishOnly runs all checks, and prepack rebuilds the executable. The repository's files list limits package contents to the distributable files. Keep the version aligned with the intended release; later releases need a new version number.
See npm's publishing guide for its account and authentication requirements.
Origins
assistanimate is a TypeScript port of the FFmpeg conversion services in Video-To-Animated-Gif-Converter. Its package and build conventions follow Assist Graph CLI.
| Original module | CLI implementation |
| --- | --- |
| api/services/converter.py | src/core/converter.ts, src/core/probe.ts, src/core/process.ts |
| api/services/resolution.py | src/core/resolution.ts |
| api/services/validation.py | src/core/validation.ts, src/core/output.ts |
The port preserves the original FPS and scale filters, metadata fallback, supported video extensions, filename sanitization, and timeout values. CLI additions include explicit output paths, overwrite protection, finite-number validation, dependency diagnostics, and JSON output.
Licence
UNLICENSED, as declared in package.json.
