# Moonraker Timelapse Docker Template This service watches a Klipper/Moonraker printer, captures a JPEG whenever the reported toolhead Z position increases by a configured layer height, and renders the captured frames to an H.264 MP4 after a successful print. Snapshot directories and videos are named with the print filename and UTC start date/time, for example `benchy_20260905-210500` and `benchy_20260905-210500.mp4`. ## Quick start 1. Copy `.env.example` to `.env`. 2. Set `MOONRAKER_URL` and `CAMERA_SNAPSHOT_URL` to the endpoints used by your printer and Mainsail. 3. Start the service: ```sh docker compose up --build -d ``` 4. Open the panel at `http://localhost:8080`. 5. Find rendered videos under `./timelapses`. Storage is organized with videos in the root timelapse folder and snapshots in a matching print folder: ```text timelapses/ ├── benchy_20260905-210500.mp4 └── benchy_20260905-210500/ ├── frame-000000.jpg └── frame-000001.jpg ``` The panel is also available when running locally with `./run.sh` at `http://localhost:8080`. Set `WEB_PORT` to use another port. It shows Moonraker connectivity, printer state, current Z height, the latest captured snapshot, and downloadable completed videos. The JSON endpoints are `/api/status` and `/api/videos`; the latest JPEG is available at `/api/snapshot`. To build the image directly instead of using Compose: ```sh ./build.sh ``` The default image is `moonraker-timelapse:latest`. Override it when publishing or testing a specific version: ```sh IMAGE_NAME=registry.example.com/you/timelapse IMAGE_TAG=0.1.0 ./build.sh ``` For local development without Docker, install Python 3 and `ffmpeg`, then run: ```sh ./run.sh ``` The script loads `.env` when present, defaults `OUTPUT_DIR` to `./timelapses`, and uses `127.0.0.1:7125` as the local Moonraker default. Set `MOONRAKER_URL` and `CAMERA_SNAPSHOT_URL` in `.env` when the printer or camera is on another host. The default camera URL is compatible with common MJPEG camera configurations using `?action=snapshot`. For a camera that exposes a different still-image endpoint, set `CAMERA_SNAPSHOT_URL` accordingly. ### Render settings Render behavior can be configured in `.env`: | Variable | Default | Purpose | | --- | --- | --- | | `VIDEO_FPS` | `12` | Playback frame rate. | | `VIDEO_DUPLICATE_END_FRAMES` | `0` | Copies of the final snapshot appended to hold the end of the video. | | `VIDEO_CODEC` | `libx264` | FFmpeg video encoder. | | `VIDEO_PRESET` | `medium` | Encoder speed/compression preset. | | `VIDEO_CRF` | `18` | H.264 quality; lower values produce larger, higher-quality files. | | `VIDEO_PIXEL_FORMAT` | `yuv420p` | Output pixel format for broad player compatibility. | For example, `VIDEO_FPS=24` and `VIDEO_DUPLICATE_END_FRAMES=48` creates a two-second hold on the final snapshot. ## How layer detection works The worker polls Moonraker's `print_stats` and `gcode_move` objects. When slicer layer metadata (`print_stats.info.current_layer`) is available, it captures once per reported layer. Otherwise it falls back to detecting Z increases using `MIN_LAYER_HEIGHT_MM`; the default poll interval is `0.5` seconds, but layer metadata is recommended because a slow poll can miss a short Z move. ## Development smoke test The Python worker uses only the standard library. Validate syntax locally with: ```sh python3 -m py_compile app.py docker compose config ``` The service intentionally treats Moonraker and camera failures as recoverable polling errors, so a printer or camera reboot does not stop the container.