Predict, detect, and photograph aircraft crossing the Sun or Moon in real time.
Capturing an aircraft silhouette against the solar or lunar disc is a rare and technically demanding shot. The geometry has to be nearly perfect, the timing is measured in fractions of a second, and the Seestar has to be tracking the Sun or Moon before the aircraft arrives. Zipcatcher automates every part of that problem.
It continuously monitors live flight traffic, projects each aircraft's path against the solar or lunar disc using high-precision ephemeris data, ranks candidates by how close they will come, and — when a high-probability transit is imminent — commands a Seestar telescope to start recording automatically. After the session it analyses the footage and produces an annotated composite image showing the aircraft's full track across the disc.
Key capabilities:
- Predicts transits up to 15 minutes ahead using real-time flight data from multiple concurrent sources
- Displays flight paths, altitudes, and probability on a live interactive map
- Controls a Seestar via direct TCP — no bridge app required
- Detects aircraft in the live RTSP stream using a frame-coherence computer-vision pipeline
- Runs a Convolutional Neural Network (CNN) transit classifier trained on real detection clips to separate genuine transits from false positives
- Produces annotated composite images from recorded video
- Sends Telegram alerts with flight details and predicted transit time
- Runs headlessly overnight on Mac, Linux, or Windows
- Prebuilt macOS/Windows installers: no Python required
- Running from source: Python 3.9+
iPhone Seestar app coexistence. The iPhone Seestar app can coexist with Zipcatcher, but Zipcatcher must win the
master_clirace. If motion commands are silently ignored, force-quit the app and let Zipcatcher reclaim master on next mode change. See docs/SEESTAR_APP_COEXISTENCE.md.
Flight data sources (all optional) — Zipcatcher can query multiple sources concurrently and merge the results. You can run the app and use Seestar control/capture without any flight API keys.
| Source | Key required? | Notes |
|---|---|---|
| OpenSky Network | No | Free, community ADS-B network |
| ADSB-One | No | Free, no authentication |
| adsb.lol | No | Free, no authentication |
| adsb.fi | No | Free open-data API |
| FlightAware AeroAPI | Yes — free personal tier | Adds airline/route metadata |
| ADS-B Exchange | Yes — ADSBX_API_KEY in .env |
Optional; skip if you don't have one |
| Local receiver (dump1090 / tar1090) | No | Point ADSB_LOCAL_URL at your own RTL-SDR receiver — optional |
You can run Zipcatcher with zero API keys. FlightAware and all other flight-data APIs are optional.
With no APIs configured, Zipcatcher still runs normally for Seestar control, recording, and gallery review; you just won't get live flight-based transit predictions.
Full setup instructions → SETUP.md Prebuilt macOS and Windows installers are available in GitHub Releases: https://github.com/Tailspin45/Zipcatcher/releases
Installer path (macOS/Windows): download and run the release installer — Python is bundled.
Source path (Python required):
make setup # create venv, install deps, create .env from .env.mock
source .venv/bin/activate
python app.py # open http://localhost:8000For headless operation with telescope control:
python3 transit_capture.py --latitude 51.5 --longitude -0.12 --target sun- Flight acquisition — queries enabled flight-data sources for aircraft inside the configured bounding box
- Position projection — extrapolates constant-velocity/heading tracks up to 15 minutes ahead
- Celestial tracking — computes Sun and Moon position with Skyfield + JPL DE421 ephemeris, including atmospheric refraction
- Angular separation — numerical optimisation finds the moment of closest approach on-sky
- Probability classification — ranks candidates using true angular separation, with azimuth differences cosine-weighted by target altitude to correct for geometric compression near the zenith
Transit candidates are graded by the predicted minimum center-to-center angular separation (d) between the aircraft and the Sun. The model uses the Sun’s angular radius (R \approx 0.266^\circ) and the detector’s disk-margin exclusion fraction (m), where (m=0.25) excludes the outer 25% of the solar radius. Thresholds are computed as (X=(1-m)R), (Y=R), and (Z=(2-m)R). A candidate is classified as high-likelihood if (d ≤ X), medium-likelihood if (X < d ≤ Y), and low-likelihood if (Y < d ≤ Z).
This grading system is operational rather than statistical. It reflects the detector’s preference for events that cross the trusted inner disk, distinguishes those from limb-region overlaps, and retains a broader outer band for larger nearby aircraft that may still produce a detectable transit. Broader monitoring thresholds may still be used to avoid missing candidates, but the grading bands provide a tighter, more meaningful basis for prioritization, logging, and review.
| Level | Separation | Meaning |
|---|---|---|
| 🟢 High | ≤ 2.0° | Direct transit very likely |
| 🟠 Medium | ≤ 4.0° | Near miss — worth recording |
| ⚪ Low | ≤ 12.0º | Possible distant transit |
When the telescope is connected, TransitDetector monitors the live RTSP stream continuously. The detector uses a multi-stage coherence pipeline:
- Score A — spike gate: detects a large, sudden per-frame anomaly consistent with a fast-moving silhouette
- Score B — consecutive gate: confirms the anomaly persists across multiple frames in a straight line
- Score B (MF) — matched-filter gate: cross-correlates the signal against a bank of transit templates covering different speeds and sizes
All three gates require score_a ≥ thresh_a before they can accumulate, preventing background noise from triggering a false detection. A hard centre-ratio gate suppresses detections where the brightness anomaly is not centred in the disc. After a confirmed detection the detector enforces a 6-second cooldown; suppressed triggers during cooldown are logged once (not once per frame).
TransitAnalyzer processes saved video to produce:
- A composite image blending every frame where the aircraft was on the disc over a clean reference background
- A sidecar JSON with frame-level signal data (scores, thresholds, triggered frames, peak time) used to annotate the scrubber in the gallery viewer
A lightweight CNN runs over detection clips to score each event as a genuine transit versus a false positive. Training data is extracted automatically from confirmed captures and stored in data/training/. The classifier can be retrained from the telescope panel when new labelled clips are available.
CNN retraining requires torch and onnx; these are now included in requirements.txt and installed by make setup.
In the telescope sidebar under Live Detection, the Detection Tester card provides rapid pipeline feedback without waiting for a real transit:
- Inject — inserts a synthetic transit and verifies the pipeline catches it
- Sweep — runs a size × speed matrix and reports which combinations are detected; highlights gaps in coverage
- Validate — runs the analyzer over all saved MP4s and reports events found per file
Two modes:
- Default — production thresholds; fewer false positives
- Sensitive — relaxed speed/travel gates, static filter disabled; better for slow or small objects
- Per-quadrant minimum altitude — set independent minimum angles for North, East, South, and West to mask out obstructions; flights are only ranked when the target is above your local horizon. Click the centre to reset all quadrants to zero
- Altitude bars — thin bars on each flight indicator show cruising altitude at a glance
- Route and track overlay — click any indicator for planned route ahead and historical track behind
- Azimuth arrows — on-map arrows point toward the Sun and Moon from your observer position
- Traffic density heatmap — toggle 🔥 to reveal accumulated flight corridors built up across polling cycles (persists in browser storage, capped at 2,000 points)
- Adjustable bounding box — drag corners to resize the flight-search area
Zipcatcher connects directly to the Seestar over TCP on port 4700.
- Auto-discovery — UDP broadcast scan on port 4720 finds the scope's IP automatically
- Smart reconnect — if the scope connection drops overnight, Zipcatcher waits until the target rises above the configured minimum altitude before attempting to reconnect, avoiding noisy retries in the middle of the night
- Solar and lunar modes — switches the scope to the correct imaging mode for the selected target
- Scenery mode — for manual positioning independent of the automated tracking
- Automatic recording — starts a configurable video pre-buffer before the predicted transit and stops after a post-buffer (defaults: 10 s each)
- GoTo — slew to any named location or entered alt/az coordinates
- Continuous nudge — fine-position the scope with hold-to-repeat joystick controls
- Autofocus — trigger a focus run from the panel
- Focus position control — focus in/out buttons move from the current position using the selected step size (10/50/100)
- Camera gain control — gain slider uses live camera gain and driver-reported min/max bounds when available
- Live preview — MJPEG stream from the scope displayed directly in the browser
The sidebar now shows Focus Position (absolute step value) when ALPACA focuser telemetry is available. Focus nudges are applied as step deltas from this current position, so the UI remains aligned with the hardware state instead of using a purely relative session counter.
If absolute focus readback is unavailable, Zipcatcher falls back to relative step behavior and marks the value as estimated.
Zipcatcher uses ALPACA for telescope motion plus camera/focuser telemetry where supported. During connection it discovers configured device numbers and connects telescope, camera, and focuser devices so focus position and camera gain can be read reliably.
- Focus — reads current focuser position from ALPACA and applies focus moves from that position.
- Gain — reads/writes camera gain via ALPACA when available (with JSON-RPC fallback).
- LP Filter / Dew Heater — currently controlled via Seestar JSON-RPC (
set_setting/pi_output_set2), not ALPACA.
The gallery (📁 Captured Files strip at the bottom of the scope panel) shows thumbnails of all recorded clips, detection frames, diff heatmaps, and analysed composites. Favorited files can be renamed directly from either the filmstrip or the expanded grid using the ✏️ button (rename is intentionally restricted to favorites).
Click any thumbnail to open the file viewer:
- Five-panel frame display — shows the current frame flanked by two frames on each side for context
- Frame scrubber — drag to seek; ◀◀ and ▶▶ buttons on either side of the frame counter play the clip in reverse or forward at native FPS; click again to stop
- 📌 Mark — first tap sets the In point, second tap sets the Out point; re-tapping replaces whichever endpoint is nearest to the current frame. The trim row above the scrubber shows the current In and Out times live
- ✂️ Trim — writes a new
trim_<filename>.mp4alongside the original (non-destructive; the original is never modified). After trimming a Replace Original button appears if you want to discard the source - Transit analysis — ☀️ Solar Transit / 🌙 Lunar Transit buttons run the post-capture analyzer and overlay signal data on the scrubber bar
- Composite — 🖼 Build Composite assembles marked frames into an annotated stack image
- Filmstrip shift-select — hold Shift to range-select multiple files for batch delete
A collapsible Data Sources panel in the sidebar shows per-source activity odometers (FlightAware, OpenSky, OpenAIP) with the last-updated timestamp and request count for the current session.
During a solar eclipse, Zipcatcher switches to timelapse mode: it captures frames at a configurable interval throughout the event and assembles them into a timelapse video. Aircraft transits detected during the eclipse are bookmarked as timestamped events within the recording.
Stabilisation (SOLAR_TIMELAPSE_STABILIZE=true) compensates for atmospheric jitter between frames.
Auto-resume (SOLAR_TIMELAPSE_AUTO_RESUME=true) restarts today's timelapse automatically after a reconnect or restart without requiring manual intervention.
Telegram alerts fire for medium and high-probability transits, including predicted transit time, flight callsign, altitude, aircraft type, and angular separation. Alerts can be muted per-session from the panel without restarting the server.
# Telescope + Telegram
python3 transit_capture.py --latitude 51.5 --longitude -0.12 --target sun
# Telegram only (no scope)
python3 transit_capture.py --latitude 51.5 --longitude -0.12 --target sun --manualBuild the signed DMG installer from the repo root — the build script handles everything:
cd electron && npx electron-builder --macThe output DMG is written to ../dist-electron/. Open it and drag Zipcatcher to Applications.
pip install -r requirements-windows.txt
python windows_monitor.pyTray icon: gray = idle · green = monitoring · orange = transit detected · red = error.
Copy .env.mock to .env and fill in the values relevant to your setup. Run python3 src/config_wizard.py --setup for interactive validation.
| Variable | Purpose |
|---|---|
AEROAPI_API_KEY |
FlightAware AeroAPI key (required if used) |
OBSERVER_LATITUDE / LONGITUDE / ELEVATION |
Your location |
LAT/LONG_LOWER_LEFT / UPPER_RIGHT |
Flight search bounding box |
TELEGRAM_BOT_TOKEN / CHAT_ID |
Telegram alerts (optional) |
ENABLE_SEESTAR / SEESTAR_HOST |
Telescope control (optional) |
SEESTAR_ALPACA_PORT |
ALPACA port (default: 32323) |
SEESTAR_ALPACA_TIMEOUT |
ALPACA request timeout in seconds |
SEESTAR_ALPACA_POLL_INTERVAL |
Telescope telemetry poll interval in seconds (2–120) |
SEESTAR_PRE_BUFFER / POST_BUFFER |
Recording window in seconds (default: 10) |
SOLAR_TIMELAPSE_AUTO_RESUME |
Auto-resume today's timelapse after reconnect (true/false) |
SOLAR_TIMELAPSE_INTERVAL |
Seconds between timelapse frames (default: 120) |
SOLAR_TIMELAPSE_STABILIZE |
Stabilize timelapse frames (true/false) |
MIN_TARGET_ALTITUDE |
Minimum target altitude for reconnect logic (default: 10°) |
GALLERY_AUTH_TOKEN |
Token required for gallery write operations |
| File | Contents |
|---|---|
| QUICKSTART.md | Fastest path to first detection |
| SETUP.md | Full setup — Telegram, Telescope, Windows |
| SECURITY.md | Securing the server on a LAN |
| ATTRIBUTION.md | Open-source library credits |
Zipcatcher binds to 0.0.0.0:8000 by default (LAN-accessible). Gallery write operations require a GALLERY_AUTH_TOKEN. See SECURITY.md before exposing the server beyond your local network.
Issues and pull requests welcome — especially transit photographs.
| Component | Project | Licence |
|---|---|---|
| Original idea and foundation code: David Bettancort Montebello | Flymoon | Public Domain |
| Interactive map | Leaflet 1.9.4 © Vladimir Agafonkin | BSD 2-Clause |
| Bounding-box drawing | Leaflet.Editable © Yoann Aubineau | MIT |
| Traffic heatmap | Leaflet.heat © Vladimir Agafonkin | MIT |
| Celestial calculations | Skyfield 1.49 © Brandon Rhodes | MIT |
| Web framework | Flask 3.0.3 © Pallets | BSD 3-Clause |
| Telegram alerts | python-telegram-bot 21.0 | LGPLv3 |
| JPL Ephemeris | DE421 — NASA/JPL | Public Domain |
| Free flight positions | OpenSky Network | Terms |
| Aviation chart overlay | OpenAIP | CC BY-NC-SA 4.0 |
See ATTRIBUTION.md for full licence texts.
MIT — see LICENSE
Pro tip: keep Flightradar24 open alongside Zipcatcher for extra situational awareness when a high-probability transit is approaching.








