• Web sitemizin içeriğine ve tüm hizmetlerimize erişim sağlamak için Web sitemize kayıt olmalı ya da giriş yapmalısınız. Web sitemize üye olmak tamamen ücretsizdir.

Resim Klasörü Analizörü -Sürüm 7-

TRWE_2012

لِيَغْفِرَ لَكَ اللّٰهُ مَا تَقَدَّمَ مِنْ ذَنْبِك
Moderatör
Konum
BERTUNA
Forum Yaşı
6 Yıl 2 Ay
Mesajlar
5,479
Tepkime puanı
17,170
Merhabalar, bu betiği tamamen kendi ihtiyacımdan geliştirdim.İsteyen kullansın, isteyen kullanmasın, güle güle kullanın..

oXbUI2d.png

KOD İÇERİĞİ : Image_Ratio_v7.ps1

Kod:
# Image Aspect Ratio Analyzer and Safe ZIP Backup Tool V7
# Scans image dimensions, calculates width/height ratio,
# identifies images below the golden ratio threshold,
# creates a ZIP backup, verifies the archive,
# and deletes originals only after successful verification.

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

function Wait-ForKeyPress {
    Write-Host ""
    Write-Host "Press any key to continue..." -ForegroundColor Cyan

    try {
        $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
    }
    catch {
        Read-Host "Press ENTER to continue" | Out-Null
    }
}

function Get-FolderSizeBytes {
    param (
        [Parameter(Mandatory = $true)]
        [string]$Path
    )

    $Total = [int64]0

    try {
        Get-ChildItem -LiteralPath $Path -File -Recurse -Force -ErrorAction Stop |
            ForEach-Object {
                $Total += [int64]$_.Length
            }
    }
    catch {
        return [int64]0
    }

    return $Total
}

function Get-FolderFileCount {
    param (
        [Parameter(Mandatory = $true)]
        [string]$Path
    )

    try {
        return [int64]@(
            Get-ChildItem -LiteralPath $Path -File -Recurse -Force -ErrorAction Stop
        ).Count
    }
    catch {
        return [int64]0
    }
}

function Convert-BytesToMB {
    param (
        [Parameter(Mandatory = $true)]
        [int64]$Bytes
    )

    return [math]::Round(($Bytes / 1MB), 3)
}

# Golden ratio threshold
$GoldenRatio = 1.61803398875

# Image file extensions
$ImageExtensions = @(
    ".jpg",
    ".jpeg",
    ".jfif",
    ".png",
    ".bmp",
    ".gif",
    ".tif",
    ".tiff",
    ".webp",
    ".avif"
)

function Get-WebPDimensions {
    param (
        [Parameter(Mandatory = $true)]
        [System.IO.FileInfo]$File
    )

    $Stream = $null
    $Reader = $null

    try {
        $Stream = [System.IO.File]::Open(
            $File.FullName,
            [System.IO.FileMode]::Open,
            [System.IO.FileAccess]::Read,
            [System.IO.FileShare]::Read
        )

        $Reader = New-Object System.IO.BinaryReader($Stream)

        $Header = $Reader.ReadBytes(12)

        if ($Header.Length -lt 12) {
            throw "Invalid WEBP file header."
        }

        $Riff = [System.Text.Encoding]::ASCII.GetString($Header, 0, 4)
        $WebP = [System.Text.Encoding]::ASCII.GetString($Header, 8, 4)

        if ($Riff -ne "RIFF" -or $WebP -ne "WEBP") {
            throw "Invalid WEBP RIFF header."
        }

        while ($Stream.Position -lt $Stream.Length) {
            $ChunkHeader = $Reader.ReadBytes(8)

            if ($ChunkHeader.Length -lt 8) {
                break
            }

            $ChunkType = [System.Text.Encoding]::ASCII.GetString($ChunkHeader, 0, 4)
            $ChunkSize = [BitConverter]::ToUInt32($ChunkHeader, 4)
            $ChunkDataStart = $Stream.Position

            if ($ChunkType -eq "VP8X") {
                if ($ChunkSize -lt 10) {
                    throw "Invalid VP8X chunk."
                }

                $Data = $Reader.ReadBytes([int]$ChunkSize)

                if ($Data.Length -lt 10) {
                    throw "Invalid VP8X chunk data."
                }

                $WidthMinusOne = [int]$Data[4] -bor ([int]$Data[5] -shl 8) -bor ([int]$Data[6] -shl 16)
                $HeightMinusOne = [int]$Data[7] -bor ([int]$Data[8] -shl 8) -bor ([int]$Data[9] -shl 16)

                return [PSCustomObject]@{
                    Width  = $WidthMinusOne + 1
                    Height = $HeightMinusOne + 1
                }
            }

            if ($ChunkType -eq "VP8 ") {
                if ($ChunkSize -lt 10) {
                    throw "Invalid VP8 chunk."
                }

                $Data = $Reader.ReadBytes([int][Math]::Min($ChunkSize, 30))

                if ($Data.Length -lt 10) {
                    throw "Invalid VP8 frame data."
                }

                if ($Data[3] -ne 0x9D -or $Data[4] -ne 0x01 -or $Data[5] -ne 0x2A) {
                    $StartCodeIndex = -1

                    for ($i = 0; $i -le $Data.Length - 6; $i++) {
                        if ($Data[$i] -eq 0x9D -and $Data[$i + 1] -eq 0x01 -and $Data[$i + 2] -eq 0x2A) {
                            $StartCodeIndex = $i
                            break
                        }
                    }

                    if ($StartCodeIndex -lt 0 -or $StartCodeIndex + 6 -ge $Data.Length) {
                        throw "Unable to locate VP8 frame dimensions."
                    }

                    $WidthOffset = $StartCodeIndex + 3
                }
                else {
                    $WidthOffset = 6
                }

                $Width = [BitConverter]::ToUInt16($Data, $WidthOffset)
                $Height = [BitConverter]::ToUInt16($Data, $WidthOffset + 2)

                return [PSCustomObject]@{
                    Width  = $Width -band 0x3FFF
                    Height = $Height -band 0x3FFF
                }
            }

            if ($ChunkType -eq "VP8L") {
                if ($ChunkSize -lt 5) {
                    throw "Invalid VP8L chunk."
                }

                $Data = $Reader.ReadBytes(5)

                if ($Data.Length -lt 5 -or $Data[0] -ne 0x2F) {
                    throw "Invalid VP8L frame data."
                }

                $Width = 1 + (
                    [int]$Data[1] -bor
                    (([int]$Data[2] -band 0x3F) -shl 8)
                )

                $Height = 1 + (
                    (([int]$Data[2] -shr 6) -band 0x03) -bor
                    ([int]$Data[3] -shl 2) -bor
                    (([int]$Data[4] -band 0x0F) -shl 10)
                )

                return [PSCustomObject]@{
                    Width  = $Width
                    Height = $Height
                }
            }

            $SkipLength = [int64]$ChunkSize

            if (($ChunkSize % 2) -ne 0) {
                $SkipLength++
            }

            $Stream.Position = $ChunkDataStart + $SkipLength
        }

        throw "No supported WEBP image chunk was found."
    }
    finally {
        if ($null -ne $Reader) {
            $Reader.Dispose()
        }
        elseif ($null -ne $Stream) {
            $Stream.Dispose()
        }
    }
}


function Get-AvifDimensions {
    param (
        [Parameter(Mandatory = $true)]
        [System.IO.FileInfo]$File
    )

    $Stream = $null
    $Reader = $null

    try {
        $Stream = [System.IO.File]::Open(
            $File.FullName,
            [System.IO.FileMode]::Open,
            [System.IO.FileAccess]::Read,
            [System.IO.FileShare]::ReadWrite
        )

        # AVIF is based on ISO Base Media File Format.
        # The image dimensions are stored in an 'ispe' property box:
        # version/flags (4 bytes), width (4 bytes), height (4 bytes).
        # Scan a bounded prefix to avoid loading a very large image into memory.
        $MaxScanBytes = [int64]1MB * 64
        $ScanLength = [int][Math]::Min($Stream.Length, $MaxScanBytes)

        if ($ScanLength -lt 16) {
            throw "AVIF file is too small."
        }

        $Buffer = New-Object byte[] $ScanLength
        $BytesRead = 0

        while ($BytesRead -lt $ScanLength) {
            $ReadCount = $Stream.Read($Buffer, $BytesRead, $ScanLength - $BytesRead)
            if ($ReadCount -le 0) {
                break
            }
            $BytesRead += $ReadCount
        }

        $Marker = [System.Text.Encoding]::ASCII.GetBytes("ispe")

        for ($Offset = 4; $Offset -le ($BytesRead - 16); $Offset++) {
            if ($Buffer[$Offset] -ne $Marker[0] -or
                $Buffer[$Offset + 1] -ne $Marker[1] -or
                $Buffer[$Offset + 2] -ne $Marker[2] -or
                $Buffer[$Offset + 3] -ne $Marker[3]) {
                continue
            }

            # The four bytes immediately before the type are the standard box size.
            $BoxSize =
                (([int64]$Buffer[$Offset - 4]) -shl 24) -bor
                (([int64]$Buffer[$Offset - 3]) -shl 16) -bor
                (([int64]$Buffer[$Offset - 2]) -shl 8) -bor
                [int64]$Buffer[$Offset - 1]

            if ($BoxSize -lt 20) {
                continue
            }

            $Width =
                (([int64]$Buffer[$Offset + 8]) -shl 24) -bor
                (([int64]$Buffer[$Offset + 9]) -shl 16) -bor
                (([int64]$Buffer[$Offset + 10]) -shl 8) -bor
                [int64]$Buffer[$Offset + 11]

            $Height =
                (([int64]$Buffer[$Offset + 12]) -shl 24) -bor
                (([int64]$Buffer[$Offset + 13]) -shl 16) -bor
                (([int64]$Buffer[$Offset + 14]) -shl 8) -bor
                [int64]$Buffer[$Offset + 15]

            if ($Width -gt 0 -and $Height -gt 0 -and $Width -le 100000 -and $Height -le 100000) {
                return [PSCustomObject]@{
                    Width  = [int]$Width
                    Height = [int]$Height
                }
            }
        }

        throw "AVIF image dimensions were not found in an ispe box."
    }
    finally {
        if ($null -ne $Reader) {
            $Reader.Dispose()
        }
        elseif ($null -ne $Stream) {
            $Stream.Dispose()
        }
    }
}


function Get-JpegDimensions {
    param (
        [Parameter(Mandatory = $true)]
        [System.IO.FileInfo]$File
    )

    $Stream = $null
    $Reader = $null

    try {
        $Stream = [System.IO.File]::Open(
            $File.FullName,
            [System.IO.FileMode]::Open,
            [System.IO.FileAccess]::Read,
            [System.IO.FileShare]::ReadWrite
        )

        $Reader = New-Object System.IO.BinaryReader($Stream)

        if ($Stream.Length -lt 4) {
            throw "JPEG file is too small."
        }

        $Byte1 = $Reader.ReadByte()
        $Byte2 = $Reader.ReadByte()

        if ($Byte1 -ne 0xFF -or $Byte2 -ne 0xD8) {
            throw "Invalid JPEG SOI marker."
        }

        while ($Stream.Position -lt $Stream.Length) {
            # Search for the next marker prefix.
            do {
                $MarkerPrefix = $Reader.ReadByte()
            } while ($MarkerPrefix -ne 0xFF -and $Stream.Position -lt $Stream.Length)

            if ($Stream.Position -ge $Stream.Length) {
                break
            }

            # Skip fill bytes 0xFF.
            do {
                $Marker = $Reader.ReadByte()
            } while ($Marker -eq 0xFF -and $Stream.Position -lt $Stream.Length)

            if ($Marker -eq 0x00) {
                throw "Invalid JPEG marker stream before SOS."
            }

            # Standalone markers do not have a length field.
            if ($Marker -eq 0xD8 -or $Marker -eq 0xD9 -or ($Marker -ge 0xD0 -and $Marker -le 0xD7) -or $Marker -eq 0x01) {
                if ($Marker -eq 0xD9) {
                    break
                }
                continue
            }

            $LengthBytes = $Reader.ReadBytes(2)

            if ($LengthBytes.Length -ne 2) {
                throw "Truncated JPEG segment length."
            }

            $SegmentLength = (([int]$LengthBytes[0] -shl 8) -bor [int]$LengthBytes[1])

            if ($SegmentLength -lt 2) {
                throw "Invalid JPEG segment length."
            }

            # SOF markers contain the primary frame dimensions.
            $IsSofMarker = (
                ($Marker -ge 0xC0 -and $Marker -le 0xC3) -or
                ($Marker -ge 0xC5 -and $Marker -le 0xC7) -or
                ($Marker -ge 0xC9 -and $Marker -le 0xCB) -or
                ($Marker -ge 0xCD -and $Marker -le 0xCF)
            )

            if ($IsSofMarker) {
                if ($SegmentLength -lt 7) {
                    throw "Invalid JPEG SOF segment."
                }

                $Segment = $Reader.ReadBytes($SegmentLength - 2)

                if ($Segment.Length -ne ($SegmentLength - 2) -or $Segment.Length -lt 5) {
                    throw "Truncated JPEG SOF segment."
                }

                $Height = (([int]$Segment[1] -shl 8) -bor [int]$Segment[2])
                $Width = (([int]$Segment[3] -shl 8) -bor [int]$Segment[4])

                if ($Width -le 0 -or $Height -le 0) {
                    throw "Invalid JPEG dimensions."
                }

                return [PSCustomObject]@{
                    Width  = $Width
                    Height = $Height
                }
            }

            # SOS begins entropy-coded image data. If no SOF was found before
            # SOS, this parser cannot safely determine the primary frame size.
            if ($Marker -eq 0xDA) {
                break
            }

            $SkipBytes = $SegmentLength - 2

            if (($Stream.Position + $SkipBytes) -gt $Stream.Length) {
                throw "Truncated JPEG segment data."
            }

            $Stream.Seek($SkipBytes, [System.IO.SeekOrigin]::Current) | Out-Null
        }

        throw "JPEG primary frame dimensions were not found."
    }
    finally {
        if ($null -ne $Reader) {
            $Reader.Dispose()
        }
        elseif ($null -ne $Stream) {
            $Stream.Dispose()
        }
    }
}

function Get-WicDimensions {
    param (
        [Parameter(Mandatory = $true)]
        [System.IO.FileInfo]$File
    )

    $Stream = $null
    $Bitmap = $null

    try {
        $Stream = [System.IO.File]::Open(
            $File.FullName,
            [System.IO.FileMode]::Open,
            [System.IO.FileAccess]::Read,
            [System.IO.FileShare]::ReadWrite
        )

        $Bitmap = New-Object System.Windows.Media.Imaging.BitmapImage
        $Bitmap.BeginInit()
        $Bitmap.CacheOption = [System.Windows.Media.Imaging.BitmapCacheOption]::OnLoad
        $Bitmap.CreateOptions = [System.Windows.Media.Imaging.BitmapCreateOptions]::PreservePixelFormat
        $Bitmap.StreamSource = $Stream
        $Bitmap.EndInit()
        $Bitmap.Freeze()

        if ($Bitmap.PixelWidth -le 0 -or $Bitmap.PixelHeight -le 0) {
            throw "WIC returned invalid image dimensions."
        }

        return [PSCustomObject]@{
            Width  = [int]$Bitmap.PixelWidth
            Height = [int]$Bitmap.PixelHeight
        }
    }
    finally {
        if ($null -ne $Bitmap) {
            $Bitmap = $null
        }

        if ($null -ne $Stream) {
            $Stream.Dispose()
        }
    }
}

function Get-ImageInformation {
    param (
        [Parameter(Mandatory = $true)]
        [System.IO.FileInfo]$File
    )

    $Image = $null
    $ReadMethod = ""

    try {
        $Extension = $File.Extension.ToLowerInvariant()
        $Dimensions = $null

        # JPEG and JFIF: prefer WIC for the primary image frame.
        # The JPEG header parser is used only as a fallback because JPEG files
        # can contain embedded thumbnail images inside APP segments.
        if ($Extension -eq ".jpg" -or $Extension -eq ".jpeg" -or $Extension -eq ".jfif") {
            try {
                $Dimensions = Get-WicDimensions -File $File
                $ReadMethod = "WIC"
            }
            catch {
                $WicError = $_.Exception.Message

                try {
                    $Dimensions = Get-JpegDimensions -File $File
                    $ReadMethod = "JPEG header parser fallback"
                }
                catch {
                    $JpegError = $_.Exception.Message

                    try {
                        $Image = [System.Drawing.Image]::FromFile($File.FullName)
                        $Dimensions = [PSCustomObject]@{
                            Width  = [int]$Image.Width
                            Height = [int]$Image.Height
                        }
                        $ReadMethod = "System.Drawing fallback"
                    }
                    catch {
                        $DrawingError = $_.Exception.Message
                        throw "WIC failed: $WicError | JPEG header parser failed: $JpegError | System.Drawing failed: $DrawingError"
                    }
                }
            }
        }
        # AVIF: prefer WIC, then use the ISO BMFF ispe parser as a codec-independent fallback.
        elseif ($Extension -eq ".avif") {
            try {
                $Dimensions = Get-WicDimensions -File $File
                $ReadMethod = "WIC"
            }
            catch {
                $WicError = $_.Exception.Message

                try {
                    $Dimensions = Get-AvifDimensions -File $File
                    $ReadMethod = "AVIF header parser fallback"
                }
                catch {
                    $AvifError = $_.Exception.Message
                    try {
                        $Image = [System.Drawing.Image]::FromFile($File.FullName)
                        $Dimensions = [PSCustomObject]@{
                            Width  = [int]$Image.Width
                            Height = [int]$Image.Height
                        }
                        $ReadMethod = "System.Drawing fallback"
                    }
                    catch {
                        $DrawingError = $_.Exception.Message
                        throw "WIC failed: $WicError | AVIF header parser failed: $AvifError | System.Drawing failed: $DrawingError"
                    }
                }
            }
        }
        # WEBP: use the dedicated parser first.
        elseif ($Extension -eq ".webp") {
            try {
                $Dimensions = Get-WebPDimensions -File $File
                $ReadMethod = "WEBP parser"
            }
            catch {
                try {
                    $Dimensions = Get-WicDimensions -File $File
                    $ReadMethod = "WIC fallback"
                }
                catch {
                    try {
                        $Image = [System.Drawing.Image]::FromFile($File.FullName)
                        $Dimensions = [PSCustomObject]@{
                            Width  = [int]$Image.Width
                            Height = [int]$Image.Height
                        }
                        $ReadMethod = "System.Drawing fallback"
                    }
                    catch {
                        throw "WEBP parser failed: $($_.Exception.Message) | WIC failed: $($_.Exception.Message) | System.Drawing failed: $($_.Exception.Message)"
                    }
                }
            }
        }
        # Other supported formats: WIC first, System.Drawing second.
        else {
            try {
                $Dimensions = Get-WicDimensions -File $File
                $ReadMethod = "WIC"
            }
            catch {
                $WicError = $_.Exception.Message

                try {
                    $Image = [System.Drawing.Image]::FromFile($File.FullName)
                    $Dimensions = [PSCustomObject]@{
                        Width  = [int]$Image.Width
                        Height = [int]$Image.Height
                    }
                    $ReadMethod = "System.Drawing fallback"
                }
                catch {
                    throw "WIC failed: $WicError | System.Drawing failed: $($_.Exception.Message)"
                }
            }
        }

        $Width = [int]$Dimensions.Width
        $Height = [int]$Dimensions.Height

        if ($Width -le 0 -or $Height -le 0) {
            throw "Invalid image dimensions."
        }

        $Ratio = [math]::Round(($Width / $Height), 6)

        [PSCustomObject]@{
            File        = $File
            Name        = $File.Name
            FileSize    = [int64]$File.Length
            SizeMB      = [math]::Round(($File.Length / 1MB), 3)
            Width       = $Width
            Height      = $Height
            Resolution  = "$Width x $Height"
            Ratio       = $Ratio
            IsGolden    = ($Ratio -ge $GoldenRatio)
            Readable    = $true
            ReadMethod  = $ReadMethod
            Error       = ""
        }
    }
    catch {
        [PSCustomObject]@{
            File        = $File
            Name        = $File.Name
            FileSize    = [int64]$File.Length
            SizeMB      = [math]::Round(($File.Length / 1MB), 3)
            Width       = 0
            Height      = 0
            Resolution  = "Unreadable"
            Ratio       = 0
            IsGolden    = $false
            Readable    = $false
            ReadMethod  = ""
            Error       = $_.Exception.Message
        }
    }
    finally {
        if ($null -ne $Image) {
            $Image.Dispose()
        }
    }
}

# Load image libraries
$SystemDrawingAvailable = $true
$WicAvailable = $true

try {
    Add-Type -AssemblyName System.Drawing
}
catch {
    $SystemDrawingAvailable = $false
}

try {
    Add-Type -AssemblyName PresentationCore
}
catch {
    $WicAvailable = $false
}

if (-not $SystemDrawingAvailable -and -not $WicAvailable) {
    Write-Host "No supported image decoding library is available." -ForegroundColor Red
    Wait-ForKeyPress
    exit 1
}

Write-Host ""
Write-Host "Image Aspect Ratio Analyzer" -ForegroundColor Cyan
Write-Host "----------------------------------------"
Write-Host ""

$TargetFolder = Read-Host "Enter the image folder path"

if ([string]::IsNullOrWhiteSpace($TargetFolder)) {
    Write-Host "No folder path was provided." -ForegroundColor Red
    Wait-ForKeyPress
    exit 1
}

if (-not (Test-Path -LiteralPath $TargetFolder -PathType Container)) {
    Write-Host "The specified folder does not exist." -ForegroundColor Red
    Wait-ForKeyPress
    exit 1
}

$TargetFolder = (Resolve-Path -LiteralPath $TargetFolder).Path

# Capture the complete folder footprint before any changes are made.
$FolderSizeBeforeBytes = Get-FolderSizeBytes -Path $TargetFolder
$FolderFileCountBefore = Get-FolderFileCount -Path $TargetFolder

# Get image files from the selected folder
$ImageFiles = Get-ChildItem -LiteralPath $TargetFolder -File |
    Where-Object {
        $ImageExtensions -contains $_.Extension.ToLowerInvariant()
    } |
    Sort-Object Name

if ($ImageFiles.Count -eq 0) {
    Write-Host ""
    Write-Host "No supported image files were found." -ForegroundColor Yellow
    Wait-ForKeyPress
    exit 0
}

Write-Host ""
Write-Host "Scanning images..." -ForegroundColor Cyan
Write-Host ""

$Results = New-Object System.Collections.Generic.List[object]

foreach ($ImageFile in $ImageFiles) {
    $Info = Get-ImageInformation -File $ImageFile
    $Results.Add($Info)
}

# Display numbered report
Write-Host ""
Write-Host "Image Analysis Results" -ForegroundColor Cyan
Write-Host "=============================================="

$Index = 1

foreach ($Item in $Results) {

    $Number = "{0:D2}" -f $Index

    if (-not $Item.Readable) {
        Write-Host "$Number - $($Item.Name) | Size: $($Item.SizeMB) MB | UNREADABLE | $($Item.Error)" -ForegroundColor Red
    }
    elseif ($Item.IsGolden) {
        Write-Host (
            "$Number - $($Item.Name) | $($Item.Resolution) | Size: $($Item.SizeMB) MB | Ratio: $($Item.Ratio) | " +
            "GOLDEN RATIO OR ABOVE"
        ) -ForegroundColor Green
    }
    else {
        Write-Host (
            "$Number - $($Item.Name) | $($Item.Resolution) | Size: $($Item.SizeMB) MB | Ratio: $($Item.Ratio) | " +
            "BELOW GOLDEN RATIO"
        ) -ForegroundColor Yellow
    }

    $Index++
}

# Select files below the golden ratio
$DeleteCandidates = @(
    $Results |
        Where-Object {
            $_.Readable -and
            -not $_.IsGolden
        }
)

$GoldenCount = @(
    $Results |
        Where-Object {
            $_.Readable -and
            $_.IsGolden
        }
).Count

$UnreadableCount = @(
    $Results |
        Where-Object {
            -not $_.Readable
        }
).Count

$AnalyzedImageSizeBytes = [int64]0
foreach ($Item in $Results) {
    $AnalyzedImageSizeBytes += [int64]$Item.FileSize
}

$DeleteSizeBytes = [int64]0
foreach ($Item in $DeleteCandidates) {
    $DeleteSizeBytes += [int64]$Item.FileSize
}

$ProtectedCount = $GoldenCount
$ProtectedImageSizeBytes = [int64]0
foreach ($Item in $Results) {
    if ($Item.Readable -and $Item.IsGolden) {
        $ProtectedImageSizeBytes += [int64]$Item.FileSize
    }
}

if ($Results.Count -gt 0) {
    $ProtectedFilePercent = [math]::Round((($ProtectedCount / $Results.Count) * 100), 2)
    $DeleteFilePercent = [math]::Round((($DeleteCandidates.Count / $Results.Count) * 100), 2)
    $UnreadableFilePercent = [math]::Round((($UnreadableCount / $Results.Count) * 100), 2)
}
else {
    $ProtectedFilePercent = 0
    $DeleteFilePercent = 0
    $UnreadableFilePercent = 0
}

if ($AnalyzedImageSizeBytes -gt 0) {
    $ProtectedImagePercent = [math]::Round((($ProtectedImageSizeBytes / $AnalyzedImageSizeBytes) * 100), 2)
    $SelectedDeleteImagePercent = [math]::Round((($DeleteSizeBytes / $AnalyzedImageSizeBytes) * 100), 2)
}
else {
    $ProtectedImagePercent = 0
    $SelectedDeleteImagePercent = 0
}

Write-Host ""
Write-Host "Summary" -ForegroundColor Cyan
Write-Host "=============================================="
Write-Host "Total images          : $($Results.Count)"
Write-Host "Protected total       : $ProtectedCount ($ProtectedFilePercent %)"
Write-Host "Golden or above       : $GoldenCount"
Write-Host "Below golden ratio    : $($DeleteCandidates.Count) ($DeleteFilePercent %)"
Write-Host "Unreadable            : $UnreadableCount ($UnreadableFilePercent %)"
Write-Host "Total image size      : $(Convert-BytesToMB -Bytes $AnalyzedImageSizeBytes) MB"
Write-Host "Protected image size  : $(Convert-BytesToMB -Bytes $ProtectedImageSizeBytes) MB ($ProtectedImagePercent %)"
Write-Host "Selected delete size  : $(Convert-BytesToMB -Bytes $DeleteSizeBytes) MB ($SelectedDeleteImagePercent %)"
Write-Host "Folder size before    : $(Convert-BytesToMB -Bytes $FolderSizeBeforeBytes) MB"
Write-Host "Folder files before   : $FolderFileCountBefore"
Write-Host "Golden ratio          : $GoldenRatio"
Write-Host ""

if ($DeleteCandidates.Count -eq 0) {
    Write-Host "No images require backup or deletion." -ForegroundColor Green
    Wait-ForKeyPress
    exit 0
}

Write-Host "Files below the golden ratio:" -ForegroundColor Yellow
Write-Host ""

$DeleteIndex = 1

foreach ($Item in $DeleteCandidates) {
    $Number = "{0:D2}" -f $DeleteIndex

    Write-Host (
        "$Number - $($Item.Name) | $($Item.Resolution) | Size: $($Item.SizeMB) MB | Ratio: $($Item.Ratio)"
    ) -ForegroundColor Yellow

    $DeleteIndex++
}

Write-Host ""
Write-Host "The files listed above will be backed up to a ZIP archive." -ForegroundColor Yellow
Write-Host "Original files will be deleted only after ZIP verification." -ForegroundColor Yellow
Write-Host ""

$Confirmation = Read-Host "Type YES to continue"

if ($Confirmation -cne "YES") {
    Write-Host ""
    Write-Host "Operation cancelled. No files were modified." -ForegroundColor Cyan
    Wait-ForKeyPress
    exit 0
}

# Generate unique ZIP name
$TimeStamp = Get-Date -Format "yyyyMMdd_HHmmss"
$ZipFileName = "Image_Backup_Below_Golden_Ratio_$TimeStamp.zip"
$ZipPath = Join-Path $TargetFolder $ZipFileName

# Prevent accidental overwrite
if (Test-Path -LiteralPath $ZipPath) {
    Write-Host "Backup ZIP already exists." -ForegroundColor Red
    Wait-ForKeyPress
    exit 1
}

Write-Host ""
Write-Host "Creating ZIP backup..." -ForegroundColor Cyan

try {
    $FilesToArchive = @(
        $DeleteCandidates |
            ForEach-Object {
                $_.File.FullName
            }
    )

    Compress-Archive `
        -LiteralPath $FilesToArchive `
        -DestinationPath $ZipPath `
        -CompressionLevel Optimal `
        -Force

    if (-not (Test-Path -LiteralPath $ZipPath -PathType Leaf)) {
        throw "ZIP archive was not created."
    }
}
catch {
    Write-Host ""
    Write-Host "ZIP creation failed." -ForegroundColor Red
    Write-Host $_.Exception.Message -ForegroundColor Red
    Write-Host "No original files were deleted." -ForegroundColor Green
    Wait-ForKeyPress
    exit 1
}

# Verify ZIP contents before deletion
Write-Host "Verifying ZIP archive..." -ForegroundColor Cyan

try {
    Add-Type -AssemblyName System.IO.Compression.FileSystem

    $Archive = [System.IO.Compression.ZipFile]::OpenRead($ZipPath)

    $ArchiveEntryNames = @(
        $Archive.Entries |
            ForEach-Object {
                [System.IO.Path]::GetFileName($_.FullName)
            }
    )

    $MissingFiles = New-Object System.Collections.Generic.List[string]

    foreach ($Item in $DeleteCandidates) {
        if ($ArchiveEntryNames -notcontains $Item.Name) {
            $MissingFiles.Add($Item.Name)
        }
    }

    $Archive.Dispose()

    if ($MissingFiles.Count -gt 0) {
        throw "ZIP verification failed. Missing files: $($MissingFiles -join ', ')"
    }

    Write-Host "ZIP verification successful." -ForegroundColor Green
}
catch {
    Write-Host ""
    Write-Host "ZIP verification failed." -ForegroundColor Red
    Write-Host $_.Exception.Message -ForegroundColor Red
    Write-Host "Original files were NOT deleted." -ForegroundColor Green
    Wait-ForKeyPress
    exit 1
}

$ZipSizeBytes = [int64](Get-Item -LiteralPath $ZipPath -ErrorAction Stop).Length

# Delete originals only after successful verification
Write-Host ""
Write-Host "Deleting original files..." -ForegroundColor Cyan

$DeletedCount = 0
$DeleteFailedCount = 0
$ActualDeletedSizeBytes = [int64]0

foreach ($Item in $DeleteCandidates) {
    try {
        Remove-Item -LiteralPath $Item.File.FullName -Force
        $DeletedCount++
        $ActualDeletedSizeBytes += [int64]$Item.FileSize

        Write-Host "Deleted: $($Item.Name)" -ForegroundColor DarkYellow
    }
    catch {
        $DeleteFailedCount++

        Write-Host "Failed:  $($Item.Name)" -ForegroundColor Red
        Write-Host "Reason:  $($_.Exception.Message)" -ForegroundColor Red
    }
}

Write-Host ""
Write-Host "Operation completed." -ForegroundColor Cyan
Write-Host "=============================================="
Write-Host "ZIP backup             : $ZipPath"
Write-Host "ZIP backup size        : $(Convert-BytesToMB -Bytes $ZipSizeBytes) MB"
Write-Host "Files deleted          : $DeletedCount"
Write-Host "Files protected        : $ProtectedCount"
Write-Host "Deletion failures      : $DeleteFailedCount"

$FolderSizeAfterBytes = Get-FolderSizeBytes -Path $TargetFolder
$FolderFileCountAfter = Get-FolderFileCount -Path $TargetFolder
$FolderSizeBeforeMB = Convert-BytesToMB -Bytes $FolderSizeBeforeBytes
$FolderSizeAfterMB = Convert-BytesToMB -Bytes $FolderSizeAfterBytes
$ActualDeletedSizeMB = Convert-BytesToMB -Bytes $ActualDeletedSizeBytes
$ZipSizeMB = Convert-BytesToMB -Bytes $ZipSizeBytes

if ($AnalyzedImageSizeBytes -gt 0) {
    $ActualImageReductionPercent = [math]::Round((($ActualDeletedSizeBytes / $AnalyzedImageSizeBytes) * 100), 2)
}
else {
    $ActualImageReductionPercent = 0
}

if ($Results.Count -gt 0) {
    $ActualDeletedFilePercent = [math]::Round((($DeletedCount / $Results.Count) * 100), 2)
    $ActualProtectedFilePercent = [math]::Round((($ProtectedCount / $Results.Count) * 100), 2)
}
else {
    $ActualDeletedFilePercent = 0
    $ActualProtectedFilePercent = 0
}

$NetReclaimedBytes = $FolderSizeBeforeBytes - $FolderSizeAfterBytes
if ($FolderSizeBeforeBytes -gt 0) {
    $NetFolderReductionPercent = [math]::Round((($NetReclaimedBytes / $FolderSizeBeforeBytes) * 100), 2)
    $GrossFolderReductionPercent = [math]::Round((($ActualDeletedSizeBytes / $FolderSizeBeforeBytes) * 100), 2)
}
else {
    $NetReclaimedBytes = [int64]0
    $NetFolderReductionPercent = 0
    $GrossFolderReductionPercent = 0
}

if ($ActualDeletedSizeBytes -gt 0) {
    $ZipCompressionPercent = [math]::Round(((1 - ($ZipSizeBytes / $ActualDeletedSizeBytes)) * 100), 2)
    $ZipToDeletedRatioPercent = [math]::Round((($ZipSizeBytes / $ActualDeletedSizeBytes) * 100), 2)
}
else {
    $ZipCompressionPercent = 0
    $ZipToDeletedRatioPercent = 0
}

Write-Host ""
Write-Host "Operation completed." -ForegroundColor Cyan
Write-Host "=============================================="
Write-Host "ZIP backup             : $ZipPath"
Write-Host "ZIP backup size        : $ZipSizeMB MB"
Write-Host "Files deleted          : $DeletedCount ($ActualDeletedFilePercent %)"
Write-Host "Files protected        : $ProtectedCount ($ActualProtectedFilePercent %)"
Write-Host "Deletion failures      : $DeleteFailedCount"
Write-Host "Folder files before    : $FolderFileCountBefore"
Write-Host "Folder files after     : $FolderFileCountAfter"

Write-Host ""
Write-Host "Size and reduction statistics" -ForegroundColor Cyan
Write-Host "----------------------------------------------"
Write-Host "Folder size before     : $FolderSizeBeforeMB MB"
Write-Host "Actual deleted size    : $ActualDeletedSizeMB MB"
Write-Host "ZIP backup size        : $ZipSizeMB MB"
Write-Host "Folder size after      : $FolderSizeAfterMB MB"
Write-Host "Net space reclaimed    : $(Convert-BytesToMB -Bytes $NetReclaimedBytes) MB"
Write-Host "Image data removed     : $ActualImageReductionPercent %"
Write-Host "Gross folder reduction : $GrossFolderReductionPercent %"
Write-Host "Net folder reduction   : $NetFolderReductionPercent %"
Write-Host "ZIP size / deleted data: $ZipToDeletedRatioPercent %"
Write-Host "ZIP compression saving : $ZipCompressionPercent %"

if ($DeleteFailedCount -eq 0) {
    Write-Host "All selected files were backed up and deleted successfully." -ForegroundColor Green
}
else {
    Write-Host "Backup completed, but some original files could not be deleted." -ForegroundColor Yellow
}

Write-Host ""

Wait-ForKeyPress
 
Geri
Üst