THAT WAS CRAPP

revert implement possible macos fallback
This commit is contained in:
gigirassy
2026-03-01 05:43:44 +01:00
parent b3c1fb4ede
commit b5cfce1cac
+35 -92
View File
@@ -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 {