subsyt/internal/metadata/metadata.go
Viktor Varland 09134c46c4
Some checks are pending
build / build (push) Waiting to run
refactor: split logic into distinct parts
2025-10-01 21:21:59 +02:00

189 lines
3.8 KiB
Go

package metadata
import (
"errors"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"git.meatbag.se/varl/subsyt/internal/model"
)
type ShowAsset struct {
Show model.Show
InfoPath string
Dir string
Images []string
}
type EpisodeAsset struct {
Episode model.Episode
InfoPath string
Dir string
MediaPath string
Sidecars []string
}
func findFiles(root, ext string) ([]string, error) {
if root == "" {
return nil, fmt.Errorf("find files: empty root")
}
if _, err := os.Stat(root); errors.Is(err, os.ErrNotExist) {
return nil, nil
} else if err != nil {
return nil, fmt.Errorf("find files: %w", err)
}
var result []string
walkErr := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
if strings.HasSuffix(path, ext) {
result = append(result, path)
}
return nil
})
if walkErr != nil {
return nil, fmt.Errorf("walk %s: %w", root, walkErr)
}
return result, nil
}
func trimInfoSuffix(path string) string {
return strings.TrimSuffix(path, ".info.json")
}
func globSidecars(base string) ([]string, error) {
matches, err := filepath.Glob(base + ".*")
if err != nil {
return nil, fmt.Errorf("glob %s.*: %w", base, err)
}
var sidecars []string
for _, match := range matches {
if strings.HasSuffix(match, ".info.json") {
continue
}
sidecars = append(sidecars, match)
}
return sidecars, nil
}
func directoryImages(dir string) ([]string, error) {
entries, err := os.ReadDir(dir)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, nil
}
return nil, fmt.Errorf("readdir %s: %w", dir, err)
}
var images []string
for _, entry := range entries {
if entry.IsDir() {
continue
}
name := entry.Name()
switch strings.ToLower(filepath.Ext(name)) {
case ".jpg", ".jpeg", ".png", ".webp":
images = append(images, filepath.Join(dir, name))
}
}
return images, nil
}
func resolveMediaPath(base string, episode model.Episode, sidecars []string) (string, []string) {
if episode.Ext != "" {
candidate := base + "." + episode.Ext
if _, err := os.Stat(candidate); err == nil {
var rest []string
for _, path := range sidecars {
if path != candidate {
rest = append(rest, path)
}
}
return candidate, rest
}
}
for _, path := range sidecars {
ext := strings.ToLower(filepath.Ext(path))
switch ext {
case ".mp4", ".mkv", ".webm", ".m4v", ".mov", ".avi":
var rest []string
for _, other := range sidecars {
if other != path {
rest = append(rest, other)
}
}
return path, rest
}
}
return "", sidecars
}
func ScanShows(root string) ([]ShowAsset, error) {
infoFiles, err := findFiles(root, ".info.json")
if err != nil {
return nil, err
}
var assets []ShowAsset
for _, infoPath := range infoFiles {
show := model.LoadShow(infoPath)
dir := filepath.Dir(infoPath)
imgs, imgErr := directoryImages(dir)
if imgErr != nil {
log.Printf("scan shows: %v\n", imgErr)
}
assets = append(assets, ShowAsset{
Show: show,
InfoPath: infoPath,
Dir: dir,
Images: imgs,
})
}
return assets, nil
}
func ScanEpisodes(root string) ([]EpisodeAsset, error) {
infoFiles, err := findFiles(root, ".info.json")
if err != nil {
return nil, err
}
var assets []EpisodeAsset
for _, infoPath := range infoFiles {
episode := model.LoadEpisode(infoPath)
base := trimInfoSuffix(infoPath)
dir := filepath.Dir(infoPath)
sidecars, sidecarErr := globSidecars(base)
if sidecarErr != nil {
return nil, sidecarErr
}
mediaPath, remaining := resolveMediaPath(base, episode, sidecars)
assets = append(assets, EpisodeAsset{
Episode: episode,
InfoPath: infoPath,
Dir: dir,
MediaPath: mediaPath,
Sidecars: remaining,
})
}
return assets, nil
}