implemented variable 'SLIDESHOW_TMPFILE' to lower memory consumption
This commit is contained in:
parent
acc7d1513b
commit
5eb3fd0ea1
6 changed files with 119 additions and 17 deletions
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
|
@ -11,7 +11,8 @@
|
||||||
"SLIDESHOW_INTERVAL": "10",
|
"SLIDESHOW_INTERVAL": "10",
|
||||||
"SLIDESHOW_DIRECTORY": "/home/velvettear/images",
|
"SLIDESHOW_DIRECTORY": "/home/velvettear/images",
|
||||||
"SLIDESHOW_SCANINTERVAL": "10",
|
"SLIDESHOW_SCANINTERVAL": "10",
|
||||||
"SLIDESHOW_RESOLUTION": "600x1024",
|
"SLIDESHOW_TMPFILE": "/tmp/.slideshow.img",
|
||||||
|
"SLIDESHOW_RESOLUTION": "",
|
||||||
"SLIDESHOW_LOGLEVEL": "debug",
|
"SLIDESHOW_LOGLEVEL": "debug",
|
||||||
"SLIDESHOW_PALETTE": "/tmp/.slideshow.palette",
|
"SLIDESHOW_PALETTE": "/tmp/.slideshow.palette",
|
||||||
"SLIDESHOW_PALETTE_ALGORITHM": "wsm",
|
"SLIDESHOW_PALETTE_ALGORITHM": "wsm",
|
||||||
|
|
|
@ -15,7 +15,8 @@ configuration is entirely done via environment variables.
|
||||||
| --------------------------- | ----------------- | -----------------------------------------------------------|
|
| --------------------------- | ----------------- | -----------------------------------------------------------|
|
||||||
| SLIDESHOW_INTERVAL | 60 | the interval of the slideshow in seconds |
|
| SLIDESHOW_INTERVAL | 60 | the interval of the slideshow in seconds |
|
||||||
| SLIDESHOW_DIRECTORY | "$HOME" | path to a directory containing images |
|
| 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_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 | | path to a file where the color palette will be stored |
|
||||||
| SLIDESHOW_PALETTE_ALGORITHM | "wsm" | the algorithm used to generate the color palette |
|
| 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 |
|
| SLIDESHOW_LOGLEVEL | "info" | the log level |
|
||||||
|
|
||||||
**note:**
|
**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:**
|
**available log levels:**
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
var Interval time.Duration
|
var Interval time.Duration
|
||||||
var Directory string
|
var Directory string
|
||||||
var ScanInterval time.Duration
|
var ScanInterval time.Duration
|
||||||
|
var TmpFile string
|
||||||
var Resolution string
|
var Resolution string
|
||||||
var PaletteFile string
|
var PaletteFile string
|
||||||
var PaletteAlgorithm int
|
var PaletteAlgorithm int
|
||||||
|
@ -45,6 +46,7 @@ func Initialize() {
|
||||||
tmpInt = 60
|
tmpInt = 60
|
||||||
}
|
}
|
||||||
ScanInterval = time.Duration(tmpInt) * time.Second
|
ScanInterval = time.Duration(tmpInt) * time.Second
|
||||||
|
TmpFile = os.Getenv("SLIDESHOW_TMPFILE")
|
||||||
Resolution = os.Getenv("SLIDESHOW_RESOLUTION")
|
Resolution = os.Getenv("SLIDESHOW_RESOLUTION")
|
||||||
if len(Resolution) > 0 {
|
if len(Resolution) > 0 {
|
||||||
width, height, found := strings.Cut(Resolution, "x")
|
width, height, found := strings.Cut(Resolution, "x")
|
||||||
|
@ -83,3 +85,8 @@ func IsResolutionSet() bool {
|
||||||
func IsPaletteSet() bool {
|
func IsPaletteSet() bool {
|
||||||
return len(PaletteFile) > 0
|
return len(PaletteFile) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if a temporary file has been specified
|
||||||
|
func IsTemporaryFileSet() bool {
|
||||||
|
return len(TmpFile) > 0
|
||||||
|
}
|
||||||
|
|
|
@ -62,6 +62,23 @@ func getColorPaletteRaw(data []byte) ([]color.Color, error) {
|
||||||
return colors, 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
|
// parse rgb color values to hex
|
||||||
func rgbToHex(color color.Color) string {
|
func rgbToHex(color color.Color) string {
|
||||||
r, g, b, _ := color.RGBA()
|
r, g, b, _ := color.RGBA()
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -13,9 +12,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// scale an image
|
// scale an image
|
||||||
func scale(image string) ([]byte, error) {
|
func scale(image string, output string) ([]byte, error) {
|
||||||
timestamp := time.Now().UnixMilli()
|
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()
|
stdout, stdoutError := cmd.StdoutPipe()
|
||||||
stderr, stderrError := cmd.StderrPipe()
|
stderr, stderrError := cmd.StderrPipe()
|
||||||
var data []byte
|
var data []byte
|
||||||
|
@ -39,6 +41,6 @@ func scale(image string) ([]byte, error) {
|
||||||
if len(errorMessage) > 0 {
|
if len(errorMessage) > 0 {
|
||||||
return data, errors.New(errorMessage)
|
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
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,30 +43,75 @@ func Start() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if config.IsResolutionSet() {
|
if config.IsResolutionSet() {
|
||||||
tmp, error := scale(image)
|
tmp, error := scale(image, config.TmpFile)
|
||||||
if error != nil {
|
if error != nil {
|
||||||
loggo.Error("encountered an error scaling an image", "image: "+image, error.Error())
|
loggo.Error("encountered an error scaling an image", "image: "+image, error.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
data = tmp
|
data = tmp
|
||||||
} else {
|
} else {
|
||||||
|
if !config.IsTemporaryFileSet() {
|
||||||
tmp, error := os.ReadFile(image)
|
tmp, error := os.ReadFile(image)
|
||||||
if error != nil {
|
if error != nil {
|
||||||
loggo.Error("encountered an erro reading an image", "image: "+image, error.Error())
|
loggo.Error("encountered an error reading an image", "image: "+image, error.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
data = tmp
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if config.IsTemporaryFileSet() {
|
||||||
|
palette, _ = getColorPalette(config.TmpFile)
|
||||||
|
} else {
|
||||||
palette, _ = getColorPaletteRaw(data)
|
palette, _ = getColorPaletteRaw(data)
|
||||||
|
}
|
||||||
loopTime = time.Since(loopTimestamp)
|
loopTime = time.Since(loopTimestamp)
|
||||||
if sleepTime > 0 {
|
if sleepTime > 0 {
|
||||||
loggo.Debug("sleeping for " + strconv.FormatInt(sleepTime.Milliseconds(), 10) + "ms before next image will be displayed...")
|
loggo.Debug("sleeping for " + strconv.FormatInt(sleepTime.Milliseconds(), 10) + "ms before next image will be displayed...")
|
||||||
time.Sleep(sleepTime)
|
time.Sleep(sleepTime)
|
||||||
}
|
}
|
||||||
go exportPalette(palette)
|
go exportPalette(palette)
|
||||||
error := setBackground(data)
|
var err error
|
||||||
if error != nil {
|
if config.IsTemporaryFileSet() {
|
||||||
loggo.Error("encountered an error setting the background image", error.Error())
|
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)
|
loggo.Info("set new background image", "image: "+image)
|
||||||
sleepTime = config.Interval - loopTime
|
sleepTime = config.Interval - loopTime
|
||||||
|
@ -113,3 +158,31 @@ func setBackground(data []byte) error {
|
||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue