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.

Projektseite ↗
HTTPShttps://zanvex.de/git/mini-video-library.git

internal/library/modules.go

Zum Verzeichnis
package 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()
}