add automatic image resizing
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,4 +3,5 @@ run_i2v.exe
|
|||||||
run_i2v_gjr.exe
|
run_i2v_gjr.exe
|
||||||
output.mp4
|
output.mp4
|
||||||
resp.txt
|
resp.txt
|
||||||
test.png
|
test.png
|
||||||
|
resized.jpg
|
||||||
|
211
cmd/cli/cli.go
211
cmd/cli/cli.go
@ -5,7 +5,11 @@ import (
|
|||||||
b64 "encoding/base64"
|
b64 "encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/urfave/cli/v2"
|
"image/draw"
|
||||||
|
|
||||||
|
"image"
|
||||||
|
"image/jpeg"
|
||||||
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
"mime"
|
"mime"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
@ -15,12 +19,15 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/nfnt/resize"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
commands := []*cli.Command{
|
commands := []*cli.Command{
|
||||||
//startCmd(),
|
|
||||||
getJobResultCmd(),
|
getJobResultCmd(),
|
||||||
|
resizeCmd(),
|
||||||
}
|
}
|
||||||
|
|
||||||
app := &cli.App{
|
app := &cli.App{
|
||||||
@ -38,6 +45,12 @@ func main() {
|
|||||||
"o",
|
"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{
|
Authors: []*cli.Author{
|
||||||
{
|
{
|
||||||
@ -48,27 +61,25 @@ func main() {
|
|||||||
Copyright: "2023 Masonite Studios LLC",
|
Copyright: "2023 Masonite Studios LLC",
|
||||||
UseShortOptionHandling: true,
|
UseShortOptionHandling: true,
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
|
var needsCleanup bool
|
||||||
// use the first argument as the file name
|
// use the first argument as the file name
|
||||||
fileLocation := c.Args().Get(0)
|
fileLocation, err := getFileLocation(c)
|
||||||
// if no argument is provided, use stdin
|
if err != nil {
|
||||||
if fileLocation == "" {
|
return fmt.Errorf("error getting file location | %w", err)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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:
|
Supported Dimensions:
|
||||||
1024x576
|
1024x576
|
||||||
576x1024
|
576x1024
|
||||||
768x768
|
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
|
// make sure the image is either jpeg or png
|
||||||
fileMimeType := mime.TypeByExtension(path.Ext(fileLocation))
|
fileMimeType := mime.TypeByExtension(path.Ext(fileLocation))
|
||||||
@ -76,8 +87,27 @@ func main() {
|
|||||||
return fmt.Errorf("unsupported file type | %v", fileMimeType)
|
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)
|
id, err := initiateGeneratingAnimation(fileLocation)
|
||||||
if err != nil {
|
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)
|
return fmt.Errorf("error making request | %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +117,13 @@ func main() {
|
|||||||
// wait for the job to finish
|
// wait for the job to finish
|
||||||
err = job(id, c.String("output"))
|
err = job(id, c.String("output"))
|
||||||
if err != nil {
|
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)
|
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) {
|
func initiateGeneratingAnimation(fileLocation string) (string, error) {
|
||||||
// get base filename from file location
|
// get base filename from file location
|
||||||
filename := path.Base(fileLocation)
|
filename := path.Base(fileLocation)
|
||||||
|
@ -3,19 +3,129 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
//func startCmd() *cli.Command {
|
func resizeCmd() *cli.Command {
|
||||||
// return &cli.Command{
|
return &cli.Command{
|
||||||
// Name: "start",
|
Name: "resize",
|
||||||
// Aliases: []string{"s"},
|
Aliases: []string{"r"},
|
||||||
// Usage: "Start the application service",
|
Flags: []cli.Flag{
|
||||||
// Action: func(c *cli.Context) error {
|
&cli.StringFlag{
|
||||||
//
|
Name: "output",
|
||||||
// return nil
|
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 {
|
func getJobResultCmd() *cli.Command {
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
|
5
go.mod
5
go.mod
@ -2,7 +2,10 @@ module i2v
|
|||||||
|
|
||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
require github.com/urfave/cli/v2 v2.26.0
|
require (
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||||
|
github.com/urfave/cli/v2 v2.26.0
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -1,5 +1,7 @@
|
|||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI=
|
github.com/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI=
|
||||||
|
Reference in New Issue
Block a user