add webp conversion
This commit is contained in:
parent
03b57e3f9f
commit
31a4fc3467
134
README.md
Normal file
134
README.md
Normal file
@ -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.
|
||||||
207
cmd/convert.go
Normal file
207
cmd/convert.go
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
1
go.mod
1
go.mod
@ -4,6 +4,7 @@ go 1.22.1
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
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/charmbracelet/bubbletea v0.25.0 // indirect
|
||||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
|
|||||||
2
go.sum
2
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 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
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 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
|
||||||
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
|
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=
|
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user