mirror of
https://codeberg.org/gigirassy/asciiwebserv
synced 2026-08-30 15:37:40 +00:00
THAT WAS CRAPP
revert implement possible macos fallback
This commit is contained in:
@@ -22,15 +22,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultListen = ":3333"
|
defaultListen = ":3333"
|
||||||
defaultDir = "./images"
|
defaultDir = "./images"
|
||||||
defaultWidth = 80
|
defaultWidth = 90
|
||||||
cacheSuffixFmt = ".ansi.%s.%d.txt"
|
cacheSuffixFmt = ".ansi.%d.txt"
|
||||||
allowedUA = "curl"
|
allowedUA = "curl"
|
||||||
maxImageDim = 2000
|
maxImageDim = 2000
|
||||||
fallbackTag = "fallback"
|
|
||||||
truecolorTag = "tc"
|
|
||||||
forceFallbackEnv = "FORCE_FALLBACK"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -51,11 +48,14 @@ func main() {
|
|||||||
log.Fatalf("failed to create images dir: %v", err)
|
log.Fatalf("failed to create images dir: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("images directory: %s", imgDir)
|
||||||
|
log.Printf("pre-generating ascii caches at width=%d ...", defaultWidth)
|
||||||
scanAndPreGen(defaultWidth)
|
scanAndPreGen(defaultWidth)
|
||||||
refreshCachedPaths()
|
refreshCachedPaths(defaultWidth)
|
||||||
|
|
||||||
http.HandleFunc("/", rootHandler)
|
http.HandleFunc("/", rootHandler)
|
||||||
|
|
||||||
|
log.Printf("listening on %s — only curl clients accepted; non-curl will be redirected to %s", listen, redirectURL)
|
||||||
if err := http.ListenAndServe(listen, nil); err != nil {
|
if err := http.ListenAndServe(listen, nil); err != nil {
|
||||||
log.Fatalf("server failed: %v", err)
|
log.Fatalf("server failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -84,10 +84,8 @@ func rootHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tag := chooseTag(ua)
|
|
||||||
|
|
||||||
cacheLock.RLock()
|
cacheLock.RLock()
|
||||||
candidates := filteredCachedPaths(width, tag)
|
candidates := filteredCachedPaths(width)
|
||||||
cacheLock.RUnlock()
|
cacheLock.RUnlock()
|
||||||
|
|
||||||
if len(candidates) == 0 {
|
if len(candidates) == 0 {
|
||||||
@@ -97,16 +95,18 @@ func rootHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
imgFile := images[rand.Intn(len(images))]
|
imgFile := images[rand.Intn(len(images))]
|
||||||
cachePath := cacheFilePath(imgFile, width, tag)
|
cachePath := cacheFilePath(imgFile, width)
|
||||||
|
|
||||||
if err := generateAndSaveASCII(imgFile, cachePath, width, tag); err != nil {
|
log.Printf("generating cache for %s (width=%d) on demand", imgFile, width)
|
||||||
|
if err := generateAndSaveASCII(imgFile, cachePath, width); err != nil {
|
||||||
|
log.Printf("generation error: %v", err)
|
||||||
http.Error(w, "failed to generate ascii\n", http.StatusInternalServerError)
|
http.Error(w, "failed to generate ascii\n", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheLock.Lock()
|
cacheLock.Lock()
|
||||||
refreshCachedPaths()
|
refreshCachedPaths(width)
|
||||||
candidates = filteredCachedPaths(width, tag)
|
candidates = filteredCachedPaths(width)
|
||||||
cacheLock.Unlock()
|
cacheLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,30 +123,13 @@ func rootHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
w.Header().Set("Content-Type", "text/plain; charset=utf8")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
if _, err := io.Copy(w, f); err != nil {
|
if _, err := io.Copy(w, f); err != nil {
|
||||||
log.Printf("write error: %v", err)
|
log.Printf("write error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func chooseTag(ua string) string {
|
|
||||||
if os.Getenv(forceFallbackEnv) == "1" {
|
|
||||||
return fallbackTag
|
|
||||||
}
|
|
||||||
if isLikelyMac(ua) {
|
|
||||||
return fallbackTag
|
|
||||||
}
|
|
||||||
return truecolorTag
|
|
||||||
}
|
|
||||||
|
|
||||||
func isLikelyMac(ua string) bool {
|
|
||||||
if strings.Contains(ua, "darwin") || strings.Contains(ua, "macintosh") || strings.Contains(ua, "mac os") || strings.Contains(ua, "macos") || strings.Contains(ua, "iphone") || strings.Contains(ua, "ipad") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func listImageFiles() []string {
|
func listImageFiles() []string {
|
||||||
var files []string
|
var files []string
|
||||||
_ = filepath.Walk(imgDir, func(path string, info os.FileInfo, err error) error {
|
_ = filepath.Walk(imgDir, func(path string, info os.FileInfo, err error) error {
|
||||||
@@ -157,40 +140,42 @@ func listImageFiles() []string {
|
|||||||
switch ext {
|
switch ext {
|
||||||
case ".jpg", ".jpeg", ".png", ".gif", ".bmp":
|
case ".jpg", ".jpeg", ".png", ".gif", ".bmp":
|
||||||
files = append(files, path)
|
files = append(files, path)
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
return files
|
return files
|
||||||
}
|
}
|
||||||
|
|
||||||
func cacheFilePath(imagePath string, width int, tag string) string {
|
func cacheFilePath(imagePath string, width int) string {
|
||||||
dir := filepath.Dir(imagePath)
|
dir := filepath.Dir(imagePath)
|
||||||
base := filepath.Base(imagePath)
|
base := filepath.Base(imagePath)
|
||||||
ext := filepath.Ext(base)
|
ext := filepath.Ext(base)
|
||||||
name := strings.TrimSuffix(base, ext)
|
name := strings.TrimSuffix(base, ext)
|
||||||
return filepath.Join(dir, fmt.Sprintf("%s"+cacheSuffixFmt, name, tag, width))
|
return filepath.Join(dir, fmt.Sprintf("%s"+cacheSuffixFmt, name, width))
|
||||||
}
|
}
|
||||||
|
|
||||||
func scanAndPreGen(width int) {
|
func scanAndPreGen(width int) {
|
||||||
images := listImageFiles()
|
images := listImageFiles()
|
||||||
for _, img := range images {
|
for _, img := range images {
|
||||||
for _, tag := range []string{truecolorTag, fallbackTag} {
|
cache := cacheFilePath(img, width)
|
||||||
cache := cacheFilePath(img, width, tag)
|
if _, err := os.Stat(cache); err == nil {
|
||||||
if _, err := os.Stat(cache); err == nil {
|
continue
|
||||||
continue
|
}
|
||||||
}
|
if err := generateAndSaveASCII(img, cache, width); err != nil {
|
||||||
_ = generateAndSaveASCII(img, cache, width, tag)
|
log.Printf("failed to generate for %s: %v", img, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshCachedPaths() {
|
func refreshCachedPaths(width int) {
|
||||||
|
suffix := fmt.Sprintf(cacheSuffixFmt, width)
|
||||||
found := []string{}
|
found := []string{}
|
||||||
_ = filepath.Walk(imgDir, func(path string, info os.FileInfo, err error) error {
|
_ = filepath.Walk(imgDir, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil || info.IsDir() {
|
if err != nil || info.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if strings.Contains(path, ".ansi.") && strings.HasSuffix(path, ".txt") {
|
if strings.HasSuffix(path, suffix) {
|
||||||
found = append(found, path)
|
found = append(found, path)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -201,8 +186,8 @@ func refreshCachedPaths() {
|
|||||||
cacheLock.Unlock()
|
cacheLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func filteredCachedPaths(width int, tag string) []string {
|
func filteredCachedPaths(width int) []string {
|
||||||
suffix := fmt.Sprintf(cacheSuffixFmt, tag, width)
|
suffix := fmt.Sprintf(cacheSuffixFmt, width)
|
||||||
out := make([]string, 0, len(cachedPaths))
|
out := make([]string, 0, len(cachedPaths))
|
||||||
for _, p := range cachedPaths {
|
for _, p := range cachedPaths {
|
||||||
if strings.HasSuffix(p, suffix) {
|
if strings.HasSuffix(p, suffix) {
|
||||||
@@ -212,7 +197,7 @@ func filteredCachedPaths(width int, tag string) []string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateAndSaveASCII(imagePath, cachePath string, width int, tag string) error {
|
func generateAndSaveASCII(imagePath, cachePath string, width int) error {
|
||||||
f, err := os.Open(imagePath)
|
f, err := os.Open(imagePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -241,17 +226,12 @@ func generateAndSaveASCII(imagePath, cachePath string, width int, tag string) er
|
|||||||
img = dst
|
img = dst
|
||||||
}
|
}
|
||||||
|
|
||||||
var outStr string
|
ansi := ImageToANSI(img, width)
|
||||||
if tag == truecolorTag {
|
|
||||||
outStr = ImageToANSI(img, width)
|
|
||||||
} else {
|
|
||||||
outStr = ImageToASCIIFallback(img, width)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Dir(cachePath), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(cachePath), 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(cachePath, []byte(outStr), 0644); err != nil {
|
if err := os.WriteFile(cachePath, []byte(ansi), 0644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,43 +274,6 @@ func ImageToANSI(src image.Image, cols int) string {
|
|||||||
return out.String()
|
return out.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ImageToASCIIFallback(src image.Image, cols int) string {
|
|
||||||
b := src.Bounds()
|
|
||||||
origW := b.Dx()
|
|
||||||
origH := b.Dy()
|
|
||||||
if origW == 0 || origH == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
ratio := 0.5
|
|
||||||
charRowsFloat := (float64(origH) / float64(origW)) * float64(cols) * ratio
|
|
||||||
charRows := int(charRowsFloat)
|
|
||||||
if charRows < 1 {
|
|
||||||
charRows = 1
|
|
||||||
}
|
|
||||||
targetH := charRows * 2
|
|
||||||
targetW := cols
|
|
||||||
|
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, targetW, targetH))
|
|
||||||
xdraw.ApproxBiLinear.Scale(dst, dst.Bounds(), src, src.Bounds(), xdraw.Over, nil)
|
|
||||||
|
|
||||||
palette := []rune("MNHQ$OC?7>!:-;,. ")
|
|
||||||
var out bytes.Buffer
|
|
||||||
for y := 0; y < targetH; y += 2 {
|
|
||||||
for x := 0; x < targetW; x++ {
|
|
||||||
tr := colorAt(dst, x, y)
|
|
||||||
br := colorAt(dst, x, y+1)
|
|
||||||
lum := (int(tr.R)+int(tr.G)+int(tr.B)+int(br.R)+int(br.G)+int(br.B))/6
|
|
||||||
idx := (lum * (len(palette) - 1)) / 255
|
|
||||||
ch := palette[idx]
|
|
||||||
out.WriteRune(ch)
|
|
||||||
out.WriteRune(ch)
|
|
||||||
}
|
|
||||||
out.WriteString("\n")
|
|
||||||
}
|
|
||||||
return out.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
type c8 struct{ R, G, B uint8 }
|
type c8 struct{ R, G, B uint8 }
|
||||||
|
|
||||||
func colorAt(img *image.RGBA, x, y int) c8 {
|
func colorAt(img *image.RGBA, x, y int) c8 {
|
||||||
|
|||||||
Reference in New Issue
Block a user