snapchat-memories-to-photos
Snapchat’s My Data export hands you a pile of files named 2019-06-14_<uuid>-main.mp4 and a JSON file that lists dates and coordinates and never says which entry belongs to which file. Import that folder into Apple Photos as it arrives and everything is dated the day you downloaded it, with no location on anything. This tool reconstructs the link between file and entry, writes real metadata onto copies, and imports the result into a Photos library you choose. It is Python, standard library only, MIT licensed, and macOS only because the last step talks to Photos.
What it does not do
Section titled “What it does not do”- It does not guess by date. Matching is 1:1 between a file and a JSON entry, and “nearest entry wins” is exactly the rule it avoids, because two memories seconds apart get silently swapped that way.
- It does not undo an import. Photos AppleScript can import, create albums and set dates. It cannot delete media items or albums:
deletefails with-1700. Import is one directional, which is the argument for a dedicated library. - It does not modify your originals. Every step works on copies.
- It does not write coordinates it does not trust. Entries at exactly
0.000000, 0.000000are Snapchat saying the location was unavailable, so the GPS tags are left off instead of putting the memory in the Gulf of Guinea. - It does not resolve conflicts on its own. Where a video carries its own
LocationInformationfrom the camera and it disagrees with the JSON,snapmem.verifyflags it rather than picking. - It does not fetch the export for you. Requesting it from Snapchat, and downloading every zip, is your job.
Requirements
Section titled “Requirements”macOS with Photospython3 >= 3.9 (stdlib only, no packages to install)exiftool brew install exiftoolffmpeg brew install ffmpeg (only for overlay merging)Unpack every zip into one folder first, then:
# match files to JSON entries and write a manifestpython3 -m snapmem.match /path/to/export --out manifest.json
# write dates, time zones and coordinates onto copiespython3 -m snapmem.prepare manifest.json --out /path/to/ready
# optional: burn text and stickers onto copies of their ownpython3 -m snapmem.overlays manifest.json --ready /path/to/ready --out /path/to/with-overlays
# check every file against the source JSON before importing anythingpython3 -m snapmem.verify manifest.json --ready /path/to/ready
# import into a Photos library of your choicepython3 -m snapmem.import_photos /path/to/ready \ --library "/Volumes/Disk/Snapchat Memories.photoslibrary" \ --album "All memories"Each module runs standalone with --help.
How it works
Section titled “How it works”The match comes from zip timestamps. memories_history.json gives Date in UTC, Media Type and Location, and no filenames. Filenames carry a date with day precision plus a random uuid, so ten memories on one day are ten indistinguishable candidates. When an archive is unpacked, each file keeps the modification time stored in the zip entry, and that value is the original capture time. The gap between it and the JSON date is constant, equal to the UTC offset applied by the unpacker, give or take a second because zip timestamps have two second granularity. snapmem.match derives that offset from your own data, constrains pairs by media type, and resolves contention by smallest gap first.
Photos and videos need different tags.
| photos | videos | |
|---|---|---|
| date | DateTimeOriginal + OffsetTimeOriginal |
Keys:CreationDate with offset |
| location | GPSLatitude / GPSLongitude |
Keys:GPSCoordinates, ISO 6709 |
Video location is where this gets quiet. ISO 6709 wants two integer digits for latitude and three for longitude. +41.902000+012.496000/ is accepted and +41.902000+12.496000/ is ignored, the item appearing with no location at all. Both read back identically under exiftool. In Python the format is f"{lat:+010.6f}{lon:+011.6f}/". Snapchat’s MP4 files also ship without the metadata handler Photos looks for, so -api QuickTimeHandler=1 is required.
Batching arguments through -@ argfile is far faster than one exiftool launch per file, but options passed as -common_args are dropped once the argfile contains -execute, and the only warning is a line buried in the output. The write appears to succeed and reads back correct. It is wrong only inside Photos. The -api options go inside each block instead. The general rule: check the effect in the destination application rather than the value read back by the tool that wrote it.
What is checked
Section titled “What is checked”snapmem.verify re-reads every prepared file and compares it against the source JSON, and it runs before the import rather than after.
The tool was tested on macOS 26 against an export of a few thousand items spanning several years. Every item matched, and every date was verified against the source JSON. In that export, 4% of entries carried 0.000000, 0.000000 as their location. Altitude and accuracy tags turned out not to be required, established by writing four copies of one file that differed only in that detail.
Some overlay PNGs arrive as 0 bytes in the export itself. snapmem.overlays skips them and reports the count.