Skip to content

Instantly share code, notes, and snippets.

@nonodev96
Last active July 10, 2025 21:08
Show Gist options
  • Select an option

  • Save nonodev96/79e912c665cf08ce755c2e7ad5220595 to your computer and use it in GitHub Desktop.

Select an option

Save nonodev96/79e912c665cf08ce755c2e7ad5220595 to your computer and use it in GitHub Desktop.
google photos takeout merge metadata
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
}
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