From 31a4fc346717d549f0cad4c0d5c1d6556cb360c9 Mon Sep 17 00:00:00 2001 From: Kristian Date: Tue, 22 Apr 2025 10:23:23 +0200 Subject: [PATCH] add webp conversion --- README.md | 134 ++++++++++++++++++++++++++++++++ cmd/convert.go | 207 +++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 + 4 files changed, 344 insertions(+) create mode 100644 README.md create mode 100644 cmd/convert.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..f3375a8 --- /dev/null +++ b/README.md @@ -0,0 +1,134 @@ +# dttt + +A versatile command-line toolkit providing various utility functions. + +## Installation + +```bash +go install git.bits.studio/public/dttt@latest +``` + +Or clone the repository and build it manually: + +```bash +git clone git.bits.studio/public/dttt +cd dttt +go build -o dttt +``` + +## Commands + +### resize + +Resize images to a specified width while maintaining aspect ratio. + +```bash +dttt resize [input_file] [output_file] [width] +``` + +Example: +```bash +dttt resize input.jpg output.jpg 300 +``` + +### convert + +Convert images between different formats including JPG, PNG, and WebP. + +```bash +# Single file conversion +dttt convert [input_file] [output_file] + +# Batch conversion +dttt convert [input_directory] [output_directory] --batch --format [format] +``` + +The output format is determined by the output file extension (for single file conversion) or the `--format` flag (for batch conversion). + +Options: +- `--quality, -q`: Quality for lossy formats (1-100, default: 90) +- `--batch, -b`: Enable batch conversion mode +- `--format, -f`: Output format for batch conversion (jpg, jpeg, png, webp) +- `--extensions, -e`: Comma-separated list of file extensions to process in batch mode (default: jpg,jpeg,png,webp) + +Examples: +```bash +# Convert single JPG to WebP +dttt convert input.jpg output.webp + +# Convert PNG to JPG with 80% quality +dttt convert input.png output.jpg --quality 80 + +# Convert WebP to PNG +dttt convert input.webp output.png + +# Batch convert all JPG and PNG files in a directory to WebP +dttt convert ./photos ./webp_photos --batch --format webp --extensions jpg,png + +# Batch convert all supported images to JPG with 85% quality +dttt convert ./input ./output --batch --format jpg --quality 85 +``` + +### rename + +Rename files in bulk based on specified patterns. + +```bash +dttt rename --pattern "prefix_###.jpg" --start 1 --directory ./images +``` + +Options: +- `--directory, -d`: Directory containing the files to rename (default: current directory) +- `--pattern, -p`: Pattern for renaming files. Use '###' as a placeholder for numeric sequence +- `--start, -s`: Start index for numeric sequence in the renaming pattern (default: 1) + +### server + +Start a simple HTTP server to serve files from a specified directory. + +```bash +dttt server [directory] +``` + +Options: +- `--port, -p`: Port number for the HTTP server (default: 8080) + +If no directory is provided, the current directory is used. + +### chat + +Start a chat server or connect to other peers. + +```bash +# Start a chat server +dttt chat + +# Connect to a chat server +dttt chat localhost:8080 +``` + +Options: +- `--nickname, -n`: Nickname for the user + +### env + +Manage environment variables. + +```bash +# List all environment variables +dttt env list + +# Set an environment variable +dttt env set KEY VALUE + +# Unset an environment variable +dttt env unset KEY +``` + +## Configuration + +You can specify a configuration file using the `--config` flag. By default, the configuration is read from `$HOME/.dttt.yaml`. + +## License + +See the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/cmd/convert.go b/cmd/convert.go new file mode 100644 index 0000000..2af1345 --- /dev/null +++ b/cmd/convert.go @@ -0,0 +1,207 @@ +package cmd + +import ( + "fmt" + "image" + "image/jpeg" + "image/png" + "log" + "os" + "path/filepath" + "strings" + + "github.com/chai2010/webp" + "github.com/spf13/cobra" +) + +// convertCmd represents the convert command +var convertCmd = &cobra.Command{ + Use: "convert [input] [output]", + Short: "Convert images between different formats", + Long: `Convert images between different formats including JPG, PNG, and WebP. +The output format is determined by the output file extension or the --format flag. + +Examples: + # Convert a single file + dttt convert input.jpg output.webp + + # Batch convert all images in a directory + dttt convert ./input_dir ./output_dir --batch --format webp + + # Batch convert with specific extensions and quality + dttt convert ./photos ./webp_photos --batch --format webp --quality 85 --extensions jpg,png`, + Args: cobra.RangeArgs(1, 2), + Run: func(cmd *cobra.Command, args []string) { + isBatch, _ := cmd.Flags().GetBool("batch") + quality, _ := cmd.Flags().GetInt("quality") + format, _ := cmd.Flags().GetString("format") + extensions, _ := cmd.Flags().GetString("extensions") + + if isBatch { + if len(args) != 2 { + log.Fatal("Batch mode requires input and output directories") + } + + inputDir := args[0] + outputDir := args[1] + + if format == "" { + log.Fatal("Batch mode requires specifying an output format with --format") + } + + batchConvert(inputDir, outputDir, format, extensions, quality) + } else { + if len(args) != 2 { + log.Fatal("Single file mode requires input and output file paths") + } + + inputFile := args[0] + outputFile := args[1] + + convertImage(inputFile, outputFile, quality) + } + }, +} + +func init() { + rootCmd.AddCommand(convertCmd) + + // Add flags + convertCmd.Flags().IntP("quality", "q", 90, "Quality for lossy formats (1-100)") + convertCmd.Flags().BoolP("batch", "b", false, "Enable batch conversion mode") + convertCmd.Flags().StringP("format", "f", "", "Output format for batch conversion (jpg, jpeg, png, webp)") + convertCmd.Flags().StringP("extensions", "e", "jpg,jpeg,png,webp", "Comma-separated list of file extensions to process in batch mode") +} + +func batchConvert(inputDir, outputDir, format, 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] + } + } + + // Ensure format has a dot prefix and is lowercase + format = strings.ToLower(format) + if !strings.HasPrefix(format, ".") { + format = "." + format + } + + // Get all files from input directory + files, err := os.ReadDir(inputDir) + if err != nil { + log.Fatal("Error reading input directory:", err) + } + + // Track conversion 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()) + baseName := strings.TrimSuffix(file.Name(), fileExt) + outputPath := filepath.Join(outputDir, baseName+format) + + // Convert the file + err := convertImageWithError(inputPath, outputPath, quality) + if err != nil { + fmt.Printf("Error converting %s: %v\n", file.Name(), err) + errorCount++ + } else { + successCount++ + } + } + + fmt.Printf("Batch conversion complete: %d files converted successfully, %d failed\n", successCount, errorCount) +} + +func convertImageWithError(inputFile, outputFile string, quality int) error { + // Open input file + input, err := os.Open(inputFile) + if err != nil { + return fmt.Errorf("error opening input file: %w", err) + } + defer input.Close() + + // Decode input image + img, format, err := image.Decode(input) + if err != nil { + return fmt.Errorf("error decoding input image: %w", err) + } + + // Create output file + output, err := os.Create(outputFile) + if err != nil { + return fmt.Errorf("error creating output file: %w", err) + } + defer output.Close() + + // Determine output format based on file extension and encode + outFormat := getOutputFormat(outputFile) + + switch strings.ToLower(filepath.Ext(outputFile)) { + case ".jpg", ".jpeg": + err = jpeg.Encode(output, img, &jpeg.Options{Quality: quality}) + case ".png": + err = png.Encode(output, img) + case ".webp": + err = webp.Encode(output, img, &webp.Options{Lossless: quality == 100, Quality: float32(quality)}) + default: + return fmt.Errorf("unsupported output format: %s", filepath.Ext(outputFile)) + } + + if err != nil { + return fmt.Errorf("error encoding output image: %w", err) + } + + fmt.Printf("Converted %s (%s) to %s\n", filepath.Base(inputFile), format, outFormat) + return nil +} + +func convertImage(inputFile, outputFile string, quality int) { + err := convertImageWithError(inputFile, outputFile, quality) + if err != nil { + log.Fatal(err) + } + fmt.Println("Image converted successfully!") +} + +func getOutputFormat(filename string) string { + ext := strings.ToLower(filepath.Ext(filename)) + switch ext { + case ".jpg", ".jpeg": + return "JPEG" + case ".png": + return "PNG" + case ".webp": + return "WebP" + default: + return ext + } +} diff --git a/go.mod b/go.mod index 7c11908..0a40a9d 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.22.1 require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/chai2010/webp v1.4.0 // indirect github.com/charmbracelet/bubbletea v0.25.0 // indirect github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect diff --git a/go.sum b/go.sum index f8c6a62..a35fcb0 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/chai2010/webp v1.4.0 h1:6DA2pkkRUPnbOHvvsmGI3He1hBKf/bkRlniAiSGuEko= +github.com/chai2010/webp v1.4.0/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU= github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=