Git Repository
Mini Video Library
Mini Video Library scannt lokale Medienordner, verwaltet Metadaten und Vorschaubilder und streamt kompatible Videos direkt per HTTP an den Browser.
HTTPS
https://zanvex.de/git/mini-video-library.gitcmd/video-library/main.go
Zum Verzeichnispackage main
import (
"context"
"flag"
"fmt"
"io"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"minivideolib/internal/config"
"minivideolib/internal/database"
"minivideolib/internal/library"
"minivideolib/internal/logging"
"minivideolib/internal/plugins"
"minivideolib/internal/scanner"
"minivideolib/internal/thumbnail"
webapp "minivideolib/internal/web"
)
func main() {
configPath := flag.String("config", "config.toml", "path to the TOML configuration")
scanOnly := flag.Bool("scan", false, "scan once and exit")
flag.Parse()
logStore := logging.NewStore(1000)
logOutput := io.Writer(os.Stdout)
if fileLog, e := logging.NewRotatingWriter("data/logs/video-library.log", 10<<20); e == nil {
defer fileLog.Close()
logOutput = io.MultiWriter(os.Stdout, fileLog)
}
logger := slog.New(logging.NewHandler(slog.NewTextHandler(logOutput, &slog.HandlerOptions{Level: slog.LevelInfo}), logStore))
cfg, err := config.Load(*configPath)
if err != nil {
logger.Error("load config", "error", err)
os.Exit(1)
}
db, err := database.Open(cfg.DatabasePath)
if err != nil {
logger.Error("open database", "error", err)
os.Exit(1)
}
defer db.Close()
thumbs := thumbnail.New(cfg.FFmpegPath, cfg.ThumbnailDir, cfg.ThumbnailWidth, cfg.ThumbnailCount, logger)
scan := scanner.New(db, cfg, thumbs, logger)
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
if *scanOnly {
if err := scan.Run(ctx); err != nil {
logger.Error("scan", "error", err)
os.Exit(1)
}
return
}
go func() {
if cfg.ScanInterval > 0 {
t := time.NewTicker(30 * time.Second)
defer t.Stop()
last := time.Now()
for {
select {
case <-ctx.Done():
return
case <-t.C:
interval := scan.Config().ScanInterval
if interval <= 0 || time.Since(last) < interval {
continue
}
if err := scan.Run(ctx); err != nil {
logger.Error("scheduled scan", "error", err)
}
last = time.Now()
}
}
}
}()
lib := library.New(db)
pluginService := plugins.New(db)
handler, err := webapp.New(lib, db, scan, cfg, *configPath, logger, logStore, pluginService)
if err != nil {
logger.Error("initialize web", "error", err)
os.Exit(1)
}
srv := &http.Server{Addr: cfg.ListenAddress, Handler: handler, ReadHeaderTimeout: 10 * time.Second, IdleTimeout: 2 * time.Minute}
go func() {
logger.Info("video library ready", "url", "http://"+cfg.ListenAddress)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Error("http server", "error", err)
cancel()
}
}()
<-ctx.Done()
shutdown, stop := context.WithTimeout(context.Background(), 10*time.Second)
defer stop()
if err := srv.Shutdown(shutdown); err != nil {
fmt.Fprintln(os.Stderr, err)
}
}