diff --git a/.vscode/launch.json b/.vscode/launch.json index 20c8228..5c29d3a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,9 +10,8 @@ "env":{ "SLIDESHOW_ADDRESS": "0.0.0.0", "SLIDESHOW_PORT": "3000", - "SLIDESHOW_DIRECTORY": "/mnt/images", + "SLIDESHOW_DIRECTORY": "/home/velvettear/images", "SLIDESHOW_SCANINTERVAL": "600", - "SLIDESHOW_RESOLUTION": "600x1024", "SLIDESHOW_LOGLEVEL": "debug", "SLIDESHOW_PALETTE_ALGORITHM": "wsm", "SLIDESHOW_PALETTE_COLORS": "16" diff --git a/README.md b/README.md index d2e0f7c..a42d9fd 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,13 @@ a simple web server serving (scaled) images and color palettes for [slideshow](h ## requirements -- [ImageMagick](https://imagemagick.org/) (optional) +- [ImageMagick](https://imagemagick.org/) (optional; for scaling) + +## endpoints + +- `/`: request the name of a random image +- `/image/[?resolution=1920x1080]`: request the named image (in the specified resolution) +- `/palette/`: request the color palette of the named image ## configuration @@ -16,13 +22,12 @@ configuration is entirely done via environment variables. | SLIDESHOW_PORT | 3000 | the port of the web server | | SLIDESHOW_DIRECTORY | "$HOME" | path to a directory containing images | | SLIDESHOW_SCANINTERVAL | 60 | the interval for directory scans in seconds | -| SLIDESHOW_RESOLUTION | | the resolution to which images are scaled (i.e. 1920x1080) | | SLIDESHOW_PALETTE_ALGORITHM | "wsm" | the algorithm used to generate the color palette | | SLIDESHOW_PALETTE_COLORS | 16 | the amount of colors generated | | SLIDESHOW_LOGLEVEL | "info" | the log level | **note:** -if `SLIDESHOW_RESOLUTION` is unset or imagemagick's `convert` command is not in your `$PATH` the images will served as they are (without any scaling). +if imagemagick's `convert` command is not in your `$PATH` the images will **not** be scaled. **available log levels:** diff --git a/internal/api/server.go b/internal/api/server.go index 50fe4df..ac7f87f 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -52,7 +52,7 @@ func serveImage(writer http.ResponseWriter, request *http.Request) { response.send(writer) return } - streamImage(writer, image) + streamImage(writer, image, request.URL.Query().Get("resolution")) } // request url: '/palette' - serve the color palette of an image diff --git a/internal/api/stream.go b/internal/api/stream.go index 05c6a07..a2ab49e 100644 --- a/internal/api/stream.go +++ b/internal/api/stream.go @@ -17,9 +17,9 @@ import ( ) // central streaming method for images -func streamImage(writer http.ResponseWriter, image string) { - if config.IsResolutionSet() { - streamScaledImage(writer, image) +func streamImage(writer http.ResponseWriter, image string, resolution string) { + if config.ConvertAvailable && config.IsValidResolution(resolution) { + streamScaledImage(writer, image, resolution) return } streamUnscaledImage(writer, image) @@ -77,10 +77,10 @@ func streamUnscaledImage(writer http.ResponseWriter, image string) { } // scale and stream an image -func streamScaledImage(writer http.ResponseWriter, image string) { +func streamScaledImage(writer http.ResponseWriter, image string, resolution string) { timestamp := time.Now().UnixMilli() var response response - cmd := exec.Command("convert", image, "-resize", config.Resolution+"^", "-gravity", "center", "-extent", config.Resolution, "-") + cmd := exec.Command("convert", image, "-resize", resolution+"^", "-gravity", "center", "-extent", resolution, "-") stdout, stdoutError := cmd.StdoutPipe() stderr, stderrError := cmd.StderrPipe() cmd.Start() @@ -120,5 +120,5 @@ func streamScaledImage(writer http.ResponseWriter, image string) { } } cmd.Wait() - loggo.InfoTimed("successfully streamed scaled image '"+image+"'", timestamp, "size: "+internal.FormatBytes(written), "resolution: "+config.Resolution) + loggo.InfoTimed("successfully streamed scaled image '"+image+"'", timestamp, "size: "+internal.FormatBytes(written), "resolution: "+resolution) } diff --git a/internal/config/config.go b/internal/config/config.go index e2869cd..a115041 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -14,10 +14,10 @@ var ServerAddress string var ServerPort int var Directory string var ScanInterval time.Duration -var Resolution string var PaletteAlgorithm int var PaletteColors int var BufferSize int +var ConvertAvailable bool // initialize the config func Initialize() { @@ -48,21 +48,6 @@ func Initialize() { tmpInt = 60 } ScanInterval = time.Duration(tmpInt) * time.Second - Resolution = os.Getenv("SLIDESHOW_RESOLUTION") - if len(Resolution) > 0 { - width, height, found := strings.Cut(Resolution, "x") - if !found { - loggo.Fatal("encountered an error parsing the configured resolution, make sure to specify the format like '1920x1080'") - } - _, error = strconv.Atoi(width) - if error != nil { - loggo.Fatal("encountered an error parsing the configured width '" + width + "'") - } - _, error = strconv.Atoi(height) - if error != nil { - loggo.Fatal("encountered an error parsing the configured height '" + height + "'") - } - } tmpString := os.Getenv("SLIDESHOW_PALETTE_ALGORITHM") if strings.ToLower(tmpString) == "wu" { PaletteAlgorithm = 0 @@ -79,23 +64,33 @@ func Initialize() { tmpInt = 4096 } BufferSize = tmpInt - if IsResolutionSet() { - checkForConvert() - } + checkConvertCommand() } // check if 'convert' is available -func checkForConvert() { - cmd := exec.Command("convert") +func checkConvertCommand() { + cmd := exec.Command("which", "convert") error := cmd.Run() + ConvertAvailable = error == nil if error == nil { return } - Resolution = "" - loggo.Warning("could not find imagemagick's 'convert' command, no scaling will be done", error.Error()) + loggo.Warning("could not find imagemagick's 'convert' command, scaling is unavailable", error.Error()) } -// check if a resolution has been specified -func IsResolutionSet() bool { - return len(Resolution) > 0 +// check if the given string is a valid resolution +func IsValidResolution(resolution string) bool { + if len(resolution) <= 0 { + return false + } + width, height, found := strings.Cut(resolution, "x") + if !found { + return false + } + _, error := strconv.Atoi(width) + if error != nil { + return false + } + _, error = strconv.Atoi(height) + return error == nil } diff --git a/internal/palette.go b/internal/palette.go deleted file mode 100644 index 9788503..0000000 --- a/internal/palette.go +++ /dev/null @@ -1,65 +0,0 @@ -package internal - -import ( - "bytes" - "fmt" - "image" - "image/color" - "strconv" - "time" - - "git.velvettear.de/velvettear/loggo" - "git.velvettear.de/velvettear/slideshow-api/internal/config" - color_thief "github.com/kennykarnama/color-thief" -) - -// extract the given amount of dominant colors of raw image bytes -func GetColorPalette(data []byte) (string, error) { - var palette string - timestamp := time.Now().UnixMilli() - amount := config.PaletteColors - img, _, error := image.Decode(bytes.NewReader(data)) - if error != nil { - loggo.ErrorTimed("encountered an error decoding the provided raw image bytes to an image", timestamp, error.Error()) - return palette, error - } - colors, error := color_thief.GetPalette(img, amount, config.PaletteAlgorithm) - if error != nil { - loggo.ErrorTimed("encountered an error generating the color palette from raw image bytes", timestamp, "colors: "+strconv.Itoa(amount)) - return palette, error - } else { - loggo.DebugTimed("generated color palette from raw image bytes", timestamp, "colors: "+strconv.Itoa(amount)) - } - 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 + "=\"" + rgbToHex(color) + "\"" - } - return palette, nil -} - -// extract the given amount of dominant colors of an image -func getColorPalette(image string) ([]color.Color, error) { - var colors []color.Color - timestamp := time.Now().UnixMilli() - amount := config.PaletteColors - colors, error := color_thief.GetPaletteFromFile(image, amount, config.PaletteAlgorithm) - if error != nil { - loggo.ErrorTimed("encountered an error generating the color palette from image", timestamp, "image: "+image, "colors: "+strconv.Itoa(amount)) - } else { - loggo.DebugTimed("generated color palette from image", timestamp, "image: "+image, "colors: "+strconv.Itoa(amount)) - } - return colors, error -} - -// parse rgb color values to hex -func rgbToHex(color color.Color) string { - r, g, b, _ := color.RGBA() - return fmt.Sprintf("#%02x%02x%02x", uint8(r>>8), uint8(g>>8), uint8(b>>8)) -}