The OSINT investigator's workbench — local-first media, geolocation proofs and case management.
Project description
Azimut
The OSINT investigator's workbench: one case, one folder, every tool. Local-first, so your media and your investigations stay on your machine.
Built for the open-source-investigation community: GeoConfirmed contributors, journalists, researchers. Reopen a case months later and everything is still there, in one plain folder you can zip, git, or share.
The name is the French word for azimuth, the compass bearing you sight along to fix a point on the map.
v0.2.0 — Proof Studio & Post outputs
| Tool | What it does |
|---|---|
| Media Library | Import local files or download by URL (X, Telegram, TikTok, YouTube, Instagram and more via yt-dlp, with a gallery-dl fallback for image-only posts). Each item gets a clean local file, metadata and a SHA-256. Multi-photo posts open a picker. |
| Inspect | A scratch workspace over any photo or video: frame adjustments, editable crop, sharpest-frame capture, hand-made collage with per-piece warp/scale/rotate, and auto-stitch to solve a panorama's layout for you. Nothing enters the case until you save. |
| Satellite | Coordinates or a place name become an imagery crop, with select-area capture, map rotation, measurement tools and reference-image overlays. Esri/OSM by default; add a Mapbox or Google key for more basemaps. |
| Proof Composer | Start a named proof from a reusable house style, select case panels with search, compose them in a grid or free layout, annotate with colored shapes/freehand/text, and export proof.png plus a re-editable spec. |
| Post Composer | Turn a proof into a prepared thread for X, Bluesky, or Mastodon: coordinates, plus code, attribution, target-specific character counts, media, and Markdown report output. |
Every tool works one-shot (a scratch session, no setup) or inside a case, a plain directory holding the whole investigation.
New in v0.2.0:
- Settings → Templates stores reusable proof house styles and post-thread skeletons. Proof templates cover background, spacing, text, footer, signature placement, and preferred colours; post templates cover the mention, tweet-1 fields, media tweet, and extra tweets.
- Proof Composer can start a named proof, search and filter case panels, apply a template, arrange panels horizontally or vertically, and draw freehand marks.
- Post Composer prepares the same thread for X, Bluesky, or Mastodon, groups selected media safely, and copies a compact Markdown report.
- Draft and template storage validates bounds and paths before writing, with atomic template saves and efficient multi-source link updates.
Install & run
pipx install azimut # isolated app install; plain `pip install azimut` also works
azimut # starts on http://127.0.0.1:8477 and opens a browser tab
Update with pipx upgrade azimut, remove with pipx uninstall azimut. Your
cases and settings live under ~/Azimut and are left untouched by both — an
upgrade never makes you redo an investigation, and an uninstall never deletes
your data (delete ~/Azimut by hand if you also want the data gone).
Azimut runs in a normal browser tab (Firefox/Chrome); there is no separate window. Closing the terminal it prints its URL into stops the app.
Ready-to-run binary (no Python)
Each release attaches a self-contained binary per OS. Download it from the Releases page and run it; it opens Azimut in your browser.
| OS | Asset |
|---|---|
| Windows | azimut-windows-x86_64.exe |
| macOS (Apple Silicon) | azimut-macos-arm64 |
| Linux | azimut-linux-x86_64 |
First run, the binaries are unsigned, so the OS warns before letting them open:
- macOS: right-click the file → Open → Open (Gatekeeper only
offers "Open" from the context menu for unidentified developers), or run
xattr -d com.apple.quarantine ./azimut-macos-*once. - Windows: SmartScreen shows "Windows protected your PC"; click More info → Run anyway.
- Linux: mark it executable with
chmod +x azimut-linux-x86_64.
To update, Settings → About → Check for updates tells you when a newer
release is out and links the download; replace the old file with the new one.
To uninstall, delete the file. Either way ~/Azimut stays put, so your cases
open unchanged in the new version.
The downloadable binaries bundle a static ffmpeg (and ffprobe), so video
thumbnails, frame scans, video enhancement, and downloads that merge separate
audio+video streams work out of the box. If you pip install azimut instead,
put ffmpeg on your PATH for those features — everything else works without it.
The bundled ffmpeg is redistributed under its own license; see
ffmpeg.org/legal.html.
From source:
git clone https://github.com/OsintMeThat/azimut && cd azimut
python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
cd frontend && npm install && npm run build && cd ..
.venv/bin/azimut
Frontend development (hot reload, proxied API):
.venv/bin/azimut --no-browser & # backend on :8477
cd frontend && npm run dev # UI on :5173
Checks (CI runs these on every push):
uv run ruff check src tests # lint
uv run mypy # type-check the backend
cd frontend && npm run check # svelte-check (blocks on errors)
Capture extension (optional)
A browser extension (Chrome/Edge and Firefox) captures external map sites straight into a case: Google Maps & Earth, Bing, Yandex, OSM, Apple Maps, Zoom Earth, Copernicus Browser and Satellites.pro, one screenshot per click with coordinates parsed from the URL. It also powers the Capture button on the Google (Maps JS) basemap. Install it from Settings → Capture extension (download the zip, load unpacked, pair with the token shown there); full instructions in extension/README.md.
Building & releasing
The Svelte frontend builds into src/azimut/static/ (git-ignored) and is
bundled into the Python wheel via hatchling artifacts. So npm run build
must run before building the package, or the shipped UI is stale.
cd frontend && npm run build && cd .. # refresh the bundled UI
uv sync --frozen # the exact deps CI tested
pipx run build # wheel + sdist (UI included)
uv run pyinstaller packaging/azimut.spec # optional: standalone binary
Dependencies
pyproject.toml declares ranges (the contract for pip install azimut
users); uv.lock pins the exact set, and is what CI and the release
builds install. The wheel only declares its dependencies, but the binary
contains them, so building it outside the lock ships whatever the resolver
happened to pick that day.
uv lock --check # CI does this: is the lock in sync with pyproject?
uv lock --upgrade # refresh everything, then run the suite
uv lock --upgrade-package yt-dlp # refresh one
Raising an upper bound is a deliberate act: bump it in pyproject.toml, run
uv lock, and make sure the suite passes before it lands. The weekly
"latest deps" CI job re-resolves past the lock, so upstream breakage shows up
as a red run of ours rather than a broken install for someone else.
yt-dlp and gallery-dl are deliberately unbounded: they track sites that change, so pinning them just schedules a breakage. They can also be updated from inside the app (Settings → About → Downloaders), which is what keeps a months-old binary working.
Releases are automated: push a semver tag and GitHub Actions
(.github/workflows/release.yml) builds the
wheel + Windows/Linux/macOS binaries, attaches them to a GitHub release, and
publishes to PyPI. Don't publish by hand.
git tag v0.2.0 && git push origin v0.2.0
One-time setup: register the repo as a
PyPI Trusted Publisher for the
azimut project (no API token to store).
Principles
- Local-first, privacy-first. No account, no telemetry, no upload; the
server binds to
127.0.0.1only, and Azimut never posts anywhere on your behalf. - The case is the product. Plain JSON + media files, versionable, portable.
- One tab = one tool, useful in 30 seconds.
- Orchestrator, not replacer. Integrate specialized services rather than cloning them.
- Tools emit facts; the analyst decides. No automated "magic button".
- Honest output. Every artifact records how it was produced.
- Free and open source. No paid key is ever required; bring your own for more basemaps.
Full spec: docs/SPEC.md.
License
AGPL-3.0-only: free and open source; hosted or modified versions must share their source.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file azimut-0.2.0.tar.gz.
File metadata
- Download URL: azimut-0.2.0.tar.gz
- Upload date:
- Size: 1.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
51720ebb3dad1f305deadada3696bafae88cc61292f63d0b2ec828fbd72649fb
|
|
| MD5 |
5df2fcc63675d92215deb8a364f10b5e
|
|
| BLAKE2b-256 |
2dc571139ef452e25ac3d96d1f7998a9aa9b3e958ca491c99de58449c3de49de
|
Provenance
The following attestation bundles were made for azimut-0.2.0.tar.gz:
Publisher:
release.yml on OsintMeThat/azimut
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
azimut-0.2.0.tar.gz -
Subject digest:
51720ebb3dad1f305deadada3696bafae88cc61292f63d0b2ec828fbd72649fb - Sigstore transparency entry: 2208560422
- Sigstore integration time:
-
Permalink:
OsintMeThat/azimut@924e757767f3f2d6d2386f91923bfdc4b7f874fa -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/OsintMeThat
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@924e757767f3f2d6d2386f91923bfdc4b7f874fa -
Trigger Event:
push
-
Statement type:
File details
Details for the file azimut-0.2.0-py3-none-any.whl.
File metadata
- Download URL: azimut-0.2.0-py3-none-any.whl
- Upload date:
- Size: 582.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
626aa593a3672bb64dcafac674bef028f7451387c5ec62026e64e35e718293b3
|
|
| MD5 |
5b9f60ee9ab40bf6ac2f535523648e05
|
|
| BLAKE2b-256 |
c013d9ec38d1625a4eadd826ac461ee888f7bbd82f943ccd05308169a0124960
|
Provenance
The following attestation bundles were made for azimut-0.2.0-py3-none-any.whl:
Publisher:
release.yml on OsintMeThat/azimut
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
azimut-0.2.0-py3-none-any.whl -
Subject digest:
626aa593a3672bb64dcafac674bef028f7451387c5ec62026e64e35e718293b3 - Sigstore transparency entry: 2208560777
- Sigstore integration time:
-
Permalink:
OsintMeThat/azimut@924e757767f3f2d6d2386f91923bfdc4b7f874fa -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/OsintMeThat
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@924e757767f3f2d6d2386f91923bfdc4b7f874fa -
Trigger Event:
push
-
Statement type: