2023-11-29 16:00:35 +01:00
|
|
|
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
|
2023-11-29 16:27:23 +01:00
|
|
|
func streamImage(writer http.ResponseWriter, image string, resolution string) {
|
|
|
|
if config.ConvertAvailable && config.IsValidResolution(resolution) {
|
|
|
|
streamScaledImage(writer, image, resolution)
|
2023-11-29 16:00:35 +01:00
|
|
|
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
|
2023-11-29 16:27:23 +01:00
|
|
|
func streamScaledImage(writer http.ResponseWriter, image string, resolution string) {
|
2023-11-29 16:00:35 +01:00
|
|
|
timestamp := time.Now().UnixMilli()
|
|
|
|
var response response
|
2023-11-29 16:27:23 +01:00
|
|
|
cmd := exec.Command("convert", image, "-resize", resolution+"^", "-gravity", "center", "-extent", resolution, "-")
|
2023-11-29 16:00:35 +01:00
|
|
|
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()
|
2023-11-29 16:27:23 +01:00
|
|
|
loggo.InfoTimed("successfully streamed scaled image '"+image+"'", timestamp, "size: "+internal.FormatBytes(written), "resolution: "+resolution)
|
2023-11-29 16:00:35 +01:00
|
|
|
}
|