<#
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)
Comments
0 B
|👍
/👎