implemented variable 'SLIDESHOW_TMPFILE' to lower memory consumption

This commit is contained in:
Daniel Sommer 2023-11-27 12:28:25 +01:00
parent acc7d1513b
commit 5eb3fd0ea1
6 changed files with 119 additions and 17 deletions

3
.vscode/launch.json vendored
View file

@ -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",

View file

@ -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:**

View file

@ -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
}

View file

@ -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()

View file

@ -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
}

View file

@ -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
}