diff --git a/ConvertTo-Jpeg.ps1 b/ConvertTo-Jpeg.ps1 index 1824dcc..f1dfb44 100644 --- a/ConvertTo-Jpeg.ps1 +++ b/ConvertTo-Jpeg.ps1 @@ -3,7 +3,7 @@ Param ( [Parameter( - Mandatory = $true, + Mandatory = $false, Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, @@ -13,12 +13,35 @@ Param ( [String[]] $Files, + [Parameter( + HelpMessage = "Output folder for converted files")] + [String] + [Alias("o")] + $OutputFolderPath, + [Parameter( HelpMessage = "Fix extension of JPEG files without the .jpg extension")] [Switch] [Alias("f")] $FixExtensionIfJpeg, + [Parameter( + HelpMessage = "Opens dialogs for input files and output folder if not supplied")] + [Switch] + [Alias("t")] + $InteractiveMode, + + [Parameter( + HelpMessage = "Also copy unconverted image files to the output folder path")] + [Switch] + [Alias("u")] + $OutputUnconverted, + + [Parameter( + HelpMessage = "Do not fail on conversion error, only write exception")] + [Switch] + $NoFailOnConvert, + [Parameter( HelpMessage = "Remove existing extension of non-JPEG files before adding .jpg")] [Switch] @@ -53,6 +76,43 @@ Begin Process { + # If no files were passed and interactive mode, open a file dialog to select them + if (!$Files -and $InteractiveMode) + { + Add-Type -AssemblyName System.Windows.Forms + $FileBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property @{ + InitialDirectory = [Environment]::GetFolderPath('Desktop') + Title = "Select Files to Convert" + Multiselect = $true + # filter selection to supported filetypes + Filter = "Image Files (*.*)|*.BMP;*.DIB;*.RLE;*.CUR;*.DDS;*.DNG;*.GIF;*.ICO;*.ICON;*.EXIF;*.JFIF;*.JPE;*.JPEG;*.JPG;*.ARW;*.CR2;*.CRW;*.DNG;*.ERF;*.KDC;*.MRW;*.NEF;*.NRW;*.ORF;*.PEF;*.RAF;*.RAW;*.RW2;*.RWL;*.SR2;*.SRW;*.AVCI;*.AVCS;*.HEIC;*.HEICS;*.HEIF;*.HEIFS;*.WEBP;*.PNG;*.TIF;*.TIFF;*.JXR;*.WDP|All files (*.*)|*.*" + } + $null = $FileBrowser.ShowDialog() + $Files = $FileBrowser.FileNames + $FileBrowser.Dispose() + } + + # If no output folder selected and interactive mode, select a folder with dialog + if (!$OutputFolderPath -and $InteractiveMode) + { + Add-Type -AssemblyName System.Windows.Forms + $OutputFolderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog -Property @{ + Description = "Select Folder to Place Converted Files" + } + $null = $OutputFolderBrowser.ShowDialog() + $OutputFolderPath = $OutputFolderBrowser.SelectedPath + $OutputFolderBrowser.Dispose() + + # If no OutputFolderPath was selected, Throw Error + if (!$OutputFolderPath) + { + Throw "Output Folder Not Selected." + } + } + + # Files that failed to be converted + $FailedFiles = New-Object -TypeName "System.Collections.ArrayList" + # Summary of imaging APIs: https://docs.microsoft.com/en-us/windows/uwp/audio-video-camera/imaging foreach ($file in $Files) { @@ -61,10 +121,27 @@ Process { try { - # Get SoftwareBitmap from input file + # Get SoftwareBitmap from input file, determine output path $file = Resolve-Path -LiteralPath $file $inputFile = AwaitOperation ([Windows.Storage.StorageFile]::GetFileFromPathAsync($file)) ([Windows.Storage.StorageFile]) $inputFolder = AwaitOperation ($inputFile.GetParentAsync()) ([Windows.Storage.StorageFolder]) + $inputExtension = $inputFile.FileType + $outputFolder = $inputFolder + # Determine output file name + # Get name of original file, including extension + $fileName = $inputFile.Name + if ($RemoveOriginalExtension) + { + # If removing original extension, get the original file name without the extension + $fileName = $inputFile.DisplayName + } + # Add .jpg to the file name + $outputFileName = $fileName + ".jpg" + + if ($OutputFolderPath) + { + $outputFolder = AwaitOperation ([Windows.Storage.StorageFolder]::GetFolderFromPathAsync($OutputFolderPath)) ([Windows.Storage.StorageFolder]) + } $inputStream = AwaitOperation ($inputFile.OpenReadAsync()) ([Windows.Storage.Streams.IRandomAccessStreamWithContentType]) $decoder = AwaitOperation ([Windows.Graphics.Imaging.BitmapDecoder]::CreateAsync($inputStream)) ([Windows.Graphics.Imaging.BitmapDecoder]) } @@ -74,38 +151,49 @@ Process Write-Host " [Unsupported]" continue } + # Check if image is already a jpg if ($decoder.DecoderInformation.CodecId -eq [Windows.Graphics.Imaging.BitmapDecoder]::JpegDecoderId) { - $extension = $inputFile.FileType - if ($FixExtensionIfJpeg -and ($extension -ne ".jpg") -and ($extension -ne ".jpeg")) + # Check if jpg files should have their extensions fixed + $ExtensionRequiresFix = $FixExtensionIfJpeg -and ($inputExtension -ne ".jpg") -and ($inputExtension -ne ".jpeg") + if ($ExtensionRequiresFix) { - # Rename JPEG-encoded files to have ".jpg" extension - $newName = $inputFile.Name -replace ($extension + "$"), ".jpg" - AwaitAction ($inputFile.RenameAsync($newName)) - Write-Host " => $newName" + $outputFileName = $inputFile.DisplayName + ".jpg" } else { - # Skip JPEG-encoded files - Write-Host " [Already JPEG]" + $outputFileName = $inputFile.Name + } + + # If OutputUnconverted and there is an OutputFolderPath + # Copy the existing file to the output folder + if ($OutputUnconverted -and $OutputFolderPath) + { + # Copy input file to output folder + Copy-Item -path $inputFile.Path -Destination $(Join-Path $outputFolder.Path $outputFileName) + Write-Host " => $(Join-Path $outputFolder.Path $outputFileName)" + continue + } + else + { + if ($ExtensionRequiresFix) + { + # Rename JPEG-encoded files to have ".jpg" extension + AwaitAction ($inputFile.RenameAsync($outputFileName)) + Write-Host " => $(Join-Path $inputFolder.Path $outputFileName)" + } + else + { + # Skip JPEG-encoded files + Write-Host " [Already JPEG]" + } + continue } - continue } $bitmap = AwaitOperation ($decoder.GetSoftwareBitmapAsync()) ([Windows.Graphics.Imaging.SoftwareBitmap]) - - # Determine output file name - # Get name of original file, including extension - $fileName = $inputFile.Name - if ($RemoveOriginalExtension) - { - # If removing original extension, get the original file name without the extension - $fileName = $inputFile.DisplayName - } - # Add .jpg to the file name - $outputFileName = $fileName + ".jpg" # Write SoftwareBitmap to output file - $outputFile = AwaitOperation ($inputFolder.CreateFileAsync($outputFileName, [Windows.Storage.CreationCollisionOption]::ReplaceExisting)) ([Windows.Storage.StorageFile]) + $outputFile = AwaitOperation ($outputFolder.CreateFileAsync($outputFileName, [Windows.Storage.CreationCollisionOption]::ReplaceExisting)) ([Windows.Storage.StorageFile]) $outputStream = AwaitOperation ($outputFile.OpenAsync([Windows.Storage.FileAccessMode]::ReadWrite)) ([Windows.Storage.Streams.IRandomAccessStream]) $encoder = AwaitOperation ([Windows.Graphics.Imaging.BitmapEncoder]::CreateAsync([Windows.Graphics.Imaging.BitmapEncoder]::JpegEncoderId, $outputStream)) ([Windows.Graphics.Imaging.BitmapEncoder]) $encoder.SetSoftwareBitmap($bitmap) @@ -113,12 +201,21 @@ Process # Do it AwaitAction ($encoder.FlushAsync()) - Write-Host " -> $outputFileName" + Write-Host " -> $(Join-Path $outputFolder.Path $outputFileName)" } catch { - # Report full details - throw $_.Exception.ToString() + if ($NoFailOnConvert) + { + # Report full details and add file to list + Write-Error $_.Exception + $FailedFiles.Add($file) + } + else + { + # Report full details + throw $_.Exception.ToString() + } } finally { @@ -127,4 +224,14 @@ Process if ($outputStream -ne $null) { [System.IDisposable]$outputStream.Dispose() } } } + + if ($FailedFiles.Count -gt 0) + { + Write-Host "The following files failed to convert." + Write-Host "You may lack the required extensions or the files may be corrupt." + foreach ($file in $FailedFiles) + { + Write-Host $file + } + } } diff --git a/README.md b/README.md index eaff7ce..f3da94a 100644 --- a/README.md +++ b/README.md @@ -23,16 +23,16 @@ Passing parameters: ```PowerShell PS C:\T> .\ConvertTo-Jpeg.ps1 C:\T\Pictures\IMG_1234.HEIC C:\T\Pictures\IMG_5678.HEIC -C:\T\Pictures\IMG_1234.HEIC -> IMG_1234.HEIC.jpg -C:\T\Pictures\IMG_5678.HEIC -> IMG_5678.HEIC.jpg +C:\T\Pictures\IMG_1234.HEIC -> C:\T\Pictures\IMG_1234.HEIC.jpg +C:\T\Pictures\IMG_5678.HEIC -> C:\T\Pictures\IMG_5678.HEIC.jpg ``` Pipeline via `dir`: ```PowerShell PS C:\T> dir C:\T\Pictures | .\ConvertTo-Jpeg.ps1 -C:\T\Pictures\IMG_1234.HEIC -> IMG_1234.HEIC.jpg -C:\T\Pictures\IMG_5678.HEIC -> IMG_5678.HEIC.jpg +C:\T\Pictures\IMG_1234.HEIC -> C:\T\Pictures\IMG_1234.HEIC.jpg +C:\T\Pictures\IMG_5678.HEIC -> C:\T\Pictures\IMG_5678.HEIC.jpg C:\T\Pictures\Kitten.jpg [Already JPEG] C:\T\Pictures\Notes.txt [Unsupported] ``` @@ -41,10 +41,41 @@ Pipeline via `Get-ChildItem`: ```PowerShell PS C:\T> Get-ChildItem C:\T\Pictures -Filter *.HEIC | .\ConvertTo-Jpeg.ps1 -C:\T\Pictures\IMG_1234.HEIC -> IMG_1234.HEIC.jpg -C:\T\Pictures\IMG_5678.HEIC -> IMG_5678.HEIC.jpg +C:\T\Pictures\IMG_1234.HEIC -> C:\T\Pictures\IMG_1234.HEIC.jpg +C:\T\Pictures\IMG_5678.HEIC -> C:\T\Pictures\IMG_5678.HEIC.jpg ``` +Dialog via `InteractiveMode` (`-t`) flag: + +```PowerShell +PS C:\T> .\ConvertTo-Jpeg.ps1 -t +C:\T\Pictures\IMG_1234.HEIC -> C:\T\Pictures\IMG_1234.HEIC.jpg +C:\T\Pictures\IMG_5678.HEIC -> C:\T\Pictures\IMG_5678.HEIC.jpg +``` + + +### Output Folder + +Choosing an output folder path for converted files. +If no output path is supplied, each converted file will be placed in the same +folder as the original. +If an output path is supplied, it will be included in the file conversion log. + +Paramater via `OutputFolderPath` (`-o`) flag: + +```PowerShell +PS C:\T> .\ConvertTo-Jpeg.ps1 -o C:\T\Documents C:\T\Pictures\IMG_1234.HEIC +C:\T\Pictures\IMG_1234.HEIC -> C:\T\Documents\IMG_1234.HEIC.jpg +``` + +Dialog via `InteractiveMode` (`-t`) flag: + +```PowerShell +PS C:\T> .\ConvertTo-Jpeg.ps1 -t C:\T\Pictures\IMG_1234.HEIC +C:\T\Pictures\IMG_1234.HEIC -> C:\T\Documents\IMG_1234.HEIC.jpg +``` + + ### Renaming Files Sometimes files have the wrong extension. @@ -54,18 +85,31 @@ switch (alias `-f`). ```PowerShell PS C:\T> dir C:\T\Pictures\*.HEIC | .\ConvertTo-Jpeg.ps1 -FixExtensionIfJpeg -C:\T\Pictures\IMG_1234 (Edited).HEIC => IMG_1234 (Edited).jpg -C:\T\Pictures\IMG_1234.HEIC -> IMG_1234.HEIC.jpg +C:\T\Pictures\IMG_ABCD.HEIC => C:\T\Pictures\IMG_ABCD.jpg +C:\T\Pictures\IMG_1234.HEIC -> C:\T\Pictures\IMG_1234.HEIC.jpg ``` +### Output Unconverted Files + +Also outputs existing files that don't require conversion but are images if an output path is specified by `-o` flag or InteractiveMode (`-t`). +Flag is `-OutputUnconverted` (`-u`.) +This includes existing JPEG-encoded files. + +```PowerShell +PS C:\T> .\ConvertTo-Jpeg.ps1 -u -o C:\T\Documents C:\T\Pictures\IMG_1234.HEIC C:\T\Pictures\IMG_ABCD.jpg +C:\T\Pictures\IMG_1234.HEIC -> C:\T\Documents\IMG_1234.HEIC.jpg +C:\T\Pictures\IMG_ABCD.jpg => C:\T\Documents\IMG_5678.jpg +``` + + ### Removing existing extensions To remove the existing extension of a file, use the `-RemoveOriginalExtension` switch (alias `-r`). ```PowerShell PS C:\T> dir C:\T\Pictures\*.HEIC | .\ConvertTo-Jpeg.ps1 -RemoveOriginalExtension -C:\T\Pictures\IMG_1234.HEIC -> IMG_1234.jpg -C:\T\Pictures\IMG_5678.HEIC -> IMG_5678.jpg +C:\T\Pictures\IMG_1234.HEIC -> C:\T\Pictures\IMG_1234.jpg +C:\T\Pictures\IMG_5678.HEIC -> C:\T\Pictures\IMG_5678.jpg ``` ## Formats