initial commit
This commit is contained in:
commit
bdaa3a97b6
14 changed files with 529 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
./slideshow-api
|
||||
__debug_bin*
|
23
.vscode/launch.json
vendored
Normal file
23
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"version": "0.0.1",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "slideshow-api-scaled",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}/main.go",
|
||||
"env":{
|
||||
"SLIDESHOW_ADDRESS": "0.0.0.0",
|
||||
"SLIDESHOW_PORT": "3000",
|
||||
"SLIDESHOW_DIRECTORY": "/home/velvettear/images",
|
||||
"SLIDESHOW_SCANINTERVAL": "10",
|
||||
"SLIDESHOW_RESOLUTION": "600x1024",
|
||||
"SLIDESHOW_LOGLEVEL": "debug",
|
||||
"SLIDESHOW_PALETTE_ALGORITHM": "wsm",
|
||||
"SLIDESHOW_PALETTE_COLORS": "16"
|
||||
},
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
]
|
||||
}
|
20
LICENSE.md
Normal file
20
LICENSE.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
# MIT License
|
||||
**Copyright (c) 2023 Daniel Sommer \<daniel.sommer@velvettear.de\>**
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next
|
||||
paragraph) shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.**
|
42
README.md
Normal file
42
README.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
# slideshow
|
||||
|
||||
a simple web server serving (scaled) images and color palettes for [slideshow](https://git.velvettear.de/velvettear/slideshow).
|
||||
|
||||
## requirements
|
||||
|
||||
- [ImageMagick](https://imagemagick.org/) (optional)
|
||||
|
||||
## configuration
|
||||
|
||||
configuration is entirely done via environment variables.
|
||||
|
||||
| variable | default | description |
|
||||
| --------------------------- | ----------------- | -----------------------------------------------------------|
|
||||
| SLIDESHOW_ADDRESS | "0.0.0.0" | the listen address of the web server |
|
||||
| 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 the images will served as they are (without any scaling).
|
||||
|
||||
**available log levels:**
|
||||
|
||||
- `debug`
|
||||
- `info`
|
||||
- `warning`
|
||||
- `error`
|
||||
- `fatal`
|
||||
|
||||
## color palette
|
||||
|
||||
**available algorithms:**
|
||||
|
||||
- `wsm`
|
||||
- `wu`
|
||||
|
||||
for more information regarding the color palette see [color-thief](https://github.com/kennykarnama/color-thief).
|
13
go.mod
Normal file
13
go.mod
Normal file
|
@ -0,0 +1,13 @@
|
|||
module git.velvettear.de/velvettear/slideshow
|
||||
|
||||
go 1.21
|
||||
|
||||
require git.velvettear.de/velvettear/loggo v0.0.0-20231113084149-980a00b4e084
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/kennykarnama/color-thief v0.0.0-20230222041546-c1bf65ec0808
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
)
|
15
go.sum
Normal file
15
go.sum
Normal file
|
@ -0,0 +1,15 @@
|
|||
git.velvettear.de/velvettear/loggo v0.0.0-20231113084149-980a00b4e084 h1:13S20q+usZ+yJr2cxxOpnPrOd5+4PwCbQg56RuWSkKk=
|
||||
git.velvettear.de/velvettear/loggo v0.0.0-20231113084149-980a00b4e084/go.mod h1:Jjjno0vz7v1Y6tCnpQHnq2TVL2+5m7TXkmNNYYREIMo=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/kennykarnama/color-thief v0.0.0-20230222041546-c1bf65ec0808 h1:9JJaKNm4eDnB/ad7rWfejfMw+1ufpchM8Eik9VcQzUQ=
|
||||
github.com/kennykarnama/color-thief v0.0.0-20230222041546-c1bf65ec0808/go.mod h1:9qLIEhoYLAXPjhQQpgR0nVnhJxNXBtAqRZAp2Dj87WQ=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
64
internal/api/server.go
Normal file
64
internal/api/server.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"git.velvettear.de/velvettear/loggo"
|
||||
"git.velvettear.de/velvettear/slideshow/internal"
|
||||
"git.velvettear.de/velvettear/slideshow/internal/config"
|
||||
)
|
||||
|
||||
// start the web server
|
||||
func Run() {
|
||||
address := config.ServerAddress + ":" + strconv.Itoa(config.ServerPort)
|
||||
loggo.Info("starting web server...", "address: "+address)
|
||||
registerHandlers()
|
||||
error := http.ListenAndServe(address, nil)
|
||||
if error != nil {
|
||||
loggo.Fatal("encountered an error starting the web server", "address: "+address, error.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// register the handlers
|
||||
func registerHandlers() {
|
||||
http.HandleFunc("/", handleImageRequest)
|
||||
}
|
||||
|
||||
// handle the request to '/image'
|
||||
func handleImageRequest(writer http.ResponseWriter, request *http.Request) {
|
||||
if !internal.HasFoundImages() {
|
||||
writer.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
name, data, error := internal.GetRandomImage()
|
||||
if error != nil {
|
||||
loggo.Error("encountered an error getting a random image", error.Error())
|
||||
writer.WriteHeader(501)
|
||||
writer.Write([]byte("encountered an internal server error (" + error.Error() + ")\n"))
|
||||
return
|
||||
}
|
||||
palette, error := internal.GetColorPalette(data)
|
||||
if error != nil {
|
||||
writer.WriteHeader(501)
|
||||
writer.Write([]byte("encountered an internal server error (" + error.Error() + ")\n"))
|
||||
return
|
||||
}
|
||||
bytes, error := json.Marshal(struct {
|
||||
Name string
|
||||
Palette string
|
||||
Data []byte
|
||||
}{
|
||||
name,
|
||||
palette,
|
||||
data,
|
||||
})
|
||||
if error != nil {
|
||||
loggo.Error("encountered an error marshalling the json response", error.Error())
|
||||
writer.WriteHeader(501)
|
||||
writer.Write([]byte("encountered an internal server error (" + error.Error() + ")\n"))
|
||||
return
|
||||
}
|
||||
writer.Write(bytes)
|
||||
}
|
44
internal/cache.go
Normal file
44
internal/cache.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// the internally pre buffered (and scaled) image
|
||||
var cachedImage []byte
|
||||
|
||||
// the internally pre buffered color palette
|
||||
var cachedPalette []byte
|
||||
|
||||
// cache an image
|
||||
func cacheImage(image []byte) {
|
||||
cachedImage = image
|
||||
}
|
||||
|
||||
// cache a color palette
|
||||
func cachePalette(colors []color.Color) {
|
||||
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 + "=\"" + rgbToHex(color) + "\""
|
||||
}
|
||||
cachedPalette = []byte(palette)
|
||||
}
|
||||
|
||||
// check if an image is cached
|
||||
func hasCachedImage() bool {
|
||||
return len(cachedImage) > 0
|
||||
}
|
||||
|
||||
// check if a color palette is cached
|
||||
func hasCachedColorPalette() bool {
|
||||
return len(cachedPalette) > 0
|
||||
}
|
80
internal/config/config.go
Normal file
80
internal/config/config.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.velvettear.de/velvettear/loggo"
|
||||
)
|
||||
|
||||
var ServerAddress string
|
||||
var ServerPort int
|
||||
var Directory string
|
||||
var ScanInterval time.Duration
|
||||
var Resolution string
|
||||
var PaletteAlgorithm int
|
||||
var PaletteColors int
|
||||
|
||||
// initialize the config
|
||||
func Initialize() {
|
||||
loggo.SetLogLevelByName(os.Getenv("SLIDESHOW_LOGLEVEL"))
|
||||
ServerAddress = os.Getenv("SLIDESHOW_ADDRESS")
|
||||
tmpInt, _ := strconv.Atoi(os.Getenv("SLIDESHOW_PORT"))
|
||||
if tmpInt <= 0 {
|
||||
tmpInt = 3000
|
||||
}
|
||||
ServerPort = tmpInt
|
||||
Directory = os.Getenv("SLIDESHOW_DIRECTORY")
|
||||
if len(Directory) == 0 {
|
||||
tmp, error := os.UserHomeDir()
|
||||
if error != nil {
|
||||
loggo.Fatal("encountered an error getting the current user's home directory", error.Error())
|
||||
}
|
||||
Directory = tmp
|
||||
}
|
||||
stats, error := os.Stat(Directory)
|
||||
if error != nil {
|
||||
loggo.Fatal("encountered an error checking the directory '"+Directory+"'", error.Error())
|
||||
}
|
||||
if !stats.IsDir() {
|
||||
loggo.Fatal("configured directory '" + Directory + "' is not a valid directory")
|
||||
}
|
||||
tmpInt, _ = strconv.Atoi(os.Getenv("SLIDESHOW_SCANINTERVAL"))
|
||||
if tmpInt <= 0 {
|
||||
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
|
||||
} else {
|
||||
PaletteAlgorithm = 1
|
||||
}
|
||||
tmpInt, _ = strconv.Atoi(os.Getenv("SLIDESHOW_PALETTE_COLORS"))
|
||||
if tmpInt <= 0 {
|
||||
tmpInt = 16
|
||||
}
|
||||
PaletteColors = tmpInt
|
||||
}
|
||||
|
||||
// check if a resolution has been specified
|
||||
func IsResolutionSet() bool {
|
||||
return len(Resolution) > 0
|
||||
}
|
65
internal/palette.go
Normal file
65
internal/palette.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.velvettear.de/velvettear/loggo"
|
||||
"git.velvettear.de/velvettear/slideshow/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))
|
||||
}
|
43
internal/scale.go
Normal file
43
internal/scale.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.velvettear.de/velvettear/loggo"
|
||||
"git.velvettear.de/velvettear/slideshow/internal/config"
|
||||
)
|
||||
|
||||
// scale an image
|
||||
func ScaleImage(image string) ([]byte, error) {
|
||||
timestamp := time.Now().UnixMilli()
|
||||
cmd := exec.Command("convert", image, "-resize", config.Resolution+"^", "-gravity", "center", "-extent", config.Resolution, "-")
|
||||
stdout, stdoutError := cmd.StdoutPipe()
|
||||
stderr, stderrError := cmd.StderrPipe()
|
||||
var data []byte
|
||||
cmd.Start()
|
||||
if stdoutError != nil {
|
||||
return data, stdoutError
|
||||
}
|
||||
if stderrError != nil {
|
||||
return data, stdoutError
|
||||
}
|
||||
data, stdoutError = io.ReadAll(stdout)
|
||||
if stdoutError != nil {
|
||||
return data, stdoutError
|
||||
}
|
||||
errorBytes, stderrError := io.ReadAll(stderr)
|
||||
if stderrError != nil {
|
||||
return data, stdoutError
|
||||
}
|
||||
cmd.Wait()
|
||||
errorMessage := strings.TrimSpace(string(errorBytes))
|
||||
if len(errorMessage) > 0 {
|
||||
return data, errors.New(errorMessage)
|
||||
}
|
||||
loggo.DebugTimed("successfully scaled image", timestamp, "image: "+image, "resolution: "+config.Resolution)
|
||||
return data, nil
|
||||
}
|
80
internal/scanner.go
Normal file
80
internal/scanner.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.velvettear.de/velvettear/loggo"
|
||||
"git.velvettear.de/velvettear/slideshow/internal/config"
|
||||
)
|
||||
|
||||
// slice of images
|
||||
var images []string
|
||||
|
||||
// temporary slice of images
|
||||
var tmpImages []string
|
||||
|
||||
// Scan the specified directory
|
||||
func Scan() {
|
||||
timestamp := time.Now()
|
||||
directory := config.Directory
|
||||
loggo.Info("scanning directory for images and subdirectories...", "interval: "+strconv.FormatFloat(config.ScanInterval.Seconds(), 'f', 0, 64)+" seconds", "directory: "+directory)
|
||||
filepath.WalkDir(directory, checkDirectory)
|
||||
images = tmpImages
|
||||
tmpImages = nil
|
||||
loggo.InfoTimed("found "+strconv.Itoa(len(images))+" image(s)", timestamp.UnixMilli())
|
||||
go scheduleRescan()
|
||||
}
|
||||
|
||||
// check if images has been found
|
||||
func HasFoundImages() bool {
|
||||
return len(images) > 0
|
||||
}
|
||||
|
||||
// get a random image
|
||||
func GetRandomImage() (string, []byte, error) {
|
||||
var name string
|
||||
var data []byte
|
||||
var error error
|
||||
if len(images) == 0 {
|
||||
return name, data, errors.New("no images have been found")
|
||||
}
|
||||
image := images[rand.Intn(len(images))]
|
||||
if config.IsResolutionSet() {
|
||||
data, error = ScaleImage(image)
|
||||
} else {
|
||||
data, error = os.ReadFile(image)
|
||||
}
|
||||
return image, data, error
|
||||
}
|
||||
|
||||
// sleep the specified interval and then trigger a rescan of the specified directory
|
||||
func scheduleRescan() {
|
||||
loggo.Debug("sleeping for " + strconv.FormatInt(config.ScanInterval.Milliseconds(), 10) + "ms before next scan...")
|
||||
time.Sleep(config.ScanInterval)
|
||||
Scan()
|
||||
}
|
||||
|
||||
// add image files to the slice of images and subdirectoies to the watcher
|
||||
func checkDirectory(path string, dir fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dir.IsDir() || !isImage(path) {
|
||||
return nil
|
||||
}
|
||||
tmpImages = append(tmpImages, path)
|
||||
loggo.Debug("added image to temporary slice of images", path)
|
||||
return nil
|
||||
}
|
||||
|
||||
// check if a file is an image
|
||||
func isImage(file string) bool {
|
||||
return strings.HasSuffix(file, ".jpeg") || strings.HasSuffix(file, ".jpg") || strings.HasSuffix(file, ".png")
|
||||
}
|
19
main.go
Normal file
19
main.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.velvettear.de/velvettear/loggo"
|
||||
"git.velvettear.de/velvettear/slideshow/internal"
|
||||
"git.velvettear.de/velvettear/slideshow/internal/api"
|
||||
"git.velvettear.de/velvettear/slideshow/internal/config"
|
||||
)
|
||||
|
||||
func main() {
|
||||
timestamp := time.Now().UnixMilli()
|
||||
loggo.Info("slideshow-api is starting now...")
|
||||
config.Initialize()
|
||||
internal.Scan()
|
||||
api.Run()
|
||||
loggo.InfoTimed("slideshow-api is shutting down now!", timestamp)
|
||||
}
|
19
slideshow-api.service
Normal file
19
slideshow-api.service
Normal file
|
@ -0,0 +1,19 @@
|
|||
[Unit]
|
||||
Description=slideshow-api
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=velvettear
|
||||
Environment="DISPLAY=:0"
|
||||
Environment="SLIDESHOW_ADDRESS=0.0.0.0"
|
||||
Environment="SLIDESHOW_PORT=3000"
|
||||
Environment="SLIDESHOW_DIRECTORY=$HOME"
|
||||
Environment="SLIDESHOW_SCANINTERVAL=60"
|
||||
Environment="SLIDESHOW_RESOLUTION="
|
||||
Environment="SLIDESHOW_PALETTE_ALGORITHM=wsm"
|
||||
Environment="SLIDESHOW_PALETTE_COLORS=16"
|
||||
Environment="SLIDESHOW_LOGLEVEL=info"
|
||||
ExecStart=/opt/slideshow-api/slideshow-api
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
Loading…
Reference in a new issue