--progress jsonl — machine-readable progress events

map, photomap, flightmap, embed, check, doctor, convert, validate, verify-sun, and fetch-log accept --progress jsonl. In this mode a command writes one JSON object per line to stdout and nothing else — human/informational output is suppressed, and warnings and log messages go to stderr. A non-zero exit code always means the run failed; for success, read the result event's ok field (see the embed nuance under the terminal rule below). Lines are pure ASCII (non-ASCII text is JSON-escaped and round-trips intact through any JSON parser), so the stream is safe to read under any pipe encoding. This is the interface for frontends and scripts:

dji-embed flightmap D:\Drone\trip --progress jsonl
{"v":1,"event":"start","command":"flightmap"}
{"v":1,"event":"progress","current":1,"total":3,"item":"DJI_0001"}
{"v":1,"event":"progress","current":2,"total":3,"item":"DJI_0002"}
{"v":1,"event":"progress","current":3,"total":3,"item":"movie"}
{"v":1,"event":"warning","message":"No GPS telemetry","item":"movie"}
{"v":1,"event":"result","ok":true,"outputs":["D:/Drone/trip/flightmap.html"],"summary":{"flights":2,"skipped":1,"joined_files":0}}

Stability

  • Every event carries "v": 1. Additive changes (new fields, new event types) do not bump v; consumers must ignore fields and event types they do not recognise. Breaking changes bump v.
  • The machine-checkable contract lives in progress_jsonl.schema.json (JSON Schema, draft 2020-12, validates any single event line). The test suite validates every emitted line against it.

Events

event fields meaning
start command (required), total (optional) First event of every run. total is omitted when the item count is not known up front (render an indeterminate progress bar).
progress current, total (required), item (optional) One item finished being picked up for processing. current counts from 1 to total.
warning message (required), item (optional) Non-fatal problem; the run continues.
result ok, outputs, summary (all required) Terminal event of a run that completed. outputs = absolute paths of files written (may be empty). summary is command-specific (below).
error message (required), item (optional) Terminal event of a run that failed; the process exits non-zero. No result follows.

Terminal rule: a stream starts with start and ends with exactly one of result or error — including on unexpected internal failures, which surface as an error event before the non-zero exit. With one documented nuance: embed processes files independently and keeps its existing exit-code behaviour, so a run where some files failed still exits 0 and ends in result with "ok": false (per-file problems are warning events; the counts are in summary). For embed, therefore, treat result.ok — not the exit code — as the success signal. For every other command result implies "ok": true.

Warnings are not live: warning events for skipped items are typically emitted after the scan completes (once the skipped set is known), not interleaved with the progress events. Attribute them by their item field, never by arrival order.

Per-command notes

map

  • One progress event per .SRT file scanned, exactly like flightmap — the photo scan is a single batch ExifTool call and emits none; start carries no total (the file count is discovered during the scan — take total from the first progress event).
  • One warning per photo without GPS (message is "No GPS data", the file name is in item) and one per SRT without GPS telemetry (message is "No GPS telemetry", the file name is in item), both emitted after their respective scans complete.
  • summary: {"photos": N, "flights": N, "skipped": N}skipped is the combined count of GPS-less photos and telemetry-less SRTs.
  • --serve cannot be combined with --progress jsonl (serving blocks forever; frontends open the written HTML themselves), same rule as photomap.

flightmap

  • One progress event per .SRT file scanned; start carries no total (the file count is discovered during the scan — take total from the first progress event).
  • One warning per SRT without GPS telemetry (message is "No GPS telemetry", the file name is in item), emitted after the scan.
  • summary: {"flights": N, "skipped": N, "joined_files": N}joined_files counts source files that were chained into multi-segment flights.

photomap

  • No progress events in v1 (the photo scan is a single batch ExifTool call); start has no total. Expect start → warnings → result.
  • summary: {"photos": N, "skipped": N} (mapped vs no-GPS).
  • --serve cannot be combined with --progress jsonl (serving blocks forever; frontends open the written HTML themselves).

embed

  • One progress event per video file.
  • summary: {"processed": N, "total": N, "warnings": N, "errors": N, "output_directory": "..."}; outputs = [output_directory].
  • ok is false when any file errored (see terminal rule above).

check

  • One progress event per path argument.
  • outputs is empty. summary: {"checked": N, "files": {"<path>": {<metadata flags>}}} — the per-file object is check's existing detection result: {"gps": bool, "altitude": bool, "creation_time": bool}. A path that does not exist or cannot be read yields {} and a warning event ("Not found or unreadable") — the run still ends in result with "ok": true.
  • A directory argument stands for its top-level media files (*.mp4/*.mov/*.jpg/*.jpeg/*.dng, case-insensitive, no recursion): start.total, the progress events and summary.files all name the expanded files, never the directory itself. A directory with no media files contributes nothing and yields one warning event ("No media files found", item = the directory) — the run still ends in result with "ok": true. A directory that cannot be listed (e.g. a permissions error) is handled the same way: it contributes nothing and yields one warning event ("Not found or unreadable", item = the directory) rather than aborting the run.

doctor

  • No progress events. One warning per missing tool (item = tool name). outputs is empty. summary: {"ok": bool, "tools": {"ffmpeg": {"present": bool}, "exiftool": {"present": bool, "version", "source", "path", "decode"}}, "system": {...}} — the exiftool extras appear only when it is present and its version is readable.
  • Missing dependencies are a report, not a crash: "ok": false with exit code 0 (same reading rule as embed).
  • summary.update_check (#319): {"consent": true|false|null, "hard_disabled": bool, "current": "<this version>", "releases_url": "<GitHub releases page>"}. Nobody is prompted, and a plain run never goes online: consent is only the remembered doctor --online/--offline choice (null = never answered). Passing the explicit --online flag is the consent — it is persisted exactly as on the interactive path and the check runs, adding "latest": "<PyPI version>"|null, "newer": bool|null (null when offline/PyPI unreachable — never an error) and, when newer, "hint" (the environment-aware upgrade command). --offline persists the refusal. DJIEMBED_NO_UPDATE_CHECK=1 blocks both the network and the persisting. This is how the desktop app offers the one-time question and the launch note without a second consent store.

convert

  • Single-file mode: start carries total: 1 and one progress event fires for the input. Batch mode (-b): start has no total (the directory is scanned inside the run); one progress event per candidate file, total on the progress events.
  • Batch files whose telemetry cannot be read (Mp4TelemetryError) are skipped with one warning each (item = file name); in single-file mode the same failure is fatal (error event, non-zero exit).
  • outputs = absolute paths of every file written. summary: {"converted": N, "skipped": N, "format": "gpx"}.

validate

  • No progress events; start has no total. One warning per issue found. outputs is empty; summary is the full validation report (total_files, valid_pairs, issues, warnings, file_analyses).
  • Findings are a report, not a command failure: a run with issues still exits 0 and ends in result with "ok": true — read summary.issues. (Text mode keeps the non-zero VALIDATION_ERROR exit for scripts.)
  • --format json cannot be combined with --progress jsonl (the result summary already carries the full report).

verify-sun

  • No progress events. One warning per analysis flag (night, very_low_sun, sun_not_computable — the flag name is the message). Flags are findings about the footage, not failures: "ok": true.
  • outputs is empty; summary is the sun summary dict (file, points, sun_computed, utc_start/utc_end, elevation/azimuth stats, flags).
  • --format json cannot be combined with --progress jsonl.

fetch-log

  • --progress jsonl requires --yes: the consent prompt cannot be answered under jsonl (there is no stdin round-trip in the contract), so consent must be obtained by the frontend before invoking the command; omitting --yes is a usage error. It also requires FLIGHTREADER_API_KEY to already be set in the environment — there is no key prompt under jsonl either.
  • One progress event is not emitted per record; records that already have a cached CSV are skipped silently and counted in summary.cached.
  • A successful run — including a run where every record was a cache hit and nothing was uploaded — ends in result with outputs = absolute paths of every available CSV (both previously cached and newly fetched) and summary: {"fetched": N, "cached": N, "failed": 0}. failed is always 0 in a result event: any per-record failure instead ends the run in error (see next point), so a non-zero failed never reaches result.
  • A record that fails to fetch (e.g. the API rejects it) is reported as a warning event (item = the record's file name) and the run continues to the remaining records; if any record failed, the stream still terminates with a single error event (not result) and a non-zero exit code, per the terminal rule.

Relation to --log-json

--log-json (env DJIEMBED_LOG_JSON) is an older, separate feature: it formats log lines as JSON for the commands that read it (e.g. doctor) and is independent of this event stream. The progress-wired commands do not change their logging format based on it; logs are human-readable text on stderr for every command, with or without --progress jsonl. Parse stdout events, treat stderr as free-form diagnostics.