slideshow-api/internal/api/stream.go

124 lines
3.7 KiB
Go

package api
import (
"errors"
"io"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
"time"
"git.velvettear.de/velvettear/loggo"
"git.velvettear.de/velvettear/slideshow-api/internal"
"git.velvettear.de/velvettear/slideshow-api/internal/config"
color_thief "github.com/kennykarnama/color-thief"
)
// central streaming method for images
func streamImage(writer http.ResponseWriter, image string, resolution string) {
if config.ConvertAvailable && config.IsValidResolution(resolution) {
streamScaledImage(writer, image, resolution)
return
}
streamUnscaledImage(writer, image)
}
// stream a color palette
func streamColorPalette(writer http.ResponseWriter, image string) {
timestamp := time.Now().UnixMilli()
var response response
amount := config.PaletteColors
colors, error := color_thief.GetPaletteFromFile(image, amount, config.PaletteAlgorithm)
if error != nil {
loggo.Error("encountered an error getting the color palette from image '"+image+"'", error.Error())
response.error = error
response.send(writer)
return
}
var palette string
for index, color := range colors {
if index > 0 {
palette += "\n"
}
tmp := strconv.Itoa(index)
if len(tmp) < 2 {
tmp = "0" + tmp
}
tmp = "SLIDESHOW_COLOR" + tmp
palette += tmp + "=\"" + internal.RgbToHex(color) + "\""
}
data := []byte(palette)
writer.Write(data)
loggo.InfoTimed("successfully streamed color palette for image '"+image+"'", timestamp, "size: "+internal.FormatBytes(int64(len(data))))
}
// stream an unscaled image
func streamUnscaledImage(writer http.ResponseWriter, image string) {
timestamp := time.Now().UnixMilli()
var response response
file, error := os.Open(image)
if error != nil {
loggo.Error("encountered an error opening image '"+image+"' for streaming", error.Error())
response.error = error
response.send(writer)
return
}
defer file.Close()
written, error := io.Copy(writer, file)
if error != nil {
loggo.Error("encountered an error piping image '"+image+"' to stream", error.Error())
response.error = error
response.send(writer)
return
}
loggo.InfoTimed("successfully streamed image '"+image+"'", timestamp, "size: "+internal.FormatBytes(written))
}
// scale and stream an image
func streamScaledImage(writer http.ResponseWriter, image string, resolution string) {
timestamp := time.Now().UnixMilli()
var response response
cmd := exec.Command("convert", image, "-resize", resolution+"^", "-gravity", "center", "-extent", resolution, "-")
stdout, stdoutError := cmd.StdoutPipe()
stderr, stderrError := cmd.StderrPipe()
cmd.Start()
if stdoutError != nil {
loggo.Error("enountered an error opening imagemagicks's stdout pipe", stdoutError.Error())
response.error = stdoutError
response.send(writer)
return
}
if stderrError != nil {
loggo.Error("enountered an error opening imagemagicks's stderr pipe", stderrError.Error())
response.error = stderrError
response.send(writer)
return
}
written, copyError := io.Copy(writer, stdout)
if copyError != nil {
loggo.Error("enountered an error piping imagemagicks's output to the stream", copyError.Error())
response.error = copyError
response.send(writer)
return
}
if written == 0 {
errorBytes, stderrError := io.ReadAll(stderr)
if stderrError != nil {
loggo.Error("enountered an error reading imagemagicks's stderr", stderrError.Error())
response.error = stderrError
response.send(writer)
return
}
if len(errorBytes) > 0 {
error := errors.New(strings.TrimSpace(string(errorBytes)))
loggo.Error("enountered an error executing imagemagick", error.Error())
response.error = error
response.send(writer)
return
}
}
cmd.Wait()
loggo.InfoTimed("successfully streamed scaled image '"+image+"'", timestamp, "size: "+internal.FormatBytes(written), "resolution: "+resolution)
}