nurjns

Snapchat Snap Map Geotagger

Jul 11th, 2025 (edited)
997
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. <#
  2. Version 1.0 - 11.07.2025 - @nurjns
  3.  
  4. 1. Snapchat Daten-Export: JSON-Datei "json/snap_map_places_history.json" und der Ordner "memories" mit den Snaps werden benötigt.
  5. 2. ExifTool wird benötigt: https://exiftool.org/
  6. 3. Ausführung in PowerShell:
  7.     cd X:\Pfad\zum\Projektordner
  8.     Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
  9.     .\geotag.ps1
  10.  
  11. Komplette Ordnerstruktur:
  12.  
  13. 📁 Projektordner/
  14. ├── 📁 exiftool_files/
  15. ├── 📁 memories/
  16. │   ├── 2024-04-06_abc123-main.jpg
  17. │   └── ...
  18. ├── 📁 json/
  19. │   └── snap_map_places_history.json
  20. ├── 📄 exiftool.exe
  21. ├── 📄 geotag.ps1
  22. #>
  23.  
  24. # Pfade
  25. $sourceFolder = 'memories'
  26. $targetFolder = 'memories_geotagged'
  27. $jsonFile = 'json\snap_map_places_history.json'
  28. $exiftool = '.\exiftool.exe'
  29.  
  30. # Ordner erstellen, falls nicht vorhanden
  31. if (-not (Test-Path $targetFolder)) {
  32.     New-Item -ItemType Directory -Path $targetFolder | Out-Null
  33.     Write-Host "📁 Zielordner wurde erstellt: $targetFolder"
  34. }
  35.  
  36. # Funktion für Umlaut-Konvertierung
  37. function ConvertUmlautsAndUrlEncode {
  38.     param([string]$str)
  39.    
  40.     # Komma entfernen
  41.     $str = $str -replace ',', ''
  42.  
  43.     # Umlaute ersetzen
  44.     $str = $str -replace 'ü', 'ue'
  45.     $str = $str -replace 'Ü', 'Ue'
  46.     $str = $str -replace 'ä', 'ae'
  47.     $str = $str -replace 'Ä', 'Ae'
  48.     $str = $str -replace 'ö', 'oe'
  49.     $str = $str -replace 'Ö', 'Oe'
  50.     $str = $str -replace 'ß', 'ss'
  51.  
  52.     # Leerzeichen zu %20 (keine weitere URL-Kodierung)
  53.     return $str -replace ' ', '%20'
  54. }
  55.  
  56. # JSON laden mit UTF8-Encoding
  57. $jsonRaw = Get-Content $jsonFile -Raw -Encoding utf8
  58. $json = $jsonRaw | ConvertFrom-Json
  59. $history = $json.'Snap Map Places History'
  60.  
  61. # Alle JPG- und PNG-Dateien laden (ohne -overlay)
  62. $files = Get-ChildItem -Path $sourceFolder | Where-Object {
  63.     $_.Extension -match '^\.(jpg|png)$' -and $_.BaseName -notmatch '-overlay'
  64. }
  65.  
  66. # Startpunkt-Abfrage (Dateiname oder Datum)
  67. $startInput = Read-Host '🔁 Falls du ab einer bestimmten Datei oder einem Datum (YYYY-MM-DD) weitermachen willst, gib es ein (oder Enter für Start ab Anfang)'
  68. $startFound = [string]::IsNullOrWhiteSpace($startInput)
  69. $startAtFile = ''
  70.  
  71. if (-not $startFound) {
  72.     if ($startInput -match '^\d{4}-\d{2}-\d{2}$') {
  73.         # Start per Datum
  74.         $match = $files | Where-Object { $_.BaseName -like "$startInput*" } | Select-Object -First 1
  75.         if ($match) {
  76.             $startAtFile = $match.Name
  77.             Write-Host '📆 Beginne ab erster Datei mit Datum' $startInput ':' $startAtFile
  78.         } else {
  79.             Write-Host '❗️Keine Datei mit Datum' $startInput 'gefunden. Starte trotzdem ganz normal.'
  80.             $startFound = $true
  81.         }
  82.     } else {
  83.         # Start per Dateiname
  84.         if ($files.Name -contains $startInput) {
  85.             $startAtFile = $startInput
  86.         } else {
  87.             Write-Host "❗️Datei '$startInput' wurde im Quellordner nicht gefunden. Starte trotzdem ganz normal."
  88.             $startFound = $true
  89.         }
  90.     }
  91. }
  92.  
  93. # Verarbeitung starten
  94. foreach ($file in $files) {
  95.     if (-not $startFound) {
  96.         if ($file.Name -eq $startAtFile) {
  97.             $startFound = $true
  98.         } else {
  99.             continue
  100.         }
  101.     }
  102.  
  103.     Write-Host "`n➡️ Verarbeite Datei: $($file.Name)"
  104.  
  105.     # Ziel-Datei prüfen
  106.     $targetFile = Join-Path $targetFolder ($file.BaseName + '_geotagged.jpg')
  107.     if (Test-Path $targetFile) {
  108.         Write-Host "⚠️  $($file.Name) wurde bereits verarbeitet. Überspringe."
  109.         continue
  110.     }
  111.  
  112.     # Datum aus Dateiname extrahieren
  113.     $datePart = $file.BaseName -split '_' | Select-Object -First 1
  114.     if (-not ($datePart -match '^\d{4}-\d{2}-\d{2}$')) {
  115.         Write-Host "❌ Ungültiges Datumsformat im Dateinamen: $($file.Name)"
  116.         continue
  117.     }
  118.  
  119.     # Einträge für Datum filtern
  120.     $matchingEntries = $history | Where-Object { ($_.Date -split ' ')[0] -eq $datePart }
  121.     if ($matchingEntries.Count -eq 0) {
  122.         Write-Host "❌ Keine Einträge für $($file.Name) gefunden."
  123.         continue
  124.     }
  125.  
  126.     # Foto öffnen (Standardprogramm)
  127.     $photoProcess = Start-Process -FilePath $file.FullName -PassThru
  128.  
  129.     if ($matchingEntries.Count -gt 1) {
  130.         Write-Host "📅 Mehrere Einträge für $datePart gefunden:"
  131.         for ($i = 0; $i -lt $matchingEntries.Count; $i++) {
  132.             $entry = $matchingEntries[$i]
  133.             Write-Host ($i+1) ':' $entry.Place '–' $entry.'Place Location' '–' $entry.Date
  134.         }
  135.         $choice = Read-Host '❓ Nummer auswählen oder (s)kip'
  136.  
  137.         # Foto schließen
  138.         try {
  139.             Start-Sleep -Seconds 1
  140.             $photoProcess.CloseMainWindow() | Out-Null
  141.             Start-Sleep -Seconds 1
  142.             if (!$photoProcess.HasExited) { $photoProcess.Kill() }
  143.         } catch {}
  144.  
  145.         if ($choice -eq 's') {
  146.             Write-Host '⏩ Übersprungen.'
  147.             if (Test-Path $targetFile) {
  148.                 Remove-Item -Path $targetFile -Force
  149.                 Write-Host "🗑️ Vorhandene Datei $targetFile gelöscht."
  150.             }
  151.             continue
  152.         }
  153.  
  154.     if ($choice -match '^\d+$' -and [int]$choice -ge 1 -and [int]$choice -le $matchingEntries.Count) {
  155.         $selected = $matchingEntries[[int]$choice - 1]
  156.     } else {
  157.         Write-Host '❌ Ungültige Eingabe. Überspringe.'
  158.         continue
  159.     }
  160.     } else {
  161.         $selected = $matchingEntries[0]
  162.         Write-Host '📍 Eintrag gefunden:' $selected.Place '–' $selected.'Place Location'
  163.         $confirm = Read-Host '✅ Verwenden? (y/n)'
  164.  
  165.         # Foto schließen
  166.         try {
  167.             Start-Sleep -Seconds 1
  168.             $photoProcess.CloseMainWindow() | Out-Null
  169.             Start-Sleep -Seconds 1
  170.             if (!$photoProcess.HasExited) { $photoProcess.Kill() }
  171.         } catch {}
  172.  
  173.         if ($confirm.ToLower() -ne 'y') {
  174.             Write-Host '⏩ Foto übersprungen.'
  175.             continue
  176.         }
  177.     }
  178.  
  179.     # API-Abfrage vorbereiten
  180.     $placeQuery = "$($selected.Place) $($selected.'Place Location')"
  181.     $encodedQuery = ConvertUmlautsAndUrlEncode $placeQuery
  182.     $apiUrl = 'https://photon.komoot.io/api/?q=' + $encodedQuery
  183.  
  184.     Write-Host '🔗 API-URL:' $apiUrl
  185.  
  186.     try {
  187.         $response = Invoke-RestMethod -Uri $apiUrl -UseBasicParsing
  188.         if ($response.features.Count -eq 0) {
  189.             Write-Host '❌ Keine Koordinaten gefunden.'
  190.             continue
  191.         }
  192.         $coords = $response.features[0].geometry.coordinates
  193.         $lon = [math]::Round($coords[0], 3)
  194.         $lat = [math]::Round($coords[1], 3)
  195.         Write-Host "🌍 Gefundene Koordinaten: $lat, $lon"
  196.     } catch {
  197.         Write-Host '❌ Fehler bei API-Anfrage:' $_
  198.         continue
  199.     }
  200.  
  201.     # Datum mit Uhrzeit aus JSON-Eintrag übernehmen und UTC+2 korrigieren
  202.     $utcString = $selected.Date.Replace(' UTC','')
  203.     $utcTime = [datetime]::ParseExact($utcString, 'yyyy-MM-dd HH:mm:ss', $null, [System.Globalization.DateTimeStyles]::AssumeUniversal)
  204.     $datetime = $utcTime.ToString('yyyy:MM:dd HH:mm:ss')
  205.  
  206.     # Datei in Zielordner kopieren
  207.     Copy-Item -Path $file.FullName -Destination $targetFile -Force
  208.  
  209.     # ExifTool-Befehl auf kopierte Datei
  210.     & $exiftool `
  211.         -quiet `
  212.         -overwrite_original `
  213.         "-GPSLatitude=$lat" `
  214.         "-GPSLatitudeRef=$(if ($lat -ge 0) {'N'} else {'S'})" `
  215.         "-GPSLongitude=$lon" `
  216.         "-GPSLongitudeRef=$(if ($lon -ge 0) {'E'} else {'W'})" `
  217.         "-DateTimeOriginal=$datetime" `
  218.         "-CreateDate=$datetime" `
  219.         "-ModifyDate=$datetime" `
  220.         "-FileModifyDate=$datetime" `
  221.         "-FileCreateDate=$datetime" `
  222.         "$targetFile"
  223.  
  224.     if ($LASTEXITCODE -eq 0) {
  225.         Write-Host '✅ Geotagging abgeschlossen:' $targetFile
  226.     } else {
  227.         Write-Host '❌ Fehler beim Schreiben der Metadaten.'
  228.     }
  229. }
  230.  
  231. [console]::beep(500,200)
Advertisement