mirror of
https://github.com/esimov/caire.git
synced 2025-10-05 08:37:01 +08:00
Code refactoring and improvements
This commit is contained in:
@@ -77,7 +77,7 @@ func (c *Carver) ComputeSeams(p *Processor, img *image.NRGBA) (*image.NRGBA, err
|
|||||||
|
|
||||||
dets := []pigo.Detection{}
|
dets := []pigo.Detection{}
|
||||||
|
|
||||||
if p.PigoFaceDetector != nil && p.FaceDetect && detAttempts < maxFaceDetAttempts {
|
if p.FaceDetector != nil && p.FaceDetect && detAttempts < maxFaceDetAttempts {
|
||||||
var ratio float64
|
var ratio float64
|
||||||
|
|
||||||
if width < height {
|
if width < height {
|
||||||
@@ -108,10 +108,10 @@ func (c *Carver) ComputeSeams(p *Processor, img *image.NRGBA) (*image.NRGBA, err
|
|||||||
}
|
}
|
||||||
// Run the classifier over the obtained leaf nodes and return the detection results.
|
// Run the classifier over the obtained leaf nodes and return the detection results.
|
||||||
// The result contains quadruplets representing the row, column, scale and detection score.
|
// The result contains quadruplets representing the row, column, scale and detection score.
|
||||||
dets = p.PigoFaceDetector.RunCascade(cParams, p.FaceAngle)
|
dets = p.FaceDetector.RunCascade(cParams, p.FaceAngle)
|
||||||
|
|
||||||
// Calculate the intersection over union (IoU) of two clusters.
|
// Calculate the intersection over union (IoU) of two clusters.
|
||||||
dets = p.PigoFaceDetector.ClusterDetections(dets, 0.1)
|
dets = p.FaceDetector.ClusterDetections(dets, 0.1)
|
||||||
|
|
||||||
if len(dets) == 0 {
|
if len(dets) == 0 {
|
||||||
// Retry detecting faces for a certain amount of time.
|
// Retry detecting faces for a certain amount of time.
|
||||||
|
@@ -250,7 +250,7 @@ func TestCarver_ShouldDetectFace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
p.PigoFaceDetector, err = p.PigoFaceDetector.Unpack(cascadeFile)
|
p.FaceDetector, err = p.FaceDetector.Unpack(cascadeFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error unpacking the cascade file: %v", err)
|
t.Fatalf("error unpacking the cascade file: %v", err)
|
||||||
}
|
}
|
||||||
@@ -282,10 +282,10 @@ func TestCarver_ShouldDetectFace(t *testing.T) {
|
|||||||
|
|
||||||
// Run the classifier over the obtained leaf nodes and return the detection results.
|
// Run the classifier over the obtained leaf nodes and return the detection results.
|
||||||
// The result contains quadruplets representing the row, column, scale and detection score.
|
// The result contains quadruplets representing the row, column, scale and detection score.
|
||||||
faces := p.PigoFaceDetector.RunCascade(cParams, p.FaceAngle)
|
faces := p.FaceDetector.RunCascade(cParams, p.FaceAngle)
|
||||||
|
|
||||||
// Calculate the intersection over union (IoU) of two clusters.
|
// Calculate the intersection over union (IoU) of two clusters.
|
||||||
faces = p.PigoFaceDetector.ClusterDetections(faces, 0.2)
|
faces = p.FaceDetector.ClusterDetections(faces, 0.2)
|
||||||
|
|
||||||
assert.Equal(1, len(faces))
|
assert.Equal(1, len(faces))
|
||||||
}
|
}
|
||||||
@@ -301,7 +301,7 @@ func TestCarver_ShouldNotRemoveFaceZone(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
p.PigoFaceDetector, err = p.PigoFaceDetector.Unpack(cascadeFile)
|
p.FaceDetector, err = p.FaceDetector.Unpack(cascadeFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error unpacking the cascade file: %v", err)
|
t.Fatalf("error unpacking the cascade file: %v", err)
|
||||||
}
|
}
|
||||||
@@ -336,10 +336,10 @@ func TestCarver_ShouldNotRemoveFaceZone(t *testing.T) {
|
|||||||
|
|
||||||
// Run the classifier over the obtained leaf nodes and return the detection results.
|
// Run the classifier over the obtained leaf nodes and return the detection results.
|
||||||
// The result contains quadruplets representing the row, column, scale and detection score.
|
// The result contains quadruplets representing the row, column, scale and detection score.
|
||||||
faces := p.PigoFaceDetector.RunCascade(cParams, p.FaceAngle)
|
faces := p.FaceDetector.RunCascade(cParams, p.FaceAngle)
|
||||||
|
|
||||||
// Calculate the intersection over union (IoU) of two clusters.
|
// Calculate the intersection over union (IoU) of two clusters.
|
||||||
faces = p.PigoFaceDetector.ClusterDetections(faces, 0.2)
|
faces = p.FaceDetector.ClusterDetections(faces, 0.2)
|
||||||
|
|
||||||
// Range over all the detected faces and draw a white rectangle mask over each of them.
|
// Range over all the detected faces and draw a white rectangle mask over each of them.
|
||||||
// We need to trick the sobel detector to consider them as important image parts.
|
// We need to trick the sobel detector to consider them as important image parts.
|
||||||
@@ -378,7 +378,7 @@ func TestCarver_ShouldNotResizeWithFaceDistorsion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
p.PigoFaceDetector, err = p.PigoFaceDetector.Unpack(cascadeFile)
|
p.FaceDetector, err = p.FaceDetector.Unpack(cascadeFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error unpacking the cascade file: %v", err)
|
t.Fatalf("error unpacking the cascade file: %v", err)
|
||||||
}
|
}
|
||||||
@@ -409,10 +409,10 @@ func TestCarver_ShouldNotResizeWithFaceDistorsion(t *testing.T) {
|
|||||||
|
|
||||||
// Run the classifier over the obtained leaf nodes and return the detection results.
|
// Run the classifier over the obtained leaf nodes and return the detection results.
|
||||||
// The result contains quadruplets representing the row, column, scale and detection score.
|
// The result contains quadruplets representing the row, column, scale and detection score.
|
||||||
faces := p.PigoFaceDetector.RunCascade(cParams, p.FaceAngle)
|
faces := p.FaceDetector.RunCascade(cParams, p.FaceAngle)
|
||||||
|
|
||||||
// Calculate the intersection over union (IoU) of two clusters.
|
// Calculate the intersection over union (IoU) of two clusters.
|
||||||
faces = p.PigoFaceDetector.ClusterDetections(faces, 0.2)
|
faces = p.FaceDetector.ClusterDetections(faces, 0.2)
|
||||||
|
|
||||||
for _, face := range faces {
|
for _, face := range faces {
|
||||||
if p.NewHeight < face.Scale {
|
if p.NewHeight < face.Scale {
|
||||||
|
@@ -1,23 +1,15 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gioui.org/app"
|
"gioui.org/app"
|
||||||
"github.com/esimov/caire"
|
"github.com/esimov/caire"
|
||||||
"github.com/esimov/caire/utils"
|
"github.com/esimov/caire/utils"
|
||||||
"golang.org/x/term"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const HelpBanner = `
|
const HelpBanner = `
|
||||||
@@ -33,22 +25,6 @@ Content aware image resize library.
|
|||||||
// pipeName indicates that stdin/stdout is being used as file names.
|
// pipeName indicates that stdin/stdout is being used as file names.
|
||||||
const pipeName = "-"
|
const pipeName = "-"
|
||||||
|
|
||||||
// maxWorkers sets the maximum number of concurrently running workers.
|
|
||||||
const maxWorkers = 20
|
|
||||||
|
|
||||||
// result holds the relevant information about the resizing process and the generated image.
|
|
||||||
type result struct {
|
|
||||||
path string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// imgfile holds the file being accessed, be it normal file or pipe name.
|
|
||||||
imgfile *os.File
|
|
||||||
// spinner is used to instantiate and call the progress indicator.
|
|
||||||
spinner *utils.Spinner
|
|
||||||
)
|
|
||||||
|
|
||||||
// Version indicates the current build version.
|
// Version indicates the current build version.
|
||||||
var Version string
|
var Version string
|
||||||
|
|
||||||
@@ -71,13 +47,11 @@ var (
|
|||||||
faceDetect = flag.Bool("face", false, "Use face detection")
|
faceDetect = flag.Bool("face", false, "Use face detection")
|
||||||
faceAngle = flag.Float64("angle", 0.0, "Face rotation angle")
|
faceAngle = flag.Float64("angle", 0.0, "Face rotation angle")
|
||||||
workers = flag.Int("conc", runtime.NumCPU(), "Number of files to process concurrently")
|
workers = flag.Int("conc", runtime.NumCPU(), "Number of files to process concurrently")
|
||||||
|
|
||||||
// Common file related variable
|
|
||||||
fs os.FileInfo
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
|
|
||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
fmt.Fprintf(os.Stderr, fmt.Sprintf(HelpBanner, Version))
|
fmt.Fprintf(os.Stderr, fmt.Sprintf(HelpBanner, Version))
|
||||||
flag.PrintDefaults()
|
flag.PrintDefaults()
|
||||||
@@ -101,13 +75,6 @@ func main() {
|
|||||||
SeamColor: *seamColor,
|
SeamColor: *seamColor,
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultMsg := fmt.Sprintf("%s %s",
|
|
||||||
utils.DecorateText("⚡ CAIRE", utils.StatusMessage),
|
|
||||||
utils.DecorateText("⇢ image resizing in progress (be patient, it may take a while)...", utils.DefaultMessage),
|
|
||||||
)
|
|
||||||
|
|
||||||
spinner = utils.NewSpinner(defaultMsg, time.Millisecond*80)
|
|
||||||
|
|
||||||
if !(*newWidth > 0 || *newHeight > 0 || *percentage || *square) {
|
if !(*newWidth > 0 || *newHeight > 0 || *percentage || *square) {
|
||||||
flag.Usage()
|
flag.Usage()
|
||||||
log.Fatal(fmt.Sprintf("%s%s",
|
log.Fatal(fmt.Sprintf("%s%s",
|
||||||
@@ -115,335 +82,21 @@ func main() {
|
|||||||
utils.DefaultColor,
|
utils.DefaultColor,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
|
op := &caire.Ops{
|
||||||
|
Src: *source,
|
||||||
|
Dst: *destination,
|
||||||
|
Workers: *workers,
|
||||||
|
PipeName: pipeName,
|
||||||
|
}
|
||||||
|
|
||||||
if *preview {
|
if *preview {
|
||||||
// When the preview mode is activated we need to execute the resizing process
|
// When the preview mode is activated we have to execute the resizing process
|
||||||
// in a separate goroutine in order to not block the Gio thread,
|
// in a separate goroutine in order to not block the Gio thread,
|
||||||
// which needs to be run on the main OS thread on operating systems like MacOS.
|
// which have to run on the main OS thread of the operating systems like MacOS.
|
||||||
go execute(proc)
|
go proc.Execute(op)
|
||||||
app.Main()
|
app.Main()
|
||||||
} else {
|
} else {
|
||||||
execute(proc)
|
proc.Execute(op)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// execute executes the image resizing process.
|
|
||||||
// In case the preview mode is activated it will be invoked in a separate goroutine
|
|
||||||
// in order to not block the main OS thread. Otherwise it will be called normally.
|
|
||||||
func execute(proc *caire.Processor) {
|
|
||||||
var err error
|
|
||||||
proc.Spinner = spinner
|
|
||||||
|
|
||||||
// Supported files
|
|
||||||
validExtensions := []string{".jpg", ".png", ".jpeg", ".bmp", ".gif"}
|
|
||||||
|
|
||||||
// Check if source path is a local image or URL.
|
|
||||||
if utils.IsValidUrl(*source) {
|
|
||||||
src, err := utils.DownloadImage(*source)
|
|
||||||
if src != nil {
|
|
||||||
defer os.Remove(src.Name())
|
|
||||||
}
|
|
||||||
defer src.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf(
|
|
||||||
utils.DecorateText("Failed to load the source image: %v", utils.ErrorMessage),
|
|
||||||
utils.DecorateText(err.Error(), utils.DefaultMessage),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fs, err = src.Stat()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf(
|
|
||||||
utils.DecorateText("Failed to load the source image: %v", utils.ErrorMessage),
|
|
||||||
utils.DecorateText(err.Error(), utils.DefaultMessage),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
img, err := os.Open(src.Name())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf(
|
|
||||||
utils.DecorateText("Unable to open the temporary image file: %v", utils.ErrorMessage),
|
|
||||||
utils.DecorateText(err.Error(), utils.DefaultMessage),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
imgfile = img
|
|
||||||
} else {
|
|
||||||
// Check if the source is a pipe name or a regular file.
|
|
||||||
if *source == pipeName {
|
|
||||||
fs, err = os.Stdin.Stat()
|
|
||||||
} else {
|
|
||||||
fs, err = os.Stat(*source)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf(
|
|
||||||
utils.DecorateText("Failed to load the source image: %v", utils.ErrorMessage),
|
|
||||||
utils.DecorateText(err.Error(), utils.DefaultMessage),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
switch mode := fs.Mode(); {
|
|
||||||
case mode.IsDir():
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
// Read destination file or directory.
|
|
||||||
_, err := os.Stat(*destination)
|
|
||||||
if err != nil {
|
|
||||||
err = os.Mkdir(*destination, 0755)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf(
|
|
||||||
utils.DecorateText("Unable to get dir stats: %v\n", utils.ErrorMessage),
|
|
||||||
utils.DecorateText(err.Error(), utils.DefaultMessage),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
proc.Preview = false
|
|
||||||
|
|
||||||
// Limit the concurrently running workers to maxWorkers.
|
|
||||||
if *workers <= 0 || *workers > maxWorkers {
|
|
||||||
*workers = runtime.NumCPU()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process recursively the image files from the specified directory concurrently.
|
|
||||||
ch := make(chan result)
|
|
||||||
done := make(chan interface{})
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
paths, errc := walkDir(done, *source, validExtensions)
|
|
||||||
|
|
||||||
wg.Add(*workers)
|
|
||||||
for i := 0; i < *workers; i++ {
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
consumer(done, paths, *destination, proc, ch)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the channel after the values are consumed.
|
|
||||||
go func() {
|
|
||||||
defer close(ch)
|
|
||||||
wg.Wait()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Consume the channel values.
|
|
||||||
for res := range ch {
|
|
||||||
if res.err != nil {
|
|
||||||
err = res.err
|
|
||||||
}
|
|
||||||
printStatus(res.path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = <-errc; err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, utils.DecorateText(err.Error(), utils.ErrorMessage))
|
|
||||||
}
|
|
||||||
|
|
||||||
case mode.IsRegular() || mode&os.ModeNamedPipe != 0: // check for regular files or pipe names
|
|
||||||
ext := filepath.Ext(*destination)
|
|
||||||
if !isValidExtension(ext, validExtensions) && *destination != pipeName {
|
|
||||||
log.Fatalf(utils.DecorateText(fmt.Sprintf("%v file type not supported", ext), utils.ErrorMessage))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = processor(*source, *destination, proc)
|
|
||||||
printStatus(*destination, err)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "\nExecution time: %s\n", utils.DecorateText(fmt.Sprintf("%s", utils.FormatTime(time.Since(now))), utils.SuccessMessage))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// walkDir starts a goroutine to walk the specified directory tree in recursive manner
|
|
||||||
// and send the path of each regular file on the string channel.
|
|
||||||
// It terminates in case done channel is closed.
|
|
||||||
func walkDir(
|
|
||||||
done <-chan interface{},
|
|
||||||
src string,
|
|
||||||
srcExts []string,
|
|
||||||
) (<-chan string, <-chan error) {
|
|
||||||
pathChan := make(chan string)
|
|
||||||
errChan := make(chan error, 1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
// Close the paths channel after Walk returns.
|
|
||||||
defer close(pathChan)
|
|
||||||
|
|
||||||
errChan <- filepath.Walk(src, func(path string, f os.FileInfo, err error) error {
|
|
||||||
isFileSupported := false
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !f.Mode().IsRegular() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the file base name.
|
|
||||||
fx := filepath.Ext(f.Name())
|
|
||||||
for _, ext := range srcExts {
|
|
||||||
if ext == fx {
|
|
||||||
isFileSupported = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isFileSupported {
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
return errors.New("directory walk cancelled")
|
|
||||||
case pathChan <- path:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}()
|
|
||||||
return pathChan, errChan
|
|
||||||
}
|
|
||||||
|
|
||||||
// consumer reads the path names from the paths channel and calls the resizing processor against the source image.
|
|
||||||
func consumer(
|
|
||||||
done <-chan interface{},
|
|
||||||
paths <-chan string,
|
|
||||||
dest string,
|
|
||||||
proc *caire.Processor,
|
|
||||||
res chan<- result,
|
|
||||||
) {
|
|
||||||
for src := range paths {
|
|
||||||
dst := filepath.Join(dest, filepath.Base(src))
|
|
||||||
err := processor(src, dst, proc)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
return
|
|
||||||
case res <- result{
|
|
||||||
path: src,
|
|
||||||
err: err,
|
|
||||||
}:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// processor calls the resizer method over the source image and returns the error in case exists.
|
|
||||||
func processor(in, out string, proc *caire.Processor) error {
|
|
||||||
var (
|
|
||||||
successMsg string
|
|
||||||
errorMsg string
|
|
||||||
)
|
|
||||||
// Start the progress indicator.
|
|
||||||
spinner.Start()
|
|
||||||
|
|
||||||
successMsg = fmt.Sprintf("%s %s %s",
|
|
||||||
utils.DecorateText("⚡ CAIRE", utils.StatusMessage),
|
|
||||||
utils.DecorateText("⇢", utils.DefaultMessage),
|
|
||||||
utils.DecorateText("the image has been resized successfully ✔", utils.SuccessMessage),
|
|
||||||
)
|
|
||||||
|
|
||||||
errorMsg = fmt.Sprintf("%s %s %s",
|
|
||||||
utils.DecorateText("⚡ CAIRE", utils.StatusMessage),
|
|
||||||
utils.DecorateText("resizing image failed...", utils.DefaultMessage),
|
|
||||||
utils.DecorateText("✘", utils.ErrorMessage),
|
|
||||||
)
|
|
||||||
|
|
||||||
src, dst, err := pathToFile(in, out)
|
|
||||||
if err != nil {
|
|
||||||
spinner.StopMsg = errorMsg
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capture CTRL-C signal and restores back the cursor visibility.
|
|
||||||
signalChan := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
|
|
||||||
go func() {
|
|
||||||
<-signalChan
|
|
||||||
func() {
|
|
||||||
spinner.RestoreCursor()
|
|
||||||
os.Remove(dst.(*os.File).Name())
|
|
||||||
os.Exit(1)
|
|
||||||
}()
|
|
||||||
}()
|
|
||||||
|
|
||||||
defer src.(*os.File).Close()
|
|
||||||
defer dst.(*os.File).Close()
|
|
||||||
|
|
||||||
err = proc.Process(src, dst)
|
|
||||||
if err != nil {
|
|
||||||
// remove the generated image file in case of an error
|
|
||||||
os.Remove(dst.(*os.File).Name())
|
|
||||||
|
|
||||||
spinner.StopMsg = errorMsg
|
|
||||||
// Stop the progress indicator.
|
|
||||||
spinner.Stop()
|
|
||||||
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
spinner.StopMsg = successMsg
|
|
||||||
// Stop the progress indicator.
|
|
||||||
spinner.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// pathToFile converts the source and destination paths to readable and writable files.
|
|
||||||
func pathToFile(in, out string) (io.Reader, io.Writer, error) {
|
|
||||||
var (
|
|
||||||
src io.Reader
|
|
||||||
dst io.Writer
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
// Check if the source path is a local image or URL.
|
|
||||||
if utils.IsValidUrl(in) {
|
|
||||||
src = imgfile
|
|
||||||
} else {
|
|
||||||
// Check if the source is a pipe name or a regular file.
|
|
||||||
if in == pipeName {
|
|
||||||
if term.IsTerminal(int(os.Stdin.Fd())) {
|
|
||||||
return nil, nil, errors.New("`-` should be used with a pipe for stdin")
|
|
||||||
}
|
|
||||||
src = os.Stdin
|
|
||||||
} else {
|
|
||||||
src, err = os.Open(in)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("unable to open the source file: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the destination is a pipe name or a regular file.
|
|
||||||
if out == pipeName {
|
|
||||||
if term.IsTerminal(int(os.Stdout.Fd())) {
|
|
||||||
return nil, nil, errors.New("`-` should be used with a pipe for stdout")
|
|
||||||
}
|
|
||||||
dst = os.Stdout
|
|
||||||
} else {
|
|
||||||
dst, err = os.OpenFile(out, os.O_CREATE|os.O_WRONLY, 0755)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("unable to create the destination file: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return src, dst, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// printStatus displays the relavant information about the image resizing process.
|
|
||||||
func printStatus(fname string, err error) {
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr,
|
|
||||||
utils.DecorateText("\nError resizing the image: %s", utils.ErrorMessage),
|
|
||||||
utils.DecorateText(fmt.Sprintf("\n\tReason: %v\n", err.Error()), utils.DefaultMessage),
|
|
||||||
)
|
|
||||||
os.Exit(0)
|
|
||||||
} else {
|
|
||||||
if fname != pipeName {
|
|
||||||
fmt.Fprintf(os.Stderr, "\nThe image has been saved as: %s %s\n\n",
|
|
||||||
utils.DecorateText(filepath.Base(fname), utils.SuccessMessage),
|
|
||||||
utils.DefaultColor,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// isValidExtension checks for the supported extensions.
|
|
||||||
func isValidExtension(ext string, extensions []string) bool {
|
|
||||||
for _, ex := range extensions {
|
|
||||||
if ex == ext {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
378
exec.go
Normal file
378
exec.go
Normal file
@@ -0,0 +1,378 @@
|
|||||||
|
package caire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/esimov/caire/utils"
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
// maxWorkers sets the maximum number of concurrently running workers.
|
||||||
|
const maxWorkers = 20
|
||||||
|
|
||||||
|
var (
|
||||||
|
// imgFile holds the file being accessed, be it normal file or pipe name.
|
||||||
|
imgFile *os.File
|
||||||
|
|
||||||
|
// Common file related variable
|
||||||
|
fs os.FileInfo
|
||||||
|
)
|
||||||
|
|
||||||
|
type Ops struct {
|
||||||
|
Src, Dst, PipeName string
|
||||||
|
Workers int
|
||||||
|
}
|
||||||
|
|
||||||
|
// result holds the relevant information about the resizing process and the generated image.
|
||||||
|
type result struct {
|
||||||
|
path string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute executes the image resizing process.
|
||||||
|
// In case the preview mode is activated it will be invoked in a separate goroutine
|
||||||
|
// in order to not block the main OS thread. Otherwise it will be called normally.
|
||||||
|
func (p *Processor) Execute(op *Ops) {
|
||||||
|
var err error
|
||||||
|
defaultMsg := fmt.Sprintf("%s %s",
|
||||||
|
utils.DecorateText("⚡ CAIRE", utils.StatusMessage),
|
||||||
|
utils.DecorateText("⇢ resizing image (be patient, it may take a while)...", utils.DefaultMessage),
|
||||||
|
)
|
||||||
|
p.Spinner = utils.NewSpinner(defaultMsg, time.Millisecond*80)
|
||||||
|
|
||||||
|
// Supported files
|
||||||
|
validExtensions := []string{".jpg", ".png", ".jpeg", ".bmp", ".gif"}
|
||||||
|
|
||||||
|
// Check if source path is a local image or URL.
|
||||||
|
if utils.IsValidUrl(op.Src) {
|
||||||
|
src, err := utils.DownloadImage(op.Src)
|
||||||
|
if src != nil {
|
||||||
|
defer os.Remove(src.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(
|
||||||
|
utils.DecorateText("Failed to load the source image: %v", utils.ErrorMessage),
|
||||||
|
utils.DecorateText(err.Error(), utils.DefaultMessage),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fs, err = src.Stat()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(
|
||||||
|
utils.DecorateText("Failed to load the source image: %v", utils.ErrorMessage),
|
||||||
|
utils.DecorateText(err.Error(), utils.DefaultMessage),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
img, err := os.Open(src.Name())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(
|
||||||
|
utils.DecorateText("Unable to open the temporary image file: %v", utils.ErrorMessage),
|
||||||
|
utils.DecorateText(err.Error(), utils.DefaultMessage),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
imgFile = img
|
||||||
|
} else {
|
||||||
|
// Check if the source is a pipe name or a regular file.
|
||||||
|
if op.Src == op.PipeName {
|
||||||
|
fs, err = os.Stdin.Stat()
|
||||||
|
} else {
|
||||||
|
fs, err = os.Stat(op.Src)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(
|
||||||
|
utils.DecorateText("Failed to load the source image: %v", utils.ErrorMessage),
|
||||||
|
utils.DecorateText(err.Error(), utils.DefaultMessage),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
switch mode := fs.Mode(); {
|
||||||
|
case mode.IsDir():
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
// Read destination file or directory.
|
||||||
|
_, err := os.Stat(op.Dst)
|
||||||
|
if err != nil {
|
||||||
|
err = os.Mkdir(op.Dst, 0755)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(
|
||||||
|
utils.DecorateText("Unable to get dir stats: %v\n", utils.ErrorMessage),
|
||||||
|
utils.DecorateText(err.Error(), utils.DefaultMessage),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.Preview = false
|
||||||
|
|
||||||
|
// Limit the concurrently running workers to maxWorkers.
|
||||||
|
if op.Workers <= 0 || op.Workers > maxWorkers {
|
||||||
|
op.Workers = runtime.NumCPU()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process recursively the image files from the specified directory concurrently.
|
||||||
|
ch := make(chan result)
|
||||||
|
done := make(chan interface{})
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
|
paths, errc := walkDir(done, op.Src, validExtensions)
|
||||||
|
|
||||||
|
wg.Add(op.Workers)
|
||||||
|
for i := 0; i < op.Workers; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
op.consumer(p, op.Dst, ch, done, paths)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the channel after the values are consumed.
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
wg.Wait()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Consume the channel values.
|
||||||
|
for res := range ch {
|
||||||
|
if res.err != nil {
|
||||||
|
err = res.err
|
||||||
|
}
|
||||||
|
op.printOpStatus(res.path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = <-errc; err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, utils.DecorateText(err.Error(), utils.ErrorMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
case mode.IsRegular() || mode&os.ModeNamedPipe != 0: // check for regular files or pipe names
|
||||||
|
ext := filepath.Ext(op.Dst)
|
||||||
|
if !isValidExtension(ext, validExtensions) && op.Dst != op.PipeName {
|
||||||
|
log.Fatalf(utils.DecorateText(fmt.Sprintf("%v file type not supported", ext), utils.ErrorMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = op.process(p, op.Src, op.Dst)
|
||||||
|
op.printOpStatus(op.Dst, err)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "\nExecution time: %s\n", utils.DecorateText(fmt.Sprintf("%s", utils.FormatTime(time.Since(now))), utils.SuccessMessage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// consumer reads the path names from the paths channel and calls the resizing processor against the source image.
|
||||||
|
func (op *Ops) consumer(
|
||||||
|
p *Processor,
|
||||||
|
dest string,
|
||||||
|
res chan<- result,
|
||||||
|
done <-chan interface{},
|
||||||
|
paths <-chan string,
|
||||||
|
) {
|
||||||
|
for src := range paths {
|
||||||
|
dst := filepath.Join(dest, filepath.Base(src))
|
||||||
|
err := op.process(p, src, dst)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
case res <- result{
|
||||||
|
path: src,
|
||||||
|
err: err,
|
||||||
|
}:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// processor calls the resizer method over the source image and returns the error in case exists.
|
||||||
|
func (op *Ops) process(p *Processor, in, out string) error {
|
||||||
|
var (
|
||||||
|
successMsg string
|
||||||
|
errorMsg string
|
||||||
|
)
|
||||||
|
// Start the progress indicator.
|
||||||
|
p.Spinner.Start()
|
||||||
|
|
||||||
|
successMsg = fmt.Sprintf("%s %s %s",
|
||||||
|
utils.DecorateText("⚡ CAIRE", utils.StatusMessage),
|
||||||
|
utils.DecorateText("⇢", utils.DefaultMessage),
|
||||||
|
utils.DecorateText("the image has been resized successfully ✔", utils.SuccessMessage),
|
||||||
|
)
|
||||||
|
|
||||||
|
errorMsg = fmt.Sprintf("%s %s %s",
|
||||||
|
utils.DecorateText("⚡ CAIRE", utils.StatusMessage),
|
||||||
|
utils.DecorateText("resizing image failed...", utils.DefaultMessage),
|
||||||
|
utils.DecorateText("✘", utils.ErrorMessage),
|
||||||
|
)
|
||||||
|
|
||||||
|
src, dst, err := op.pathToFile(in, out)
|
||||||
|
if err != nil {
|
||||||
|
p.Spinner.StopMsg = errorMsg
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture CTRL-C signal and restores back the cursor visibility.
|
||||||
|
signalChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
|
||||||
|
go func() {
|
||||||
|
<-signalChan
|
||||||
|
func() {
|
||||||
|
p.Spinner.RestoreCursor()
|
||||||
|
os.Remove(dst.(*os.File).Name())
|
||||||
|
os.Exit(1)
|
||||||
|
}()
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if img, ok := src.(*os.File); ok {
|
||||||
|
if err := img.Close(); err != nil {
|
||||||
|
log.Printf("could not close the opened file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if img, ok := dst.(*os.File); ok {
|
||||||
|
if err := img.Close(); err != nil {
|
||||||
|
log.Printf("could not close the opened file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = p.Process(src, dst)
|
||||||
|
if err != nil {
|
||||||
|
// remove the generated image file in case of an error
|
||||||
|
os.Remove(dst.(*os.File).Name())
|
||||||
|
|
||||||
|
p.Spinner.StopMsg = errorMsg
|
||||||
|
// Stop the progress indicator.
|
||||||
|
p.Spinner.Stop()
|
||||||
|
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
p.Spinner.StopMsg = successMsg
|
||||||
|
// Stop the progress indicator.
|
||||||
|
p.Spinner.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pathToFile converts the source and destination paths to readable and writable files.
|
||||||
|
func (op *Ops) pathToFile(in, out string) (io.Reader, io.Writer, error) {
|
||||||
|
var (
|
||||||
|
src io.Reader
|
||||||
|
dst io.Writer
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
// Check if the source path is a local image or URL.
|
||||||
|
if utils.IsValidUrl(in) {
|
||||||
|
src = imgFile
|
||||||
|
} else {
|
||||||
|
// Check if the source is a pipe name or a regular file.
|
||||||
|
if in == op.PipeName {
|
||||||
|
if term.IsTerminal(int(os.Stdin.Fd())) {
|
||||||
|
return nil, nil, errors.New("`-` should be used with a pipe for stdin")
|
||||||
|
}
|
||||||
|
src = os.Stdin
|
||||||
|
} else {
|
||||||
|
src, err = os.Open(in)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("unable to open the source file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the destination is a pipe name or a regular file.
|
||||||
|
if out == op.PipeName {
|
||||||
|
if term.IsTerminal(int(os.Stdout.Fd())) {
|
||||||
|
return nil, nil, errors.New("`-` should be used with a pipe for stdout")
|
||||||
|
}
|
||||||
|
dst = os.Stdout
|
||||||
|
} else {
|
||||||
|
dst, err = os.OpenFile(out, os.O_CREATE|os.O_WRONLY, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("unable to create the destination file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return src, dst, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// printOpStatus displays the relevant information about the image resizing process.
|
||||||
|
func (op *Ops) printOpStatus(fname string, err error) {
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(
|
||||||
|
utils.DecorateText("\nError resizing the image: %s", utils.ErrorMessage),
|
||||||
|
utils.DecorateText(fmt.Sprintf("\n\tReason: %v\n", err.Error()), utils.DefaultMessage),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
if fname != op.PipeName {
|
||||||
|
fmt.Fprintf(os.Stderr, "\nThe image has been saved as: %s %s\n\n",
|
||||||
|
utils.DecorateText(filepath.Base(fname), utils.SuccessMessage),
|
||||||
|
utils.DefaultColor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// walkDir starts a new goroutine to walk the specified directory tree
|
||||||
|
// in recursive manner and sends the path of each regular file to a new channel.
|
||||||
|
// It finishes in case the done channel is getting closed.
|
||||||
|
func walkDir(
|
||||||
|
done <-chan interface{},
|
||||||
|
src string,
|
||||||
|
srcExts []string,
|
||||||
|
) (<-chan string, <-chan error) {
|
||||||
|
pathChan := make(chan string)
|
||||||
|
errChan := make(chan error, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// Close the paths channel after Walk returns.
|
||||||
|
defer close(pathChan)
|
||||||
|
|
||||||
|
errChan <- filepath.Walk(src, func(path string, f os.FileInfo, err error) error {
|
||||||
|
isFileSupported := false
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !f.Mode().IsRegular() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the file base name.
|
||||||
|
fx := filepath.Ext(f.Name())
|
||||||
|
for _, ext := range srcExts {
|
||||||
|
if ext == fx {
|
||||||
|
isFileSupported = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isFileSupported {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return errors.New("directory walk cancelled")
|
||||||
|
case pathChan <- path:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
return pathChan, errChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidExtension checks for the supported extensions.
|
||||||
|
func isValidExtension(ext string, extensions []string) bool {
|
||||||
|
for _, ex := range extensions {
|
||||||
|
if ex == ext {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
2
go.mod
2
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module github.com/esimov/caire
|
module github.com/esimov/caire
|
||||||
|
|
||||||
go 1.19
|
go 1.22
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gioui.org v0.3.1
|
gioui.org v0.3.1
|
||||||
|
2
go.sum
2
go.sum
@@ -1,4 +1,5 @@
|
|||||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
|
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
|
||||||
|
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
|
||||||
gioui.org v0.3.1 h1:hslYkrkIWvx28Mxe3A87opl+8s9mnWsnWmPDh11+zco=
|
gioui.org v0.3.1 h1:hslYkrkIWvx28Mxe3A87opl+8s9mnWsnWmPDh11+zco=
|
||||||
gioui.org v0.3.1/go.mod h1:2atiYR4upH71/6ehnh6XsUELa7JZOrOHHNMDxGBZF0Q=
|
gioui.org v0.3.1/go.mod h1:2atiYR4upH71/6ehnh6XsUELa7JZOrOHHNMDxGBZF0Q=
|
||||||
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
||||||
@@ -17,6 +18,7 @@ github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzP
|
|||||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo=
|
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo=
|
||||||
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
|
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
|
||||||
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
|
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
|
||||||
|
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
@@ -78,7 +78,7 @@ type Processor struct {
|
|||||||
RMask *image.NRGBA
|
RMask *image.NRGBA
|
||||||
GuiDebug *image.NRGBA
|
GuiDebug *image.NRGBA
|
||||||
FaceAngle float64
|
FaceAngle float64
|
||||||
PigoFaceDetector *pigo.Pigo
|
FaceDetector *pigo.Pigo
|
||||||
Spinner *utils.Spinner
|
Spinner *utils.Spinner
|
||||||
|
|
||||||
vRes bool
|
vRes bool
|
||||||
@@ -476,13 +476,13 @@ func (p *Processor) calculateFitness(img *image.NRGBA, c *Carver) *image.NRGBA {
|
|||||||
func (p *Processor) Process(r io.Reader, w io.Writer) error {
|
func (p *Processor) Process(r io.Reader, w io.Writer) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Instantiate a new Pigo object in case the face detection option is used.
|
|
||||||
p.PigoFaceDetector = pigo.NewPigo()
|
|
||||||
|
|
||||||
if p.FaceDetect {
|
if p.FaceDetect {
|
||||||
|
// Instantiate a new Pigo object in case the face detection option is used.
|
||||||
|
p.FaceDetector = pigo.NewPigo()
|
||||||
|
|
||||||
// Unpack the binary file. This will return the number of cascade trees,
|
// Unpack the binary file. This will return the number of cascade trees,
|
||||||
// the tree depth, the threshold and the prediction from tree's leaf nodes.
|
// the tree depth, the threshold and the prediction from tree's leaf nodes.
|
||||||
p.PigoFaceDetector, err = p.PigoFaceDetector.Unpack(cascadeFile)
|
p.FaceDetector, err = p.FaceDetector.Unpack(cascadeFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error unpacking the cascade file: %v", err)
|
return fmt.Errorf("error unpacking the cascade file: %v", err)
|
||||||
}
|
}
|
||||||
@@ -494,6 +494,8 @@ func (p *Processor) Process(r io.Reader, w io.Writer) error {
|
|||||||
|
|
||||||
src, _, err := image.Decode(r)
|
src, _, err := image.Decode(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Println("err:", err)
|
||||||
|
os.Exit(2)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@@ -2,10 +2,9 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -17,32 +16,35 @@ func DownloadImage(url string) (*os.File, error) {
|
|||||||
// Retrieve the url and decode the response body.
|
// Retrieve the url and decode the response body.
|
||||||
res, err := http.Get(url)
|
res, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New(fmt.Sprintf("unable to download image file from URI: %s, status %v", url, res.Status))
|
return nil, fmt.Errorf("unable to download image file from URI: %s, status %v", url, res.Status)
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(res.Body)
|
data, err := io.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New(fmt.Sprintf("unable to read response body: %s", err))
|
return nil, fmt.Errorf("unable to read response body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpfile, err := ioutil.TempFile("/tmp", "image")
|
tmpfile, err := os.CreateTemp("/tmp", "image")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New(fmt.Sprintf("unable to create temporary file: %v", err))
|
return nil, fmt.Errorf("unable to create temporary file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the image binary data into the temporary file.
|
// Copy the image binary data into the temporary file.
|
||||||
_, err = io.Copy(tmpfile, bytes.NewBuffer(data))
|
_, err = io.Copy(tmpfile, bytes.NewBuffer(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New(fmt.Sprintf("unable to copy the source URI into the destination file"))
|
return nil, fmt.Errorf("unable to copy the source URI into the destination file")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctype, err := DetectContentType(tmpfile.Name())
|
ctype, err := DetectContentType(tmpfile.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(ctype.(string), "image") {
|
if !strings.Contains(ctype.(string), "image") {
|
||||||
return nil, errors.New("the downloaded file is a valid image type.")
|
return nil, fmt.Errorf("the downloaded file is not a valid image type")
|
||||||
}
|
}
|
||||||
|
|
||||||
return tmpfile, nil
|
return tmpfile, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +69,11 @@ func DetectContentType(fname string) (interface{}, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer func() {
|
||||||
|
if err := file.Close(); err != nil {
|
||||||
|
log.Printf("could not close the opened file: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Only the first 512 bytes are used to sniff the content type.
|
// Only the first 512 bytes are used to sniff the content type.
|
||||||
buffer := make([]byte, 512)
|
buffer := make([]byte, 512)
|
||||||
|
Reference in New Issue
Block a user