I wanted to import my Instagram posts into grain.social, the Atmosphere photo sharing app, using their tool at https://grain.social/settings/import
When I began the process, all of my single-image posts were labeled "invalid date", while the multi-image posts found the title and date correctly.
Instagram writes the post-level creation_timestamp in
your_instagram_activity/media/posts_1.json only for multi-image (carousel)
posts. For single-image posts the post object has no top-level
creation_timestamp at all — the date lives one level down, in
media[0].creation_timestamp.
In the export I looked at: 211 posts, of which 208 single-image (no top-level timestamp -> "Invalid date") and 3 carousels (timestamp present -> parsed correctly).
Fall back to the media entries when the post-level field is absent:
ts = post.get("creation_timestamp") or min(m["creation_timestamp"] for m in post["media"])The same applies to captions: for single-image posts the caption is in
media[0].title, not post.title, so those are likely being dropped silently
today.
The sibling file posts.json (Meta's newer label_values schema) does carry a
top-level timestamp on every post, which makes it look like the easier file to
parse. Two problems:
-
Its timestamps are wrong for older posts. For 117 of the 211 posts it reports the date of a mid-2015 Instagram backfill/migration rather than the original post date. True dates spanning
2010-12-19through2015-06-06are all reported as June–September 2015. The per-mediacreation_timestampvalues inposts_1.jsonpreserve the correct dates.Example — same post, same media file, same caption:
file reported date posts_1.json(media[0].creation_timestamp)2014-07-20 posts.json(timestamp)2015-08-29 -
It lists only one media entry per post. For carousels it points at a duplicate album-cover file named after the album's
fbid(e.g.media/posts/17866952645520164.jpg, byte-identical to the first member), so the remaining images are lost.posts_1.jsonhad 217 media entries whereposts.jsonhad 211.
Also note posts.json has a Creation time label whose timestamp_value is
0 on every post, and Original width / Original height / File size in bytes are "0" throughout — they look populated but aren't.
Parse posts_1.json, with the media-level fallback for both date and caption.
Treat the top-level timestamp in posts.json as unreliable for anything
before roughly mid-2015.
fix_instagram_export.py in this gist applies all of the above to an export.
Stdlib only, no dependencies. It never modifies the input zip — it writes a new
one, rebuilt entry by entry so the archive structure the importer expects is
preserved exactly.
python3 fix_instagram_export.py instagram-export.zip
your_instagram_activity/media/posts_1.json: dates promoted to post level on 208 posts
your_instagram_activity/media/posts.json: 117 timestamps corrected, 3 carousels restored, 211 'Creation time' fields filled
wrote instagram-export-patched.zip
One packaging note for anyone repacking an export by hand: re-zipping the folder
in macOS Finder nests everything under an extra directory and adds __MACOSX/,
which importers looking for your_instagram_activity/... at the archive root
will read as an empty export.