New Video Cleaner

version 2.0.3

ready to start cleaning

About

This is a Windows PowerShell script that removes extra bytes from the end of MP4, MOV, and WMV video files. If such extra bytes are found and removed, they are saved to a text file with the same name as the video file for examination.

Does it work?

I have tested it thoroughly myself and it has worked every time. I use it myself instead of the old cleaner.

Disclaimer

The script is not copyrighted and contains no copyrighted material. It is published here under the MIT license.

Although the software is thoroughly tested and the risk of something wrong is going small, the software is provided as-is without any guarantees.

📼 Making a temporary copy of files before cleaning them is recommended in case something goes wrong.

How to use

  1. Create a new text file anywhere on your computer. The desktop will do, or in the same folder as your video files.
  2. Copy the code from the section below, 👇 The Code, using the handy copy button in the top corner.
  3. Paste that code into the text file.
  4. Rename the text file videocleaner.ps1 or whatever name you prefer.
  5. Run the script one of two ways…

Right-click on the script file and choose Run with PowerShell. The script will open.

Basic usage:

  • To clean one file, drag it onto the script window and the filename will be entered for you. Then hit enter.
  • To clean multiple files, enter all the filenames separated by spaces. You can still use drag-and-drop for this but remember to put a space bewteen the filenames.
  • You can add -y to your input if you want to skip confirmation and clean all the files if any extra bytes are found.

Direct mode

You can also call this script from PowerShell if you want. You would call it like this:

powershell.exe -ExecutionPolicy Bypass -File .\videocleaner.ps1

where videocleaner.ps1 is whatever filename you chose.

The options and syntax for direct mode are the same as for prompt mode. Example for cleaning multiple files and bypassing confirmation:

powershell.exe -ExecutionPolicy Bypass -File .\videocleaner.ps1 file1.mp4 file2.mp4 "filename with spaces.mov" -y

The Code

⎗
✓
# Video Cleaner 2.0.3

# Set the execution policy for the current scope
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass

# Function to display help information
function Show-Help {
    Write-Host @"

Video Cleaner 2.0.3 - MIT License

This script detects and removes trailing junk data from video files.

There are two ways to run this script:

1. Prompt Mode (*Recommended)
   Right-click on the script file and choose "Run with PowerShell".

2. Direct Mode
   Open PowerShell and run the following command:
   powershell.exe -ExecutionPolicy Bypass -File .\<script_name>.ps1
   Replace <script_name>.ps1 with the actual name of the script file.

To use the script, provide at least one video file path as an argument.
You can enter multiple file paths separated by spaces. Alternatively,
you can drag and drop the video file(s) onto the PowerShell window.

If trailing junk data is found in a video file, the script will prompt
you for confirmation before removing it. To automatically remove
trailing junk data without confirmation, use the -y flag. The position
of the -y flag does not matter; it can be placed before or after the
file paths.

Examples of Direct mode use:
.\<script_name>.ps1 "C:\path\to\video1.mp4" "C:\path\to\video2.mp4"
.\<script_name>.ps1 -y "C:\path\to\video1.mp4" "C:\path\to\video2.mp4"

Disclaimer:
The author assumes no responsibility for your use of this script,
whether it fails to remove data or renders video files unplayable.
The user is responsible for making backups of videos before using the
script to alter the files. The script itself is free of copyright and
is provided under the MIT license.

"@
}

# Function to detect the file type based on the file header
function Get-FileType($header) {
    if ($header -match "^.{4}(ftyp|moov|mdat|free|skip)") {
        return "mp4/mov"
    }
    elseif ($header.SubString(0, 4) -eq [System.Text.Encoding]::ASCII.GetString([byte[]] @(0x30, 0x26, 0xb2, 0x75))) {
        return "wmv"
    }
    else {
        return "unknown"
    }
}

# Function to get the minimum chunk size based on the file type
function Get-MinChunkSize($fileType) {
    switch ($fileType) {
        "mp4/mov" { return 8 }
        "wmv" { return 24 }
        default { throw "Unknown file type" }
    }
}

# Function to find the trailing junk bytes
function Find-TrailingJunkBytes($filePath) {
    $fileStream = New-Object System.IO.FileStream($filePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read)
    $header = New-Object byte[] 16
    $null = $fileStream.Read($header, 0, 16)
    $fileType = Get-FileType ([System.Text.Encoding]::ASCII.GetString($header))

    if ($fileType -eq "unknown") {
        Write-Host "  Unsupported file type: $filePath`n" -ForegroundColor $Host.PrivateData.WarningForegroundColor -BackgroundColor $Host.PrivateData.WarningBackgroundColor
        $fileStream.Close()
        return -1
    }

    $minChunkSize = Get-MinChunkSize $fileType

    $fileSize = $fileStream.Length
    Write-Host "  File type: $fileType, Size: $fileSize bytes"

    $position = 0
    $lastValidChunkEnd = 0

    while ($position -lt $fileSize) {
        $remainingBytes = $fileSize - $position
        $fileStream.Position = $position

        if ($remainingBytes -lt $minChunkSize) {
            break
        }

        $chunkHeader = New-Object byte[] $minChunkSize
        $bytesRead = $fileStream.Read($chunkHeader, 0, $minChunkSize)

        if ($bytesRead -lt $minChunkSize) {
            break
        }

        # Decode chunk length based on file type
        $chunkLength = switch ($fileType) {
            "mp4/mov" { [System.Net.IPAddress]::NetworkToHostOrder([BitConverter]::ToInt32($chunkHeader, 0)) }
            "wmv" { [BitConverter]::ToUInt64($chunkHeader, 16) }
            default { 0 }
        }

        # Check for problems with chunk size
        if ($chunkLength -le 0 -or $chunkLength -gt $remainingBytes) {
            break
        }

        $position += $chunkLength
        $lastValidChunkEnd = $position
    }

    $junkBytes = $fileSize - $lastValidChunkEnd
    if ($junkBytes -gt 0) {
        Write-Host "  Trailing junk bytes found: $junkBytes"
    }
    else {
        Write-Host "  No trailing junk bytes found"
    }

    $fileStream.Close()
    return $junkBytes
}

# Function to remove trailing junk bytes from the file
function Remove-TrailingJunkBytes($filePath, $junkBytes) {
    $fileStream = [System.IO.File]::Open($filePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite)
    $fileStream.SetLength($fileStream.Length - $junkBytes)
    $fileStream.Close()
    Write-Host "  Truncated $filePath to $($fileStream.Length) bytes"
}

# Function to backup the trailing junk bytes to a file
function Backup-TrailingJunkBytes($filePath, $junkBytes) {
    $backupFilePath = "$filePath.junk.txt"
    $fileBytes = [System.IO.File]::ReadAllBytes($filePath)
    $junkStartIndex = $fileBytes.Length - $junkBytes
    $junkEndIndex = $fileBytes.Length - 1
    $junkBytes = $fileBytes[$junkStartIndex..$junkEndIndex]

    [System.IO.File]::WriteAllBytes($backupFilePath, $junkBytes)
    Write-Host "  Backed up trailing junk bytes to $backupFilePath"
}

# Function to process a single file
function Invoke-FileCleanup($filePath, $autoRemove) {
    # Check if the file exists
    if (!(Test-Path $filePath -PathType Leaf)) {
        Write-Host "The specified file does not exist: $filePath" -ForegroundColor $Host.PrivateData.WarningForegroundColor -BackgroundColor $Host.PrivateData.WarningBackgroundColor
        Write-Host ""
        return
    }

    Write-Host "`nProcessing $filePath"

    $junkBytes = Find-TrailingJunkBytes $filePath

    if ($junkBytes -eq -1) {
        # Unsupported file type, skip further processing
        return
    }

    if ($junkBytes -gt 256) {
        Write-Host "  No trailing junk bytes found in $filePath (false positive detected)" -ForegroundColor Green -BackgroundColor Black
        Write-Host ""
        return
    }

    if ($junkBytes -gt 0) {
        Write-Host "  Trailing junk bytes found: $junkBytes" -ForegroundColor $Host.PrivateData.WarningForegroundColor -BackgroundColor $Host.PrivateData.WarningBackgroundColor

        if ($autoRemove) {
            Backup-TrailingJunkBytes $filePath $junkBytes
            Remove-TrailingJunkBytes $filePath $junkBytes
            Write-Host "  Removed trailing junk bytes from $filePath" -ForegroundColor Green -BackgroundColor Black
        }
        else {
            $title = "Confirmation"
            $message = "Do you want to remove the trailing junk bytes?"
            $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Remove the trailing junk bytes"
            $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Keep the trailing junk bytes"
            $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
            $result = $Host.UI.PromptForChoice($title, $message, $options, 1)

            if ($result -eq 0) {
                Backup-TrailingJunkBytes $filePath $junkBytes
                Remove-TrailingJunkBytes $filePath $junkBytes
                Write-Host "  Removed trailing junk bytes from $filePath" -ForegroundColor Green -BackgroundColor Black
                Write-Host ""
            }
            else {
                Write-Host "  Trailing junk bytes NOT removed from $filePath`n" -ForegroundColor $Host.PrivateData.ErrorForegroundColor -BackgroundColor $Host.PrivateData.ErrorBackgroundColor
            }
        }
    }
    else {
        Write-Host "  No trailing junk bytes found in $filePath" -ForegroundColor Green -BackgroundColor Black
        Write-Host ""
    }
}

# Check if the -help flag is provided
if ($args -contains "-help") {
    Show-Help
    exit 0
}

# Check if the -y flag is provided
$autoRemove = $args -contains "-y"

# Remove the -y flag from the arguments if present
$fileArgs = $args | Where-Object { $_ -ne "-y" }

# Check if file paths are provided as arguments (Direct mode)
if ($fileArgs.Count -gt 0) {
    Write-Host "Video Cleaner 2.0.3"
    foreach ($filePath in $fileArgs) {
        Invoke-FileCleanup $filePath $autoRemove
    }
}
elseif ($autoRemove) {
    Write-Host "Please provide at least one file path along with the -y flag."
}
else {
    # Prompt mode
    Write-Host "Video Cleaner 2.0.3"
    Write-Host "Use at your own risk. See help for details.`n"

    while ($true) {
        $userInput = Read-Host "Enter file path(s), 'q' to quit, or 'help'/'h' for more info"
        if ($userInput -eq "q") {
            break
        }
        elseif ($userInput -eq "help" -or $userInput -eq "h") {
            Show-Help
            continue
        }

        # Check if the -y flag is provided in the user input
        $autoRemove = $userInput -split ' ' -contains "-y"

        # Split the user input by spaces, except for quoted sections
        $filePaths = $userInput | ForEach-Object {
            $_ -split '(?<=^[^"]*(?:"[^"]*"[^"]*)*) (?=(?:[^"]*"[^"]*")*[^"]*$)'
        } | Where-Object { $_ -ne "" -and $_ -ne "-y" }

        foreach ($filePath in $filePaths) {
            # Remove any surrounding quotes from the file path
            $filePath = $filePath.Trim('"')
            Invoke-FileCleanup $filePath $autoRemove
        }
    }
}

Technical Explanation

The Video Cleaner script is designed to detect and remove unnecessary or harmful bytes appended to the end of video files. This script supports MP4, MOV, and WMV video file formats.

File Type Detection

The script starts by detecting the file type of the input video file. It reads the first 16 bytes of the file, known as the file header, and analyzes the signature or magic bytes to determine the file type.

  • For MP4 and MOV files, the script checks for the presence of specific chunk types such as "ftyp", "moov", "mdat", "free", or "skip" at the beginning of the file.
  • For WMV files, the script checks for the ASCII representation of the byte sequence 0x30, 0x26, 0xb2, 0x75.

If the file type is not recognized, the script considers it an unsupported file type and skips further processing.

Chunk Size and Structure

Video files are typically composed of chunks or atoms, which are self-contained units of data. Each chunk has a specific structure depending on the file format.

  • In MP4 and MOV files, each chunk consists of a 4-byte size field (in big-endian order) followed by a 4-byte chunk type identifier.
  • In WMV files, each chunk consists of a 16-byte GUID (Globally Unique Identifier) followed by an 8-byte size field (in little-endian order).

The script determines the minimum chunk size based on the file type: 8 bytes for MP4 and MOV files, and 24 bytes for WMV files.

Detecting Trailing Junk Bytes

To detect trailing junk bytes, the script iterates through the chunks of the video file from the beginning to the end. It reads the chunk headers and calculates the expected chunk sizes based on the file format.

For each chunk, the script compares the remaining bytes in the file with the expected chunk size. If the remaining bytes are less than the minimum chunk size or if the chunk size is invalid (less than or equal to 0 or greater than the remaining bytes), the script considers the remaining bytes as trailing junk.

The script also handles cases where the file ends unexpectedly or if there is a small junk section at the end of the file.

Removing Trailing Junk Bytes

If trailing junk bytes are detected, the script provides an option to remove them. It prompts the user for confirmation before proceeding with the removal process.

To remove the trailing junk bytes, the script performs the following steps:

  1. It opens the video file using [System.IO.File]::Open() with read and write access.
  2. It truncates the file by setting its length to the original length minus the number of junk bytes using the SetLength() method.
  3. It closes the file stream.

Backup and Confirmation

Before removing the trailing junk bytes, the script creates a backup of the junk bytes in a separate file with the ".junk.txt" extension. This allows the user to review the removed bytes if needed.

The script prompts the user for confirmation before removing the trailing junk bytes. It provides an option to automatically remove the junk bytes without confirmation using the -y flag.

Edit

Pub: 13 Apr 2024 23:15 UTC

Edit: 19 May 2024 17:48 UTC

Views: 1064