nurjns icon

Google Fotos Takeout ExifTool Metadata Script

nurjns | PRO | 01/15/26 01:45:47 PM UTC (Edited) | 0 ⭐ | 6355 👁️ | Never ⏰ | []
PowerShell |

6.72 KB

|

None

|

0 👍

/

0 👎

<#
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