Last active
July 10, 2025 21:08
-
-
Save nonodev96/79e912c665cf08ce755c2e7ad5220595 to your computer and use it in GitHub Desktop.
google photos takeout merge metadata
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function Invoke-MetadataSync { | |
| param ( | |
| [Parameter(Mandatory)] | |
| [string]$folder | |
| ) | |
| # Extensiones válidas (en minúsculas) | |
| $extensions = @(".jpg", ".jpeg", ".png", ".gif", ".heic", ".mp4", ".mov", ".webp") | |
| $notMatched = @() | |
| # Obtener todos los JSON del directorio (una sola vez) | |
| $jsonFiles = Get-ChildItem -Path $folder -Filter *.json | |
| # Obtener todos los archivos multimedia válidos (case-insensitive) | |
| $mediaFiles = Get-ChildItem -Path $folder -File | Where-Object { | |
| $extensions -contains $_.Extension.ToLower() | |
| } | |
| function Get-CleanBaseName { | |
| param ($name) | |
| # Elimina sufijos como "-ha editado", "(1)", "~3" | |
| $name = $name -replace '-ha editado', '' | |
| $name = $name -replace '\(\d+\)', '' | |
| $name = $name -replace '~\d+', '' | |
| return $name.Trim() | |
| } | |
| foreach ($mediaFile in $mediaFiles) { | |
| $fileName = $mediaFile.Name | |
| $cleanBase = Get-CleanBaseName $mediaFile.BaseName | |
| # Buscar todos los JSON cuyo nombre base empiece por los primeros 30 caracteres del nombre limpio | |
| $jsonMatches = $jsonFiles | Where-Object { | |
| $_.BaseName.StartsWith($cleanBase.Substring(0, [Math]::Min(30, $cleanBase.Length))) | |
| } | |
| if ($jsonMatches.Count -gt 0) { | |
| # Elegir el que tenga el nombre más largo (mejor coincidencia) | |
| $bestJson = $jsonMatches | Sort-Object { - ($_.BaseName.Length) } | Select-Object -First 1 | |
| & exiftool.exe ` | |
| -d %s ` | |
| -tagsFromFile "$($bestJson.FullName)" ` | |
| "-DateTimeOriginal<PhotoTakenTimeTimestamp" ` | |
| "-CreateDate<PhotoTakenTimeTimestamp" ` | |
| "-MediaCreateDate<PhotoTakenTimeTimestamp" ` | |
| "-TrackCreateDate<PhotoTakenTimeTimestamp" ` | |
| "-FileCreateDate<PhotoTakenTimeTimestamp" ` | |
| "-FileModifyDate<PhotoTakenTimeTimestamp" ` | |
| -overwrite_original ` | |
| -progress "$($mediaFile.FullName)" | |
| Write-Host "👌 YES | JSON found for [$fileName] -> [$($bestJson.Name)]" | |
| } | |
| else { | |
| Write-Host "❌ NO | JSON not found for [$fileName]" | |
| $notMatched += $mediaFile | |
| } | |
| } | |
| # Mostrar los archivos no emparejados | |
| if ($notMatched.Count -gt 0) { | |
| Write-Host "`nLos siguientes archivos no se pudieron emparejar con un JSON:" | |
| $notMatched | ForEach-Object { Write-Host $_.Name } | |
| } | |
| else { | |
| Write-Host "`nTodos los archivos se emparejaron correctamente." | |
| } | |
| } | |
| $root = ".\Takeout\Google Fotos\Photos from 2019" | |
| Invoke-MetadataSync -folder $root | |
| $dirs = Get-ChildItem -Path $root -Directory -Recurse | |
| foreach ($dir in $dirs) { | |
| Write-Host "📁 $($dir.FullName)" | |
| Invoke-MetadataSync -folder $dir.FullName | |
| } | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function Invoke-FileTypeInfo { | |
| param ( | |
| [string]$Folder = ".", | |
| [switch]$Recurse = $true | |
| ) | |
| if (-not (Get-Command exiftool -ErrorAction SilentlyContinue)) { | |
| Write-Error "❌ exiftool no está instalado o no está en el PATH." | |
| return | |
| } | |
| # Mapeo de tipos reales a extensiones estándar | |
| $typeToExtension = @{ | |
| "JPEG" = ".jpg" | |
| "PNG" = ".png" | |
| "WEBP" = ".webp" | |
| "GIF" = ".gif" | |
| "MP4" = ".mp4" | |
| "MOV" = ".mov" | |
| } | |
| $files = Get-ChildItem -Path $Folder -File -Recurse:$Recurse | |
| $changed = @() | |
| foreach ($file in $files) { | |
| $ext = $file.Extension.ToLower() | |
| $actualType = & exiftool -s3 -FileType "$($file.FullName)" 2>$null | |
| if ($actualType) { | |
| $actualType = $actualType.ToUpper() | |
| $actualType = $actualType.Trim() | |
| } else { | |
| continue # Si no se puede detectar el tipo, saltamos | |
| } | |
| if ($typeToExtension.ContainsKey($actualType)) { | |
| $expectedExt = $typeToExtension[$actualType] | |
| if ($expectedExt -ne $ext) { | |
| # Rutas de las subcarpetas | |
| $tmpDir = Join-Path $file.DirectoryName "tmp" | |
| $badDir = Join-Path $file.DirectoryName "original_bad_extension" | |
| # Crear las carpetas si no existen | |
| foreach ($dir in @($tmpDir, $badDir)) { | |
| if (-not (Test-Path $dir)) { | |
| New-Item -ItemType Directory -Path $dir | Out-Null | |
| } | |
| } | |
| # Crear copia corregida en carpeta tmp | |
| $newFileName = [System.IO.Path]::ChangeExtension($file.BaseName, $expectedExt) | |
| $destTmpFile = Join-Path $tmpDir $newFileName | |
| Copy-Item -Path $file.FullName -Destination $destTmpFile -Force | |
| Write-Host "✅ Copied: '$($file.Name)' -> 'tmp\$newFileName'" -ForegroundColor Green | |
| # Mover original a carpeta original_bad_extension | |
| $destBadFile = Join-Path $badDir $file.Name | |
| Move-Item -Path $file.FullName -Destination $destBadFile -Force | |
| Write-Host "📦 Moved original to: 'original_bad_extension\$($file.Name)'" -ForegroundColor Yellow | |
| $changed += $destTmpFile | |
| } | |
| } | |
| } | |
| Write-Host "`n📁 Procesados: $($files.Count) archivos en '$Folder'" | |
| if ($changed.Count -gt 0) { | |
| Write-Host "📌 Archivos corregidos copiados en 'tmp', originales movidos a 'original_bad_extension':" | |
| $changed | ForEach-Object { Write-Host " $_" } | |
| } else { | |
| Write-Host "✔️ No se detectaron inconsistencias." | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment