mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-23 19:49:43 +01:00
Add standard CLI support for Image Resizer
This commit is contained in:
@@ -131,6 +131,8 @@
|
||||
|
||||
"PowerToys.ImageResizer.exe",
|
||||
"PowerToys.ImageResizer.dll",
|
||||
"WinUI3Apps\\PowerToys.ImageResizerCLI.exe",
|
||||
"WinUI3Apps\\PowerToys.ImageResizerCLI.dll",
|
||||
"PowerToys.ImageResizerExt.dll",
|
||||
"PowerToys.ImageResizerContextMenu.dll",
|
||||
"ImageResizerContextMenuPackage.msix",
|
||||
|
||||
@@ -442,6 +442,10 @@
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/imageresizer/ImageResizerCLI/ImageResizerCLI.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
</Folder>
|
||||
<Folder Name="/modules/imageresizer/Tests/">
|
||||
<Project Path="src/modules/imageresizer/tests/ImageResizer.UnitTests.csproj">
|
||||
|
||||
182
doc/devdocs/cli-conventions.md
Normal file
182
doc/devdocs/cli-conventions.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# CLI Conventions
|
||||
|
||||
This document describes the conventions for implementing command-line interfaces (CLI) in PowerToys modules.
|
||||
|
||||
## Library
|
||||
|
||||
Use the **System.CommandLine** library for CLI argument parsing. This is already defined in `Directory.Packages.props`:
|
||||
|
||||
```xml
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
```
|
||||
|
||||
Add the reference to your project:
|
||||
|
||||
```xml
|
||||
<PackageReference Include="System.CommandLine" />
|
||||
```
|
||||
|
||||
## Option Naming
|
||||
|
||||
### Aliases Pattern
|
||||
|
||||
Define option aliases as static readonly arrays following this pattern:
|
||||
|
||||
```csharp
|
||||
private static readonly string[] AliasesSilent = ["--silent", "-s"];
|
||||
private static readonly string[] AliasesWidth = ["--width", "-w"];
|
||||
private static readonly string[] AliasesHelp = ["--help"];
|
||||
```
|
||||
|
||||
When creating dedicated option types (for example, `sealed class FooOption : Option<T>`),
|
||||
avoid naming a member `Aliases` (it hides `Option.Aliases`). Prefer `_aliases`.
|
||||
|
||||
### Naming Rules
|
||||
|
||||
1. **Long form**: Use `--kebab-case` (e.g., `--shrink-only`, `--keep-date-modified`)
|
||||
2. **Short form**: Use single `-x` character (e.g., `-s`, `-w`, `-h`)
|
||||
3. **No short form** for less common options (e.g., `--shrink-only`, `--ignore-orientation`)
|
||||
|
||||
## Option Definition
|
||||
|
||||
Create options using `Option<T>` with descriptive help text:
|
||||
|
||||
```csharp
|
||||
var silentOption = new Option<bool>(AliasesSilent, "Run in silent mode without UI");
|
||||
var widthOption = new Option<double?>(AliasesWidth, "Set width in pixels");
|
||||
var unitOption = new Option<ResizeUnit?>(AliasesUnit, "Set unit (Pixel, Percent, Inch, Centimeter)");
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
Add validators for options that require range or format checking:
|
||||
|
||||
```csharp
|
||||
qualityOption.AddValidator(result =>
|
||||
{
|
||||
var value = result.GetValueOrDefault<int?>();
|
||||
if (value.HasValue && (value.Value < 1 || value.Value > 100))
|
||||
{
|
||||
result.ErrorMessage = "JPEG quality must be between 1 and 100.";
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## RootCommand Setup
|
||||
|
||||
Create a `RootCommand` with a description and add all options:
|
||||
|
||||
```csharp
|
||||
public static RootCommand CreateRootCommand()
|
||||
{
|
||||
var rootCommand = new RootCommand("PowerToys Module Name - Brief description")
|
||||
{
|
||||
silentOption,
|
||||
widthOption,
|
||||
heightOption,
|
||||
// ... other options
|
||||
filesArgument,
|
||||
};
|
||||
|
||||
return rootCommand;
|
||||
}
|
||||
```
|
||||
|
||||
## Parsing
|
||||
|
||||
Parse arguments and extract values:
|
||||
|
||||
```csharp
|
||||
public static CliOptions Parse(string[] args)
|
||||
{
|
||||
var options = new CliOptions();
|
||||
var rootCommand = CreateRootCommand();
|
||||
// Note: with the pinned System.CommandLine version in this repo,
|
||||
// RootCommand.Parse(args) may not be available. Use Parser instead.
|
||||
var parseResult = new Parser(rootCommand).Parse(args);
|
||||
|
||||
// Extract values
|
||||
options.Silent = parseResult.GetValueForOption(silentOption);
|
||||
options.Width = parseResult.GetValueForOption(widthOption);
|
||||
|
||||
return options;
|
||||
}
|
||||
```
|
||||
|
||||
### Parse/Validation Errors
|
||||
|
||||
If parsing or validation fails, return a non-zero exit code (and typically print
|
||||
the errors plus usage):
|
||||
|
||||
```csharp
|
||||
if (parseResult.Errors.Count > 0)
|
||||
{
|
||||
foreach (var error in parseResult.Errors)
|
||||
{
|
||||
Console.Error.WriteLine(error.Message);
|
||||
}
|
||||
|
||||
PrintUsage();
|
||||
return 1;
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Awake Module
|
||||
|
||||
Reference implementation: `src/modules/Awake/Awake/Program.cs`
|
||||
|
||||
```csharp
|
||||
private static readonly string[] _aliasesConfigOption = ["--use-pt-config", "-c"];
|
||||
private static readonly string[] _aliasesDisplayOption = ["--display-on", "-d"];
|
||||
private static readonly string[] _aliasesTimeOption = ["--time-limit", "-t"];
|
||||
private static readonly string[] _aliasesPidOption = ["--pid", "-p"];
|
||||
private static readonly string[] _aliasesExpireAtOption = ["--expire-at", "-e"];
|
||||
```
|
||||
|
||||
### ImageResizer Module
|
||||
|
||||
Reference implementation:
|
||||
|
||||
- `src/modules/imageresizer/ui/Cli/Commands/ImageResizerRootCommand.cs`
|
||||
- `src/modules/imageresizer/ui/Models/CliOptions.cs`
|
||||
- `src/modules/imageresizer/ui/Cli/ImageResizerCliExecutor.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class DestinationOption : Option<string>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--destination", "-d"];
|
||||
|
||||
public DestinationOption()
|
||||
: base(_aliases, "Set destination directory")
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Help Output
|
||||
|
||||
Provide a `PrintUsage()` method for custom help formatting if needed:
|
||||
|
||||
```csharp
|
||||
public static void PrintUsage()
|
||||
{
|
||||
Console.WriteLine("ModuleName - PowerToys Module CLI");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Usage: PowerToys.ModuleName.exe [options] [arguments...]");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Options:");
|
||||
Console.WriteLine(" --option, -o <value> Description of the option");
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Consistency**: Follow existing patterns in the codebase (Awake, ImageResizer)
|
||||
2. **Documentation**: Always provide help text for each option
|
||||
3. **Validation**: Validate input values and provide clear error messages
|
||||
4. **Nullable types**: Use `Option<T?>` for optional parameters
|
||||
5. **Boolean flags**: Use `Option<bool>` for flags that don't require values
|
||||
6. **Enum support**: System.CommandLine automatically handles enum parsing
|
||||
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\..\Common.SelfContained.props" />
|
||||
<Import Project="..\..\..\Common.Dotnet.AotCompatibility.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyTitle>PowerToys.ImageResizerCLI</AssemblyTitle>
|
||||
<AssemblyDescription>PowerToys Image Resizer Command Line Interface</AssemblyDescription>
|
||||
<Description>PowerToys Image Resizer CLI</Description>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Platforms>x64;ARM64</Platforms>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\</OutputPath>
|
||||
<AssemblyName>PowerToys.ImageResizerCLI</AssemblyName>
|
||||
<NoWarn>$(NoWarn);SA1500;SA1402;CA1852</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ui\ImageResizerUI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Force using WindowsDesktop runtime to ensure consistent dll versions with other projects -->
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.WindowsDesktop.App.WPF" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
34
src/modules/imageresizer/ImageResizerCLI/Program.cs
Normal file
34
src/modules/imageresizer/ImageResizerCLI/Program.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
using ImageResizer.Cli;
|
||||
using ManagedCommon;
|
||||
|
||||
namespace ImageResizerCLI;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
string appLanguage = LanguageHelper.LoadLanguage();
|
||||
if (!string.IsNullOrEmpty(appLanguage))
|
||||
{
|
||||
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(appLanguage);
|
||||
}
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
// Ignore invalid culture and fall back to default.
|
||||
}
|
||||
|
||||
Console.InputEncoding = Encoding.Unicode;
|
||||
return ImageResizerCliExecutor.RunStandalone(args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
using ImageResizer.Cli.Options;
|
||||
|
||||
namespace ImageResizer.Cli.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Root command for the ImageResizer CLI.
|
||||
/// </summary>
|
||||
public sealed class ImageResizerRootCommand : RootCommand
|
||||
{
|
||||
public ImageResizerRootCommand()
|
||||
: base("PowerToys Image Resizer - Resize images from command line")
|
||||
{
|
||||
HelpOption = new HelpOption();
|
||||
ShowConfigOption = new ShowConfigOption();
|
||||
DestinationOption = new DestinationOption();
|
||||
WidthOption = new WidthOption();
|
||||
HeightOption = new HeightOption();
|
||||
UnitOption = new UnitOption();
|
||||
FitOption = new FitOption();
|
||||
SizeOption = new SizeOption();
|
||||
ShrinkOnlyOption = new ShrinkOnlyOption();
|
||||
ReplaceOption = new ReplaceOption();
|
||||
IgnoreOrientationOption = new IgnoreOrientationOption();
|
||||
RemoveMetadataOption = new RemoveMetadataOption();
|
||||
QualityOption = new QualityOption();
|
||||
KeepDateModifiedOption = new KeepDateModifiedOption();
|
||||
FileNameOption = new FileNameOption();
|
||||
FilesArgument = new FilesArgument();
|
||||
|
||||
AddOption(HelpOption);
|
||||
AddOption(ShowConfigOption);
|
||||
AddOption(DestinationOption);
|
||||
AddOption(WidthOption);
|
||||
AddOption(HeightOption);
|
||||
AddOption(UnitOption);
|
||||
AddOption(FitOption);
|
||||
AddOption(SizeOption);
|
||||
AddOption(ShrinkOnlyOption);
|
||||
AddOption(ReplaceOption);
|
||||
AddOption(IgnoreOrientationOption);
|
||||
AddOption(RemoveMetadataOption);
|
||||
AddOption(QualityOption);
|
||||
AddOption(KeepDateModifiedOption);
|
||||
AddOption(FileNameOption);
|
||||
AddArgument(FilesArgument);
|
||||
}
|
||||
|
||||
public HelpOption HelpOption { get; }
|
||||
|
||||
public ShowConfigOption ShowConfigOption { get; }
|
||||
|
||||
public DestinationOption DestinationOption { get; }
|
||||
|
||||
public WidthOption WidthOption { get; }
|
||||
|
||||
public HeightOption HeightOption { get; }
|
||||
|
||||
public UnitOption UnitOption { get; }
|
||||
|
||||
public FitOption FitOption { get; }
|
||||
|
||||
public SizeOption SizeOption { get; }
|
||||
|
||||
public ShrinkOnlyOption ShrinkOnlyOption { get; }
|
||||
|
||||
public ReplaceOption ReplaceOption { get; }
|
||||
|
||||
public IgnoreOrientationOption IgnoreOrientationOption { get; }
|
||||
|
||||
public RemoveMetadataOption RemoveMetadataOption { get; }
|
||||
|
||||
public QualityOption QualityOption { get; }
|
||||
|
||||
public KeepDateModifiedOption KeepDateModifiedOption { get; }
|
||||
|
||||
public FileNameOption FileNameOption { get; }
|
||||
|
||||
public FilesArgument FilesArgument { get; }
|
||||
}
|
||||
}
|
||||
188
src/modules/imageresizer/ui/Cli/ImageResizerCliExecutor.cs
Normal file
188
src/modules/imageresizer/ui/Cli/ImageResizerCliExecutor.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using ImageResizer.Models;
|
||||
using ImageResizer.Properties;
|
||||
|
||||
namespace ImageResizer.Cli
|
||||
{
|
||||
/// <summary>
|
||||
/// Centralizes the Image Resizer CLI execution logic for the dedicated CLI host.
|
||||
/// </summary>
|
||||
public static class ImageResizerCliExecutor
|
||||
{
|
||||
/// <summary>
|
||||
/// Entry point used by the dedicated CLI host.
|
||||
/// </summary>
|
||||
/// <param name="args">Command-line arguments.</param>
|
||||
/// <returns>Exit code.</returns>
|
||||
public static int RunStandalone(string[] args)
|
||||
{
|
||||
var cliOptions = CliOptions.Parse(args);
|
||||
|
||||
if (cliOptions.ParseErrors.Count > 0)
|
||||
{
|
||||
foreach (var error in cliOptions.ParseErrors)
|
||||
{
|
||||
Console.Error.WriteLine(error);
|
||||
}
|
||||
|
||||
CliOptions.PrintUsage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (cliOptions.ShowHelp)
|
||||
{
|
||||
CliOptions.PrintUsage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cliOptions.ShowConfig)
|
||||
{
|
||||
CliOptions.PrintConfig(Settings.Default);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cliOptions.Files.Count == 0 && string.IsNullOrEmpty(cliOptions.PipeName))
|
||||
{
|
||||
CliOptions.PrintUsage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
return RunSilentMode(cliOptions);
|
||||
}
|
||||
|
||||
private static int RunSilentMode(CliOptions cliOptions)
|
||||
{
|
||||
var batch = ResizeBatch.FromCliOptions(Console.In, cliOptions);
|
||||
var settings = Settings.Default;
|
||||
ApplyCliOptionsToSettings(cliOptions, settings);
|
||||
|
||||
Console.WriteLine($"Processing {batch.Files.Count} file(s)...");
|
||||
|
||||
var errors = batch.Process(
|
||||
(completed, total) =>
|
||||
{
|
||||
var progress = (int)((completed / total) * 100);
|
||||
Console.Write($"\rProgress: {progress}% ({completed}/{(int)total})");
|
||||
},
|
||||
settings,
|
||||
CancellationToken.None);
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
var errorList = errors.ToList();
|
||||
if (errorList.Count > 0)
|
||||
{
|
||||
Console.Error.WriteLine($"Completed with {errorList.Count} error(s):");
|
||||
foreach (var error in errorList)
|
||||
{
|
||||
Console.Error.WriteLine($" {error.File}: {error.Error}");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
Console.WriteLine("All files processed successfully.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies CLI options to the settings, overriding default values.
|
||||
/// </summary>
|
||||
/// <param name="cliOptions">The CLI options to apply.</param>
|
||||
/// <param name="settings">The settings to modify.</param>
|
||||
private static void ApplyCliOptionsToSettings(CliOptions cliOptions, Settings settings)
|
||||
{
|
||||
// If custom width/height specified, use custom size
|
||||
if (cliOptions.Width.HasValue || cliOptions.Height.HasValue)
|
||||
{
|
||||
if (cliOptions.Width.HasValue)
|
||||
{
|
||||
settings.CustomSize.Width = cliOptions.Width.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If only height specified, set width to 0 for auto-calculation in Fit mode
|
||||
settings.CustomSize.Width = 0;
|
||||
}
|
||||
|
||||
if (cliOptions.Height.HasValue)
|
||||
{
|
||||
settings.CustomSize.Height = cliOptions.Height.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If only width specified, set height to 0 for auto-calculation in Fit mode
|
||||
settings.CustomSize.Height = 0;
|
||||
}
|
||||
|
||||
if (cliOptions.Unit.HasValue)
|
||||
{
|
||||
settings.CustomSize.Unit = cliOptions.Unit.Value;
|
||||
}
|
||||
|
||||
if (cliOptions.Fit.HasValue)
|
||||
{
|
||||
settings.CustomSize.Fit = cliOptions.Fit.Value;
|
||||
}
|
||||
|
||||
// Select custom size (index = Sizes.Count)
|
||||
settings.SelectedSizeIndex = settings.Sizes.Count;
|
||||
}
|
||||
else if (cliOptions.SizeIndex.HasValue)
|
||||
{
|
||||
// Use preset size by index
|
||||
if (cliOptions.SizeIndex.Value >= 0 && cliOptions.SizeIndex.Value < settings.Sizes.Count)
|
||||
{
|
||||
settings.SelectedSizeIndex = cliOptions.SizeIndex.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine($"Warning: Invalid size index {cliOptions.SizeIndex.Value}. Using default.");
|
||||
}
|
||||
}
|
||||
|
||||
// Apply other options
|
||||
if (cliOptions.ShrinkOnly.HasValue)
|
||||
{
|
||||
settings.ShrinkOnly = cliOptions.ShrinkOnly.Value;
|
||||
}
|
||||
|
||||
if (cliOptions.Replace.HasValue)
|
||||
{
|
||||
settings.Replace = cliOptions.Replace.Value;
|
||||
}
|
||||
|
||||
if (cliOptions.IgnoreOrientation.HasValue)
|
||||
{
|
||||
settings.IgnoreOrientation = cliOptions.IgnoreOrientation.Value;
|
||||
}
|
||||
|
||||
if (cliOptions.RemoveMetadata.HasValue)
|
||||
{
|
||||
settings.RemoveMetadata = cliOptions.RemoveMetadata.Value;
|
||||
}
|
||||
|
||||
if (cliOptions.JpegQualityLevel.HasValue)
|
||||
{
|
||||
settings.JpegQualityLevel = cliOptions.JpegQualityLevel.Value;
|
||||
}
|
||||
|
||||
if (cliOptions.KeepDateModified.HasValue)
|
||||
{
|
||||
settings.KeepDateModified = cliOptions.KeepDateModified.Value;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(cliOptions.FileName))
|
||||
{
|
||||
settings.FileName = cliOptions.FileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/DestinationOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/DestinationOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class DestinationOption : Option<string>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--destination", "-d"];
|
||||
|
||||
public DestinationOption()
|
||||
: base(_aliases, "Set destination directory")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/FileNameOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/FileNameOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class FileNameOption : Option<string>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--filename", "-n"];
|
||||
|
||||
public FileNameOption()
|
||||
: base(_aliases, "Set output filename format (%1=original name, %2=size name)")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/modules/imageresizer/ui/Cli/Options/FilesArgument.cs
Normal file
17
src/modules/imageresizer/ui/Cli/Options/FilesArgument.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class FilesArgument : Argument<string[]>
|
||||
{
|
||||
public FilesArgument()
|
||||
: base("files", "Image files to resize")
|
||||
{
|
||||
Arity = ArgumentArity.ZeroOrMore;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/FitOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/FitOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class FitOption : Option<ImageResizer.Models.ResizeFit?>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--fit", "-f"];
|
||||
|
||||
public FitOption()
|
||||
: base(_aliases, "Set fit mode (Fill, Fit, Stretch)")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/HeightOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/HeightOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class HeightOption : Option<double?>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--height", "-h"];
|
||||
|
||||
public HeightOption()
|
||||
: base(_aliases, "Set height")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/HelpOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/HelpOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class HelpOption : Option<bool>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--help", "-?", "/?"];
|
||||
|
||||
public HelpOption()
|
||||
: base(_aliases, "Show help information")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class IgnoreOrientationOption : Option<bool>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--ignore-orientation"];
|
||||
|
||||
public IgnoreOrientationOption()
|
||||
: base(_aliases, "Ignore image orientation")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class KeepDateModifiedOption : Option<bool>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--keep-date-modified"];
|
||||
|
||||
public KeepDateModifiedOption()
|
||||
: base(_aliases, "Keep original date modified")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/modules/imageresizer/ui/Cli/Options/QualityOption.cs
Normal file
26
src/modules/imageresizer/ui/Cli/Options/QualityOption.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class QualityOption : Option<int?>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--quality", "-q"];
|
||||
|
||||
public QualityOption()
|
||||
: base(_aliases, "Set JPEG quality level (1-100)")
|
||||
{
|
||||
AddValidator(result =>
|
||||
{
|
||||
var value = result.GetValueOrDefault<int?>();
|
||||
if (value.HasValue && (value.Value < 1 || value.Value > 100))
|
||||
{
|
||||
result.ErrorMessage = "JPEG quality must be between 1 and 100.";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class RemoveMetadataOption : Option<bool>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--remove-metadata"];
|
||||
|
||||
public RemoveMetadataOption()
|
||||
: base(_aliases, "Remove metadata from resized images")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/ReplaceOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/ReplaceOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class ReplaceOption : Option<bool>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--replace", "-r"];
|
||||
|
||||
public ReplaceOption()
|
||||
: base(_aliases, "Replace original files")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/ShowConfigOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/ShowConfigOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class ShowConfigOption : Option<bool>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--show-config", "--config"];
|
||||
|
||||
public ShowConfigOption()
|
||||
: base(_aliases, "Show current configuration")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/ShrinkOnlyOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/ShrinkOnlyOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class ShrinkOnlyOption : Option<bool>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--shrink-only"];
|
||||
|
||||
public ShrinkOnlyOption()
|
||||
: base(_aliases, "Only shrink images, don't enlarge")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/modules/imageresizer/ui/Cli/Options/SizeOption.cs
Normal file
26
src/modules/imageresizer/ui/Cli/Options/SizeOption.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class SizeOption : Option<int?>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--size"];
|
||||
|
||||
public SizeOption()
|
||||
: base(_aliases, "Use preset size by index (0-based)")
|
||||
{
|
||||
AddValidator(result =>
|
||||
{
|
||||
var value = result.GetValueOrDefault<int?>();
|
||||
if (value.HasValue && value.Value < 0)
|
||||
{
|
||||
result.ErrorMessage = "Size index must be a non-negative integer.";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/UnitOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/UnitOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class UnitOption : Option<ImageResizer.Models.ResizeUnit?>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--unit", "-u"];
|
||||
|
||||
public UnitOption()
|
||||
: base(_aliases, "Set unit (Pixel, Percent, Inch, Centimeter)")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/WidthOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/WidthOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class WidthOption : Option<double?>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--width", "-w"];
|
||||
|
||||
public WidthOption()
|
||||
: base(_aliases, "Set width")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" />
|
||||
<PackageReference Include="System.CommandLine" />
|
||||
<PackageReference Include="System.IO.Abstractions" />
|
||||
<PackageReference Include="WPF-UI" />
|
||||
</ItemGroup>
|
||||
|
||||
256
src/modules/imageresizer/ui/Models/CliOptions.cs
Normal file
256
src/modules/imageresizer/ui/Models/CliOptions.cs
Normal file
@@ -0,0 +1,256 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
using System.CommandLine.Parsing;
|
||||
|
||||
using ImageResizer.Cli.Commands;
|
||||
|
||||
#pragma warning disable SA1649 // File name should match first type name
|
||||
#pragma warning disable SA1402 // File may only contain a single type
|
||||
|
||||
namespace ImageResizer.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the command-line options for ImageResizer CLI mode.
|
||||
/// </summary>
|
||||
public class CliOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to show help information.
|
||||
/// </summary>
|
||||
public bool ShowHelp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to show current configuration.
|
||||
/// </summary>
|
||||
public bool ShowConfig { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the destination directory for resized images.
|
||||
/// </summary>
|
||||
public string DestinationDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the resized image.
|
||||
/// </summary>
|
||||
public double? Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the height of the resized image.
|
||||
/// </summary>
|
||||
public double? Height { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the resize unit (Pixel, Percent, Inch, Centimeter).
|
||||
/// </summary>
|
||||
public ResizeUnit? Unit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the resize fit mode (Fill, Fit, Stretch).
|
||||
/// </summary>
|
||||
public ResizeFit? Fit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the preset size to use.
|
||||
/// </summary>
|
||||
public int? SizeIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to only shrink images (not enlarge).
|
||||
/// </summary>
|
||||
public bool? ShrinkOnly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to replace the original file.
|
||||
/// </summary>
|
||||
public bool? Replace { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to ignore orientation when resizing.
|
||||
/// </summary>
|
||||
public bool? IgnoreOrientation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to remove metadata from the resized image.
|
||||
/// </summary>
|
||||
public bool? RemoveMetadata { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the JPEG quality level (1-100).
|
||||
/// </summary>
|
||||
public int? JpegQualityLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to keep the date modified.
|
||||
/// </summary>
|
||||
public bool? KeepDateModified { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the output filename format.
|
||||
/// </summary>
|
||||
public string FileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of files to process.
|
||||
/// </summary>
|
||||
public ICollection<string> Files { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pipe name for receiving file list.
|
||||
/// </summary>
|
||||
public string PipeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets parse/validation errors produced by System.CommandLine.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> ParseErrors { get; private set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Converts a boolean value to nullable bool (true -> true, false -> null).
|
||||
/// </summary>
|
||||
private static bool? ToBoolOrNull(bool value) => value ? true : null;
|
||||
|
||||
/// <summary>
|
||||
/// Parses command-line arguments into CliOptions using System.CommandLine.
|
||||
/// </summary>
|
||||
/// <param name="args">The command-line arguments.</param>
|
||||
/// <returns>A CliOptions instance with parsed values.</returns>
|
||||
public static CliOptions Parse(string[] args)
|
||||
{
|
||||
var options = new CliOptions();
|
||||
var cmd = new ImageResizerRootCommand();
|
||||
|
||||
// Parse using System.CommandLine
|
||||
var parseResult = new Parser(cmd).Parse(args);
|
||||
|
||||
if (parseResult.Errors.Count > 0)
|
||||
{
|
||||
var errors = new List<string>(parseResult.Errors.Count);
|
||||
foreach (var error in parseResult.Errors)
|
||||
{
|
||||
errors.Add(error.Message);
|
||||
}
|
||||
|
||||
options.ParseErrors = new ReadOnlyCollection<string>(errors);
|
||||
}
|
||||
|
||||
// Extract values from parse result using strongly typed options
|
||||
options.ShowHelp = parseResult.GetValueForOption(cmd.HelpOption);
|
||||
options.ShowConfig = parseResult.GetValueForOption(cmd.ShowConfigOption);
|
||||
options.DestinationDirectory = parseResult.GetValueForOption(cmd.DestinationOption);
|
||||
options.Width = parseResult.GetValueForOption(cmd.WidthOption);
|
||||
options.Height = parseResult.GetValueForOption(cmd.HeightOption);
|
||||
options.Unit = parseResult.GetValueForOption(cmd.UnitOption);
|
||||
options.Fit = parseResult.GetValueForOption(cmd.FitOption);
|
||||
options.SizeIndex = parseResult.GetValueForOption(cmd.SizeOption);
|
||||
|
||||
// Convert bool to nullable bool (true -> true, false -> null)
|
||||
options.ShrinkOnly = ToBoolOrNull(parseResult.GetValueForOption(cmd.ShrinkOnlyOption));
|
||||
options.Replace = ToBoolOrNull(parseResult.GetValueForOption(cmd.ReplaceOption));
|
||||
options.IgnoreOrientation = ToBoolOrNull(parseResult.GetValueForOption(cmd.IgnoreOrientationOption));
|
||||
options.RemoveMetadata = ToBoolOrNull(parseResult.GetValueForOption(cmd.RemoveMetadataOption));
|
||||
options.KeepDateModified = ToBoolOrNull(parseResult.GetValueForOption(cmd.KeepDateModifiedOption));
|
||||
|
||||
options.JpegQualityLevel = parseResult.GetValueForOption(cmd.QualityOption);
|
||||
|
||||
options.FileName = parseResult.GetValueForOption(cmd.FileNameOption);
|
||||
|
||||
// Get files from arguments
|
||||
var files = parseResult.GetValueForArgument(cmd.FilesArgument);
|
||||
if (files != null)
|
||||
{
|
||||
const string pipeNamePrefix = "\\\\.\\pipe\\";
|
||||
foreach (var file in files)
|
||||
{
|
||||
// Check for pipe name (must be at the start of the path)
|
||||
if (file.StartsWith(pipeNamePrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
options.PipeName = file.Substring(pipeNamePrefix.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
options.Files.Add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints current configuration to the console.
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings to display.</param>
|
||||
public static void PrintConfig(ImageResizer.Properties.Settings settings)
|
||||
{
|
||||
Console.OutputEncoding = System.Text.Encoding.UTF8;
|
||||
Console.WriteLine("ImageResizer - Current Configuration");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("General Settings:");
|
||||
Console.WriteLine($" Shrink Only: {settings.ShrinkOnly}");
|
||||
Console.WriteLine($" Replace Original: {settings.Replace}");
|
||||
Console.WriteLine($" Ignore Orientation: {settings.IgnoreOrientation}");
|
||||
Console.WriteLine($" Remove Metadata: {settings.RemoveMetadata}");
|
||||
Console.WriteLine($" Keep Date Modified: {settings.KeepDateModified}");
|
||||
Console.WriteLine($" JPEG Quality: {settings.JpegQualityLevel}");
|
||||
Console.WriteLine($" PNG Interlace: {settings.PngInterlaceOption}");
|
||||
Console.WriteLine($" TIFF Compress: {settings.TiffCompressOption}");
|
||||
Console.WriteLine($" Filename Format: {settings.FileName}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Custom Size:");
|
||||
Console.WriteLine($" Width: {settings.CustomSize.Width} {settings.CustomSize.Unit}");
|
||||
Console.WriteLine($" Height: {settings.CustomSize.Height} {settings.CustomSize.Unit}");
|
||||
Console.WriteLine($" Fit Mode: {settings.CustomSize.Fit}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Preset Sizes:");
|
||||
for (int i = 0; i < settings.Sizes.Count; i++)
|
||||
{
|
||||
var size = settings.Sizes[i];
|
||||
var selected = i == settings.SelectedSizeIndex ? "*" : " ";
|
||||
Console.WriteLine($" [{i}]{selected} {size.Name}: {size.Width}x{size.Height} {size.Unit} ({size.Fit})");
|
||||
}
|
||||
|
||||
if (settings.SelectedSizeIndex >= settings.Sizes.Count)
|
||||
{
|
||||
Console.WriteLine($" [Custom]* {settings.CustomSize.Width}x{settings.CustomSize.Height} {settings.CustomSize.Unit} ({settings.CustomSize.Fit})");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints usage information to the console.
|
||||
/// </summary>
|
||||
public static void PrintUsage()
|
||||
{
|
||||
Console.OutputEncoding = System.Text.Encoding.UTF8;
|
||||
Console.WriteLine("ImageResizer - PowerToys Image Resizer CLI");
|
||||
Console.WriteLine();
|
||||
|
||||
var cmd = new ImageResizerRootCommand();
|
||||
|
||||
// Print usage line
|
||||
Console.WriteLine("Usage: PowerToys.ImageResizerCLI.exe [options] [files...]");
|
||||
Console.WriteLine();
|
||||
|
||||
// Print options from the command definition
|
||||
Console.WriteLine("Options:");
|
||||
foreach (var option in cmd.Options)
|
||||
{
|
||||
var aliases = string.Join(", ", option.Aliases);
|
||||
var description = option.Description ?? string.Empty;
|
||||
Console.WriteLine($" {aliases,-30} {description}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Examples:");
|
||||
Console.WriteLine(" PowerToys.ImageResizerCLI.exe --help");
|
||||
Console.WriteLine(" PowerToys.ImageResizerCLI.exe --width 800 --height 600 image.jpg");
|
||||
Console.WriteLine(" PowerToys.ImageResizerCLI.exe -w 50 -h 50 -u Percent *.jpg");
|
||||
Console.WriteLine(" PowerToys.ImageResizerCLI.exe --size 0 -d \"C:\\Output\" photo.png");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,44 +26,45 @@ namespace ImageResizer.Models
|
||||
|
||||
public ICollection<string> Files { get; } = new List<string>();
|
||||
|
||||
public static ResizeBatch FromCommandLine(TextReader standardInput, string[] args)
|
||||
/// <summary>
|
||||
/// Creates a ResizeBatch from CliOptions.
|
||||
/// </summary>
|
||||
/// <param name="standardInput">Standard input stream for reading additional file paths.</param>
|
||||
/// <param name="options">The parsed CLI options.</param>
|
||||
/// <returns>A ResizeBatch instance.</returns>
|
||||
public static ResizeBatch FromCliOptions(TextReader standardInput, CliOptions options)
|
||||
{
|
||||
var batch = new ResizeBatch();
|
||||
const string pipeNamePrefix = "\\\\.\\pipe\\";
|
||||
string pipeName = null;
|
||||
|
||||
for (var i = 0; i < args?.Length; i++)
|
||||
var batch = new ResizeBatch
|
||||
{
|
||||
if (args[i] == "/d")
|
||||
{
|
||||
batch.DestinationDirectory = args[++i];
|
||||
continue;
|
||||
}
|
||||
else if (args[i].Contains(pipeNamePrefix))
|
||||
{
|
||||
pipeName = args[i].Substring(pipeNamePrefix.Length);
|
||||
continue;
|
||||
}
|
||||
DestinationDirectory = options.DestinationDirectory,
|
||||
};
|
||||
|
||||
batch.Files.Add(args[i]);
|
||||
foreach (var file in options.Files)
|
||||
{
|
||||
// Convert relative paths to absolute paths
|
||||
var absolutePath = Path.IsPathRooted(file) ? file : Path.GetFullPath(file);
|
||||
batch.Files.Add(absolutePath);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(pipeName))
|
||||
if (string.IsNullOrEmpty(options.PipeName))
|
||||
{
|
||||
// NB: We read these from stdin since there are limits on the number of args you can have
|
||||
// Only read from stdin if it's redirected (piped input), not from interactive terminal
|
||||
string file;
|
||||
if (standardInput != null)
|
||||
if (standardInput != null && Console.IsInputRedirected)
|
||||
{
|
||||
while ((file = standardInput.ReadLine()) != null)
|
||||
{
|
||||
batch.Files.Add(file);
|
||||
// Convert relative paths to absolute paths
|
||||
var absolutePath = Path.IsPathRooted(file) ? file : Path.GetFullPath(file);
|
||||
batch.Files.Add(absolutePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (NamedPipeClientStream pipeClient =
|
||||
new NamedPipeClientStream(".", pipeName, PipeDirection.In))
|
||||
new NamedPipeClientStream(".", options.PipeName, PipeDirection.In))
|
||||
{
|
||||
// Connect to the pipe or wait until the pipe is available.
|
||||
pipeClient.Connect();
|
||||
@@ -84,17 +85,26 @@ namespace ImageResizer.Models
|
||||
return batch;
|
||||
}
|
||||
|
||||
public static ResizeBatch FromCommandLine(TextReader standardInput, string[] args)
|
||||
{
|
||||
var options = CliOptions.Parse(args);
|
||||
return FromCliOptions(standardInput, options);
|
||||
}
|
||||
|
||||
public IEnumerable<ResizeError> Process(Action<int, double> reportProgress, CancellationToken cancellationToken)
|
||||
{
|
||||
// NOTE: Settings.Default is captured once before parallel processing.
|
||||
// Any changes to settings on disk during this batch will NOT be reflected until the next batch.
|
||||
// This improves performance and predictability by avoiding repeated mutex acquisition and behaviour change results in a batch.
|
||||
return Process(reportProgress, Settings.Default, cancellationToken);
|
||||
}
|
||||
|
||||
public IEnumerable<ResizeError> Process(Action<int, double> reportProgress, Settings settings, CancellationToken cancellationToken)
|
||||
{
|
||||
double total = Files.Count;
|
||||
int completed = 0;
|
||||
var errors = new ConcurrentBag<ResizeError>();
|
||||
|
||||
// NOTE: Settings.Default is captured once before parallel processing.
|
||||
// Any changes to settings on disk during this batch will NOT be reflected until the next batch.
|
||||
// This improves performance and predictability by avoiding repeated mutex acquisition and behaviour change results in a batch.
|
||||
var settings = Settings.Default;
|
||||
|
||||
// TODO: If we ever switch to Windows.Graphics.Imaging, we can get a lot more throughput by using the async
|
||||
// APIs and a custom SynchronizationContext
|
||||
Parallel.ForEach(
|
||||
|
||||
@@ -15,6 +15,7 @@ using System.IO.Abstractions;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using System.Threading;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
@@ -30,6 +31,7 @@ namespace ImageResizer.Properties
|
||||
{
|
||||
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
|
||||
WriteIndented = true,
|
||||
TypeInfoResolver = new DefaultJsonTypeInfoResolver(),
|
||||
};
|
||||
|
||||
private static readonly CompositeFormat ValueMustBeBetween = System.Text.CompositeFormat.Parse(Properties.Resources.ValueMustBeBetween);
|
||||
|
||||
Reference in New Issue
Block a user