A CLI tool that makes the EXIF DateTimeOriginal of burst-shot JPEGs unique by advancing them 1 second at a time in filename order.
Google Photos ignores the sub-second timestamps (SubSecTimeOriginal) of burst photos, so photos taken within the same second do not keep their display order. By making the timestamps unique at second granularity before uploading, this tool ensures the photos are ordered as shot.
- Collects the JPEGs (
.jpg/.jpeg, case-insensitive) directly under the given directory. Subdirectories are not recursed into. - Sorts them by natural filename order (
image_2.jpgcomes beforeimage_10.jpg). - Assigns, in sort order, new time = max(original time, previous file's new time + 1 second). The first file keeps its original time. Only burst shots collapsed into the same second are pushed forward 1 second at a time; original capture times of separate scenes with time gaps are preserved (only when a push-out catches up with the next scene is that scene shifted by the minimum necessary amount). The order is always monotonically increasing by filename.
- In addition to
DateTimeOriginal(0x9003),DateTimeDigitized(0x9004) andDateTime(0x0132) are set to the same value if they exist (missing tags are not created). Files are saved in place. Files that need no change (already unique) are not written at all, so re-running is idempotent.
The EXIF rewrite is done by in-place replacement of the target tag values (fixed 19 bytes) inside the APP1 segment. The JPEG is never decoded or rebuilt, so every other byte — including the image scan data — is bit-identical before and after processing (guaranteed by tests). Writes go to a temporary file followed by an atomic rename, so a crash mid-write never corrupts the original file.
Download the prebuilt binary for your platform (Linux x86_64 / aarch64, macOS Apple Silicon / Intel, Windows x86_64) from Releases, extract the archive, and place lapse somewhere on your PATH.
Alternatively, build from source with Cargo:
cargo install --path .This is a destructive operation, so first check the timestamps to be written with --dry-run.
# Preview (no rewriting)
lapse --dry-run /path/to/photos
# Run
lapse /path/to/photos
# Run while printing before/after timestamps
lapse --verbose /path/to/photosIf no target JPEGs are found, the command fails. Files whose DateTimeOriginal cannot be read (e.g. no EXIF) are skipped individually and summarized at the end (exit code 1).
cargo testSample JPEGs for testing (with EXIF containing Make / Model / all three date-time tags / GPS / MakerNote) are generated inside the tests, which verify:
- Lossless guarantee: the file length is unchanged and the differing bytes are confined to the value areas of the target date-time tags (19 bytes × 3 locations). The scan data (entropy-coded section) is byte-identical, and the decoded pixel arrays match bit for bit.
- Other EXIF tags preserved: all tags other than the date-time tags (including GPS and MakerNote) are unchanged in both value and set.
- Target tag update:
DateTimeOriginalbecomes "base time + index seconds" inYYYY:MM:DD HH:MM:SSformat. Natural-order sorting and minute carry-over are also verified. - File integrity: the file still decodes as a valid JPEG after processing.
- Pure Rust implementation. No dependency on external processes such as ExifTool.
- EXIF-writing crates (
little_exifetc.) rebuild segments and risk corrupting MakerNote offsets, so instead a minimal in-house TIFF/IFD parser locates the tag value offsets for in-place replacement (see the selection notes at the top ofsrc/exif_patch.rs). - Second assignment is finalized sequentially after sorting; only file I/O and rewriting are parallelized with rayon.