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.gitinternal/library/modules.go
Zum Verzeichnispackage library
import (
"context"
"encoding/json"
"os"
"path/filepath"
"strings"
)
type SmartCollection struct {
ID, NameDE, NameEN, DescriptionDE, DescriptionEN string
Count int
}
func (s *Service) SmartCollections(ctx context.Context) ([]SmartCollection, error) {
items := []SmartCollection{
{"continue", "Weiterschauen", "Continue watching", "Angefangene, noch nicht beendete Videos", "Started but unfinished videos", 0},
{"recent", "Neu hinzugefügt", "Recently added", "In den letzten 30 Tagen hinzugefügt", "Added during the last 30 days", 0},
{"unwatched", "Noch ungesehen", "Unwatched", "Videos ohne Wiedergabestatus", "Videos not marked as watched", 0},
{"favorites", "Favoriten", "Favorites", "Deine markierten Favoriten", "Your marked favorites", 0},
{"no-cover", "Ohne Cover", "Missing artwork", "Videos ohne Poster und Vorschaubild", "Videos without poster or thumbnail", 0},
{"episodes", "Erkannte Episoden", "Detected episodes", "Per Dateiname erkannte Serienfolgen", "Episodes recognized from filenames", 0},
{"4k", "4K-Bibliothek", "4K library", "Videos in Ultra HD", "Ultra HD videos", 0},
}
for i := range items {
count, err := s.Count(ctx, Query{Collection: items[i].ID})
if err != nil {
return nil, err
}
items[i].Count = count
}
return items, nil
}
type Diagnostic struct {
VideoID int64 `json:"video_id"`
Title string `json:"title"`
Path string `json:"path"`
Level string `json:"level"`
Code string `json:"code"`
Message string `json:"message"`
}
func (s *Service) Diagnostics(ctx context.Context) ([]Diagnostic, error) {
rows, err := s.db.QueryContext(ctx, "SELECT id,title,path,size,duration,video_codec,width,height,bitrate,container,audio_json,poster,thumbnail FROM videos ORDER BY title COLLATE NOCASE")
if err != nil {
return nil, err
}
defer rows.Close()
var out []Diagnostic
for rows.Next() {
var id, size, bitrate int64
var title, path, codec, container, audio, poster, thumbnail string
var duration float64
var width, height int
if err = rows.Scan(&id, &title, &path, &size, &duration, &codec, &width, &height, &bitrate, &container, &audio, &poster, &thumbnail); err != nil {
return nil, err
}
add := func(level, code, message string) {
out = append(out, Diagnostic{id, title, path, level, code, message})
}
if _, e := os.Stat(path); e != nil {
add("error", "missing-file", "Datei ist nicht mehr vorhanden")
continue
}
if size < 1024*1024 {
add("warning", "small-file", "Datei ist kleiner als 1 MiB")
}
if duration <= 0 {
add("error", "duration", "Keine gültige Laufzeit erkannt")
}
if codec == "" || width == 0 || height == 0 {
add("error", "video-track", "Videospur ist unvollständig oder nicht lesbar")
}
if bitrate <= 0 {
add("warning", "bitrate", "Keine Bitrate erkannt")
}
var tracks []any
if json.Unmarshal([]byte(audio), &tracks) != nil || len(tracks) == 0 {
add("warning", "audio-track", "Keine Audiospur erkannt")
}
if poster == "" && thumbnail == "" {
add("info", "artwork", "Kein Cover oder Vorschaubild vorhanden")
}
ext := strings.ToLower(filepath.Ext(path))
c := strings.ToLower(container)
if (ext == ".mkv" && !strings.Contains(c, "matroska")) || (ext == ".mp4" && !strings.Contains(c, "mov") && !strings.Contains(c, "mp4")) {
add("warning", "container", "Dateiendung und erkannter Container passen möglicherweise nicht zusammen")
}
nfo := strings.TrimSuffix(path, filepath.Ext(path)) + ".nfo"
if _, e := os.Stat(nfo); e != nil {
add("info", "nfo", "Keine lokale NFO-Datei vorhanden")
}
}
return out, rows.Err()
}