Skip to content

Commit

Permalink
Added bin piping support for #28
Browse files Browse the repository at this point in the history
  • Loading branch information
TheZoraiz committed Nov 14, 2022
1 parent a6a4120 commit fbf87d3
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 48 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ Example:
ascii-image-converter myImage.jpeg
```

> **Note:** Piped binary input is also supported
> ```
> cat myImage.png | ascii-image-converter
> ```
### Flags
#### --color OR -C
Expand Down
12 changes: 9 additions & 3 deletions aic_package/convert_gif.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,26 @@ as an ascii art gif.
Multi-threading has been implemented in multiple places due to long execution time
*/
func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes []byte, localGif *os.File) error {
func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes, pipedInputBytes []byte, localGif *os.File) error {

var (
originalGif *gif.GIF
err error
)

if pathIsURl {
if isInputFromPipe() {
originalGif, err = gif.DecodeAll(bytes.NewReader(pipedInputBytes))
} else if pathIsURl {
originalGif, err = gif.DecodeAll(bytes.NewReader(urlImgBytes))
} else {
originalGif, err = gif.DecodeAll(localGif)
}
if err != nil {
return fmt.Errorf("can't decode %v: %v", gifPath, err)
if isInputFromPipe() {
return fmt.Errorf("can't decode piped input: %v", err)
} else {
return fmt.Errorf("can't decode %v: %v", gifPath, err)
}
}

var (
Expand Down
12 changes: 9 additions & 3 deletions aic_package/convert_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,26 @@ import (
)

// This function decodes the passed image and returns an ascii art string, optionaly saving it as a .txt and/or .png file
func pathIsImage(imagePath, urlImgName string, pathIsURl bool, urlImgBytes []byte, localImg *os.File) (string, error) {
func pathIsImage(imagePath, urlImgName string, pathIsURl bool, urlImgBytes, pipedInputBytes []byte, localImg *os.File) (string, error) {

var (
imData image.Image
err error
)

if pathIsURl {
if isInputFromPipe() {
imData, _, err = image.Decode(bytes.NewReader(pipedInputBytes))
} else if pathIsURl {
imData, _, err = image.Decode(bytes.NewReader(urlImgBytes))
} else {
imData, _, err = image.Decode(localImg)
}
if err != nil {
return "", fmt.Errorf("can't decode %v: %v", imagePath, err)
if isInputFromPipe() {
return "", fmt.Errorf("can't decode piped input: %v", err)
} else {
return "", fmt.Errorf("can't decode %v: %v", imagePath, err)
}
}

imgSet, err := imgManip.ConvertToAsciiPixels(imData, dimensions, width, height, flipX, flipY, full, braille, dither)
Expand Down
88 changes: 66 additions & 22 deletions aic_package/convert_root.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ import (
"github.com/golang/freetype/truetype"
)

var pipedInputTypes = []string{
"image/png",
"image/jpeg",
"image/webp",
"image/tiff",
"image/bmp",
}

// Return default configuration for flags.
// Can be sent directly to ConvertImage() for default ascii art
func DefaultFlags() Flags {
Expand Down Expand Up @@ -98,42 +106,78 @@ func Convert(filePath string, flags Flags) (string, error) {
dither = flags.Dither
onlySave = flags.OnlySave

inputIsGif = path.Ext(filePath) == ".gif"

// Declared at the start since some variables are initially used in conditional blocks
var (
localFile *os.File
urlImgBytes []byte
urlImgName string = ""
err error
localFile *os.File
urlImgBytes []byte
urlImgName string = ""
pipedInputBytes []byte
err error
)

pathIsURl := isURL(filePath)

// Different modes of reading data depending upon whether or not filePath is a url
if pathIsURl {
fmt.Printf("Fetching file from url...\r")

retrievedImage, err := http.Get(filePath)
if err != nil {
return "", fmt.Errorf("can't fetch content: %v", err)
if !isInputFromPipe() {
if pathIsURl {
fmt.Printf("Fetching file from url...\r")

retrievedImage, err := http.Get(filePath)
if err != nil {
return "", fmt.Errorf("can't fetch content: %v", err)
}

urlImgBytes, err = ioutil.ReadAll(retrievedImage.Body)
if err != nil {
return "", fmt.Errorf("failed to read fetched content: %v", err)
}
defer retrievedImage.Body.Close()

urlImgName = path.Base(filePath)
fmt.Printf(" \r") // To erase "Fetching image from url..." text from terminal

} else {

localFile, err = os.Open(filePath)
if err != nil {
return "", fmt.Errorf("unable to open file: %v", err)
}
defer localFile.Close()

}

urlImgBytes, err = ioutil.ReadAll(retrievedImage.Body)
} else {
// Check file/data type of piped input

pipedInputBytes, err = ioutil.ReadAll(os.Stdin)
if err != nil {
return "", fmt.Errorf("failed to read fetched content: %v", err)
return "", fmt.Errorf("unable to read piped input: %v", err)
}
defer retrievedImage.Body.Close()

urlImgName = path.Base(filePath)
fmt.Printf(" \r") // To erase "Fetching image from url..." text from terminal
fileType := http.DetectContentType(pipedInputBytes)
invalidInput := true

} else {
if fileType == "image/gif" {
inputIsGif = true
invalidInput = false

localFile, err = os.Open(filePath)
if err != nil {
return "", fmt.Errorf("unable to open file: %v", err)
} else {
for _, inputType := range pipedInputTypes {
if fileType == inputType {
invalidInput = false
break
}
}
}
defer localFile.Close()

// Not sure if I should uncomment this.
// The output may be piped to another program and a warning would contaminate that
if invalidInput {
// fmt.Println("Warning: file type of piped input could not be determined, treating it as an image")
}
}

// If path to font file is provided, use it
Expand All @@ -151,9 +195,9 @@ func Convert(filePath string, flags Flags) (string, error) {
tempFont, _ = truetype.Parse(embeddedDejaVuObliqueFont)
}

if path.Ext(filePath) == ".gif" {
return "", pathIsGif(filePath, urlImgName, pathIsURl, urlImgBytes, localFile)
if inputIsGif {
return "", pathIsGif(filePath, urlImgName, pathIsURl, urlImgBytes, pipedInputBytes, localFile)
} else {
return pathIsImage(filePath, urlImgName, pathIsURl, urlImgBytes, localFile)
return pathIsImage(filePath, urlImgName, pathIsURl, urlImgBytes, pipedInputBytes, localFile)
}
}
12 changes: 12 additions & 0 deletions aic_package/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ func createSaveFileName(imagePath, urlImgName, label string) (string, error) {
return newName + label, nil
}

if isInputFromPipe() {
if inputIsGif {
return "piped-gif" + label, nil
}
return "piped-img" + label, nil
}

fileInfo, err := os.Stat(imagePath)
if err != nil {
return "", err
Expand Down Expand Up @@ -161,3 +168,8 @@ func clearScreen() {
os.Exit(0)
}
}

func isInputFromPipe() bool {
fileInfo, _ := os.Stdin.Stat()
return fileInfo.Mode()&os.ModeCharDevice == 0
}
1 change: 1 addition & 0 deletions aic_package/vars.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,5 @@ var (
threshold int
dither bool
onlySave bool
inputIsGif bool
)
46 changes: 29 additions & 17 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ var (

// Root commands
rootCmd = &cobra.Command{
Use: "ascii-image-converter [image paths/urls]",
Use: "ascii-image-converter [image paths/urls or piped stdin]",
Short: "Converts images and gifs into ascii art",
Version: "1.12.0",
Version: "1.13.0",
Long: "This tool converts images into ascii art and prints them on the terminal.\nFurther configuration can be managed with flags.",

// Not RunE since help text is getting larger and seeing it for every error impacts user experience
Expand Down Expand Up @@ -93,28 +93,40 @@ var (
OnlySave: onlySave,
}

for _, imagePath := range args {
if isInputFromPipe() {
printAscii("", flags)
return
}

if asciiArt, err := aic_package.Convert(imagePath, flags); err == nil {
fmt.Printf("%s", asciiArt)
} else {
fmt.Printf("Error: %v\n", err)

// Because this error will then be thrown for every image path/url passed
// if save path is invalid
if err.Error()[:15] == "can't save file" {
fmt.Println()
return
}
}
if !onlySave {
fmt.Println()
for _, imagePath := range args {
if err := printAscii(imagePath, flags); err != nil {
return
}
}
},
}
)

func printAscii(imagePath string, flags aic_package.Flags) error {

if asciiArt, err := aic_package.Convert(imagePath, flags); err == nil {
fmt.Printf("%s", asciiArt)
} else {
fmt.Printf("Error: %v\n", err)

// Because this error will then be thrown for every image path/url passed
// if save path is invalid
if err.Error()[:15] == "can't save file" {
fmt.Println()
return err
}
}
if !onlySave {
fmt.Println()
}
return nil
}

// Cobra configuration from here on

func Execute() {
Expand Down
10 changes: 8 additions & 2 deletions cmd/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package cmd

import (
"fmt"
"os"
"path"
)

Expand Down Expand Up @@ -59,8 +60,8 @@ func checkInputAndFlags(args []string) bool {
return true
}

if len(args) < 1 {
fmt.Printf("Error: Need at least 1 input path/url\nUse the -h flag for more info\n\n")
if !isInputFromPipe() && len(args) < 1 {
fmt.Printf("Error: Need at least 1 input path/url or piped input\nUse the -h flag for more info\n\n")
return true
}

Expand Down Expand Up @@ -168,3 +169,8 @@ func checkInputAndFlags(args []string) bool {

return false
}

func isInputFromPipe() bool {
fileInfo, _ := os.Stdin.Stat()
return fileInfo.Mode()&os.ModeCharDevice == 0
}
2 changes: 1 addition & 1 deletion snapcraft.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: ascii-image-converter
base: core18
version: "1.11.0"
version: "1.13.0"
summary: Convert images and gifs into ascii art
description: |
ascii-image-converter is a command-line tool that converts images into ascii art and prints
Expand Down

0 comments on commit fbf87d3

Please sign in to comment.