204 lines
5.3 KiB
Go
204 lines
5.3 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"image"
|
|
"image/jpeg"
|
|
"image/png"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/chai2010/webp"
|
|
"github.com/nfnt/resize"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// resizeCmd represents the resize command
|
|
var resizeCmd = &cobra.Command{
|
|
Use: "resize [input] [output] [width]",
|
|
Short: "Resize images to the specified width",
|
|
Long: `Resize images to the specified width using Lanczos3 interpolation.
|
|
Single file mode:
|
|
dttt resize input.jpg output.jpg 300
|
|
|
|
Batch mode (resize all images in a directory):
|
|
dttt resize ./photos ./resized 800 --batch`,
|
|
Args: cobra.RangeArgs(2, 3),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
isBatch, _ := cmd.Flags().GetBool("batch")
|
|
quality, _ := cmd.Flags().GetInt("quality")
|
|
extensions, _ := cmd.Flags().GetString("extensions")
|
|
|
|
if isBatch {
|
|
if len(args) != 3 {
|
|
log.Fatal("Batch mode requires input directory, output directory, and width")
|
|
}
|
|
|
|
inputDir := args[0]
|
|
outputDir := args[1]
|
|
widthStr := args[2]
|
|
|
|
width, err := strconv.Atoi(widthStr)
|
|
if err != nil {
|
|
log.Fatal("Invalid width:", err)
|
|
}
|
|
|
|
batchResize(inputDir, outputDir, width, extensions, quality)
|
|
} else {
|
|
if len(args) != 3 {
|
|
log.Fatal("Single file mode requires input file, output file, and width")
|
|
}
|
|
|
|
inputFile := args[0]
|
|
outputFile := args[1]
|
|
widthStr := args[2]
|
|
|
|
width, err := strconv.Atoi(widthStr)
|
|
if err != nil {
|
|
log.Fatal("Invalid width:", err)
|
|
}
|
|
|
|
resizeImage(inputFile, outputFile, width, quality)
|
|
}
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
rootCmd.AddCommand(resizeCmd)
|
|
|
|
// Add flags
|
|
resizeCmd.Flags().BoolP("batch", "b", false, "Enable batch resizing mode for directories")
|
|
resizeCmd.Flags().IntP("quality", "q", 90, "Quality for lossy formats (1-100)")
|
|
resizeCmd.Flags().StringP("extensions", "e", "jpg,jpeg,png,webp", "Comma-separated list of file extensions to process in batch mode")
|
|
}
|
|
|
|
func batchResize(inputDir, outputDir string, width int, extensions string, quality int) {
|
|
// Ensure output directory exists
|
|
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
|
log.Fatal("Failed to create output directory:", err)
|
|
}
|
|
|
|
// Parse extensions
|
|
validExts := strings.Split(extensions, ",")
|
|
for i, ext := range validExts {
|
|
validExts[i] = strings.ToLower(strings.TrimSpace(ext))
|
|
if !strings.HasPrefix(validExts[i], ".") {
|
|
validExts[i] = "." + validExts[i]
|
|
}
|
|
}
|
|
|
|
// Get all files from input directory
|
|
files, err := os.ReadDir(inputDir)
|
|
if err != nil {
|
|
log.Fatal("Error reading input directory:", err)
|
|
}
|
|
|
|
// Track statistics
|
|
successCount := 0
|
|
errorCount := 0
|
|
|
|
for _, file := range files {
|
|
if file.IsDir() {
|
|
continue
|
|
}
|
|
|
|
// Check if file has a valid extension
|
|
fileExt := strings.ToLower(filepath.Ext(file.Name()))
|
|
isValidExt := false
|
|
for _, ext := range validExts {
|
|
if ext == fileExt {
|
|
isValidExt = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !isValidExt {
|
|
continue
|
|
}
|
|
|
|
// Generate input and output file paths
|
|
inputPath := filepath.Join(inputDir, file.Name())
|
|
outputPath := filepath.Join(outputDir, file.Name())
|
|
|
|
// Resize the file
|
|
err := resizeImageWithError(inputPath, outputPath, width, quality)
|
|
if err != nil {
|
|
fmt.Printf("Error resizing %s: %v\n", file.Name(), err)
|
|
errorCount++
|
|
} else {
|
|
successCount++
|
|
}
|
|
}
|
|
|
|
fmt.Printf("Batch resize complete: %d files resized successfully, %d failed\n", successCount, errorCount)
|
|
}
|
|
|
|
func resizeImageWithError(inputFile, outputFile string, width, quality int) error {
|
|
// Open the input image file
|
|
input, err := os.Open(inputFile)
|
|
if err != nil {
|
|
return fmt.Errorf("error opening input file: %w", err)
|
|
}
|
|
defer input.Close()
|
|
|
|
// Decode the input image file
|
|
img, format, err := image.Decode(input)
|
|
if err != nil {
|
|
return fmt.Errorf("error decoding input image: %w", err)
|
|
}
|
|
|
|
// Resize the image to the target width
|
|
resizedImg := resize.Resize(uint(width), 0, img, resize.Lanczos3)
|
|
|
|
// Create the output image file
|
|
output, err := os.Create(outputFile)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating output file: %w", err)
|
|
}
|
|
defer output.Close()
|
|
|
|
// Determine the output format based on the file extension
|
|
fileExt := strings.ToLower(filepath.Ext(outputFile))
|
|
|
|
// Encode based on output format
|
|
switch fileExt {
|
|
case ".jpg", ".jpeg":
|
|
err = jpeg.Encode(output, resizedImg, &jpeg.Options{Quality: quality})
|
|
case ".png":
|
|
err = png.Encode(output, resizedImg)
|
|
case ".webp":
|
|
err = webp.Encode(output, resizedImg, &webp.Options{Lossless: quality == 100, Quality: float32(quality)})
|
|
default:
|
|
// Use input format if output format is not recognized
|
|
switch format {
|
|
case "jpeg":
|
|
err = jpeg.Encode(output, resizedImg, &jpeg.Options{Quality: quality})
|
|
case "png":
|
|
err = png.Encode(output, resizedImg)
|
|
case "webp":
|
|
err = webp.Encode(output, resizedImg, &webp.Options{Lossless: quality == 100, Quality: float32(quality)})
|
|
default:
|
|
// Default to JPEG if format is unknown
|
|
err = jpeg.Encode(output, resizedImg, &jpeg.Options{Quality: quality})
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("error encoding resized image: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Resized %s from %s to %dpx width\n", filepath.Base(inputFile), format, width)
|
|
return nil
|
|
}
|
|
|
|
func resizeImage(inputFile, outputFile string, width, quality int) {
|
|
err := resizeImageWithError(inputFile, outputFile, width, quality)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fmt.Println("Image resized successfully!")
|
|
}
|