From af7e90612bd3e774e0aec41acd291f5e128b9a62 Mon Sep 17 00:00:00 2001 From: notquitenothing Date: Sun, 12 May 2024 12:48:17 -0500 Subject: [PATCH 1/2] Added an output folder parameter. Added intereactive mode that when active, opens dialog boxes to select input files and output folder if they are not provided. Added flag to also copy unconverted image files, such as those that are already in jpeg format, to output folder. When a file fails to convert, add it to a list that is printed at the end of execution instead of exiting immediately. Conversion output now includes full output file path since it can be selected. --- ConvertTo-Jpeg.ps1 | 129 +++++++++++++++++++++++++++++++++++++++------ README.md | 60 ++++++++++++++++++--- 2 files changed, 164 insertions(+), 25 deletions(-) diff --git a/ConvertTo-Jpeg.ps1 b/ConvertTo-Jpeg.ps1 index d827a1e..903b65f 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,10 +13,28 @@ 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] - $FixExtensionIfJpeg + $FixExtensionIfJpeg, + + [Parameter( + HelpMessage = "Opens dialogs for input files and output folder if not supplied")] + [Switch] + [Alias("t")] + $InteractiveMode, + + [Parameter( + HelpMessage = "Also output unconverted image files to the output folder path")] + [Switch] + [Alias("u")] + $OutputUnconverted ) Begin @@ -46,6 +64,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) { @@ -54,10 +109,18 @@ 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 + $outputFileName = $inputFile.Name + ".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]) } @@ -67,28 +130,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]) # Write SoftwareBitmap to output file - $outputFileName = $inputFile.Name + ".jpg"; - $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) @@ -96,12 +180,13 @@ Process # Do it AwaitAction ($encoder.FlushAsync()) - Write-Host " -> $outputFileName" + Write-Host " -> $(Join-Path $outputFolder.Path $outputFileName)" } catch { - # Report full details - throw $_.Exception.ToString() + # Report full details and add file to list + Write-Error $_.Exception + $FailedFiles.Add($file) } finally { @@ -110,4 +195,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 a2813c9..4608546 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. @@ -53,10 +84,23 @@ To rename JPEG-encoded files that don't have the standard `.jpg` extension, use ```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`). +This includes existing JPEG-encoded files. + +```PowerShell +PS C:\T> .\ConvertTo-Jpeg.ps1 -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 +``` + + ## Formats | Decoder | Extensions | From 043b6a7c242208f4ea97ffb859c7bb7c14bfdb75 Mon Sep 17 00:00:00 2001 From: notquitenothing Date: Mon, 5 Aug 2024 22:08:08 -0500 Subject: [PATCH 2/2] Added ability to not exit on conversion failure with flag -NoFailOnConvert --- ConvertTo-Jpeg.ps1 | 21 +++++++++++++++++---- README.md | 6 +++--- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/ConvertTo-Jpeg.ps1 b/ConvertTo-Jpeg.ps1 index 5e3bbba..f1dfb44 100644 --- a/ConvertTo-Jpeg.ps1 +++ b/ConvertTo-Jpeg.ps1 @@ -32,11 +32,16 @@ Param ( $InteractiveMode, [Parameter( - HelpMessage = "Also output unconverted image files to the output folder path")] + 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] @@ -200,9 +205,17 @@ Process } catch { - # Report full details and add file to list - Write-Error $_.Exception - $FailedFiles.Add($file) + if ($NoFailOnConvert) + { + # Report full details and add file to list + Write-Error $_.Exception + $FailedFiles.Add($file) + } + else + { + # Report full details + throw $_.Exception.ToString() + } } finally { diff --git a/README.md b/README.md index f098690..f3da94a 100644 --- a/README.md +++ b/README.md @@ -91,12 +91,12 @@ 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`). +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 -o C:\T\Documents C:\T\Pictures\IMG_1234.HEIC C:\T\Pictures\IMG_ABCD.jpg +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 ```