summaryrefslogtreecommitdiff
path: root/internal/scanner
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-04 08:26:08 +0300
committerPaul Buetow <paul@buetow.org>2026-05-04 08:26:08 +0300
commit81737024f1d39c022d7901de380a5d87df70d4b4 (patch)
tree3c7a6b79cdb951a30a00e9da9e783a7bd3e409cc /internal/scanner
parentb15b271dd350e97396f0a7096c71dabc045c5d83 (diff)
task 9: Centralize file extension and media type mappings in internal/mediatype
Create a new internal/mediatype package with a single source of truth for: - Extension-to-media-type mappings (TypeForExt) - Extension-to-MIME mappings (MIMETypeForExt) - Supported extension checks (IsSupportedExt, IsImageExt, IsCoverImageExt) Replace duplicated logic across: - internal/service/service.go (supportedExtensions, guessMediaType) - internal/scanner/scanner.go (mediaExtensions, imageExtensions, mediaTypeFromExt) - internal/api/handlers.go (probe.MimeTypeForFilename) - internal/probe/probe.go (imageExtensions, isImagePath) - internal/probe/remux.go (MimeTypeForFilename) Divergent defaults unified: both scanner and service now default unknown extensions to video (model.MediaTypeVideo) via mediatype.TypeForExt. Update tests to use mediatype package and remove obsolete scanner tests. Update AGENTS.md to reflect the new package.
Diffstat (limited to 'internal/scanner')
-rw-r--r--internal/scanner/scanner.go53
-rw-r--r--internal/scanner/scanner_test.go35
2 files changed, 4 insertions, 84 deletions
diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go
index 117f99b..765bec1 100644
--- a/internal/scanner/scanner.go
+++ b/internal/scanner/scanner.go
@@ -10,6 +10,7 @@ import (
"strings"
"codeberg.org/snonux/player/internal/clock"
+ "codeberg.org/snonux/player/internal/mediatype"
"codeberg.org/snonux/player/internal/model"
"codeberg.org/snonux/player/internal/probe"
"codeberg.org/snonux/player/internal/repository"
@@ -148,7 +149,7 @@ func (s *FSScanner) scanSet(ctx context.Context, root, setPath string, progress
// First pass: gather images per directory.
coverImages := make(map[string]string)
_ = s.fs.WalkDir(setPath, func(path string, d fs.DirEntry, err error) error {
- if err != nil || d.IsDir() || !isImageFile(path) {
+ if err != nil || d.IsDir() || !mediatype.IsCoverImageExt(path) {
if d != nil && d.IsDir() && strings.HasPrefix(d.Name(), ".") && path != setPath {
return filepath.SkipDir
}
@@ -173,7 +174,7 @@ func (s *FSScanner) scanSet(ctx context.Context, root, setPath string, progress
}
return nil
}
- if !isMediaFile(path) {
+ if !mediatype.IsSupportedExt(path) {
return nil
}
relPath, err := filepath.Rel(setPath, path)
@@ -202,7 +203,7 @@ func (s *FSScanner) scanSet(ctx context.Context, root, setPath string, progress
}
meta.FileSizeBytes = info.Size()
- mediaType := mediaTypeFromExt(path)
+ mediaType := mediatype.TypeForExt(path)
var thumbnailPath string
if mediaType == model.MediaTypeVideo {
thumbDir := filepath.Join(setPath, ".thumbnails")
@@ -289,7 +290,6 @@ func (s *FSScanner) scanSet(ctx context.Context, root, setPath string, progress
return nil
}
-// findCoverImage walks up from a file's directory toward the set root.
func findCoverImage(filePath string, coverImages map[string]string, setPath string) string {
for dir := filepath.Dir(filePath); len(dir) >= len(setPath); dir = filepath.Dir(dir) {
if coverRel, ok := coverImages[dir]; ok {
@@ -301,48 +301,3 @@ func findCoverImage(filePath string, coverImages map[string]string, setPath stri
}
return ""
}
-
-var mediaExtensions = map[string]struct{}{
- ".mp4": {}, ".mkv": {}, ".avi": {}, ".mov": {}, ".webm": {},
- ".mp3": {}, ".flac": {}, ".wav": {}, ".aac": {}, ".ogg": {}, ".m4a": {}, ".opus": {}, ".m4b": {},
- ".jpg": {}, ".jpeg": {}, ".png": {}, ".gif": {}, ".webp": {}, ".bmp": {}, ".avif": {}, ".svg": {},
-}
-
-// imageExtensions lists file extensions recognized as cover/artwork images.
-var imageExtensions = map[string]struct{}{
- ".jpg": {}, ".jpeg": {}, ".png": {}, ".gif": {},
-}
-
-func isImageFile(path string) bool {
- base := filepath.Base(path)
- if strings.HasPrefix(base, ".") {
- return false
- }
- ext := strings.ToLower(filepath.Ext(path))
- _, ok := imageExtensions[ext]
- return ok
-}
-
-func isMediaFile(path string) bool {
- base := filepath.Base(path)
- if strings.HasPrefix(base, "._") {
- return false
- }
- ext := strings.ToLower(filepath.Ext(path))
- _, ok := mediaExtensions[ext]
- return ok
-}
-
-func mediaTypeFromExt(path string) model.MediaType {
- ext := strings.ToLower(filepath.Ext(path))
- switch ext {
- case ".mp4", ".mkv", ".avi", ".mov", ".webm":
- return model.MediaTypeVideo
- case ".mp3", ".wav", ".flac", ".aac", ".ogg", ".m4a", ".opus", ".m4b":
- return model.MediaTypeAudio
- case ".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".avif", ".svg":
- return model.MediaTypeImage
- default:
- return model.MediaTypeAudio
- }
-}
diff --git a/internal/scanner/scanner_test.go b/internal/scanner/scanner_test.go
index 2d5d7c8..ec837bd 100644
--- a/internal/scanner/scanner_test.go
+++ b/internal/scanner/scanner_test.go
@@ -413,39 +413,4 @@ func TestFSScanner_Scan(t *testing.T) {
})
}
-func Test_isMediaFile(t *testing.T) {
- cases := []struct {
- path string
- want bool
- }{
- {"movie.mp4", true},
- {"song.MP3", true},
- {"archive.zip", false},
- {"photo.jpg", true},
- {"", false},
- }
- for _, c := range cases {
- if got := isMediaFile(c.path); got != c.want {
- t.Errorf("isMediaFile(%q) = %v, want %v", c.path, got, c.want)
- }
- }
-}
-func Test_mediaTypeFromExt(t *testing.T) {
- cases := []struct {
- path string
- want model.MediaType
- }{
- {"a.mp4", model.MediaTypeVideo},
- {"a.mkv", model.MediaTypeVideo},
- {"a.mp3", model.MediaTypeAudio},
- {"a.FLAC", model.MediaTypeAudio},
- {"a.jpg", model.MediaTypeImage},
- {"a.png", model.MediaTypeImage},
- }
- for _, c := range cases {
- if got := mediaTypeFromExt(c.path); got != c.want {
- t.Errorf("mediaTypeFromExt(%q) = %v, want %v", c.path, got, c.want)
- }
- }
-}