code.pablomurad.com

fast-scripts

28 double-click Windows batch tools for video, audio and files

first commit
author
Pablo Murad
date
commit
4cc24033f9f7af95a0a2a0e504ff61b89eba05bb
parent
1fffbaba5e7e56b64d1082b038429d835dcfdaa1
changes
25 files +1582 −14
@@ -68,6 +68,8 @@ # ------------------------------------------------------------ # 1) Verificação de dependências
 # ------------------------------------------------------------
 Titulo 'Converter Imagens'
+Write-Host '  Desenvolvido por Pablo Murad - 2026' -ForegroundColor DarkGray
+Write-Host ''
 Write-Host 'Verificando o que é necessário...' -ForegroundColor Gray
 
 $magick = Get-Command magick -ErrorAction SilentlyContinue
@@ -0,0 +1,104 @@+<# :
+@echo off
+powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "Invoke-Expression ([System.IO.File]::ReadAllText('%~f0',[System.Text.Encoding]::UTF8))"
+exit /b %errorlevel%
+#>
+
+# ============================================================
+#  Imagens e PDF - junta imagens em PDF, ou PDF em imagens (ImageMagick)
+#  Desenvolvido por Pablo Murad - 2026
+# ============================================================
+
+try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
+try { $Host.UI.RawUI.WindowTitle = 'Imagens e PDF' } catch {}
+
+function Pausar { Write-Host ''; Write-Host 'Pressione Enter para fechar...' -ForegroundColor DarkGray; [void][System.Console]::ReadLine() }
+function Titulo($t) {
+    Write-Host ''
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+    Write-Host "  $t" -ForegroundColor Cyan
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+}
+
+$extImg = @('.jpg','.jpeg','.png','.webp','.bmp','.tif','.tiff')
+
+Titulo 'Imagens e PDF'
+Write-Host '  Desenvolvido por Pablo Murad - 2026' -ForegroundColor DarkGray
+Write-Host ''
+Write-Host 'Verificando o que é necessário...' -ForegroundColor Gray
+$magick = Get-Command magick -ErrorAction SilentlyContinue
+if (-not $magick) { Write-Host ''; Write-Host '[ERRO] ImageMagick não encontrado.' -ForegroundColor Red; Write-Host 'Instale com:  winget install ImageMagick.ImageMagick' -ForegroundColor White; Pausar; exit 1 }
+Write-Host "[OK] ImageMagick: $($magick.Source)" -ForegroundColor Green
+
+Titulo 'O que você quer fazer?'
+Write-Host '  [1] Imagens -> PDF   (junta todas as imagens de uma pasta num PDF)' -ForegroundColor White
+Write-Host '  [2] PDF -> Imagens   (salva cada página como imagem)' -ForegroundColor White
+$acao = $null
+while (-not $acao) { $a = Read-Host 'Opção (1-2)'; switch ($a.Trim()) { '1'{$acao='img2pdf'} '2'{$acao='pdf2img'} default{Write-Host '[!] Digite 1 ou 2.' -ForegroundColor Yellow} } }
+
+if ($acao -eq 'img2pdf') {
+    # ---- Imagens -> PDF ----
+    $pasta = $null
+    while (-not $pasta) {
+        Titulo 'Pasta com as imagens'
+        $p = (Read-Host 'Cole o caminho da pasta').Trim().Trim('"')
+        if (Test-Path -LiteralPath $p -PathType Container) { $pasta = (Resolve-Path -LiteralPath $p).Path } else { Write-Host '[!] Pasta não encontrada.' -ForegroundColor Yellow }
+    }
+    $imagens = @(Get-ChildItem -LiteralPath $pasta -File | Where-Object { $extImg -contains $_.Extension.ToLowerInvariant() } | Sort-Object Name)
+    if ($imagens.Count -eq 0) { Write-Host 'Nenhuma imagem nessa pasta.' -ForegroundColor Yellow; Pausar; exit 0 }
+    Write-Host ''
+    Write-Host "$($imagens.Count) imagens encontradas (em ordem alfabética)." -ForegroundColor Cyan
+    $nome = (Read-Host 'Nome do PDF de saída (Enter = saida.pdf)').Trim()
+    if ([string]::IsNullOrWhiteSpace($nome)) { $nome = 'saida.pdf' }
+    if ($nome -notmatch '\.pdf$') { $nome += '.pdf' }
+    $pdf = Join-Path $pasta $nome
+    Write-Host ''; Write-Host "Gerando: $pdf ..." -ForegroundColor Cyan
+    $imgArgs = @($imagens.FullName) + @('-auto-orient', $pdf)
+    & magick @imgArgs
+    if ($LASTEXITCODE -eq 0 -and (Test-Path -LiteralPath $pdf)) {
+        Titulo 'Concluído'
+        Write-Host "[OK] PDF criado com $($imagens.Count) páginas." -ForegroundColor Green
+        Write-Host "Arquivo: $pdf"
+    } else { Write-Host '[ERRO] Falha ao gerar o PDF.' -ForegroundColor Red }
+    Pausar; exit 0
+}
+
+# ---- PDF -> Imagens ----
+$pdfAlvo = $null; $modoArquivo = $false
+while (-not $pdfAlvo) {
+    Titulo 'PDF de entrada'
+    $p = (Read-Host 'Cole o caminho de um PDF ou de uma pasta com PDFs').Trim().Trim('"')
+    if ((Test-Path -LiteralPath $p -PathType Leaf) -and ($p -match '\.pdf$')) { $pdfAlvo = (Resolve-Path -LiteralPath $p).Path; $modoArquivo = $true }
+    elseif (Test-Path -LiteralPath $p -PathType Container) { $pdfAlvo = (Resolve-Path -LiteralPath $p).Path; $modoArquivo = $false }
+    else { Write-Host '[!] Caminho inválido (informe um .pdf ou uma pasta).' -ForegroundColor Yellow }
+}
+Titulo 'Formato das imagens'
+Write-Host '  [1] PNG (sem perdas)   [2] JPG (menor)' -ForegroundColor Gray
+$fmt = Read-Host 'Formato (Enter = 1)'
+$extSaida = if ($fmt.Trim() -eq '2') { '.jpg' } else { '.png' }
+$dpiEnt = Read-Host 'Qualidade em DPI (Enter = 150; 300 = alta)'
+$dpi = if (($dpiEnt.Trim()) -and ($dpiEnt -as [int])) { [int]$dpiEnt } else { 150 }
+
+if ($modoArquivo) { $pdfs = @(Get-Item -LiteralPath $pdfAlvo) } else { $pdfs = @(Get-ChildItem -LiteralPath $pdfAlvo -File | Where-Object { $_.Extension.ToLowerInvariant() -eq '.pdf' }) }
+if ($pdfs.Count -eq 0) { Write-Host 'Nenhum PDF encontrado.' -ForegroundColor Yellow; Pausar; exit 0 }
+
+Titulo 'Processando'
+$ok=0;$err=0
+foreach ($pf in $pdfs) {
+    $outDir = Join-Path $pf.DirectoryName ($pf.BaseName + '-imagens')
+    New-Item -ItemType Directory -Path $outDir -Force | Out-Null
+    $padraoSaida = Join-Path $outDir ('pagina-%03d' + $extSaida)
+    Write-Host ''; Write-Host $pf.Name -ForegroundColor Magenta
+    $imgArgs = @('-density',"$dpi",$pf.FullName)
+    if ($extSaida -eq '.jpg') { $imgArgs += @('-quality','90','-background','white','-alpha','remove') }
+    $imgArgs += $padraoSaida
+    & magick @imgArgs
+    if ($LASTEXITCODE -eq 0) {
+        $qtd = @(Get-ChildItem -LiteralPath $outDir -File).Count
+        Write-Host "   [OK] $qtd página(s) -> $outDir" -ForegroundColor Green; $ok++
+    } else { Write-Host '   [ERRO] Falha ao converter.' -ForegroundColor Red; $err++ }
+}
+Titulo 'Concluído'
+Write-Host "PDFs convertidos: $ok" -ForegroundColor Green
+Write-Host "Erros: $err" -ForegroundColor $(if ($err -gt 0){'Red'}else{'Gray'})
+Pausar
@@ -0,0 +1,134 @@+<# :
+@echo off
+powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "Invoke-Expression ([System.IO.File]::ReadAllText('%~f0',[System.Text.Encoding]::UTF8))"
+exit /b %errorlevel%
+#>
+
+# ============================================================
+#  Marca d'água - aplica texto ou logo em imagens (ImageMagick)
+#  Desenvolvido por Pablo Murad - 2026
+# ============================================================
+
+try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
+try { $Host.UI.RawUI.WindowTitle = "Marca d'água" } catch {}
+
+function Pausar { Write-Host ''; Write-Host 'Pressione Enter para fechar...' -ForegroundColor DarkGray; [void][System.Console]::ReadLine() }
+function Titulo($t) {
+    Write-Host ''
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+    Write-Host "  $t" -ForegroundColor Cyan
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+}
+
+$configDir  = Join-Path $env:APPDATA 'MarcaDagua'
+$configFile = Join-Path $configDir 'config.json'
+function Carregar-Config { if (Test-Path -LiteralPath $configFile) { try { return Get-Content -LiteralPath $configFile -Raw -Encoding UTF8 | ConvertFrom-Json } catch {} }; return $null }
+function Salvar-Config($o) { try { New-Item -ItemType Directory -Path $configDir -Force | Out-Null; $o | ConvertTo-Json | Set-Content -LiteralPath $configFile -Encoding UTF8 } catch {} }
+
+$extensoes = @('.jpg','.jpeg','.png','.webp','.bmp','.tif','.tiff')
+$posicoes = @(
+    [pscustomobject]@{ Id=1; Nome='Inferior direita';  Gravity='SouthEast' },
+    [pscustomobject]@{ Id=2; Nome='Inferior esquerda'; Gravity='SouthWest' },
+    [pscustomobject]@{ Id=3; Nome='Superior direita';  Gravity='NorthEast' },
+    [pscustomobject]@{ Id=4; Nome='Superior esquerda'; Gravity='NorthWest' },
+    [pscustomobject]@{ Id=5; Nome='Centro';            Gravity='Center'    }
+)
+
+Titulo "Marca d'água"
+Write-Host '  Desenvolvido por Pablo Murad - 2026' -ForegroundColor DarkGray
+Write-Host ''
+Write-Host 'Verificando o que é necessário...' -ForegroundColor Gray
+$magick = Get-Command magick -ErrorAction SilentlyContinue
+if (-not $magick) { Write-Host ''; Write-Host '[ERRO] ImageMagick não encontrado.' -ForegroundColor Red; Write-Host 'Instale com:  winget install ImageMagick.ImageMagick' -ForegroundColor White; Pausar; exit 1 }
+Write-Host "[OK] ImageMagick: $($magick.Source)" -ForegroundColor Green
+$config = Carregar-Config
+
+# Pasta ou imagem
+$entradaAtual = if ($config -and $config.UltimaEntrada) { $config.UltimaEntrada } else { Join-Path $env:USERPROFILE 'Downloads\Imagens' }
+$modoArquivo = $false; $arquivoUnico = $null
+while ($true) {
+    Titulo 'Pasta ou imagem'
+    Write-Host "  $entradaAtual" -ForegroundColor White
+    Write-Host 'Enter = confirmar | ou cole uma PASTA ou uma IMAGEM' -ForegroundColor DarkGray
+    $resp = Read-Host 'Pasta/imagem'
+    if (-not [string]::IsNullOrWhiteSpace($resp)) { $entradaAtual = $resp.Trim().Trim('"') }
+    if (Test-Path -LiteralPath $entradaAtual -PathType Leaf) {
+        if ($extensoes -notcontains [System.IO.Path]::GetExtension($entradaAtual).ToLowerInvariant()) { Write-Host '[!] Não é uma imagem suportada.' -ForegroundColor Yellow; continue }
+        $modoArquivo = $true; $arquivoUnico = (Resolve-Path -LiteralPath $entradaAtual).Path; break
+    } elseif (Test-Path -LiteralPath $entradaAtual -PathType Container) { $modoArquivo = $false; break }
+    Write-Host '[!] Caminho não encontrado.' -ForegroundColor Yellow
+}
+if ($modoArquivo) { $arquivoObj = Get-Item -LiteralPath $arquivoUnico; $origem = $arquivoObj.DirectoryName } else { $origem = (Resolve-Path -LiteralPath $entradaAtual).Path }
+$destino = Join-Path $origem 'ComMarca'
+
+# Tipo de marca
+Titulo "Tipo de marca d'água"
+Write-Host '  [1] Texto' -ForegroundColor White
+Write-Host '  [2] Logo (arquivo PNG, de preferência com fundo transparente)' -ForegroundColor White
+$tipo = $null
+while (-not $tipo) { $t = Read-Host 'Tipo (1-2)'; switch ($t.Trim()) { '1'{$tipo='texto'} '2'{$tipo='logo'} default{Write-Host '[!] Digite 1 ou 2.' -ForegroundColor Yellow} } }
+
+$texto = $null; $logo = $null; $pct = 20
+if ($tipo -eq 'texto') {
+    while ([string]::IsNullOrWhiteSpace($texto)) { $texto = Read-Host 'Texto da marca' }
+} else {
+    while (-not $logo) {
+        $l = (Read-Host 'Caminho do logo (PNG)').Trim().Trim('"')
+        if (Test-Path -LiteralPath $l -PathType Leaf) { $logo = (Resolve-Path -LiteralPath $l).Path } else { Write-Host '[!] Arquivo não encontrado.' -ForegroundColor Yellow }
+    }
+    $p = Read-Host 'Tamanho do logo em % da largura (Enter = 20)'
+    if (-not [string]::IsNullOrWhiteSpace($p) -and ($p -as [int])) { $pct = [int]$p }
+}
+
+# Posição
+Titulo 'Posição'
+foreach ($ps in $posicoes) { Write-Host ("  [{0}] {1}" -f $ps.Id,$ps.Nome) -ForegroundColor Gray }
+$posicao = $null
+while (-not $posicao) { $e = Read-Host 'Posição (1-5, Enter = 1)'; if ([string]::IsNullOrWhiteSpace($e)) { $e = '1' }; $posicao = $posicoes | Where-Object { $_.Id -eq ($e -as [int]) } | Select-Object -First 1; if (-not $posicao) { Write-Host '[!] Inválido.' -ForegroundColor Yellow } }
+$grav = $posicao.Gravity
+$offset = if ($grav -eq 'Center') { '+0+0' } else { '+25+25' }
+
+Salvar-Config ([pscustomobject]@{ UltimaEntrada=$origem; UltimoTipo=$tipo; UltimoTexto=$texto; UltimoLogo=$logo; UltimaPosicao=$posicao.Id })
+
+# Coleta
+if ($modoArquivo) { $arquivos = @($arquivoObj) } else {
+    $arquivos = @(Get-ChildItem -LiteralPath $origem -Recurse -File -ErrorAction SilentlyContinue | Where-Object {
+        ($extensoes -contains $_.Extension.ToLowerInvariant()) -and (-not $_.FullName.StartsWith($destino,[System.StringComparison]::OrdinalIgnoreCase)) })
+}
+Titulo 'Processando'
+Write-Host "Marca: $tipo   |   Posição: $($posicao.Nome)   |   Destino: $destino"
+Write-Host "Imagens encontradas: $($arquivos.Count)" -ForegroundColor Cyan
+if ($arquivos.Count -eq 0) { Write-Host 'Nenhuma imagem encontrada.' -ForegroundColor Yellow; Pausar; exit 0 }
+New-Item -ItemType Directory -Path $destino -Force | Out-Null
+
+$ok=0;$ign=0;$err=0;$i=0
+foreach ($a in $arquivos) {
+    $i++
+    $rel = $a.FullName.Substring($origem.Length).TrimStart('\'); $sub = Split-Path $rel -Parent
+    $pastaSaida = if ([string]::IsNullOrWhiteSpace($sub)) { $destino } else { Join-Path $destino $sub }
+    New-Item -ItemType Directory -Path $pastaSaida -Force | Out-Null
+    $saida = Join-Path $pastaSaida $a.Name
+    Write-Host ''; Write-Host ("[{0}/{1}] {2}" -f $i,$arquivos.Count,$a.Name) -ForegroundColor Magenta
+    if (Test-Path -LiteralPath $saida) { Write-Host '   [IGNORADO] Já existe.' -ForegroundColor Yellow; $ign++; continue }
+
+    if ($tipo -eq 'texto') {
+        $w = [int](& magick identify -format '%w' "$($a.FullName)[0]" 2>$null)
+        if (-not $w -or $w -le 0) { $w = 1000 }
+        $ps = [int]($w / 22); if ($ps -lt 12) { $ps = 12 }
+        $imgArgs = @($a.FullName,'-gravity',$grav,'-pointsize',"$ps",'-fill','rgba(255,255,255,0.85)','-undercolor','rgba(0,0,0,0.35)','-annotate',$offset,$texto,$saida)
+    } else {
+        $w = [int](& magick identify -format '%w' "$($a.FullName)[0]" 2>$null)
+        if (-not $w -or $w -le 0) { $w = 1000 }
+        $lw = [int]($w * $pct / 100); if ($lw -lt 10) { $lw = 10 }
+        $imgArgs = @($a.FullName,'(',$logo,'-resize',"${lw}x",')','-gravity',$grav,'-geometry',$offset,'-composite',$saida)
+    }
+    & magick @imgArgs
+    if ($LASTEXITCODE -eq 0 -and (Test-Path -LiteralPath $saida)) { Write-Host '   [OK] Marca aplicada.' -ForegroundColor Green; $ok++ }
+    else { Write-Host '   [ERRO] Falha.' -ForegroundColor Red; if (Test-Path -LiteralPath $saida) { Remove-Item -LiteralPath $saida -Force -ErrorAction SilentlyContinue }; $err++ }
+}
+Titulo 'Concluído'
+Write-Host "Aplicadas: $ok" -ForegroundColor Green
+Write-Host "Ignoradas: $ign" -ForegroundColor Yellow
+Write-Host "Erros:     $err" -ForegroundColor $(if ($err -gt 0){'Red'}else{'Gray'})
+Write-Host "Destino:   $destino"
+Pausar
@@ -0,0 +1,108 @@+<# :
+@echo off
+powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "Invoke-Expression ([System.IO.File]::ReadAllText('%~f0',[System.Text.Encoding]::UTF8))"
+exit /b %errorlevel%
+#>
+
+# ============================================================
+#  Achar duplicados - encontra arquivos iguais por conteúdo (hash)
+#  Desenvolvido por Pablo Murad - 2026
+# ============================================================
+
+try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
+try { $Host.UI.RawUI.WindowTitle = 'Achar duplicados' } catch {}
+
+function Pausar { Write-Host ''; Write-Host 'Pressione Enter para fechar...' -ForegroundColor DarkGray; [void][System.Console]::ReadLine() }
+function Titulo($t) {
+    Write-Host ''
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+    Write-Host "  $t" -ForegroundColor Cyan
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+}
+function Tam($bytes) { if ($bytes -ge 1GB) { '{0:N2} GB' -f ($bytes/1GB) } elseif ($bytes -ge 1MB) { '{0:N1} MB' -f ($bytes/1MB) } else { '{0:N0} KB' -f ($bytes/1KB) } }
+
+Titulo 'Achar duplicados'
+Write-Host '  Desenvolvido por Pablo Murad - 2026' -ForegroundColor DarkGray
+
+$pasta = $null
+while (-not $pasta) {
+    Write-Host ''
+    $p = (Read-Host 'Cole o caminho da pasta (verifica também as subpastas)').Trim().Trim('"')
+    if (Test-Path -LiteralPath $p -PathType Container) { $pasta = (Resolve-Path -LiteralPath $p).Path } else { Write-Host '[!] Pasta não encontrada.' -ForegroundColor Yellow }
+}
+
+Write-Host ''
+Write-Host 'Analisando... (agrupa por tamanho e depois compara o conteúdo)' -ForegroundColor Gray
+$todos = @(Get-ChildItem -LiteralPath $pasta -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_.Length -gt 0 })
+
+# 1) agrupa por tamanho (rápido); 2) só calcula hash de quem tem tamanho repetido
+$porTamanho = $todos | Group-Object Length | Where-Object { $_.Count -gt 1 }
+$grupos = @()
+foreach ($g in $porTamanho) {
+    $porHash = $g.Group | Group-Object { (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash }
+    foreach ($h in ($porHash | Where-Object { $_.Count -gt 1 })) {
+        $grupos += ,@($h.Group | Sort-Object FullName)
+    }
+}
+
+if ($grupos.Count -eq 0) { Titulo 'Resultado'; Write-Host 'Nenhum arquivo duplicado encontrado.' -ForegroundColor Green; Pausar; exit 0 }
+
+$totalDup = 0; $espaco = 0
+foreach ($grp in $grupos) { $totalDup += ($grp.Count - 1); $espaco += ($grp.Count - 1) * $grp[0].Length }
+
+Titulo 'Duplicados encontrados'
+Write-Host ("Grupos de iguais: {0}   |   Cópias extras: {1}   |   Espaço recuperável: {2}" -f $grupos.Count, $totalDup, (Tam $espaco)) -ForegroundColor Cyan
+Write-Host ''
+$mostra = [Math]::Min($grupos.Count, 15)
+for ($i=0; $i -lt $mostra; $i++) {
+    $grp = $grupos[$i]
+    Write-Host ("Grupo {0} ({1} cópias, {2} cada):" -f ($i+1), $grp.Count, (Tam $grp[0].Length)) -ForegroundColor White
+    for ($j=0; $j -lt $grp.Count; $j++) {
+        $marca = if ($j -eq 0) { '[manter] ' } else { '[extra]  ' }
+        $cor   = if ($j -eq 0) { 'Green' } else { 'DarkGray' }
+        Write-Host ("   $marca" + $grp[$j].FullName) -ForegroundColor $cor
+    }
+}
+if ($grupos.Count -gt $mostra) { Write-Host ("... e mais {0} grupo(s)." -f ($grupos.Count - $mostra)) -ForegroundColor DarkGray }
+
+Titulo 'O que fazer com as cópias extras?'
+Write-Host '  [1] Só listar (nada é alterado) - padrão' -ForegroundColor White
+Write-Host '  [2] Mover as cópias extras para uma subpasta "_Duplicados"' -ForegroundColor White
+Write-Host '  [3] Apagar as cópias extras (mantém sempre 1 original)' -ForegroundColor White
+$acao = Read-Host 'Opção (Enter = 1)'
+if ([string]::IsNullOrWhiteSpace($acao)) { $acao = '1' }
+
+if ($acao -eq '1') { Write-Host ''; Write-Host 'Nada foi alterado. Relatório acima.' -ForegroundColor Gray; Pausar; exit 0 }
+
+if ($acao -eq '3') {
+    Write-Host ''
+    Write-Host 'ATENÇÃO: isto APAGA as cópias extras permanentemente.' -ForegroundColor Red
+    $conf = Read-Host 'Digite APAGAR para confirmar'
+    if ($conf -cne 'APAGAR') { Write-Host 'Cancelado.' -ForegroundColor Yellow; Pausar; exit 0 }
+}
+
+$feitos=0;$err=0
+$destDup = Join-Path $pasta '_Duplicados'
+foreach ($grp in $grupos) {
+    for ($j=1; $j -lt $grp.Count; $j++) {
+        $f = $grp[$j]
+        try {
+            if ($acao -eq '2') {
+                New-Item -ItemType Directory -Path $destDup -Force | Out-Null
+                $base=[System.IO.Path]::GetFileNameWithoutExtension($f.Name); $ext=$f.Extension
+                $dest=Join-Path $destDup $f.Name; $n=1
+                while (Test-Path -LiteralPath $dest) { $dest=Join-Path $destDup ("$base ($n)$ext"); $n++ }
+                Move-Item -LiteralPath $f.FullName -Destination $dest -ErrorAction Stop
+            } else {
+                Remove-Item -LiteralPath $f.FullName -Force -ErrorAction Stop
+            }
+            $feitos++
+        } catch { Write-Host "   [ERRO] $($f.Name): $($_.Exception.Message)" -ForegroundColor Red; $err++ }
+    }
+}
+Titulo 'Concluído'
+$verbo = if ($acao -eq '2') { 'Movidas' } else { 'Apagadas' }
+Write-Host "$verbo`: $feitos cópia(s) extra(s)" -ForegroundColor Green
+Write-Host "Erros:   $err" -ForegroundColor $(if ($err -gt 0){'Red'}else{'Gray'})
+if ($acao -eq '2') { Write-Host "Movidos para: $destDup" }
+Pausar
@@ -0,0 +1,83 @@+<# :
+@echo off
+powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "Invoke-Expression ([System.IO.File]::ReadAllText('%~f0',[System.Text.Encoding]::UTF8))"
+exit /b %errorlevel%
+#>
+
+# ============================================================
+#  Organizar por tipo - separa arquivos em subpastas por categoria
+#  Desenvolvido por Pablo Murad - 2026
+# ============================================================
+
+try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
+try { $Host.UI.RawUI.WindowTitle = 'Organizar por tipo' } catch {}
+
+function Pausar { Write-Host ''; Write-Host 'Pressione Enter para fechar...' -ForegroundColor DarkGray; [void][System.Console]::ReadLine() }
+function Titulo($t) {
+    Write-Host ''
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+    Write-Host "  $t" -ForegroundColor Cyan
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+}
+function Nome-Livre($dir,$nome) {
+    $base=[System.IO.Path]::GetFileNameWithoutExtension($nome); $ext=[System.IO.Path]::GetExtension($nome)
+    $t=Join-Path $dir $nome; $n=1
+    while (Test-Path -LiteralPath $t) { $t=Join-Path $dir ("$base ($n)$ext"); $n++ }
+    return $t
+}
+
+# Mapa extensão -> categoria
+$categorias = [ordered]@{
+    'Imagens'     = @('.jpg','.jpeg','.png','.gif','.webp','.bmp','.tif','.tiff','.heic','.heif','.svg','.raw','.cr2','.nef')
+    'Vídeos'      = @('.mp4','.mkv','.avi','.mov','.wmv','.flv','.webm','.m4v','.mpg','.mpeg','.ts','.3gp','.vob')
+    'Áudio'       = @('.mp3','.m4a','.wav','.flac','.aac','.ogg','.wma','.opus')
+    'Documentos'  = @('.pdf','.doc','.docx','.xls','.xlsx','.ppt','.pptx','.txt','.odt','.ods','.csv','.rtf','.md','.epub')
+    'Compactados' = @('.zip','.rar','.7z','.tar','.gz','.bz2','.xz')
+    'Programas'   = @('.exe','.msi','.bat','.cmd','.ps1')
+}
+
+Titulo 'Organizar por tipo'
+Write-Host '  Desenvolvido por Pablo Murad - 2026' -ForegroundColor DarkGray
+
+$pasta = $null
+while (-not $pasta) {
+    Write-Host ''
+    $p = (Read-Host 'Cole o caminho da pasta a organizar').Trim().Trim('"')
+    if (Test-Path -LiteralPath $p -PathType Container) { $pasta = (Resolve-Path -LiteralPath $p).Path } else { Write-Host '[!] Pasta não encontrada.' -ForegroundColor Yellow }
+}
+
+# Só arquivos no primeiro nível (não entra em subpastas)
+$nomesCategorias = @($categorias.Keys) + 'Outros'
+$arquivos = @(Get-ChildItem -LiteralPath $pasta -File)
+if ($arquivos.Count -eq 0) { Write-Host 'Nenhum arquivo solto nessa pasta.' -ForegroundColor Yellow; Pausar; exit 0 }
+
+# Descobre a categoria de cada arquivo
+function Categoria-De($ext) {
+    foreach ($cat in $categorias.Keys) { if ($categorias[$cat] -contains $ext) { return $cat } }
+    return 'Outros'
+}
+$planos = foreach ($a in $arquivos) {
+    [pscustomobject]@{ Arquivo=$a; Categoria=(Categoria-De $a.Extension.ToLowerInvariant()) }
+}
+
+Titulo 'Prévia'
+$planos | Group-Object Categoria | Sort-Object Name | ForEach-Object {
+    Write-Host ("  {0,-14} {1} arquivo(s)" -f $_.Name, $_.Count) -ForegroundColor Gray
+}
+Write-Host ''
+$ok = Read-Host "Mover $($arquivos.Count) arquivo(s) para subpastas por categoria? (s/N)"
+if ($ok -notmatch '^(s|sim|y|yes)$') { Write-Host 'Cancelado.' -ForegroundColor Yellow; Pausar; exit 0 }
+
+$movidos=0;$err=0
+foreach ($pl in $planos) {
+    $destPasta = Join-Path $pasta $pl.Categoria
+    New-Item -ItemType Directory -Path $destPasta -Force | Out-Null
+    $destino = Nome-Livre $destPasta $pl.Arquivo.Name
+    try { Move-Item -LiteralPath $pl.Arquivo.FullName -Destination $destino -ErrorAction Stop; $movidos++ }
+    catch { Write-Host "   [ERRO] $($pl.Arquivo.Name): $($_.Exception.Message)" -ForegroundColor Red; $err++ }
+}
+Titulo 'Concluído'
+Write-Host "Movidos: $movidos" -ForegroundColor Green
+Write-Host "Erros:   $err" -ForegroundColor $(if ($err -gt 0){'Red'}else{'Gray'})
+Write-Host "Pasta:   $pasta"
+Pausar
@@ -0,0 +1,144 @@+<# :
+@echo off
+powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "Invoke-Expression ([System.IO.File]::ReadAllText('%~f0',[System.Text.Encoding]::UTF8))"
+exit /b %errorlevel%
+#>
+
+# ============================================================
+#  Renomear em lote - por sequência, data ou localizar/substituir
+#  Também organiza fotos/arquivos por data em pastas
+#  Desenvolvido por Pablo Murad - 2026
+# ============================================================
+
+try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
+try { $Host.UI.RawUI.WindowTitle = 'Renomear em lote' } catch {}
+
+function Pausar { Write-Host ''; Write-Host 'Pressione Enter para fechar...' -ForegroundColor DarkGray; [void][System.Console]::ReadLine() }
+function Titulo($t) {
+    Write-Host ''
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+    Write-Host "  $t" -ForegroundColor Cyan
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+}
+function Nome-Livre($dir,$nome,$reservados) {
+    $base=[System.IO.Path]::GetFileNameWithoutExtension($nome); $ext=[System.IO.Path]::GetExtension($nome)
+    $t=Join-Path $dir $nome; $n=1
+    while ((Test-Path -LiteralPath $t) -or ($reservados -contains $t)) { $t=Join-Path $dir ("$base ($n)$ext"); $n++ }
+    return $t
+}
+
+$magick = Get-Command magick -ErrorAction SilentlyContinue
+$extImgExif = @('.jpg','.jpeg','.tif','.tiff','.heic','.heif','.png','.webp')
+function Data-Do-Arquivo($file) {
+    if ($magick -and ($extImgExif -contains $file.Extension.ToLowerInvariant())) {
+        try {
+            $d = & magick identify -format '%[EXIF:DateTimeOriginal]' "$($file.FullName)[0]" 2>$null
+            if ($d -and ($d.Trim() -match '^\d{4}:\d{2}:\d{2}\s\d{2}:\d{2}:\d{2}$')) {
+                return [datetime]::ParseExact($d.Trim(),'yyyy:MM:dd HH:mm:ss',$null)
+            }
+        } catch {}
+    }
+    return $file.LastWriteTime
+}
+
+Titulo 'Renomear em lote'
+Write-Host '  Desenvolvido por Pablo Murad - 2026' -ForegroundColor DarkGray
+
+$pasta = $null
+while (-not $pasta) {
+    Write-Host ''
+    $p = (Read-Host 'Cole o caminho da pasta').Trim().Trim('"')
+    if (Test-Path -LiteralPath $p -PathType Container) { $pasta = (Resolve-Path -LiteralPath $p).Path } else { Write-Host '[!] Pasta não encontrada.' -ForegroundColor Yellow }
+}
+$arquivos = @(Get-ChildItem -LiteralPath $pasta -File | Sort-Object Name)
+if ($arquivos.Count -eq 0) { Write-Host 'Nenhum arquivo nessa pasta.' -ForegroundColor Yellow; Pausar; exit 0 }
+
+Titulo 'Modo de renomeação'
+Write-Host '  [1] Sequência         (ex.: Foto_001, Foto_002, ...)' -ForegroundColor White
+Write-Host '  [2] Data/hora         (ex.: 2026-09-11_14-30-05)' -ForegroundColor White
+Write-Host '  [3] Localizar e substituir no nome' -ForegroundColor White
+Write-Host '  [4] Mover para pastas por data (AAAA-MM)' -ForegroundColor White
+$modo = $null
+while (-not $modo) { $m = Read-Host 'Modo (1-4)'; if ('1','2','3','4' -contains $m.Trim()) { $modo = $m.Trim() } else { Write-Host '[!] Digite 1 a 4.' -ForegroundColor Yellow } }
+
+# Monta o plano: cada item = { Origem ; Destino(caminho completo) }
+$reservados = @()
+$plano = @()
+
+switch ($modo) {
+    '1' {
+        $prefixo = Read-Host 'Prefixo (ex.: Foto)'; if ([string]::IsNullOrWhiteSpace($prefixo)) { $prefixo = 'Arquivo' }
+        $iniEnt = Read-Host 'Número inicial (Enter = 1)'; $ini = if ($iniEnt -as [int]) { [int]$iniEnt } else { 1 }
+        $digEnt = Read-Host 'Quantos dígitos (Enter = 3)'; $dig = if ($digEnt -as [int]) { [int]$digEnt } else { 3 }
+        $c = $ini
+        foreach ($a in $arquivos) {
+            $novo = ('{0}_{1}{2}' -f $prefixo, ($c.ToString().PadLeft($dig,'0')), $a.Extension.ToLowerInvariant())
+            $dest = Nome-Livre $pasta $novo $reservados; $reservados += $dest
+            $plano += [pscustomobject]@{ Origem=$a; Destino=$dest }; $c++
+        }
+    }
+    '2' {
+        foreach ($a in $arquivos) {
+            $dt = Data-Do-Arquivo $a
+            $novo = ('{0}{1}' -f $dt.ToString('yyyy-MM-dd_HH-mm-ss'), $a.Extension.ToLowerInvariant())
+            $dest = Nome-Livre $pasta $novo $reservados; $reservados += $dest
+            $plano += [pscustomobject]@{ Origem=$a; Destino=$dest }
+        }
+    }
+    '3' {
+        $de = Read-Host 'Localizar (texto no nome)'
+        if ([string]::IsNullOrEmpty($de)) { Write-Host 'Nada para localizar.' -ForegroundColor Yellow; Pausar; exit 0 }
+        $para = Read-Host 'Substituir por (pode deixar vazio para remover)'
+        foreach ($a in $arquivos) {
+            if ($a.Name -like "*$de*") {
+                $novoNome = $a.Name.Replace($de,$para)
+                if ([string]::IsNullOrWhiteSpace([System.IO.Path]::GetFileNameWithoutExtension($novoNome))) { continue }
+                $dest = Nome-Livre $pasta $novoNome $reservados; $reservados += $dest
+                $plano += [pscustomobject]@{ Origem=$a; Destino=$dest }
+            }
+        }
+        if ($plano.Count -eq 0) { Write-Host "Nenhum arquivo contém '$de'." -ForegroundColor Yellow; Pausar; exit 0 }
+    }
+    '4' {
+        foreach ($a in $arquivos) {
+            $dt = Data-Do-Arquivo $a
+            $subPasta = Join-Path $pasta $dt.ToString('yyyy-MM')
+            $dest = Join-Path $subPasta $a.Name
+            $plano += [pscustomobject]@{ Origem=$a; Destino=$dest }
+        }
+    }
+}
+
+# Prévia
+Titulo 'Prévia'
+$acao = if ($modo -eq '4') { 'MOVER' } else { 'RENOMEAR' }
+$mostra = [Math]::Min($plano.Count, 12)
+for ($i=0; $i -lt $mostra; $i++) {
+    $rel = $plano[$i].Destino.Substring($pasta.Length).TrimStart('\')
+    Write-Host ("  {0}" -f $plano[$i].Origem.Name) -ForegroundColor Gray -NoNewline
+    Write-Host ("   ->   {0}" -f $rel) -ForegroundColor White
+}
+if ($plano.Count -gt $mostra) { Write-Host ("  ... e mais {0} arquivo(s)." -f ($plano.Count - $mostra)) -ForegroundColor DarkGray }
+Write-Host ''
+$ok = Read-Host "$acao $($plano.Count) arquivo(s)? (s/N)"
+if ($ok -notmatch '^(s|sim|y|yes)$') { Write-Host 'Cancelado.' -ForegroundColor Yellow; Pausar; exit 0 }
+
+$feitos=0;$err=0
+foreach ($p in $plano) {
+    try {
+        if ($modo -eq '4') {
+            $dir = Split-Path $p.Destino -Parent
+            New-Item -ItemType Directory -Path $dir -Force | Out-Null
+            $destFinal = Nome-Livre $dir (Split-Path $p.Destino -Leaf) @()
+            Move-Item -LiteralPath $p.Origem.FullName -Destination $destFinal -ErrorAction Stop
+        } else {
+            Rename-Item -LiteralPath $p.Origem.FullName -NewName (Split-Path $p.Destino -Leaf) -ErrorAction Stop
+        }
+        $feitos++
+    } catch { Write-Host "   [ERRO] $($p.Origem.Name): $($_.Exception.Message)" -ForegroundColor Red; $err++ }
+}
+Titulo 'Concluído'
+Write-Host "Processados: $feitos" -ForegroundColor Green
+Write-Host "Erros:       $err" -ForegroundColor $(if ($err -gt 0){'Red'}else{'Gray'})
+Write-Host "Pasta:       $pasta"
+Pausar
@@ -6,20 +6,6 @@ ![PowerShell](https://img.shields.io/badge/PowerShell-5391FE?style=flat&logo=powershell&logoColor=white) ![Batch](https://img.shields.io/badge/Batch-.cmd-4D4D4D?style=flat)
 ![Windows](https://img.shields.io/badge/Windows-0078D6?style=flat&logo=windows&logoColor=white)
 
-## Programas
-
-### Converter Imagens
-
-`Imagens/converter-imagens.cmd`
-
-Redimensiona e converte imagens em lote com ImageMagick. Aceita uma pasta ou um arquivo, escolhe formato (JPG, PNG, WebP ou original) e tamanho (pixels, porcentagem ou só converter). A saída vai para a pasta `Convertido`.
-
-### Transcodificar
-
-`Vídeos/transcodificar.cmd`
-
-Converte vídeos em lote com FFmpeg para MP4 (H.264 + AAC). Aceita uma pasta ou um arquivo e oferece perfis de qualidade (da máxima à compacta). A saída vai para a pasta `Transcodificado`.
-
 ---
 
 **Pablo Murad** — pablomurad[at]pm[dot]me
@@ -0,0 +1,128 @@+<# :
+@echo off
+powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "Invoke-Expression ([System.IO.File]::ReadAllText('%~f0',[System.Text.Encoding]::UTF8))"
+exit /b %errorlevel%
+#>
+
+# ============================================================
+#  Criar GIF / Slideshow - vídeo -> GIF, ou fotos -> vídeo (FFmpeg)
+#  Desenvolvido por Pablo Murad - 2026
+# ============================================================
+
+try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
+try { $Host.UI.RawUI.WindowTitle = 'Criar GIF / Slideshow' } catch {}
+
+function Pausar { Write-Host ''; Write-Host 'Pressione Enter para fechar...' -ForegroundColor DarkGray; [void][System.Console]::ReadLine() }
+function Titulo($t) {
+    Write-Host ''
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+    Write-Host "  $t" -ForegroundColor Cyan
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+}
+
+$extVideo = @('.mp4','.mkv','.avi','.mov','.wmv','.flv','.webm','.m4v','.mpg','.mpeg','.ts','.3gp','.vob')
+$extImg   = @('.jpg','.jpeg','.png','.webp','.bmp','.tif','.tiff')
+
+Titulo 'Criar GIF / Slideshow'
+Write-Host '  Desenvolvido por Pablo Murad - 2026' -ForegroundColor DarkGray
+Write-Host ''
+Write-Host 'Verificando o que é necessário...' -ForegroundColor Gray
+$ffmpeg = Get-Command ffmpeg -ErrorAction SilentlyContinue
+if (-not $ffmpeg) { Write-Host ''; Write-Host '[ERRO] FFmpeg não encontrado.' -ForegroundColor Red; Write-Host 'Instale com:  winget install Gyan.FFmpeg' -ForegroundColor White; Pausar; exit 1 }
+Write-Host "[OK] FFmpeg: $($ffmpeg.Source)" -ForegroundColor Green
+
+Titulo 'O que você quer fazer?'
+Write-Host '  [1] Vídeo -> GIF' -ForegroundColor White
+Write-Host '  [2] Fotos (pasta) -> vídeo (slideshow)' -ForegroundColor White
+$acao = $null
+while (-not $acao) { $a = Read-Host 'Opção (1-2)'; switch ($a.Trim()) { '1'{$acao='gif'} '2'{$acao='slide'} default{Write-Host '[!] Digite 1 ou 2.' -ForegroundColor Yellow} } }
+
+if ($acao -eq 'gif') {
+    # ---------------- Vídeo -> GIF ----------------
+    $entrada = $null; $modoArquivo = $false; $arquivoUnico = $null
+    while (-not $entrada) {
+        Titulo 'Pasta ou vídeo'
+        $p = (Read-Host 'Cole o caminho de um VÍDEO ou de uma PASTA').Trim().Trim('"')
+        if (Test-Path -LiteralPath $p -PathType Leaf) {
+            if ($extVideo -notcontains [System.IO.Path]::GetExtension($p).ToLowerInvariant()) { Write-Host '[!] Não é um vídeo suportado.' -ForegroundColor Yellow; continue }
+            $entrada = (Resolve-Path -LiteralPath $p).Path; $modoArquivo = $true; $arquivoUnico = $entrada
+        } elseif (Test-Path -LiteralPath $p -PathType Container) { $entrada = (Resolve-Path -LiteralPath $p).Path }
+        else { Write-Host '[!] Caminho não encontrado.' -ForegroundColor Yellow }
+    }
+    $lEnt = Read-Host 'Largura do GIF em px (Enter = 480)'; $larg = if ($lEnt -as [int]) { [int]$lEnt } else { 480 }
+    $fEnt = Read-Host 'Quadros por segundo / FPS (Enter = 12)'; $fps = if ($fEnt -as [int]) { [int]$fEnt } else { 12 }
+
+    if ($modoArquivo) { $arquivoObj = Get-Item -LiteralPath $arquivoUnico; $origem = $arquivoObj.DirectoryName; $videos = @($arquivoObj) }
+    else { $origem = $entrada; $videos = @(Get-ChildItem -LiteralPath $origem -File | Where-Object { $extVideo -contains $_.Extension.ToLowerInvariant() }) }
+    $destino = Join-Path $origem 'GIF'
+
+    Titulo 'Processando'
+    Write-Host "Largura: $larg px   |   FPS: $fps   |   Destino: $destino"
+    Write-Host "Vídeos: $($videos.Count)" -ForegroundColor Cyan
+    if ($videos.Count -eq 0) { Write-Host 'Nenhum vídeo encontrado.' -ForegroundColor Yellow; Pausar; exit 0 }
+    New-Item -ItemType Directory -Path $destino -Force | Out-Null
+
+    $ok=0;$err=0;$i=0
+    foreach ($v in $videos) {
+        $i++
+        $saida = Join-Path $destino ($v.BaseName + '.gif')
+        Write-Host ''; Write-Host ("[{0}/{1}] {2}" -f $i,$videos.Count,$v.Name) -ForegroundColor Magenta
+        if (Test-Path -LiteralPath $saida) { Write-Host '   [IGNORADO] Já existe.' -ForegroundColor Yellow; continue }
+        $paleta = Join-Path $env:TEMP ("paleta_{0}.png" -f ([guid]::NewGuid().ToString('N')))
+        $filtro = "fps=$fps,scale=${larg}:-1:flags=lanczos"
+        & ffmpeg -hide_banner -nostdin -i $v.FullName -vf "$filtro,palettegen" -y $paleta 2>$null
+        & ffmpeg -hide_banner -nostdin -i $v.FullName -i $paleta -lavfi "$filtro [x]; [x][1:v] paletteuse" -y $saida 2>$null
+        Remove-Item -LiteralPath $paleta -Force -ErrorAction SilentlyContinue
+        if ($LASTEXITCODE -eq 0 -and (Test-Path -LiteralPath $saida)) { Write-Host '   [OK] GIF criado.' -ForegroundColor Green; $ok++ }
+        else { Write-Host '   [ERRO] Falha.' -ForegroundColor Red; if (Test-Path -LiteralPath $saida) { Remove-Item -LiteralPath $saida -Force -ErrorAction SilentlyContinue }; $err++ }
+    }
+    Titulo 'Concluído'
+    Write-Host "GIFs criados: $ok" -ForegroundColor Green
+    Write-Host "Erros: $err" -ForegroundColor $(if ($err -gt 0){'Red'}else{'Gray'})
+    Write-Host "Destino: $destino"
+    Pausar; exit 0
+}
+
+# ---------------- Fotos -> vídeo (slideshow) ----------------
+$pasta = $null
+while (-not $pasta) {
+    Titulo 'Pasta com as fotos'
+    $p = (Read-Host 'Cole o caminho da pasta').Trim().Trim('"')
+    if (Test-Path -LiteralPath $p -PathType Container) { $pasta = (Resolve-Path -LiteralPath $p).Path } else { Write-Host '[!] Pasta não encontrada.' -ForegroundColor Yellow }
+}
+$fotos = @(Get-ChildItem -LiteralPath $pasta -File | Where-Object { $extImg -contains $_.Extension.ToLowerInvariant() } | Sort-Object Name)
+if ($fotos.Count -eq 0) { Write-Host 'Nenhuma imagem nessa pasta.' -ForegroundColor Yellow; Pausar; exit 0 }
+Write-Host ''; Write-Host "$($fotos.Count) fotos encontradas (em ordem alfabética)." -ForegroundColor Cyan
+
+$sEnt = Read-Host 'Segundos por foto (Enter = 2)'; $seg = if ($sEnt -as [double]) { [double]$sEnt } else { 2 }
+$nome = (Read-Host 'Nome do vídeo de saída (Enter = slideshow.mp4)').Trim()
+if ([string]::IsNullOrWhiteSpace($nome)) { $nome = 'slideshow.mp4' }
+if ($nome -notmatch '\.mp4$') { $nome += '.mp4' }
+$saida = Join-Path $pasta $nome
+
+# Monta arquivo de lista para o demuxer concat (caminhos com / e aspas)
+$listaTxt = Join-Path $env:TEMP ("slide_{0}.txt" -f ([guid]::NewGuid().ToString('N')))
+$sb = New-Object System.Text.StringBuilder
+foreach ($f in $fotos) {
+    $cam = $f.FullName -replace '\\','/'
+    [void]$sb.AppendLine("file '$cam'")
+    [void]$sb.AppendLine("duration $seg")
+}
+# repete a última foto (exigência do concat p/ respeitar a última duração)
+$ultimo = $fotos[-1].FullName -replace '\\','/'
+[void]$sb.AppendLine("file '$ultimo'")
+[System.IO.File]::WriteAllText($listaTxt, $sb.ToString(), (New-Object System.Text.UTF8Encoding($false)))
+
+Titulo 'Gerando slideshow'
+Write-Host "Saída: $saida" -ForegroundColor Cyan
+$vf = 'scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2:color=black,format=yuv420p'
+& ffmpeg -hide_banner -nostdin -f concat -safe 0 -i $listaTxt -vf $vf -r 30 -c:v libx264 -preset medium -crf 20 -movflags +faststart -y $saida 2>$null
+$rc = $LASTEXITCODE
+Remove-Item -LiteralPath $listaTxt -Force -ErrorAction SilentlyContinue
+
+Titulo 'Concluído'
+if ($rc -eq 0 -and (Test-Path -LiteralPath $saida)) {
+    Write-Host "[OK] Slideshow criado com $($fotos.Count) fotos." -ForegroundColor Green
+    Write-Host "Arquivo: $saida"
+} else { Write-Host '[ERRO] Falha ao gerar o slideshow.' -ForegroundColor Red }
+Pausar
@@ -0,0 +1,115 @@+<# :
+@echo off
+powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "Invoke-Expression ([System.IO.File]::ReadAllText('%~f0',[System.Text.Encoding]::UTF8))"
+exit /b %errorlevel%
+#>
+
+# ============================================================
+#  Extrair Áudio - tira o áudio de vídeos (FFmpeg)
+#  Desenvolvido por Pablo Murad - 2026
+# ============================================================
+
+try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
+try { $Host.UI.RawUI.WindowTitle = 'Extrair Áudio' } catch {}
+
+function Pausar { Write-Host ''; Write-Host 'Pressione Enter para fechar...' -ForegroundColor DarkGray; [void][System.Console]::ReadLine() }
+function Titulo($t) {
+    Write-Host ''
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+    Write-Host "  $t" -ForegroundColor Cyan
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+}
+
+$configDir  = Join-Path $env:APPDATA 'ExtrairAudio'
+$configFile = Join-Path $configDir 'config.json'
+function Carregar-Config { if (Test-Path -LiteralPath $configFile) { try { return Get-Content -LiteralPath $configFile -Raw -Encoding UTF8 | ConvertFrom-Json } catch {} }; return $null }
+function Salvar-Config($e,$f) { try { New-Item -ItemType Directory -Path $configDir -Force | Out-Null; [pscustomobject]@{ UltimaEntrada=$e; UltimoFormato=$f } | ConvertTo-Json | Set-Content -LiteralPath $configFile -Encoding UTF8 } catch {} }
+
+$extensoes = @('.mp4','.mkv','.avi','.mov','.wmv','.flv','.webm','.m4v','.mpg','.mpeg','.mts','.m2ts','.ts','.3gp','.vob')
+
+$formatos = @(
+    [pscustomobject]@{ Id=1; Nome='MP3';       Desc='Compatível com tudo (192 kbps)';    Ext='.mp3'; Args=@('-c:a','libmp3lame','-b:a','192k') },
+    [pscustomobject]@{ Id=2; Nome='M4A (AAC)';  Desc='Ótima qualidade x tamanho (192 kbps)'; Ext='.m4a'; Args=@('-c:a','aac','-b:a','192k') },
+    [pscustomobject]@{ Id=3; Nome='WAV';        Desc='Sem compressão (arquivo grande)';   Ext='.wav'; Args=@('-c:a','pcm_s16le') }
+)
+
+Titulo 'Extrair Áudio'
+Write-Host '  Desenvolvido por Pablo Murad - 2026' -ForegroundColor DarkGray
+Write-Host ''
+Write-Host 'Verificando o que é necessário...' -ForegroundColor Gray
+$ffmpeg = Get-Command ffmpeg -ErrorAction SilentlyContinue
+if (-not $ffmpeg) {
+    Write-Host ''; Write-Host '[ERRO] FFmpeg não encontrado.' -ForegroundColor Red
+    Write-Host 'Instale com:  winget install Gyan.FFmpeg' -ForegroundColor White
+    Pausar; exit 1
+}
+Write-Host "[OK] FFmpeg: $($ffmpeg.Source)" -ForegroundColor Green
+$config = Carregar-Config
+
+# Pasta ou vídeo
+$entradaAtual = if ($config -and $config.UltimaEntrada) { $config.UltimaEntrada } else { Join-Path $env:USERPROFILE 'Downloads\Vídeos' }
+$modoArquivo = $false; $arquivoUnico = $null
+while ($true) {
+    Titulo 'Pasta ou vídeo'
+    Write-Host "  $entradaAtual" -ForegroundColor White
+    Write-Host 'Enter = confirmar | ou cole uma PASTA ou um VÍDEO' -ForegroundColor DarkGray
+    $resp = Read-Host 'Pasta/vídeo'
+    if (-not [string]::IsNullOrWhiteSpace($resp)) { $entradaAtual = $resp.Trim().Trim('"') }
+    if (Test-Path -LiteralPath $entradaAtual -PathType Leaf) {
+        if ($extensoes -notcontains [System.IO.Path]::GetExtension($entradaAtual).ToLowerInvariant()) { Write-Host '[!] Não é um vídeo suportado.' -ForegroundColor Yellow; continue }
+        $modoArquivo = $true; $arquivoUnico = (Resolve-Path -LiteralPath $entradaAtual).Path; break
+    } elseif (Test-Path -LiteralPath $entradaAtual -PathType Container) { $modoArquivo = $false; break }
+    Write-Host "[!] Caminho não encontrado." -ForegroundColor Yellow
+}
+if ($modoArquivo) { $arquivoObj = Get-Item -LiteralPath $arquivoUnico; $origem = $arquivoObj.DirectoryName } else { $origem = (Resolve-Path -LiteralPath $entradaAtual).Path }
+$destino = Join-Path $origem 'Áudio'
+
+# Formato
+$fmtPadrao = if ($config -and $config.UltimoFormato) { [int]$config.UltimoFormato } else { 1 }
+Titulo 'Formato do áudio'
+foreach ($f in $formatos) {
+    $m = if ($f.Id -eq $fmtPadrao) { '>' } else { ' ' }
+    $c = if ($f.Id -eq $fmtPadrao) { 'White' } else { 'Gray' }
+    Write-Host ("{0} [{1}] {2}" -f $m,$f.Id,$f.Nome) -ForegroundColor $c
+    Write-Host ("       {0}" -f $f.Desc) -ForegroundColor DarkGray
+}
+Write-Host "Enter = padrão [$fmtPadrao]" -ForegroundColor DarkGray
+$formato = $null
+while (-not $formato) {
+    $e = Read-Host 'Formato (1-3)'; if ([string]::IsNullOrWhiteSpace($e)) { $e = "$fmtPadrao" }
+    $formato = $formatos | Where-Object { $_.Id -eq ($e -as [int]) } | Select-Object -First 1
+    if (-not $formato) { Write-Host '[!] Inválido.' -ForegroundColor Yellow }
+}
+Salvar-Config $origem $formato.Id
+
+# Coleta
+if ($modoArquivo) { $arquivos = @($arquivoObj) } else {
+    $arquivos = @(Get-ChildItem -LiteralPath $origem -Recurse -File -ErrorAction SilentlyContinue | Where-Object {
+        ($extensoes -contains $_.Extension.ToLowerInvariant()) -and (-not $_.FullName.StartsWith($destino,[System.StringComparison]::OrdinalIgnoreCase)) })
+}
+Titulo 'Processando'
+Write-Host "Formato: $($formato.Nome)   |   Destino: $destino"
+Write-Host "Vídeos encontrados: $($arquivos.Count)" -ForegroundColor Cyan
+if ($arquivos.Count -eq 0) { Write-Host 'Nenhum vídeo encontrado.' -ForegroundColor Yellow; Pausar; exit 0 }
+New-Item -ItemType Directory -Path $destino -Force | Out-Null
+
+$ok=0;$ign=0;$err=0;$i=0
+foreach ($a in $arquivos) {
+    $i++
+    $rel = $a.FullName.Substring($origem.Length).TrimStart('\'); $sub = Split-Path $rel -Parent
+    $pastaSaida = if ([string]::IsNullOrWhiteSpace($sub)) { $destino } else { Join-Path $destino $sub }
+    New-Item -ItemType Directory -Path $pastaSaida -Force | Out-Null
+    $saida = Join-Path $pastaSaida ($a.BaseName + $formato.Ext)
+    Write-Host ''; Write-Host ("[{0}/{1}] {2}" -f $i,$arquivos.Count,$a.Name) -ForegroundColor Magenta
+    if (Test-Path -LiteralPath $saida) { Write-Host '   [IGNORADO] Já existe.' -ForegroundColor Yellow; $ign++; continue }
+    $ffArgs = @('-hide_banner','-nostdin','-i',$a.FullName,'-vn') + $formato.Args + @('-y',$saida)
+    & ffmpeg @ffArgs
+    if ($LASTEXITCODE -eq 0 -and (Test-Path -LiteralPath $saida)) { Write-Host '   [OK] Áudio extraído.' -ForegroundColor Green; $ok++ }
+    else { Write-Host '   [ERRO] Falha (o vídeo tem áudio?).' -ForegroundColor Red; if (Test-Path -LiteralPath $saida) { Remove-Item -LiteralPath $saida -Force -ErrorAction SilentlyContinue }; $err++ }
+}
+Titulo 'Concluído'
+Write-Host "Extraídos: $ok" -ForegroundColor Green
+Write-Host "Ignorados: $ign" -ForegroundColor Yellow
+Write-Host "Erros:     $err" -ForegroundColor $(if ($err -gt 0){'Red'}else{'Gray'})
+Write-Host "Destino:   $destino"
+Pausar
@@ -68,6 +68,8 @@ # ------------------------------------------------------------ # 1) Verificação de dependências
 # ------------------------------------------------------------
 Titulo 'Transcodificar Vídeos'
+Write-Host '  Desenvolvido por Pablo Murad - 2026' -ForegroundColor DarkGray
+Write-Host ''
 Write-Host 'Verificando o que é necessário...' -ForegroundColor Gray
 
 $ffmpeg = Get-Command ffmpeg -ErrorAction SilentlyContinue
@@ -0,0 +1,23 @@+# Achar duplicados
+
+Programa que encontra arquivos iguais pelo conteúdo (não só pelo nome) dentro de uma pasta e subpastas.
+
+## Como abrir
+
+Abra o arquivo `achar-duplicados.cmd` na pasta `Organizar` (duplo clique ou Execute).
+
+## Passo a passo
+
+1. Cole o caminho da pasta a analisar.
+2. Aguarde a análise (pastas grandes podem demorar).
+3. Se houver duplicados, veja o resumo: grupos de iguais, cópias extras e espaço que dá para recuperar. Até 15 grupos são listados na tela; o primeiro de cada grupo é marcado como manter.
+4. Escolha o que fazer:
+   - **Só listar** (Enter = padrão) — nada é alterado.
+   - **Mover as cópias extras** para a subpasta `_Duplicados`.
+   - **Apagar as cópias extras** — pede confirmação digitando `APAGAR`; sempre mantém 1 original.
+
+## Dicas
+
+- Pastas com muitos arquivos iguais de tamanho passam por uma comparação mais profunda — paciência.
+- A opção de apagar é permanente; se tiver dúvida, use só listar ou mover.
+- Pressione Enter no final para fechar a janela.
@@ -0,0 +1,45 @@+# Converter Imagens
+
+Programa para redimensionar e/ou converter imagens, uma de cada vez ou várias de uma pasta.
+
+## Como abrir
+
+Abra o arquivo `converter-imagens.cmd` na pasta `Imagens` (duplo clique ou Execute).
+
+Na primeira vez, se faltar algo necessário no computador, o programa avisa e mostra o que instalar. Depois de instalar, feche e abra de novo.
+
+## Passo a passo
+
+1. **Pasta ou imagem**  
+   Confirme o caminho sugerido (Enter) ou cole/digite o caminho de uma pasta ou de uma imagem.  
+   Se a pasta não existir, o programa pergunta se quer criá-la.
+
+2. **Formato de saída**  
+   Escolha o número da opção:
+   - manter o formato original (só redimensiona)
+   - JPG
+   - PNG
+   - WebP  
+
+   Enter usa a opção marcada como padrão (a última que você usou, quando houver).
+
+3. **Tamanho**  
+   Escolha:
+   - pixels do maior lado (ex.: 1920)
+   - porcentagem (ex.: 50)
+   - não redimensionar (só muda o formato)  
+
+   Depois informe o valor, se for o caso. Enter pode repetir o valor usado da última vez.
+
+4. **Aguarde o processamento**  
+   O programa lista cada imagem e mostra se foi convertida, ignorada (já existia no destino) ou se deu erro.
+
+5. **Resultado**  
+   Os arquivos ficam na pasta `Convertido`, dentro da pasta de origem (ou ao lado da imagem única). A estrutura de subpastas é preservada.
+
+## Dicas
+
+- Você pode processar uma pasta inteira (incluindo subpastas) ou só um arquivo.
+- Formatos comuns de entrada são aceitos (fotos e imagens do dia a dia).
+- O programa lembra pasta, formato e tamanho usados nas últimas execuções.
+- Pressione Enter no final para fechar a janela.
@@ -0,0 +1,35 @@+# Criar GIF / Slideshow
+
+Programa para transformar vídeo em GIF, ou uma pasta de fotos em um vídeo (slideshow).
+
+## Como abrir
+
+Abra o arquivo `criar-gif.cmd` na pasta `Vídeos` (duplo clique ou Execute).
+
+Na primeira vez, se faltar algo necessário no computador, o programa avisa e mostra o que instalar. Depois de instalar, feche e abra de novo.
+
+## O que escolher
+
+1. **Vídeo → GIF**
+2. **Fotos (pasta) → vídeo (slideshow)**
+
+---
+
+### Vídeo → GIF
+
+1. Cole o caminho de um vídeo ou de uma pasta com vídeos.
+2. Informe a largura do GIF em pixels (Enter = 480).
+3. Informe os quadros por segundo (Enter = 12).
+4. Os GIFs ficam na pasta `GIF`, ao lado dos vídeos. Arquivos que já existem são ignorados.
+
+### Fotos → slideshow
+
+1. Cole o caminho da pasta com as fotos.
+2. Informe quantos segundos cada foto aparece (Enter = 2).
+3. Digite o nome do vídeo (Enter = `slideshow.mp4`).
+4. O arquivo é criado **dentro da mesma pasta**. A ordem das fotos é alfabética.
+
+## Dicas
+
+- GIFs muito longos ou com muita resolução ficam grandes; use largura e FPS menores se precisar.
+- Pressione Enter no final para fechar a janela.
@@ -0,0 +1,34 @@+# Extrair Áudio
+
+Programa para tirar o áudio de vídeos, um de cada vez ou vários de uma pasta.
+
+## Como abrir
+
+Abra o arquivo `extrair-audio.cmd` na pasta `Vídeos` (duplo clique ou Execute).
+
+Na primeira vez, se faltar algo necessário no computador, o programa avisa e mostra o que instalar. Depois de instalar, feche e abra de novo.
+
+## Passo a passo
+
+1. **Pasta ou vídeo**  
+   Confirme o caminho sugerido (Enter) ou cole/digite uma pasta ou um arquivo de vídeo.
+
+2. **Formato do áudio**  
+   Escolha:
+   - MP3 — compatível com tudo  
+   - M4A — bom equilíbrio qualidade × tamanho  
+   - WAV — sem compressão (arquivo maior)  
+
+   Enter usa o formato padrão (o último usado, quando houver).
+
+3. **Aguarde o processamento**  
+   Cada vídeo é listado; arquivos que já existem no destino são ignorados.
+
+4. **Resultado**  
+   Os áudios ficam na pasta `Áudio`, dentro da pasta de origem. Subpastas são preservadas.
+
+## Dicas
+
+- O programa lembra a última pasta e o formato usados.
+- Se um vídeo não tiver faixa de áudio, a extração pode falhar.
+- Pressione Enter no final para fechar a janela.
@@ -0,0 +1,35 @@+# Imagens e PDF
+
+Programa para juntar imagens em um PDF, ou transformar páginas de PDF em imagens.
+
+## Como abrir
+
+Abra o arquivo `imagens-e-pdf.cmd` na pasta `Imagens` (duplo clique ou Execute).
+
+Na primeira vez, se faltar algo necessário no computador, o programa avisa e mostra o que instalar. Depois de instalar, feche e abra de novo.
+
+## O que escolher
+
+1. **Imagens → PDF** — junta as imagens de uma pasta em um único PDF.
+2. **PDF → Imagens** — salva cada página do PDF como imagem.
+
+---
+
+### Imagens → PDF
+
+1. Cole o caminho da pasta com as imagens.
+2. Confira quantas imagens foram encontradas (ordem alfabética).
+3. Digite o nome do PDF (Enter = `saida.pdf`).
+4. O arquivo é criado **dentro da mesma pasta**.
+
+### PDF → Imagens
+
+1. Cole o caminho de um PDF ou de uma pasta com PDFs.
+2. Escolha o formato: PNG (Enter = padrão) ou JPG.
+3. Informe a qualidade em DPI (Enter = 150; 300 = alta).
+4. As imagens vão para uma pasta com o nome do PDF + `-imagens` (ex.: `documento-imagens`).
+
+## Dicas
+
+- Nas imagens → PDF, a ordem das páginas segue o nome dos arquivos (A–Z).
+- Pressione Enter no final para fechar a janela.
@@ -0,0 +1,21 @@+# Limpeza segura
+
+Programa que limpa arquivos temporários do sistema e, se quiser, esvazia a Lixeira — sempre com prévia antes de apagar.
+
+## Como abrir
+
+Abra o arquivo `limpeza-segura.cmd` na pasta `Úteis` (duplo clique ou Execute).
+
+## Passo a passo
+
+1. Aguarde a análise. Nada é apagado ainda.
+2. Veja a prévia: temporários do usuário, do Windows e cache de atualizações, com tamanho aproximado e total recuperável.
+3. Confirme com `s` (ou `sim`) para limpar esses temporários. Qualquer outra resposta pula essa etapa. Arquivos em uso são pulados automaticamente.
+4. Em seguida, o programa pergunta se quer **esvaziar a Lixeira** também (`s`/`sim` ou não).
+5. Ao terminar, veja quanto espaço foi liberado (quando a limpeza rodou).
+
+## Dicas
+
+- A limpeza é só de pastas temporárias/cache listadas — não mexe em Documentos, Downloads, etc.
+- Esvaziar a Lixeira é permanente; confirme só se tiver certeza.
+- Pressione Enter no final para fechar a janela.
@@ -0,0 +1,32 @@+# Marca d'água
+
+Programa para aplicar texto ou logo em imagens, uma de cada vez ou várias de uma pasta.
+
+## Como abrir
+
+Abra o arquivo `marca-dagua.cmd` na pasta `Imagens` (duplo clique ou Execute).
+
+Na primeira vez, se faltar algo necessário no computador, o programa avisa e mostra o que instalar. Depois de instalar, feche e abra de novo.
+
+## Passo a passo
+
+1. **Pasta ou imagem**  
+   Confirme o caminho sugerido (Enter) ou cole/digite uma pasta ou uma imagem.
+
+2. **Tipo de marca**  
+   - **Texto** — digite o texto que aparecerá na foto.  
+   - **Logo** — informe o caminho de um PNG (de preferência com fundo transparente) e, se quiser, o tamanho em % da largura (Enter = 20).
+
+3. **Posição**  
+   Escolha: inferior direita (padrão), inferior esquerda, superior direita, superior esquerda ou centro.
+
+4. **Aguarde o processamento**  
+   Cada imagem é listada; as que já existem no destino são ignoradas.
+
+5. **Resultado**  
+   As fotos ficam na pasta `ComMarca`, dentro da pasta de origem. Subpastas são preservadas.
+
+## Dicas
+
+- O programa lembra a última pasta e opções usadas.
+- Pressione Enter no final para fechar a janela.
@@ -0,0 +1,20 @@+# Organizar por tipo
+
+Programa que separa os arquivos soltos de uma pasta em subpastas por categoria (imagens, vídeos, áudio, documentos, etc.).
+
+## Como abrir
+
+Abra o arquivo `organizar-por-tipo.cmd` na pasta `Organizar` (duplo clique ou Execute).
+
+## Passo a passo
+
+1. Cole o caminho da pasta a organizar.
+2. Veja a prévia: quantos arquivos vão para cada categoria (Imagens, Vídeos, Áudio, Documentos, Compactados, Programas, Outros).
+3. Confirme com `s` (ou `sim`) para mover. Qualquer outra resposta cancela.
+4. Os arquivos são movidos para subpastas com esses nomes, **dentro da mesma pasta**. Se já existir um arquivo com o mesmo nome, um número é acrescentado.
+
+## Dicas
+
+- Só organiza arquivos **soltos** na pasta (não entra em subpastas já existentes).
+- Confira a prévia antes de confirmar.
+- Pressione Enter no final para fechar a janela.
@@ -0,0 +1,19 @@+# Relatório do sistema
+
+Programa que mostra um resumo do computador: nome, sistema, processador, memória, vídeo, discos e espaço nas unidades.
+
+## Como abrir
+
+Abra o arquivo `relatorio-sistema.cmd` na pasta `Úteis` (duplo clique ou Execute).
+
+## Passo a passo
+
+1. O relatório aparece na tela em seções (computador, processador e memória, vídeo, discos, espaço nas unidades).
+2. No final, escolha se quer **salvar num arquivo .txt** (`s`/`sim` ou não).  
+   Se salvar, o arquivo vai para a **Área de trabalho**, com data e hora no nome.
+3. Pressione Enter para fechar.
+
+## Dicas
+
+- Útil para guardar um “retrato” do PC ou enviar para alguém que vá te ajudar.
+- Unidades quase cheias aparecem em destaque na tela.
@@ -0,0 +1,23 @@+# Renomear em lote
+
+Programa para renomear vários arquivos de uma pasta de uma vez, ou organizá-los em pastas por data.
+
+## Como abrir
+
+Abra o arquivo `renomear-em-lote.cmd` na pasta `Organizar` (duplo clique ou Execute).
+
+## Passo a passo
+
+1. Cole o caminho da pasta (só arquivos **nessa pasta**, sem subpastas).
+2. Escolha o modo:
+   - **Sequência** — prefixo + número (ex.: Foto_001). Informe prefixo, número inicial e quantos dígitos.
+   - **Data/hora** — nome com data e hora do arquivo (em fotos, tenta usar a data da foto quando disponível).
+   - **Localizar e substituir** — troca um trecho do nome por outro (pode deixar o substituto vazio para remover).
+   - **Mover para pastas por data** — cria pastas `AAAA-MM` e move os arquivos conforme a data.
+3. Veja a prévia na tela e confirme com `s` (ou `sim`). Qualquer outra resposta cancela.
+
+## Dicas
+
+- Sempre há prévia antes de aplicar — confira antes de confirmar.
+- Se o novo nome já existir, o programa evita conflito acrescentando um número.
+- Pressione Enter no final para fechar a janela.
@@ -0,0 +1,39 @@+# Transcodificar
+
+Programa para converter vídeos, um de cada vez ou vários de uma pasta, com escolha de qualidade.
+
+## Como abrir
+
+Abra o arquivo `transcodificar.cmd` na pasta `Vídeos` (duplo clique ou Execute).
+
+Na primeira vez, se faltar algo necessário no computador, o programa avisa e mostra o que instalar. Depois de instalar, feche e abra de novo.
+
+## Passo a passo
+
+1. **Pasta ou vídeo**  
+   Confirme o caminho sugerido (Enter) ou cole/digite o caminho de uma pasta ou de um arquivo de vídeo.  
+   Se a pasta não existir, o programa pergunta se quer criá-la.
+
+2. **Qualidade de saída**  
+   Escolha o número da opção:
+   - Máxima (resolução original)
+   - Alta (1080p)
+   - Média (720p)
+   - Baixa (480p)
+   - Mínima (compacta, para prévia ou envio rápido)  
+
+   Enter usa a opção marcada como padrão (a última que você usou; se for a primeira vez, costuma ser a mínima).
+
+3. **Aguarde o processamento**  
+   O programa lista cada vídeo e mostra se foi convertido, ignorado (já existia no destino) ou se deu erro. Vídeos longos podem demorar.
+
+4. **Resultado**  
+   Os arquivos ficam na pasta `Transcodificado`, dentro da pasta de origem (ou ao lado do vídeo único), em formato adequado para uso geral. A estrutura de subpastas é preservada.
+
+## Dicas
+
+- Você pode processar uma pasta inteira (incluindo subpastas) ou só um arquivo.
+- Formatos comuns de vídeo são aceitos.
+- Qualidade maior = arquivo maior e conversão mais lenta; qualidade menor = arquivo mais leve.
+- O programa lembra a pasta e a qualidade usadas nas últimas execuções.
+- Pressione Enter no final para fechar a janela.
@@ -0,0 +1,26 @@+# Verificar Discos
+
+Programa para consultar a saúde dos HDs e SSDs do computador. Só lê informações; não apaga nem altera dados.
+
+## Como abrir
+
+Abra o arquivo `verificar-discos.cmd` na pasta `Úteis` (duplo clique ou Execute).
+
+O Windows pode pedir permissão de administrador — aceite para ver o máximo de informações. Sem isso, parte dos dados pode faltar.
+
+## Menu
+
+Depois de abrir, escolha uma opção pelo número:
+
+1. **Diagnóstico de saúde** — rápido e recomendado no dia a dia. Mostra, para cada disco, se está bem, temperatura, desgaste, horas ligado e erros, quando disponíveis.
+2. **Teste de superfície** — lê o disco inteiro em busca de problemas. Pode levar horas. É seguro (só leitura). Você escolhe qual disco testar; Enter cancela. Use Ctrl+C se quiser interromper no meio.
+3. **Resumo simples** — lista rápida de cada disco com OK ou aviso/falha.
+0. **Sair** — fecha o programa (Enter vazio também sai).
+
+Após as opções 1, 2 ou 3, pressione Enter para voltar ao menu.
+
+## Dicas
+
+- Comece pelo diagnóstico de saúde (opção 1); use o teste de superfície só se quiser uma verificação mais profunda.
+- O teste de superfície demora muito em discos grandes — deixe o PC ligado e evite desligar no meio.
+- Se algum disco aparecer com aviso ou falha, considere fazer backup e procurar assistência.
@@ -0,0 +1,101 @@+<# :
+@echo off
+powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "Invoke-Expression ([System.IO.File]::ReadAllText('%~f0',[System.Text.Encoding]::UTF8))"
+exit /b %errorlevel%
+#>
+
+# ============================================================
+#  Limpeza segura - remove temporários e esvazia a lixeira
+#  Mostra uma prévia do que será apagado antes de fazer
+#  Desenvolvido por Pablo Murad - 2026
+# ============================================================
+
+try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
+try { $Host.UI.RawUI.WindowTitle = 'Limpeza segura' } catch {}
+
+function Pausar { Write-Host ''; Write-Host 'Pressione Enter para fechar...' -ForegroundColor DarkGray; [void][System.Console]::ReadLine() }
+function Titulo($t) {
+    Write-Host ''
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+    Write-Host "  $t" -ForegroundColor Cyan
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+}
+function Tam($bytes) {
+    if ($bytes -ge 1GB) { '{0:N2} GB' -f ($bytes/1GB) }
+    elseif ($bytes -ge 1MB) { '{0:N1} MB' -f ($bytes/1MB) }
+    else { '{0:N0} KB' -f ($bytes/1KB) }
+}
+function Medir($caminho) {
+    if (-not (Test-Path -LiteralPath $caminho)) { return [pscustomobject]@{ Itens=0; Bytes=0 } }
+    $itens = @(Get-ChildItem -LiteralPath $caminho -Recurse -Force -ErrorAction SilentlyContinue)
+    $bytes = ($itens | Where-Object { -not $_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum
+    return [pscustomobject]@{ Itens=$itens.Count; Bytes=[int64]$bytes }
+}
+
+# Locais seguros de limpeza (apenas pastas de temporários/cache)
+$alvos = @(
+    [pscustomobject]@{ Nome='Temporários do usuário';        Caminho=$env:TEMP },
+    [pscustomobject]@{ Nome='Temporários do Windows';        Caminho='C:\Windows\Temp' },
+    [pscustomobject]@{ Nome='Cache do Windows Update (Download)'; Caminho='C:\Windows\SoftwareDistribution\Download' }
+)
+
+Titulo 'Limpeza segura'
+Write-Host '  Desenvolvido por Pablo Murad - 2026' -ForegroundColor DarkGray
+Write-Host ''
+Write-Host 'Analisando o que pode ser limpo... (nada é apagado ainda)' -ForegroundColor Gray
+
+$totalBytes = 0
+foreach ($alvo in $alvos) {
+    $m = Medir $alvo.Caminho
+    $alvo | Add-Member -NotePropertyName Bytes -NotePropertyValue $m.Bytes -Force
+    $alvo | Add-Member -NotePropertyName Itens -NotePropertyValue $m.Itens -Force
+    $totalBytes += $m.Bytes
+}
+
+Titulo 'Prévia (o que será limpo)'
+foreach ($alvo in $alvos) {
+    $cor = if ($alvo.Bytes -gt 0) { 'White' } else { 'DarkGray' }
+    Write-Host ("  {0,-38} {1,10}  ({2} itens)" -f $alvo.Nome, (Tam $alvo.Bytes), $alvo.Itens) -ForegroundColor $cor
+    Write-Host ("     {0}" -f $alvo.Caminho) -ForegroundColor DarkGray
+}
+Write-Host ''
+Write-Host ("Total recuperável (aprox.): {0}" -f (Tam $totalBytes)) -ForegroundColor Cyan
+Write-Host '(Arquivos em uso são pulados automaticamente.)' -ForegroundColor DarkGray
+
+Write-Host ''
+$ok = Read-Host 'Limpar os temporários listados acima? (s/N)'
+if ($ok -match '^(s|sim|y|yes)$') {
+    Titulo 'Limpando'
+    foreach ($alvo in $alvos) {
+        if (-not (Test-Path -LiteralPath $alvo.Caminho)) { continue }
+        Write-Host ("  {0}..." -f $alvo.Nome) -ForegroundColor Gray
+        Get-ChildItem -LiteralPath $alvo.Caminho -Force -ErrorAction SilentlyContinue | ForEach-Object {
+            Remove-Item -LiteralPath $_.FullName -Recurse -Force -ErrorAction SilentlyContinue
+        }
+    }
+    # Recalcula o que sobrou p/ estimar o que foi liberado
+    $sobrou = 0
+    foreach ($alvo in $alvos) { $sobrou += (Medir $alvo.Caminho).Bytes }
+    $liberado = $totalBytes - $sobrou
+    if ($liberado -lt 0) { $liberado = 0 }
+    Write-Host ''
+    Write-Host ("[OK] Liberado aproximadamente: {0}" -f (Tam $liberado)) -ForegroundColor Green
+} else {
+    Write-Host 'Temporários não foram alterados.' -ForegroundColor Yellow
+}
+
+Write-Host ''
+$lix = Read-Host 'Esvaziar a Lixeira também? (s/N)'
+if ($lix -match '^(s|sim|y|yes)$') {
+    try {
+        Clear-RecycleBin -Force -ErrorAction Stop
+        Write-Host '[OK] Lixeira esvaziada.' -ForegroundColor Green
+    } catch {
+        if ("$($_.Exception.Message)" -match 'empty|vazia|0x8000FFFF') { Write-Host '[OK] A Lixeira já estava vazia.' -ForegroundColor Green }
+        else { Write-Host "[!] Não foi possível esvaziar a Lixeira: $($_.Exception.Message)" -ForegroundColor Yellow }
+    }
+}
+
+Titulo 'Concluído'
+Write-Host 'Limpeza finalizada.' -ForegroundColor Green
+Pausar
@@ -0,0 +1,87 @@+<# :
+@echo off
+powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "Invoke-Expression ([System.IO.File]::ReadAllText('%~f0',[System.Text.Encoding]::UTF8))"
+exit /b %errorlevel%
+#>
+
+# ============================================================
+#  Relatório do sistema - specs, memória, discos e espaço
+#  Desenvolvido por Pablo Murad - 2026
+# ============================================================
+
+try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
+try { $Host.UI.RawUI.WindowTitle = 'Relatório do sistema' } catch {}
+
+function Pausar { Write-Host ''; Write-Host 'Pressione Enter para fechar...' -ForegroundColor DarkGray; [void][System.Console]::ReadLine() }
+function GB($bytes) { '{0:N1} GB' -f ($bytes/1GB) }
+
+$linhas = New-Object System.Collections.Generic.List[string]
+function Add($txt, $cor='Gray') { Write-Host $txt -ForegroundColor $cor; $linhas.Add($txt) }
+function Sec($t) {
+    Write-Host ''
+    Write-Host "== $t ==" -ForegroundColor Cyan
+    $linhas.Add(''); $linhas.Add("== $t ==")
+}
+
+Write-Host ''
+Write-Host '============================================================' -ForegroundColor DarkCyan
+Write-Host '  Relatório do sistema' -ForegroundColor Cyan
+Write-Host '  Desenvolvido por Pablo Murad - 2026' -ForegroundColor DarkGray
+Write-Host '============================================================' -ForegroundColor DarkCyan
+$linhas.Add('=== Relatório do sistema - Desenvolvido por Pablo Murad - 2026 ===')
+$linhas.Add("Gerado em: $(Get-Date -Format 'dd/MM/yyyy HH:mm')")
+
+try {
+    $os  = Get-CimInstance Win32_OperatingSystem
+    $cs  = Get-CimInstance Win32_ComputerSystem
+    $cpu = Get-CimInstance Win32_Processor | Select-Object -First 1
+
+    Sec 'Computador'
+    Add ("Nome:      {0}" -f $env:COMPUTERNAME)
+    Add ("Usuário:   {0}" -f $env:USERNAME)
+    Add ("Fabricante:{0} {1}" -f $cs.Manufacturer, $cs.Model)
+    Add ("Sistema:   {0}" -f $os.Caption)
+    Add ("Versão:    {0} (build {1})" -f $os.Version, $os.BuildNumber)
+    $boot = $os.LastBootUpTime
+    $up = (Get-Date) - $boot
+    Add ("Ligado há: {0}d {1}h {2}min  (desde {3:dd/MM HH:mm})" -f $up.Days, $up.Hours, $up.Minutes, $boot)
+
+    Sec 'Processador e memória'
+    Add ("CPU:       {0}" -f $cpu.Name.Trim())
+    Add ("Núcleos:   {0} físicos / {1} lógicos" -f $cpu.NumberOfCores, $cpu.NumberOfLogicalProcessors)
+    $ramTotal = $os.TotalVisibleMemorySize * 1KB
+    $ramLivre = $os.FreePhysicalMemory * 1KB
+    $ramUso   = $ramTotal - $ramLivre
+    $pct = if ($ramTotal -gt 0) { [int](($ramUso / $ramTotal) * 100) } else { 0 }
+    Add ("Memória:   {0} no total  |  {1} em uso ({2}%)  |  {3} livre" -f (GB $ramTotal), (GB $ramUso), $pct, (GB $ramLivre))
+
+    Sec 'Vídeo'
+    Get-CimInstance Win32_VideoController | ForEach-Object { Add ("GPU:       {0}" -f $_.Name) }
+
+    Sec 'Discos físicos'
+    Get-PhysicalDisk | Sort-Object DeviceId | ForEach-Object {
+        $tam = if ($_.Size -ge 1TB) { '{0:N2} TB' -f ($_.Size/1TB) } else { GB $_.Size }
+        $cor = if ($_.HealthStatus -eq 'Healthy') { 'Green' } elseif ($_.HealthStatus -eq 'Warning') { 'Yellow' } else { 'Red' }
+        Add ("[{0}] {1,-26} {2,10}  {3}  ({4})" -f $_.DeviceId, $_.FriendlyName, $tam, $_.HealthStatus, $_.MediaType) $cor
+    }
+
+    Sec 'Espaço nas unidades'
+    Get-Volume | Where-Object { $_.DriveLetter -and $_.Size -gt 0 } | Sort-Object DriveLetter | ForEach-Object {
+        $usado = $_.Size - $_.SizeRemaining
+        $pctU = if ($_.Size -gt 0) { [int](($usado / $_.Size) * 100) } else { 0 }
+        $rot = if ($_.FileSystemLabel) { $_.FileSystemLabel } else { '(sem rótulo)' }
+        $cor = if ($pctU -ge 90) { 'Red' } elseif ($pctU -ge 75) { 'Yellow' } else { 'Gray' }
+        Add ("{0}:  {1,-18} {2} livres de {3}  ({4}% usado)" -f $_.DriveLetter, $rot, (GB $_.SizeRemaining), (GB $_.Size), $pctU) $cor
+    }
+} catch {
+    Write-Host "[ERRO] $($_.Exception.Message)" -ForegroundColor Red
+}
+
+Write-Host ''
+$salvar = Read-Host 'Salvar este relatório num arquivo .txt? (s/N)'
+if ($salvar -match '^(s|sim|y|yes)$') {
+    $arq = Join-Path ([Environment]::GetFolderPath('Desktop')) ("relatorio-sistema_{0}.txt" -f (Get-Date -Format 'yyyy-MM-dd_HH-mm'))
+    try { $linhas | Set-Content -LiteralPath $arq -Encoding UTF8; Write-Host "[OK] Salvo em: $arq" -ForegroundColor Green }
+    catch { Write-Host "[ERRO] Não foi possível salvar: $($_.Exception.Message)" -ForegroundColor Red }
+}
+Pausar
@@ -0,0 +1,222 @@+<# :
+@echo off
+set "SELF=%~f0"
+powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "Invoke-Expression ([System.IO.File]::ReadAllText($env:SELF,[System.Text.Encoding]::UTF8))"
+exit /b %errorlevel%
+#>
+
+# ============================================================
+#  Verificar Discos - saúde dos HDs/SSDs (somente leitura)
+#  Desenvolvido por Pablo Murad - 2026
+# ============================================================
+
+try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
+try { $Host.UI.RawUI.WindowTitle = 'Verificar Discos' } catch {}
+
+# ------------------------------------------------------------
+# Auto-elevação para administrador (necessário p/ ler tudo)
+# ------------------------------------------------------------
+$souAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
+    ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+if (-not $souAdmin) {
+    Write-Host 'Solicitando privilégios de administrador...' -ForegroundColor Yellow
+    try {
+        Start-Process -FilePath 'cmd.exe' -ArgumentList '/c', "`"$env:SELF`"" -Verb RunAs | Out-Null
+    } catch {
+        Write-Host '[!] Sem elevação. Alguns dados (temperatura, testes) podem faltar.' -ForegroundColor Yellow
+        Start-Sleep -Seconds 2
+    }
+    exit
+}
+
+function Pausar {
+    Write-Host ''
+    Write-Host 'Pressione Enter para voltar ao menu...' -ForegroundColor DarkGray
+    [void][System.Console]::ReadLine()
+}
+
+function Titulo($texto) {
+    Write-Host ''
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+    Write-Host "  $texto" -ForegroundColor Cyan
+    Write-Host '============================================================' -ForegroundColor DarkCyan
+}
+
+function Cor-Saude($status) {
+    switch ("$status") {
+        'Healthy' { 'Green' }
+        'Warning' { 'Yellow' }
+        default   { if ([string]::IsNullOrWhiteSpace("$status")) { 'DarkGray' } else { 'Red' } }
+    }
+}
+
+function Formatar-Tamanho($bytes) {
+    if (-not $bytes) { return '-' }
+    $gb = [double]$bytes / 1GB
+    if ($gb -ge 1024) { return ('{0:N2} TB' -f ($gb / 1024)) }
+    return ('{0:N0} GB' -f $gb)
+}
+
+$smartctl = Get-Command smartctl -ErrorAction SilentlyContinue
+
+# ------------------------------------------------------------
+# Ação 1: Diagnóstico de saúde (rápido, somente leitura)
+# ------------------------------------------------------------
+function Diagnostico-Saude {
+    Titulo 'Diagnóstico de saúde'
+
+    $discos = Get-PhysicalDisk | Sort-Object DeviceId
+    foreach ($d in $discos) {
+        $cor = Cor-Saude $d.HealthStatus
+        Write-Host ''
+        Write-Host ("Disco {0}: {1}" -f $d.DeviceId, $d.FriendlyName) -ForegroundColor White
+        Write-Host ("   Tipo:      {0}   |   Tamanho: {1}   |   Barramento: {2}" -f `
+            $d.MediaType, (Formatar-Tamanho $d.Size), $d.BusType) -ForegroundColor Gray
+        Write-Host ("   Saúde:     {0}   (operacional: {1})" -f $d.HealthStatus, ($d.OperationalStatus -join ',')) -ForegroundColor $cor
+
+        # Contadores de confiabilidade (temperatura, desgaste, erros)
+        try {
+            $rc = $d | Get-StorageReliabilityCounter -ErrorAction Stop
+            $partes = @()
+            if ($null -ne $rc.Temperature)      { $partes += "Temp: $($rc.Temperature) C" }
+            if ($null -ne $rc.Wear)             { $partes += "Desgaste: $($rc.Wear)%" }
+            if ($null -ne $rc.PowerOnHours)     { $partes += "Horas ligado: $($rc.PowerOnHours)" }
+            if ($null -ne $rc.ReadErrorsTotal)  { $partes += "Erros leitura: $($rc.ReadErrorsTotal)" }
+            if ($null -ne $rc.WriteErrorsTotal) { $partes += "Erros escrita: $($rc.WriteErrorsTotal)" }
+            if ($partes.Count -gt 0) { Write-Host ("   " + ($partes -join '   |   ')) -ForegroundColor DarkGray }
+        } catch {}
+
+        # SMART detalhado via smartctl, se disponível
+        if ($smartctl) {
+            try {
+                $dev = "/dev/pd$($d.DeviceId)"
+                $saidaS = & smartctl -H -A -d ata $dev 2>$null
+                if (-not $saidaS) { $saidaS = & smartctl -H -A $dev 2>$null }
+                $linhaSaude = $saidaS | Select-String 'SMART overall-health|test result'
+                if ($linhaSaude) { Write-Host ("   SMART:     " + ($linhaSaude[0].Line.Trim())) -ForegroundColor Gray }
+                $attrs = $saidaS | Select-String 'Reallocated_Sector|Current_Pending|Offline_Uncorrectable|Power_On_Hours'
+                foreach ($a in $attrs) { Write-Host ("      " + ($a.Line.Trim() -replace '\s{2,}', '  ')) -ForegroundColor DarkGray }
+            } catch {}
+        }
+    }
+
+    if (-not $smartctl) {
+        Write-Host ''
+        Write-Host '(Dica: para detalhes SMART avançados instale o smartmontools:' -ForegroundColor DarkGray
+        Write-Host ' winget install smartmontools )' -ForegroundColor DarkGray
+    }
+}
+
+# ------------------------------------------------------------
+# Ação 2: Resumo simples
+# ------------------------------------------------------------
+function Resumo-Simples {
+    Titulo 'Resumo simples'
+    Get-PhysicalDisk | Sort-Object DeviceId | ForEach-Object {
+        $ok  = $_.HealthStatus -eq 'Healthy'
+        $txt = if ($ok) { 'OK' } else { "FALHA/AVISO ($($_.HealthStatus))" }
+        $cor = Cor-Saude $_.HealthStatus
+        Write-Host ('[{0}] {1,-32} {2,10}   ' -f $_.DeviceId, $_.FriendlyName, (Formatar-Tamanho $_.Size)) -NoNewline
+        Write-Host $txt -ForegroundColor $cor
+    }
+}
+
+# ------------------------------------------------------------
+# Ação 3: Teste de superfície (leitura sequencial, só leitura)
+# ------------------------------------------------------------
+function Teste-Superficie {
+    Titulo 'Teste de superfície (somente leitura)'
+    $discos = Get-PhysicalDisk | Sort-Object DeviceId
+    foreach ($d in $discos) {
+        Write-Host ('  [{0}] {1}  ({2})' -f $d.DeviceId, $d.FriendlyName, (Formatar-Tamanho $d.Size)) -ForegroundColor Gray
+    }
+    Write-Host ''
+    Write-Host 'AVISO: lê todos os setores do disco escolhido. Pode levar HORAS.' -ForegroundColor Yellow
+    Write-Host 'É seguro (apenas leitura). Ctrl+C cancela a qualquer momento.' -ForegroundColor DarkGray
+    $sel = Read-Host 'Número do disco para testar (Enter = cancelar)'
+    if ([string]::IsNullOrWhiteSpace($sel)) { Write-Host 'Cancelado.' -ForegroundColor Gray; return }
+
+    $alvo = $discos | Where-Object { "$($_.DeviceId)" -eq $sel.Trim() } | Select-Object -First 1
+    if (-not $alvo) { Write-Host '[!] Disco não encontrado.' -ForegroundColor Yellow; return }
+
+    $total = [int64]$alvo.Size
+    $caminho = "\\.\PhysicalDrive$($alvo.DeviceId)"
+    Write-Host ''
+    Write-Host "Lendo $caminho  ($(Formatar-Tamanho $total))..." -ForegroundColor Cyan
+
+    $bloco = 8MB
+    $buffer = New-Object byte[] $bloco
+    $lidos = [int64]0
+    $errosRegioes = @()
+    $inicio = Get-Date
+    $ultimoPct = -1
+
+    try {
+        $fs = [System.IO.File]::Open($caminho, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
+    } catch {
+        Write-Host "[ERRO] Não foi possível abrir o disco: $($_.Exception.Message)" -ForegroundColor Red
+        return
+    }
+
+    try {
+        while ($lidos -lt $total) {
+            try {
+                $n = $fs.Read($buffer, 0, $bloco)
+                if ($n -le 0) { break }
+                $lidos += $n
+            } catch {
+                $errosRegioes += ('offset {0:N0} MB' -f ($lidos / 1MB))
+                # pula o bloco problemático e continua
+                $lidos += $bloco
+                try { $fs.Seek($lidos, [System.IO.SeekOrigin]::Begin) | Out-Null } catch { break }
+            }
+
+            $pct = [int](($lidos / $total) * 100)
+            if ($pct -ne $ultimoPct) {
+                $ultimoPct = $pct
+                $decorrido = (Get-Date) - $inicio
+                $mbs = if ($decorrido.TotalSeconds -gt 0) { ($lidos / 1MB) / $decorrido.TotalSeconds } else { 0 }
+                Write-Host ("`r  Progresso: {0,3}%   ({1:N0}/{2:N0} MB)   {3:N0} MB/s   Erros: {4}   " -f `
+                    $pct, ($lidos / 1MB), ($total / 1MB), $mbs, $errosRegioes.Count) -NoNewline -ForegroundColor Cyan
+            }
+        }
+    } finally {
+        $fs.Close()
+    }
+
+    Write-Host ''
+    Write-Host ''
+    if ($errosRegioes.Count -eq 0) {
+        Write-Host "[OK] Nenhum erro de leitura. Todos os setores lidos com sucesso." -ForegroundColor Green
+    } else {
+        Write-Host ("[!] {0} regiões com erro de leitura (possíveis setores defeituosos):" -f $errosRegioes.Count) -ForegroundColor Red
+        $errosRegioes | Select-Object -First 20 | ForEach-Object { Write-Host "     $_" -ForegroundColor Red }
+        if ($errosRegioes.Count -gt 20) { Write-Host "     ... e mais $($errosRegioes.Count - 20)." -ForegroundColor Red }
+    }
+}
+
+# ------------------------------------------------------------
+# Menu principal
+# ------------------------------------------------------------
+while ($true) {
+    Titulo 'Verificar Discos'
+    Write-Host '  Desenvolvido por Pablo Murad - 2026' -ForegroundColor DarkGray
+    Write-Host ''
+    Write-Host '  [1] Diagnóstico de saúde (rápido, recomendado)' -ForegroundColor White
+    Write-Host '        SMART, temperatura, erros e desgaste de todos os discos' -ForegroundColor DarkGray
+    Write-Host '  [2] Teste de superfície (lê todos os setores - demorado)' -ForegroundColor White
+    Write-Host '        Procura setores defeituosos num disco escolhido' -ForegroundColor DarkGray
+    Write-Host '  [3] Resumo simples (só OK/Falha por disco)' -ForegroundColor White
+    Write-Host '  [0] Sair' -ForegroundColor White
+    Write-Host ''
+    $op = Read-Host 'Escolha (0-3)'
+
+    switch ($op.Trim()) {
+        '1' { Diagnostico-Saude; Pausar }
+        '2' { Teste-Superficie;  Pausar }
+        '3' { Resumo-Simples;    Pausar }
+        '0' { break }
+        ''  { break }
+        default { Write-Host '[!] Opção inválida.' -ForegroundColor Yellow; Start-Sleep -Seconds 1 }
+    }
+}