mirror of
https://github.com/TheZoraiz/ascii-image-converter.git
synced 2026-05-17 00:45:58 +03:00
Added bin piping support for #28
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
@@ -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)
|
||||
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 {
|
||||
// Check file/data type of piped input
|
||||
|
||||
localFile, err = os.Open(filePath)
|
||||
pipedInputBytes, err = ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to open file: %v", err)
|
||||
return "", fmt.Errorf("unable to read piped input: %v", err)
|
||||
}
|
||||
defer localFile.Close()
|
||||
|
||||
fileType := http.DetectContentType(pipedInputBytes)
|
||||
invalidInput := true
|
||||
|
||||
if fileType == "image/gif" {
|
||||
inputIsGif = true
|
||||
invalidInput = false
|
||||
|
||||
} else {
|
||||
for _, inputType := range pipedInputTypes {
|
||||
if fileType == inputType {
|
||||
invalidInput = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -161,3 +168,8 @@ func clearScreen() {
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func isInputFromPipe() bool {
|
||||
fileInfo, _ := os.Stdin.Stat()
|
||||
return fileInfo.Mode()&os.ModeCharDevice == 0
|
||||
}
|
||||
|
||||
@@ -127,4 +127,5 @@ var (
|
||||
threshold int
|
||||
dither bool
|
||||
onlySave bool
|
||||
inputIsGif bool
|
||||
)
|
||||
|
||||
46
cmd/root.go
46
cmd/root.go
@@ -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
|
||||
@@ -93,28 +93,40 @@ var (
|
||||
OnlySave: onlySave,
|
||||
}
|
||||
|
||||
if isInputFromPipe() {
|
||||
printAscii("", flags)
|
||||
return
|
||||
}
|
||||
|
||||
for _, imagePath := range args {
|
||||
|
||||
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()
|
||||
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() {
|
||||
|
||||
10
cmd/util.go
10
cmd/util.go
@@ -18,6 +18,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -168,3 +169,8 @@ func checkInputAndFlags(args []string) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func isInputFromPipe() bool {
|
||||
fileInfo, _ := os.Stdin.Stat()
|
||||
return fileInfo.Mode()&os.ModeCharDevice == 0
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user