Add support streaming as ascii to terminal

This commit is contained in:
Alex X
2024-05-16 11:35:25 +03:00
parent 75020d4df7
commit 41badbfb8e
5 changed files with 227 additions and 25 deletions
+6
View File
@@ -0,0 +1,6 @@
## Useful links
- https://en.wikipedia.org/wiki/ANSI_escape_code
- https://paulbourke.net/dataformats/asciiart/
- https://github.com/kutuluk/xterm-color-chart
- https://github.com/hugomd/parrot.live
+140
View File
@@ -0,0 +1,140 @@
package ascii
import (
"bytes"
"fmt"
"image/jpeg"
"io"
"net/http"
)
func NewWriter(w io.Writer, foreground, background, text string) io.Writer {
a := &writer{wr: w, buf: []byte(clearScreen)}
var idx0 uint8
// https://en.wikipedia.org/wiki/ANSI_escape_code
switch foreground {
case "8":
a.color = func(r, g, b uint8) {
if idx := xterm256color(r, g, b, 8); idx != idx0 {
idx0 = idx
a.buf = append(a.buf, fmt.Sprintf("\033[%dm", 30+idx)...)
}
}
case "256":
a.color = func(r, g, b uint8) {
if idx := xterm256color(r, g, b, 255); idx != idx0 {
idx0 = idx
a.buf = append(a.buf, fmt.Sprintf("\033[38;5;%dm", idx)...)
}
}
case "rgb":
a.color = func(r, g, b uint8) {
a.buf = append(a.buf, fmt.Sprintf("\033[38;2;%d;%d;%dm", r, g, b)...)
}
}
switch background {
case "8":
a.color = func(r, g, b uint8) {
if idx := xterm256color(r, g, b, 8); idx != idx0 {
idx0 = idx
a.buf = append(a.buf, fmt.Sprintf("\033[%dm", 40+idx)...)
}
}
case "256":
a.color = func(r, g, b uint8) {
if idx := xterm256color(r, g, b, 255); idx != idx0 {
a.buf = append(a.buf, fmt.Sprintf("\033[48;5;%dm", idx)...)
}
}
case "rgb":
a.color = func(r, g, b uint8) {
a.buf = append(a.buf, fmt.Sprintf("\033[48;2;%d;%d;%dm", r, g, b)...)
}
}
var ascii string
switch text {
case "":
ascii = ` .::--~~==++**##%%$@`
case " ":
a.text = func(r, g, b uint32) {
a.buf = append(a.buf, ' ')
}
case " ":
a.text = func(r, g, b uint32) {
a.buf = append(a.buf, ' ', ' ')
}
default:
ascii = text
}
if ascii != "" {
k := float64(len(ascii)-1) / 255
a.text = func(r, g, b uint32) {
gray := (19595*r + 38470*g + 7471*b + 1<<15) >> 24 // uint8
i := uint8(float64(gray) * k)
a.buf = append(a.buf, ascii[i])
}
}
return a
}
type writer struct {
wr io.Writer
buf []byte
color func(r, g, b uint8)
text func(r, g, b uint32)
}
// https://stackoverflow.com/questions/37774983/clearing-the-screen-by-printing-a-character
const clearScreen = "\033[2J" + "\033[H"
func (a *writer) Write(p []byte) (n int, err error) {
img, err := jpeg.Decode(bytes.NewReader(p))
if err != nil {
return 0, err
}
a.buf = a.buf[:len(clearScreen)]
w := img.Bounds().Dy()
h := img.Bounds().Dx()
for y := 0; y < w; y++ {
for x := 0; x < h; x++ {
r, g, b, _ := img.At(x, y).RGBA()
if a.color != nil {
a.color(uint8(r>>8), uint8(g>>8), uint8(b>>8))
}
a.text(r, g, b)
}
a.buf = append(a.buf, '\n')
}
a.buf = append(a.buf, "\033[0m\n"...)
if n, err = a.wr.Write(a.buf); err == nil {
a.wr.(http.Flusher).Flush()
}
return
}
const x256r = "\x00\x80\x00\x80\x00\x80\x00\xc0\x80\xff\x00\xff\x00\xff\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x5f\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x08\x12\x1c\x26\x30\x3a\x44\x4e\x58\x60\x66\x76\x80\x8a\x94\x9e\xa8\xb2\xbc\xc6\xd0\xda\xe4\xee"
const x256g = "\x00\x00\x80\x80\x00\x00\x80\xc0\x80\x00\xff\xff\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x5f\x5f\x5f\x5f\x5f\x5f\x87\x87\x87\x87\x87\x87\xaf\xaf\xaf\xaf\xaf\xaf\xd7\xd7\xd7\xd7\xd7\xd7\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x5f\x5f\x5f\x5f\x5f\x5f\x87\x87\x87\x87\x87\x87\xaf\xaf\xaf\xaf\xaf\xaf\xd7\xd7\xd7\xd7\xd7\xd7\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x5f\x5f\x5f\x5f\x5f\x5f\x87\x87\x87\x87\x87\x87\xaf\xaf\xaf\xaf\xaf\xaf\xd7\xd7\xd7\xd7\xd7\xd7\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x5f\x5f\x5f\x5f\x5f\x5f\x87\x87\x87\x87\x87\x87\xaf\xaf\xaf\xaf\xaf\xaf\xd7\xd7\xd7\xd7\xd7\xd7\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x5f\x5f\x5f\x5f\x5f\x5f\x87\x87\x87\x87\x87\x87\xaf\xaf\xaf\xaf\xaf\xaf\xd7\xd7\xd7\xd7\xd7\xd7\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x5f\x5f\x5f\x5f\x5f\x5f\x87\x87\x87\x87\x87\x87\xaf\xaf\xaf\xaf\xaf\xaf\xd7\xd7\xd7\xd7\xd7\xd7\xff\xff\xff\xff\xff\xff\x08\x12\x1c\x26\x30\x3a\x44\x4e\x58\x60\x66\x76\x80\x8a\x94\x9e\xa8\xb2\xbc\xc6\xd0\xda\xe4\xee"
const x256b = "\x00\x00\x00\x00\x80\x80\x80\xc0\x80\x00\x00\x00\xff\xff\xff\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x00\x5f\x87\xaf\xd7\xff\x08\x12\x1c\x26\x30\x3a\x44\x4e\x58\x60\x66\x76\x80\x8a\x94\x9e\xa8\xb2\xbc\xc6\xd0\xda\xe4\xee"
func xterm256color(r, g, b uint8, n int) (index uint8) {
best := uint16(0xFFFF)
for i := 0; i < n; i++ {
diff := uint16(r-x256r[i]) + uint16(g-x256g[i]) + uint16(b-x256b[i])
if diff < best {
best = diff
index = uint8(i)
}
}
return
}
+36
View File
@@ -0,0 +1,36 @@
package mjpeg
import (
"io"
"net/http"
"strconv"
)
func NewWriter(w io.Writer) io.Writer {
h := w.(http.ResponseWriter).Header()
h.Set("Content-Type", "multipart/x-mixed-replace; boundary=frame")
return &writer{wr: w, buf: []byte(header)}
}
const header = "--frame\r\nContent-Type: image/jpeg\r\nContent-Length: "
type writer struct {
wr io.Writer
buf []byte
}
func (w *writer) Write(p []byte) (n int, err error) {
w.buf = w.buf[:len(header)]
w.buf = append(w.buf, strconv.Itoa(len(p))...)
w.buf = append(w.buf, "\r\n\r\n"...)
w.buf = append(w.buf, p...)
w.buf = append(w.buf, "\r\n"...)
// Chrome bug: mjpeg image always shows the second to last image
// https://bugs.chromium.org/p/chromium/issues/detail?id=527446
if n, err = w.wr.Write(w.buf); err == nil {
w.wr.(http.Flusher).Flush()
}
return
}