[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-96154":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":15,"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":21,"hasPages":19,"topics":22,"createdAt":10,"pushedAt":10,"updatedAt":23,"readmeContent":24,"aiSummary":25,"trendingCount":15,"starSnapshotCount":15,"syncStatus":26,"lastSyncTime":27,"discoverSource":28},96154,"whiteboard-animator","masihsultani\u002Fwhiteboard-animator","masihsultani","Turn a whiteboard-style image into a hand-drawn reveal video. The render engine behind Kinoslide.","",null,"Python",157,15,101,0,33,46.91,"MIT License",false,"main",true,[],"2026-09-20 04:01:32","# whiteboard-animator\n\n**Turn a whiteboard-style image into a hand-drawn animation. One command, CPU only.**\n\n![A sun, a leaf and a glucose molecule drawing themselves](examples\u002Fphotosynthesis\u002F01_light_to_glucose.gif)\n\n```bash\npip install whiteboard-animator\nwhiteboard-animate sketch.png --duration 8 -o sketch.mp4\n```\n\nGive it a finished whiteboard picture and it writes the text word by word, traces the outlines, fills the shapes with brush strokes, and draws branched line art one stroke at a time, the way a person at a whiteboard would. Add a narration file and the drawing paces itself to the voice.\n\nThis is the render engine behind the Whiteboard format at [Kinoslide](https:\u002F\u002Fkinoslide.ai), released so anyone can animate their own images.\n\n## Examples\n\nThe examples below show standalone illustrations and scenes from a photosynthesis lecture animated with the CLI.\n\n\u003Ctable>\n  \u003Ctr>\n    \u003Ctd>\u003Cimg src=\"examples\u002Fgyroscope.gif\" alt=\"A gyroscope with a wave drawn along its axis\" width=\"100%\">\u003C\u002Ftd>\n    \u003Ctd>\u003Cimg src=\"examples\u002Fequation.gif\" alt=\"The photosynthesis equation with icons\" width=\"100%\">\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n    \u003Ctd align=\"center\">\u003Ccode>examples\u002Fgyroscope.png\u003C\u002Fcode>\u003C\u002Ftd>\n    \u003Ctd align=\"center\">\u003Ccode>examples\u002Fequation.png\u003C\u002Fcode>\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n    \u003Ctd>\u003Cimg src=\"examples\u002Fphotosynthesis\u002F03_carbon_fixation.gif\" alt=\"Carbon fixation in the Calvin cycle\" width=\"100%\">\u003C\u002Ftd>\n    \u003Ctd>\u003Cimg src=\"examples\u002Fphotosynthesis\u002F04_energy_flow.gif\" alt=\"Energy flow from sunlight to glucose\" width=\"100%\">\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n    \u003Ctd align=\"center\">\u003Ccode>examples\u002Fphotosynthesis\u002F03_carbon_fixation.png\u003C\u002Fcode>\u003C\u002Ftd>\n    \u003Ctd align=\"center\">\u003Ccode>examples\u002Fphotosynthesis\u002F04_energy_flow.png\u003C\u002Fcode>\u003C\u002Ftd>\n  \u003C\u002Ftr>\n\u003C\u002Ftable>\n\n### Full videos made with this engine\n\nComplete narrated videos using this engine. Click to watch on YouTube.\n\n\u003Ctable>\n  \u003Ctr>\n    \u003Ctd>\u003Ca href=\"https:\u002F\u002Fyoutu.be\u002FKfqNNl99Tv4\">\u003Cimg src=\"https:\u002F\u002Fimg.youtube.com\u002Fvi\u002FKfqNNl99Tv4\u002Fmaxresdefault.jpg\" alt=\"The Yen Carry Trade Unwind\" width=\"100%\">\u003C\u002Fa>\u003C\u002Ftd>\n    \u003Ctd>\u003Ca href=\"https:\u002F\u002Fyoutu.be\u002F9Z_6x8KZwww\">\u003Cimg src=\"https:\u002F\u002Fimg.youtube.com\u002Fvi\u002F9Z_6x8KZwww\u002Fmaxresdefault.jpg\" alt=\"General Relativity in 2 minutes\" width=\"100%\">\u003C\u002Fa>\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n    \u003Ctd align=\"center\">\u003Ca href=\"https:\u002F\u002Fyoutu.be\u002FKfqNNl99Tv4\">The Yen Carry Trade Unwind\u003C\u002Fa>\u003C\u002Ftd>\n    \u003Ctd align=\"center\">\u003Ca href=\"https:\u002F\u002Fyoutu.be\u002F9Z_6x8KZwww\">General Relativity in 2 minutes\u003C\u002Fa>\u003C\u002Ftd>\n  \u003C\u002Ftr>\n\u003C\u002Ftable>\n\nTry one:\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Fmasihsultani\u002Fwhiteboard-animator\ncd whiteboard-animator && pip install -e .\nwhiteboard-animate examples\u002Fgyroscope.png --duration 8 -o gyroscope.mp4\n```\n\n## What it does with your image\n\n1. **Finds the ink.** Every connected blob of non-white pixels becomes a component. A bundled [CRAFT](https:\u002F\u002Fgithub.com\u002Fclovaai\u002FCRAFT-pytorch) text detector (ONNX, CPU) marks which components are text so words are written rather than traced like shapes.\n2. **Orders the components the way a hand would.** Containers before contents, shapes before their labels, text in reading order, small dots attached to the glyph they belong to.\n3. **Gives each one a time slot.** Slots scale with the square root of area so a big fill does not hog the timeline. With a region plan (below), slots use estimated narration pacing instead.\n4. **Assigns every pixel a reveal time.** Strokes follow their skeleton from a real endpoint, so a V starts at a tip and not the apex. Closed outlines get one travelling front. Fills get an outline pass, then either an angled sweep or bristled brush strokes depending on size. Line art with junctions is decomposed into sequential pen paths so an X or a grid does not grow from the middle outward.\n5. **Streams frames to ffmpeg.** Newly finished pixels are committed once, and only pixels currently fading are blended each frame.\n\nThere is no model at render time apart from the small text detector. No GPU, no training, no API keys.\n\n## Install\n\nPython 3.10+, with `ffmpeg` and `ffprobe` on your PATH.\n\nInstall FFmpeg first:\n\n- **macOS, using [Homebrew](https:\u002F\u002Fformulae.brew.sh\u002Fformula\u002Fffmpeg):** `brew install ffmpeg`\n- **Ubuntu\u002FDebian:** `sudo apt update && sudo apt install ffmpeg`\n- **Windows:** choose a Windows build linked from the [FFmpeg download page](https:\u002F\u002Fffmpeg.org\u002Fdownload.html), extract it, and add its `bin` directory (containing `ffmpeg.exe` and `ffprobe.exe`) to your user `Path`. Reopen your terminal.\n\nVerify both commands:\n\n```bash\nffmpeg -version\nffprobe -version\n```\n\nUse a Python 3.10+ interpreter; on systems where `python` points to an older version, use `python3` (or `py -3` on Windows). A virtual environment keeps the dependencies separate from other projects:\n\n```bash\npython -m venv .venv\n```\n\nActivate it with `source .venv\u002Fbin\u002Factivate` on macOS\u002FLinux or `.venv\\Scripts\\Activate.ps1` in Windows PowerShell, then install the package:\n\n```bash\npython -m pip install whiteboard-animator\n```\n\nThe package includes an approximately 83 MB text detection model, plus scientific Python dependencies. The model is installed with the package; rendering does not download it at runtime.\n\nRendering needs no API key. The one optional feature that does is `--detect-regions`, which asks Gemini to work out the drawing order from the image and narration. It needs the `gemini` extra and `GOOGLE_API_KEY`. You can skip it and write a region plan by hand (see below).\n\n```bash\npip install 'whiteboard-animator[gemini]'\n```\n\nFrom a checkout:\n\n```bash\npython -m pip install -e '.[dev]'\npython -m pytest\n```\n\nThe test suite includes the real bundled model and CLI-to-MP4 checks using FFmpeg. These integration tests are skipped if FFmpeg or ffprobe is missing locally; CI installs both and runs them explicitly. Use `python -m pytest -m \"not integration\"` for just the unit tests.\n\n### Troubleshooting\n\n- **`ffmpeg` or `ffprobe` not found:** install FFmpeg using the steps above and check that both version commands work in the same terminal where you run the animator. A Python package named `ffmpeg` does not install these executables.\n- **`whiteboard-animate` not found:** activate the environment where you installed the package, or run `python -m whiteboard_animator.cli` with the same arguments.\n- **Pip tries to compile OpenCV:** try `python -m pip install --only-binary=opencv-python-headless whiteboard-animator` to select a compatible prebuilt OpenCV wheel. If none is available, use a Python version and platform supported by OpenCV's wheels.\n- **Input or output errors:** use readable image\u002Faudio files, a positive duration for silent scenes, and an output ending in `.mp4` inside an existing writable directory. The CLI identifies missing files and invalid region JSON before rendering that scene.\n- **FFmpeg encoding fails:** read the diagnostic included in the error and check that `ffmpeg -encoders` lists `libx264`. Use `--verbose` to see rendering stages.\n\n## Usage\n\nFixed length, no audio:\n\n```bash\nwhiteboard-animate scene.png --duration 8 -o scene.mp4\n```\n\nWith narration. The drawing finishes inside the audio and the finished frame holds until the audio ends:\n\n```bash\nwhiteboard-animate scene.png --audio scene.wav -o scene.mp4\n```\n\nSeveral scenes, concatenated in order:\n\n```bash\nwhiteboard-animate a.png b.png c.png --audio a.wav b.wav c.wav -o lecture.mp4\n```\n\nQuality presets: `low` (20 fps, 500k), `medium` (24 fps, 1500k, default), `high` (24 fps, 3000k). Images whose longest side exceeds 1280px are downscaled.\n\n### Pace the drawing using narration text\n\nBy default the whole image draws over the first 70% of the scene in a heuristic order. A region plan specifies what the image contains, the drawing order, and the narration text associated with each part.\n\nWith a region plan, the engine allocates the first 75% of the audio to drawing windows. Each region's share blends its fraction of annotation characters (70% weight) and bounding-box area (30% weight). If all annotations are empty, it uses box area alone. A region can finish early and hold until the next window.\n\nThis estimates pacing from text; it does not analyze speech or align to spoken-word timestamps. Pauses, changes in speaking rate, and uneven phrase lengths can cause the drawing to lead or lag the voice. `--detect-regions` proposes regions and their order, but does not add audio alignment. For exact cue times, pass explicit `start` and `end` times in an `element_plan` to the lower-level `WhiteboardAnimator.render_to_file` API.\n\n```bash\nwhiteboard-animate scene.png --audio scene.wav --regions scene.regions.json -o scene.mp4\n```\n\nA plan is JSON. Boxes are normalized 0 to 1000 with the origin at the top left:\n\n```json\n{\n  \"idea\": \"Photosynthesis in one picture\",\n  \"regions\": [\n    {\n      \"label\": \"sun\",\n      \"role\": \"main_concept\",\n      \"object\": \"a sun with rays\",\n      \"reveal_order\": 1,\n      \"box\": {\"ymin\": 120, \"xmin\": 40, \"ymax\": 620, \"xmax\": 380},\n      \"expected_visual\": \"Sun with orange rays\",\n      \"annotation\": \"Photosynthesis starts with sunlight\",\n      \"reveal\": \"fill\"\n    }\n  ]\n}\n```\n\nWith the `gemini` extra and `GOOGLE_API_KEY` set, the plan can be detected from the image and the narration text:\n\n```bash\nwhiteboard-animate scene.png --audio scene.wav \\\n  --detect-regions --narration \"Photosynthesis starts with sunlight. ...\" \\\n  --save-regions -o scene.mp4\n```\n\n`--save-regions` writes the detected plan next to the output so you can edit it and re-render with `--regions`.\n\n## Python API\n\n```python\nfrom whiteboard_animator import Scene, SnippetRegionPlan, render_video\n\nplan = SnippetRegionPlan.model_validate_json(open(\"a.regions.json\").read())\nrender_video(\n    [Scene(\"a.png\", audio=\"a.wav\", region_plan=plan), Scene(\"b.png\", audio=\"b.wav\")],\n    \"lecture.mp4\",\n    quality=\"high\",\n)\n```\n\nLower level, `WhiteboardAnimator.render_to_file(img_array, draw_duration, total_duration, output_path, fps=24, bitrate=\"1500k\", element_plan=None)` takes an RGB numpy array and writes a silent MP4. The constructor exposes every tuning knob: fade length, fill detection thresholds, brush angle and width, the S-curve that decides when a fill uses brush strokes instead of a sweep, and the line-art decomposition thresholds.\n\n## What makes a good input\n\nThe engine expects ink on white. Pixels lighter than 240 gray are background, and near-white is snapped to white. Clean marker-style drawings with a handful of flat colors animate best. Photos, gradients, and textured or colored backgrounds will not.\n\n## Engine alone vs Kinoslide\n\n| | This repo | Kinoslide |\n|---|---|---|\n| Animate an image you already have | yes | yes |\n| Write the script from a PDF or prompt | | yes |\n| Generate the scene images | | yes |\n| Narration (Gemini and ElevenLabs voices) | bring your own audio | yes |\n| Narration pacing | estimated from a region plan | automatic |\n| Multiple scenes joined into one video | yes | yes |\n| Hosted rendering, sharing, editing | | yes |\n\n## Text detection model\n\n`whiteboard_animator\u002Fmodels\u002Fcraft.onnx` (83 MB) is an ONNX export of `craft_mlt_25k` from [CRAFT-pytorch](https:\u002F\u002Fgithub.com\u002Fclovaai\u002FCRAFT-pytorch) (MIT). Set `CRAFT_MODEL_PATH` to use a different file. If the model cannot be loaded the engine still runs and treats text as ordinary strokes.\n\n## Contributing\n\nIssues and pull requests are welcome. Things we would like help with:\n\n- Non-white backgrounds (dark boards, paper textures)\n- SVG input, tracing real vector paths instead of a raster skeleton\n- Better ordering for dense diagrams without a region plan\n- A hand or marker sprite that follows the pen position\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n","whiteboard-animator 是一个将静态白板风格图像自动转换为手绘式逐帧揭示动画的命令行工具。它基于 CPU 运行，支持文本逐词书写、轮廓逐线描摹、形状渐进填充及分支线条分步绘制，并可依据音频脚本自动同步绘制节奏。项目内置轻量 CRAFT 文本检测模型（ONNX 格式），能准确区分文字与图形组件，并按符合人类书写逻辑的顺序渲染。适用于教育视频制作、知识科普讲解、在线课程素材生成等需低成本快速产出白板动画的场景。",2,"2026-09-11 02:30:06","CREATED_QUERY"]