package metadata import ( "errors" "fmt" "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 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) base := trimInfoSuffix(infoPath) sidecars, sidecarErr := globSidecars(base) if sidecarErr != nil { return nil, sidecarErr } var images []string for _, path := range sidecars { ext := strings.ToLower(filepath.Ext(path)) switch ext { case ".jpg", ".jpeg", ".png", ".webp": images = append(images, path) } } assets = append(assets, ShowAsset{ Show: show, InfoPath: infoPath, Dir: dir, Images: images, }) } 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 }