detect-album-conflicts.sh — Reference¶
Scans all Proton Photos albums and finds photos/videos whose captureTime
does not match the album's expected year. This typically affects videos
(.MP4, .MOV) whose EXIF was missing at upload time, causing proton-drive to
fall back to the upload timestamp instead of the original recording date.
Outputs a TSV file ready for fix-photo-date.sh, plus a human-readable
summary and (optionally) a JSON report.
- Platform: Linux (GNU
date/coreutils). - Dependencies:
proton-driveCLI (authenticated),jq.
Usage¶
detect-album-conflicts.sh [options]
Flags¶
| Flag | Description |
|---|---|
--fix-tsv FILE |
Output a TSV ready for fix-photo-date.sh (filename, nodeUid, date) |
-n, --dry-run |
Read-only: scan and report, do not write fix TSV |
-v, --verbose |
List each conflicting file per album |
--summary-only |
Just show per-album conflict counts (no details) |
--json |
Output results as JSON (machine-readable) |
--cache-dir DIR |
Cache album photo JSONs in DIR for offline reuse on subsequent runs (only album list fetched live when cache is populated) |
--year YYYY |
Only check albums with this leading year |
--min-year YYYY |
Only check albums with year >= this (skip older) |
--max-conflict PCT |
If conflict % exceeds this, warn (default: 20) |
-h, --help |
Show help text |
Environment variables¶
| Var | Default | Description |
|---|---|---|
CLI |
proton-drive |
Path to the proton-drive binary |
LOG_DIR |
$HOME/gphoto2proton/logs |
Run logs directory |
PROTON_DRIVE_CREDENTIALS_STORE |
pass |
Secret store for the proton-drive CLI session |
Fix TSV format¶
The --fix-tsv FILE output is a three-column TSV:
<filename><TAB><nodeUid><TAB><suggested date>
The middle nodeUid column pins each conflict to its exact Proton photo uid.
This lets fix-photo-date.sh resolve duplicate filenames unambiguously
(fix-photo-date.sh still accepts the older two-column format too).
Example:
IMG_0715.MOV PNR_VlVhf...~2Z09zS8Z-tg... 2025-08-15 12:00:00
How it works¶
For each album:
- Album year — reads the first 4-digit year from the album name (leading
or embedded). If none is found, fetches the album photos and uses the most
common
captureTimeyear (majority vote). - Filter — if
--yearor--min-yearwas given, albums not matching the filter are skipped (without an API call for albums with a year in the name). - Fetch photos —
proton-drive album photos -d --jsonfor the album. If--cache-dir DIRis given, each album's JSON is saved toDIR/<uid>.json. On subsequent runs, cache files are read instead of making API calls (saves ~7 minutes on a full scan of 450 albums). - Detect conflicts — any photo whose
captureTimedoesn't start with the album year is flagged. Photos with null/empty captureTime are counted separately. - Suggest fix date — for each conflict, a default date is proposed using the album year + month inferred from the album name (e.g. "Août" → August, "28 Mai" → 28th of May), falling back to July 15th.
Album year inference¶
Albums without a year in their name (e.g. Trash, Failed Videos,
Halloween - 31 Ottobre in GeSI con ByteCode) are handled by sampling the
captureTime of every photo in the album and taking the most frequent year.
This allows the script to still detect conflicts for these albums.
Month detection from album names¶
The script recognizes month names in English, Italian, and French in any case. A word-boundary check prevents false matches (e.g. "mai" inside "semaine" will not trigger May).
When a day number precedes the month name (e.g. "28 Mai"), that day is used. Otherwise the 15th of the month is used as a neutral default.
Caching¶
With 450+ albums and 63,000+ photos, scanning all albums live takes ~7
minutes, executing one proton-drive album photos API call per album.
The --cache-dir DIR flag caches each album's JSON response to a local file.
On subsequent runs, cache files are read instead of API calls — the scan
completes in ~5 seconds.
How to use¶
# First run: populate the cache from the API
./detect-album-conflicts.sh --cache-dir ~/photo-cache --fix-tsv fixes-all.tsv
# Subsequent runs: reuse the cache (no API calls for photo data)
./detect-album-conflicts.sh --cache-dir ~/photo-cache --year 2025 --summary-only
To refresh the cache (e.g. after uploading new photos), just delete the cache directory:
rm -rf ~/photo-cache
./detect-album-conflicts.sh --cache-dir ~/photo-cache --fix-tsv fixes-all.tsv
The cache directory stores one JSON file per album (uid-based filename). Total size for 450 albums: ~129 MB.
Examples¶
Full scan, human output¶
./detect-album-conflicts.sh
[14:52:32] detect-album-conflicts: scan started, log=...
[14:52:34] found 450 albums
[!!] 2025 - Août - Semaine à Rome year=2025 total=297 conflicts=1 missing_capture=0 (0%)
IMG_0715.MOV captureTime=2026-07-29T21:56:17.000Z → fix: 2025-08-15 12:00:00
...
Generate fix TSV for one year¶
./detect-album-conflicts.sh --year 2025 --fix-tsv fixes-2025.tsv
Then fix with:
./fix-photo-date.sh --file fixes-2025.tsv --yes
Or, to restore album membership after fixing (using the cache the scanner built):
./detect-album-conflicts.sh --cache-dir ~/photo-cache --fix-tsv fixes-2025.tsv
./fix-photo-date.sh --file fixes-2025.tsv --album-cache ~/photo-cache --yes
Summary-only (compact)¶
./detect-album-conflicts.sh --summary-only --year 2025
2025 - Août - Semaine à Rome year=2025 total=297 conflicts=1 missing_capture=0
2025 - Août - Semaine à Chamonix year=2025 total=147 conflicts=6 missing_capture=0
JSON machine-readable¶
./detect-album-conflicts.sh --json > report.json
{
"summary": {
"albums_checked": 450,
"total_photos": 34961,
"total_conflicts": 601,
"high_conflict_albums": ["2005 - Erasmus Parties (100%)"]
},
"albums": [
{"name": "...", "year": "2025", "total": 297, "conflicts": 1, "...": ""}
]
}
Check albums from a minimum year¶
./detect-album-conflicts.sh --min-year 2020 --verbose
Typical workflow¶
Local machine¶
# 1. Scan and generate fix TSV
./detect-album-conflicts.sh --cache-dir ~/photo-cache --fix-tsv conflicts.tsv
# 2. Review the output
less conflicts.tsv
# 3. Apply fixes (dry-run first), restoring albums from the same cache
./fix-photo-date.sh --file conflicts.tsv --album-cache ~/photo-cache --dry-run
./fix-photo-date.sh --file conflicts.tsv --album-cache ~/photo-cache --yes
# 4. Re-scan from cache to verify (refresh the cache first — it is stale
# after fixing)
rm -rf ~/photo-cache
./detect-album-conflicts.sh --cache-dir ~/photo-cache --summary-only
Server (cache then fix)¶
# 1. First run: populate cache + generate fix TSV (takes ~7 min)
ssh server01
cd ~/gphoto2proton
./detect-album-conflicts.sh --cache-dir ~/gphoto2proton/photo-cache \
--fix-tsv ~/gphoto2proton/fixes-all.tsv
# 2. Subsequent runs: instant (from cache)
./detect-album-conflicts.sh --cache-dir ~/gphoto2proton/photo-cache \
--year 2025 --summary-only
# 3. Fix videos with wrong dates (restoring albums from the same cache)
./fix-photo-date.sh --file ~/gphoto2proton/fixes-all.tsv \
--album-cache ~/gphoto2proton/photo-cache --yes
# 4. Refresh cache after fixing and re-check
rm -rf ~/gphoto2proton/photo-cache
./detect-album-conflicts.sh --cache-dir ~/gphoto2proton/photo-cache \
--summary-only
Output & logs¶
All diagnostic messages go to stderr so that --json and --fix-tsv
output is clean (pipe-friendly).
Run logs are written to $LOG_DIR/detect-album-conflicts-*.log.
Exit codes¶
| Code | Meaning |
|---|---|
| 0 | All OK |
| 1 | Authentication failure or album list error |
| 2+ | Internal script error (may set -euo pipefail exit) |