add automatic image resizing

This commit is contained in:
2023-12-23 00:50:50 -07:00
parent 90d092bc17
commit f0292c325e
5 changed files with 326 additions and 27 deletions

View File

@ -5,7 +5,11 @@ import (
b64 "encoding/base64"
"encoding/json"
"fmt"
"github.com/urfave/cli/v2"
"image/draw"
"image"
"image/jpeg"
"image/png"
"io"
"mime"
"mime/multipart"
@ -15,12 +19,15 @@ import (
"path"
"strings"
"time"
"github.com/nfnt/resize"
"github.com/urfave/cli/v2"
)
func main() {
commands := []*cli.Command{
//startCmd(),
getJobResultCmd(),
resizeCmd(),
}
app := &cli.App{
@ -38,6 +45,12 @@ func main() {
"o",
},
},
&cli.StringFlag{
Name: "format",
Aliases: []string{"f"},
Usage: "The format to resize the image to wide, tall, or square",
Value: "wide",
},
},
Authors: []*cli.Author{
{
@ -48,27 +61,25 @@ func main() {
Copyright: "2023 Masonite Studios LLC",
UseShortOptionHandling: true,
Action: func(c *cli.Context) error {
var needsCleanup bool
// use the first argument as the file name
fileLocation := c.Args().Get(0)
// if no argument is provided, use stdin
if fileLocation == "" {
fmt.Println("No file provided, using stdin.")
// read from stdin
var err error
fileLocationBytes, err := io.ReadAll(os.Stdin)
if err != nil {
return fmt.Errorf("error reading from stdin | %w", err)
}
fileLocation = string(fileLocationBytes)
fileLocation, err := getFileLocation(c)
if err != nil {
return fmt.Errorf("error getting file location | %w", err)
}
// TODO: preprocess the image to make sure it is the right size
// preprocess the image to make sure it is the right size
/*
Supported Dimensions:
1024x576
576x1024
768x768
*/
// get the format
format := c.String("format")
if format != "wide" && format != "tall" && format != "square" {
return fmt.Errorf("invalid format %s", format)
}
// make sure the image is either jpeg or png
fileMimeType := mime.TypeByExtension(path.Ext(fileLocation))
@ -76,8 +87,27 @@ func main() {
return fmt.Errorf("unsupported file type | %v", fileMimeType)
}
// resize the image
tempLocation, err := resizeImage(fileLocation, format)
if err != nil {
return fmt.Errorf("error resizing image | %w", err)
}
if tempLocation != fileLocation {
fmt.Printf("Resized image to %s\n", tempLocation)
needsCleanup = true
fileLocation = tempLocation // use the resized image
}
id, err := initiateGeneratingAnimation(fileLocation)
if err != nil {
if needsCleanup {
// remove the temp file
err = cleanUpTempFile(tempLocation)
if err != nil {
return fmt.Errorf("error removing temp file | %w", err)
}
}
return fmt.Errorf("error making request | %w", err)
}
@ -87,6 +117,13 @@ func main() {
// wait for the job to finish
err = job(id, c.String("output"))
if err != nil {
if needsCleanup {
// remove the temp file
err = cleanUpTempFile(tempLocation)
if err != nil {
return fmt.Errorf("error removing temp file | %w", err)
}
}
return fmt.Errorf("error getting job result | %w", err)
}
@ -101,6 +138,152 @@ func main() {
}
}
func cleanUpTempFile(fileLocation string) error {
// remove the temp file
err := os.Remove(fileLocation)
if err != nil {
return fmt.Errorf("error removing temp file | %w", err)
}
return nil
}
func resizeImage(fileLocation string, format string) (string, error) {
// load the image
file, err := os.Open(fileLocation)
if err != nil {
return "", fmt.Errorf("error opening file | %w", err)
}
defer file.Close()
var img image.Image
fileMimeType := mime.TypeByExtension(path.Ext(fileLocation))
if fileMimeType == "image/jpeg" {
// decode the image
img, err = jpeg.Decode(file)
if err != nil {
return "", fmt.Errorf("error decoding jpeg image | %w", err)
}
} else if fileMimeType == "image/png" {
// decode the image
img, err = png.Decode(file)
if err != nil {
return "", fmt.Errorf("error decoding png image | %w", err)
}
} else {
return "", fmt.Errorf("unsupported file type | %v", fileMimeType)
}
// check if the image is already the correct size
rect := img.Bounds()
width := rect.Max.X - rect.Min.X
height := rect.Max.Y - rect.Min.Y
x1 := 1024
y1 := 576
if format == "wide" {
if width == 1024 && height == 576 {
fmt.Println("Image is already the correct size.")
return fileLocation, nil
}
}
if format == "tall" {
if width == 576 && height == 1024 {
fmt.Println("Image is already the correct size.")
return fileLocation, nil
}
x1 = 576
y1 = 1024
}
if format == "square" {
if width == 768 && height == 768 {
fmt.Println("Image is already the correct size.")
return fileLocation, nil
}
x1 = 768
y1 = 768
}
inFormat := "wide"
if width < height {
inFormat = "tall"
}
if width == height {
inFormat = "square"
}
// if not, resize the image
// scale the original image to the new size
var resizedImage image.Image
if format == "wide" {
resizedImage = resize.Resize(uint(x1), 0, img, resize.Lanczos3)
}
if format == "tall" {
resizedImage = resize.Resize(0, uint(y1), img, resize.Lanczos3)
}
if format == "square" {
if inFormat == "wide" {
resizedImage = resize.Resize(0, uint(y1), img, resize.Lanczos3)
}
if inFormat == "tall" {
resizedImage = resize.Resize(uint(x1), 0, img, resize.Lanczos3)
}
if inFormat == "square" {
resizedImage = resize.Resize(uint(x1), uint(y1), img, resize.Lanczos3)
}
}
// crop the image to the final correct size
// start by getting the center of the image
tempBounds := resizedImage.Bounds()
x0 := tempBounds.Max.X/2 - x1/2
y0 := tempBounds.Max.Y/2 - y1/2
xMax := x0 + x1
yMax := y0 + y1
croppedImageRect := image.Rect(x0, y0, xMax, yMax)
rgba := convertToRGBA(resizedImage)
croppedImage := rgba.SubImage(croppedImageRect)
// save the image to a temp file
tempFile, err := os.CreateTemp("", "i2v*"+path.Ext(fileLocation))
if err != nil {
return "", fmt.Errorf("error creating temp file | %w", err)
}
defer tempFile.Close()
// encode the image to the temp file
err = jpeg.Encode(tempFile, croppedImage, nil)
if err != nil {
return "", fmt.Errorf("error encoding resized image | %w", err)
}
// return the temp file location
return tempFile.Name(), nil
}
func convertToRGBA(img image.Image) *image.RGBA {
bounds := img.Bounds()
rgba := image.NewRGBA(bounds)
draw.Draw(rgba, bounds, img, bounds.Min, draw.Src)
return rgba
}
func getFileLocation(c *cli.Context) (string, error) {
fileLocation := c.Args().Get(0)
// if no argument is provided, use stdin
if fileLocation == "" {
fmt.Println("No file provided, using stdin.")
// read from stdin
var err error
fileLocationBytes, err := io.ReadAll(os.Stdin)
if err != nil {
return "", fmt.Errorf("error reading from stdin | %w", err)
}
fileLocation = string(fileLocationBytes)
}
return fileLocation, nil
}
func initiateGeneratingAnimation(fileLocation string) (string, error) {
// get base filename from file location
filename := path.Base(fileLocation)

View File

@ -3,19 +3,129 @@ package main
import (
"fmt"
"github.com/urfave/cli/v2"
"io"
"os"
)
//func startCmd() *cli.Command {
// return &cli.Command{
// Name: "start",
// Aliases: []string{"s"},
// Usage: "Start the application service",
// Action: func(c *cli.Context) error {
//
// return nil
// },
// }
//}
func resizeCmd() *cli.Command {
return &cli.Command{
Name: "resize",
Aliases: []string{"r"},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "output",
Value: "./resized.jpg",
Usage: "path to output resized file (should be a jpg)",
Aliases: []string{
"o",
},
},
&cli.StringFlag{
Name: "format",
Aliases: []string{"f"},
Usage: "The format to resize the image to wide, tall, or square",
Value: "wide",
},
},
Usage: "Resize an image to a supported size for the API",
Action: func(c *cli.Context) error {
var needsCleanup bool
// get the image
// use the first argument as the file name
fileLocation, err := getFileLocation(c)
if err != nil {
return fmt.Errorf("error getting file location | %w", err)
}
// get the format
format := c.String("format")
if format != "wide" && format != "tall" && format != "square" {
return fmt.Errorf("invalid format %s", format)
}
// get output file
output := c.String("output")
// resize the image
tempLocation, err := resizeImage(fileLocation, format)
if err != nil {
return fmt.Errorf("error resizing image | %w", err)
}
if tempLocation != fileLocation {
fmt.Printf("Resized image to %s\n", tempLocation)
needsCleanup = true
}
// copy the temp image to the output file
err = copyFile(tempLocation, output, 1024)
if err != nil {
return fmt.Errorf("error copying file | %w", err)
}
if needsCleanup {
// remove the temp file
err = cleanUpTempFile(tempLocation)
if err != nil {
return fmt.Errorf("error removing temp file | %w", err)
}
}
return nil
},
}
}
// copyFile copies a file from src to dst
// BUFFERSIZE is the size of the buffer to use when copying
// https://opensource.com/article/18/6/copying-files-go
func copyFile(src, dst string, BUFFERSIZE int64) error {
sourceFileStat, err := os.Stat(src)
if err != nil {
return err
}
if !sourceFileStat.Mode().IsRegular() {
return fmt.Errorf("%s is not a regular file.", src)
}
source, err := os.Open(src)
if err != nil {
return err
}
defer source.Close()
_, err = os.Stat(dst)
if err == nil {
return fmt.Errorf("File %s already exists.", dst)
}
destination, err := os.Create(dst)
if err != nil {
return err
}
defer destination.Close()
if err != nil {
panic(err)
}
buf := make([]byte, BUFFERSIZE)
for {
n, err := source.Read(buf)
if err != nil && err != io.EOF {
return err
}
if n == 0 {
break
}
if _, err := destination.Write(buf[:n]); err != nil {
return err
}
}
return err
}
func getJobResultCmd() *cli.Command {
return &cli.Command{