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, amount int, algorithm string) { timestamp := time.Now().UnixMilli() var response response algo := 1 algorithm = strings.ToLower(algorithm) if algorithm == "wu" { algo = 0 } else { algorithm = "wsm" } colors, error := color_thief.GetPaletteFromFile(image, amount, algo) 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))), "colors: "+strconv.Itoa(amount), "algorithm: "+algorithm) } // 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) }