The number goes before .json, after everything else
Cameras reuse names (IMG_2710 comes round again after 9,999 shots), scanners produce Scan35.jpg more than once, and two phones upload the same DSC_0101.JPG. Google keeps both and appends (1), (2) and so on to the later ones, before the extension. The JSON for that copy takes the plain name, adds the extension, adds the .supplemental-metadata suffix in new exports, applies the 51-character cut, and only then appends the (N) and .json.
| File | Its JSON | Reported in |
|---|---|---|
IMG_2710(1).HEIC | IMG_2710.HEIC(1).json | immich-go test suite (old format) |
DSC_0101(1).JPG | DSC_0101.JPG(1).json | immich-go test suite (old format) |
IMG_3479(1).JPG | IMG_3479.JPG(1).json | immich-go documentation |
image(11).jpg | image.jpg(11).json | GooglePhotosTakeoutHelper "bracket swap" code |
Scan35(1).jpg | Scan35.jpg.supplemental-metadata(1).json | immich-go test suite (new format) |
CLIP0001(10).AVI | CLIP0001.AVI.supplemental-metadata(10).json | immich-go test suite (new format) |
IMG_1976(1).HEIC | IMG_1976.HEIC.supplemental-metadata(1).json | GPTH_Neo issue #123 |
Screenshot_20231027_123303_Facebook(1).jpg | Screenshot_20231027_123303_Facebook.jpg.supple(1).json (54 characters) | immich-go test suite |
The last row is the one that breaks a naive "51 characters at most" check. The cut happens first, leaving …Facebook.jpg.supple at 46 characters, and then (1).json is added, so the finished name is 54 characters long. Any matcher that truncates the whole thing to 51 will never find it.
Examples from immich-go matcher_test.go, immich-go docs, GPTH json_extractor.dart and GPTH_Neo #123.
Two pairs that must never match
immich-go's tests include two negative cases, and they are the ones that separate a careful matcher from a sloppy one:
DSC_0104(1).JPGmust not be paired withDSC_0104.JPG.json. That JSON belongs to the first copy,DSC_0104.JPG.DSC_0103.JPGmust not be paired withDSC_0103.JPG(1).json. That JSON belongs to the second copy, which may be in a different zip part.
immich-go's matcher strips the trailing (digits) from both the media name and the JSON name and requires the two numbers to be equal, or both absent. Getting this wrong is worse than finding nothing: the copy is a different photo, with a different date and often a different place, and the wrong JSON writes both into it.
Why the title field cannot tell the copies apart
Every copy's JSON carries the plain name in its title field. IMG_2710.HEIC(1).json says "title": "IMG_2710.HEIC", exactly as the first copy's JSON does; the GPTH_Neo changelog for 6.1.10 records this while fixing issue #133. A matcher that pairs media with JSON by title therefore has two JSONs claiming the same name and no way to say which is which. The number in the file name is the only thing that separates them.
What FixMyTakeout does with a numbered file
For IMG_2710(1).HEIC the tool splits the name into the core IMG_2710.HEIC and the number 1, then looks in the same folder for, in order:
IMG_2710.HEIC.supplemental-metadata(1).jsonIMG_2710.HEIC(1).jsonIMG_2710(1).HEIC.supplemental-metadata.jsonandIMG_2710(1).HEIC.json, in case the export kept the number in place. (GPTH_Neo's matcher also accepts a third arrangement,IMG_2710.HEIC(1).supplemental-metadata.json, for which no real export has been reported.)- A JSON whose name is the core plus any shortened
supplemental-metadatasegment plus the same(1).
The number must agree at every step, so DSC_0104(1).JPG never takes DSC_0104.JPG.json, and the plain DSC_0103.JPG never takes DSC_0103.JPG(1).json. The 54-character case works because the tool cuts first and appends the number afterwards, the same order Google uses. When the tool falls back to the JSON's title as a last resort, it only accepts a JSON whose own (N) equals the file's, and only when exactly one JSON qualifies. Edited copies with a number, such as simple_file_20200101-edited(1).jpg, are handled on the edited copies page, and Live Photo videos with a number, such as IMG_1976(1).MP4, on the Live Photos page.
Check the copies in your export
- Search a
Photos fromfolder for(1). Each hit should have a JSON with the same number just before.json. - Open
fixmytakeout-report.csvafter a run and filtermatched_byforduplicate number. Thesidecarcolumn shows exactly which JSON each copy was given. - Compare the
date_writtenofIMG_2710.HEICandIMG_2710(1).HEIC. They are different photos and should normally have different dates.