From 5eb3fd0ea11ecb303bd4c3ae6a0b4a818d11b43c Mon Sep 17 00:00:00 2001 From: velvettear Date: Mon, 27 Nov 2023 12:28:25 +0100 Subject: [PATCH] implemented variable 'SLIDESHOW_TMPFILE' to lower memory consumption --- .vscode/launch.json | 3 +- README.md | 6 ++- internal/config/config.go | 7 +++ internal/slideshow/palette.go | 17 ++++++ internal/slideshow/scale.go | 10 ++-- internal/slideshow/slideshow.go | 93 +++++++++++++++++++++++++++++---- 6 files changed, 119 insertions(+), 17 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 934ef79..8b368f6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,8 @@ "SLIDESHOW_INTERVAL": "10", "SLIDESHOW_DIRECTORY": "/home/velvettear/images", "SLIDESHOW_SCANINTERVAL": "10", - "SLIDESHOW_RESOLUTION": "600x1024", + "SLIDESHOW_TMPFILE": "/tmp/.slideshow.img", + "SLIDESHOW_RESOLUTION": "", "SLIDESHOW_LOGLEVEL": "debug", "SLIDESHOW_PALETTE": "/tmp/.slideshow.palette", "SLIDESHOW_PALETTE_ALGORITHM": "wsm", diff --git a/README.md b/README.md index 6f36882..9dbb898 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ configuration is entirely done via environment variables. | --------------------------- | ----------------- | -----------------------------------------------------------| | SLIDESHOW_INTERVAL | 60 | the interval of the slideshow in seconds | | SLIDESHOW_DIRECTORY | "$HOME" | path to a directory containing images | -| SLIDESHOW_SCANINTERVAL | 60 | the interval for directory scans in seconds +| SLIDESHOW_SCANINTERVAL | 60 | the interval for directory scans in seconds | +| SLIDESHOW_TMPFILE | | path to a temporary file | | SLIDESHOW_RESOLUTION | | the resolution to which images are scaled (i.e. 1920x1080) | | SLIDESHOW_PALETTE | | path to a file where the color palette will be stored | | SLIDESHOW_PALETTE_ALGORITHM | "wsm" | the algorithm used to generate the color palette | @@ -23,7 +24,8 @@ configuration is entirely done via environment variables. | SLIDESHOW_LOGLEVEL | "info" | the log level | **note:** -if no resolution is set the images will be displayed as they are (without any scaling). +- if `SLIDESHOW_RESOLUTION` is unset the images will be displayed as they are (without any scaling). +- if `SLIDESHOW_TMPFILE` is set a temporary file will be used instead of keeping the image internally buffered. this could be useful on systems with limited ram. **available log levels:** diff --git a/internal/config/config.go b/internal/config/config.go index fa0097b..6024ee3 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -12,6 +12,7 @@ import ( var Interval time.Duration var Directory string var ScanInterval time.Duration +var TmpFile string var Resolution string var PaletteFile string var PaletteAlgorithm int @@ -45,6 +46,7 @@ func Initialize() { tmpInt = 60 } ScanInterval = time.Duration(tmpInt) * time.Second + TmpFile = os.Getenv("SLIDESHOW_TMPFILE") Resolution = os.Getenv("SLIDESHOW_RESOLUTION") if len(Resolution) > 0 { width, height, found := strings.Cut(Resolution, "x") @@ -83,3 +85,8 @@ func IsResolutionSet() bool { func IsPaletteSet() bool { return len(PaletteFile) > 0 } + +// check if a temporary file has been specified +func IsTemporaryFileSet() bool { + return len(TmpFile) > 0 +} diff --git a/internal/slideshow/palette.go b/internal/slideshow/palette.go index 451fc36..5f16e1e 100644 --- a/internal/slideshow/palette.go +++ b/internal/slideshow/palette.go @@ -62,6 +62,23 @@ func getColorPaletteRaw(data []byte) ([]color.Color, error) { return colors, error } +// extract the given amount of dominant colors of an image +func getColorPalette(image string) ([]color.Color, error) { + var colors []color.Color + if !config.IsPaletteSet() { + return colors, nil + } + 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() diff --git a/internal/slideshow/scale.go b/internal/slideshow/scale.go index f993e91..7e34e52 100644 --- a/internal/slideshow/scale.go +++ b/internal/slideshow/scale.go @@ -4,7 +4,6 @@ import ( "errors" "io" "os/exec" - "strconv" "strings" "time" @@ -13,9 +12,12 @@ import ( ) // scale an image -func scale(image string) ([]byte, error) { +func scale(image string, output string) ([]byte, error) { timestamp := time.Now().UnixMilli() - cmd := exec.Command("convert", image, "-resize", config.Resolution+"^", "-gravity", "center", "-extent", config.Resolution, "-") + if len(output) == 0 { + output = "-" + } + cmd := exec.Command("convert", image, "-resize", config.Resolution+"^", "-gravity", "center", "-extent", config.Resolution, output) stdout, stdoutError := cmd.StdoutPipe() stderr, stderrError := cmd.StderrPipe() var data []byte @@ -39,6 +41,6 @@ func scale(image string) ([]byte, error) { if len(errorMessage) > 0 { return data, errors.New(errorMessage) } - loggo.DebugTimed("successfully scaled image", timestamp, "image: "+image, "resolution: "+config.Resolution, "size: "+strconv.Itoa(len(data))) + loggo.DebugTimed("successfully scaled image", timestamp, "image: "+image, "resolution: "+config.Resolution) return data, nil } diff --git a/internal/slideshow/slideshow.go b/internal/slideshow/slideshow.go index ae74d41..76ff575 100644 --- a/internal/slideshow/slideshow.go +++ b/internal/slideshow/slideshow.go @@ -43,30 +43,75 @@ func Start() { } } if config.IsResolutionSet() { - tmp, error := scale(image) + tmp, error := scale(image, config.TmpFile) if error != nil { loggo.Error("encountered an error scaling an image", "image: "+image, error.Error()) continue } data = tmp } else { - tmp, error := os.ReadFile(image) - if error != nil { - loggo.Error("encountered an erro reading an image", "image: "+image, error.Error()) - continue + if !config.IsTemporaryFileSet() { + tmp, error := os.ReadFile(image) + if error != nil { + loggo.Error("encountered an error reading an image", "image: "+image, error.Error()) + continue + } + data = tmp + } else { + file, error := os.Open(image) + if error != nil { + loggo.Error("encountered an error opening an image", "image: "+image, error.Error()) + continue + } + defer file.Close() + tmpFile, error := os.Create(config.TmpFile) + if error != nil { + loggo.Error("encountered an error opening the temporary file", "path: "+config.TmpFile, error.Error()) + continue + } + defer tmpFile.Close() + var errorMessage string + buffer := make([]byte, 4096) + for { + read, error := file.Read(buffer) + if error != nil && error != io.EOF { + errorMessage = error.Error() + break + } + if read == 0 { + break + } + _, error = tmpFile.Write(buffer[:read]) + if error != nil { + errorMessage = error.Error() + break + } + } + if len(errorMessage) > 0 { + loggo.Error("encountered an error reading an image", "image: "+image, errorMessage) + continue + } } - data = tmp } - palette, _ = getColorPaletteRaw(data) + if config.IsTemporaryFileSet() { + palette, _ = getColorPalette(config.TmpFile) + } else { + palette, _ = getColorPaletteRaw(data) + } loopTime = time.Since(loopTimestamp) if sleepTime > 0 { loggo.Debug("sleeping for " + strconv.FormatInt(sleepTime.Milliseconds(), 10) + "ms before next image will be displayed...") time.Sleep(sleepTime) } go exportPalette(palette) - error := setBackground(data) - if error != nil { - loggo.Error("encountered an error setting the background image", error.Error()) + var err error + if config.IsTemporaryFileSet() { + err = setBackgroundImage(config.TmpFile) + } else { + err = setBackground(data) + } + if err != nil { + loggo.Error("encountered an error setting the background image", err.Error()) } loggo.Info("set new background image", "image: "+image) sleepTime = config.Interval - loopTime @@ -113,3 +158,31 @@ func setBackground(data []byte) error { } return nil } + +// set the background image via 'feh' +func setBackgroundImage(image string) error { + cmd := exec.Command("feh", "--no-fehbg", "--bg-fill", image) + stdout, stdoutError := cmd.StdoutPipe() + stderr, stderrError := cmd.StderrPipe() + cmd.Start() + if stdoutError != nil { + return stdoutError + } + if stderrError != nil { + return stderrError + } + _, stdoutError = io.ReadAll(stdout) + if stdoutError != nil { + return stdoutError + } + errorBytes, stderrError := io.ReadAll(stderr) + if stderrError != nil { + return stderrError + } + cmd.Wait() + error := strings.TrimSpace(string(errorBytes)) + if len(error) > 0 { + return errors.New(error) + } + return nil +}