<# Dieses Script verarbeitet Google Fotos Takeout-Exporte, ordnet die passenden Metadaten aus den JSON-Dateien den Bild- und Videodateien zu, schreibt diese Metadaten in die Dateien und passt das Dateizeitstempel an. .jfif-Dateien werden automatisch in .jpg umbenannt. Alle Aktivitäten werden in eine "log.txt" geloggt. OPTIONAL: In Schritt 3 werden die Originaldateien gelöscht, wenn eine bearbeitete Variante existiert. Ist das nicht gewünscht, diesen Part einfach rauslöschen. "exiftool-XXX.zip" hier herunterladen, entpacken, in das Verzeichnis kopieren und die EXE umbenennen in "exiftool.exe": https://exiftool.org/ Version 1.0.4 - 05.10.2025 - @nurjns #> $exiftoolPath = Join-Path $PSScriptRoot 'exiftool.exe' $logPath = Join-Path $PSScriptRoot 'log.txt' "`n--- Lauf gestartet: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') ---`n" | Out-File $logPath -Encoding UTF8 # 1. .jfif zu .jpg umbenennen Get-ChildItem -Recurse -Filter *.jfif | ForEach-Object { try { $newPath = $_.FullName -replace '\.jfif$', '.jpg' Rename-Item $_.FullName $newPath Add-Content $logPath "Umbenannt: $($_.FullName) zu $newPath" } catch { Add-Content $logPath "FEHLER beim Umbenennen: $($_.FullName) - $_" } } # 2. Umlaute ersetzen $extensionstmp = @('*.jpg', '*.jpeg', '*.png', '*.mp4', '*.mov', '*.heic', '*.json') # Alle Dateien rekursiv finden $files = Get-ChildItem -Path . -Include $extensionstmp -Recurse -File foreach ($file in $files) { $originalName = $file.BaseName $extension = $file.Extension $directory = $file.Directory.FullName Write-Host "Prüfe: $($file.Name)" # Umlaute ersetzen $newName = $originalName.Replace("ä", "ae").Replace("ö", "oe").Replace("ü", "ue").Replace("Ä", "Ae").Replace("Ö", "Oe").Replace("Ü", "Ue").Replace("ß", "ss") # Prüfen ob Änderung notwendig if ($originalName -ne $newName) { $newFullName = Join-Path $directory ($newName + $extension) if (Test-Path $newFullName) { Write-Host "WARNUNG: Zieldatei existiert bereits - übersprungen" -ForegroundColor Yellow } else { try { Write-Host "Benenne um: '$($file.Name)' zu '$($newName + $extension)'" Rename-Item -Path $file.FullName -NewName ($newName + $extension) } catch { Write-Host "FEHLER: $($_.Exception.Message)" -ForegroundColor Red } } } else { Write-Host "Keine Änderung bei Dateiname erforderlich" -ForegroundColor Gray } Write-Host "" } # 3. Dateien erfassen $extensions = @('*.jpg', '*.jpeg', '*.png', '*.mp4', '*.mov', '*.heic') $files = Get-ChildItem -Recurse -File -Include $extensions # 4. Originale löschen, wenn bearbeitete Variante existiert (mit Suffix -bear*) $grouped = $files | Group-Object { $_.Name -replace '-bear\w*(?=\.\w+$)', '' } foreach ($group in $grouped) { $original = $group.Group | Where-Object { $_.Name -notmatch '-bear\w*(?=\.\w+$)' } $edited = $group.Group | Where-Object { $_.Name -match '-bear\w*(?=\.\w+$)' } if ($original.Count -gt 0 -and $edited.Count -gt 0) { foreach ($file in $original) { try { Remove-Item $file.FullName -Force Add-Content $logPath "Original geloescht: $($file.FullName)" } catch { Add-Content $logPath "FEHLER beim Loeschen: $($file.FullName) - $_" } } } } # 5. Metadaten schreiben und NTFS-Zeit setzen (mit Endungskorrektur) $files = Get-ChildItem -Recurse -File -Include $extensions # 5a. Falsche Dateiendungen korrigieren (z.B. PNG mit JPEG-Inhalt) foreach ($file in $files) { try { $magic = Get-Content -Path $file.FullName -Encoding Byte -TotalCount 4 $isJpeg = ($magic[0] -eq 0xFF -and $magic[1] -eq 0xD8 -and $magic[2] -eq 0xFF) $wrongExt = $file.Extension -ieq '.png' if ($isJpeg -and $wrongExt) { $newPath = [System.IO.Path]::ChangeExtension($file.FullName, '.jpg') Rename-Item $file.FullName $newPath -Force Add-Content $logPath "Falsche Endung korrigiert: $($file.FullName) zu $newPath" } } catch { Add-Content $logPath "FEHLER bei Endungskorrektur: $($file.FullName) - $_" } } # 5b. Nach Endungskorrekturen erneut Dateiliste holen $files = Get-ChildItem -Recurse -File -Include $extensions foreach ($file in $files) { Write-Host "Bearbeite Datei: $($file.Name)" # Basisname bereinigen: ohne -bear*, (Zahl) und evtl. abschließendem _ $basenameWithoutExt = [System.IO.Path]::GetFileNameWithoutExtension($file.Name) $basenameClean = $basenameWithoutExt -replace '-bear\w*$', '' -replace '\(\d+\)$', '' -replace '_$' $base = [regex]::Escape($basenameClean) # JSON-Datei suchen mit tolerantem Match (inkl. *.supplemental-metadata(45).json) $jsonPath = Get-ChildItem -Path $file.DirectoryName -Filter "*.json" -File | Where-Object { $jsonName = $_.Name $jsonNameNoExt = [System.IO.Path]::GetFileNameWithoutExtension($jsonName) # Klammern und evtl. abschließendes "_" entfernen, aber rest beibehalten $jsonNameClean = $jsonNameNoExt -replace '\(\d+\)$', '' -replace '_$' # Match wenn Dateiname mit dem bereinigten Basisnamen beginnt und eine "sup*" JSON ist $jsonNameClean -eq $basenameClean -or ( $jsonNameClean -match "^$base" -and $jsonName -match '\.sup(p|pl|plemental)?.*\.json$' ) } | Select-Object -ExpandProperty FullName -First 1 if (-not $jsonPath) { Add-Content $logPath "FEHLER: Keine JSON gefunden: $($file.FullName)" continue } # JSON laden try { $json = Get-Content $jsonPath -Raw | ConvertFrom-Json $description = $json.description $timestamp = [double]::Parse($json.photoTakenTime.timestamp) $dateTaken = [DateTimeOffset]::FromUnixTimeSeconds($timestamp).ToLocalTime().ToString('yyyy:MM:dd HH:mm:ss') } catch { Add-Content $logPath "FEHLER beim Parsen: $jsonPath - $_" continue } # ExifTool & NTFS-Zeit setzen try { & $exiftoolPath ` -overwrite_original ` -Description="$description" ` -EXIF:DateTimeOriginal="$dateTaken" ` -QuickTime:CreateDate="$dateTaken" ` -QuickTime:ModifyDate="$dateTaken" ` -XMP:CreateDate="$dateTaken" ` -XMP:ModifyDate="$dateTaken" ` -XMP:DateTimeOriginal="$dateTaken" ` -filemodifydate="$dateTaken" ` -filecreatedate="$dateTaken" ` "$($file.FullName)" | Out-Null if ($dateTaken) { $escapedPath = $file.FullName.Replace("'", "''") $cmd = "powershell -NoLogo -NoProfile -Command ""(Get-Item '$escapedPath').LastWriteTimeUtc = [datetime]::ParseExact('$dateTaken', 'yyyy:MM:dd HH:mm:ss', `$null)""" Start-Process -FilePath 'cmd.exe' -ArgumentList "/c $cmd" -NoNewWindow -Wait Add-Content $logPath "Metadaten & NTFS-Zeit geschrieben: $($file.FullName)" } else { Add-Content $logPath "FEHLER: Kein gültiges Datum für NTFS-Zeit bei: $($file.FullName)" } } catch { Add-Content $logPath "FEHLER bei Metadaten/NTFS-Zeit: $($file.FullName) - $_" } } [console]::beep(500,200)