diff --git a/.gitignore b/.gitignore
index 5180999f..f229b066 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,5 +30,14 @@ yarn-error.log*
bin/
build/
+# Vue distributable files
/frontend/dist/*
!/frontend/dist/.gitkeep
+
+# Playwright files
+/frontend/test-results/
+/frontend/playwright-report/
+/frontend/playwright/.cache/
+
+default.nix
+Dockerfile.dev
diff --git a/files/file.go b/files/file.go
index 4d7c8f9f..cffccf66 100644
--- a/files/file.go
+++ b/files/file.go
@@ -9,12 +9,14 @@ import (
"hash"
"image"
"io"
+ "io/fs"
"log"
"mime"
"net/http"
"os"
"path"
"path/filepath"
+ "regexp"
"strings"
"time"
@@ -27,6 +29,11 @@ import (
const PermFile = 0644
const PermDir = 0755
+var (
+ reSubDirs = regexp.MustCompile("(?i)^sub(s|titles)$")
+ reSubExts = regexp.MustCompile("(?i)(.vtt|.srt|.ass|.ssa)$")
+)
+
// FileInfo describes a file.
type FileInfo struct {
*Listing
@@ -277,8 +284,8 @@ func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
return nil
}
-func calculateImageResolution(fs afero.Fs, filePath string) (*ImageResolution, error) {
- file, err := fs.Open(filePath)
+func calculateImageResolution(fs_ afero.Fs, filePath string) (*ImageResolution, error) {
+ file, err := fs_.Open(filePath)
if err != nil {
return nil, err
}
@@ -343,12 +350,45 @@ func (i *FileInfo) detectSubtitles() {
base := strings.TrimSuffix(i.Name, ext)
for _, f := range dir {
- if !f.IsDir() && strings.HasPrefix(f.Name(), base) && strings.HasSuffix(f.Name(), ".vtt") {
- i.Subtitles = append(i.Subtitles, path.Join(parentDir, f.Name()))
+ // load all supported subtitles from subs directories
+ // should cover all instances of subtitle distributions
+ // like tv-shows with multiple episodes in single dir
+ if f.IsDir() && reSubDirs.MatchString(f.Name()) {
+ subsDir := path.Join(parentDir, f.Name())
+ i.loadSubtitles(subsDir, base, true)
+ } else if isSubtitleMatch(f, base) {
+ i.addSubtitle(path.Join(parentDir, f.Name()))
}
}
}
+func (i *FileInfo) loadSubtitles(subsPath, baseName string, recursive bool) {
+ dir, err := afero.ReadDir(i.Fs, subsPath)
+ if err == nil {
+ for _, f := range dir {
+ if isSubtitleMatch(f, "") {
+ i.addSubtitle(path.Join(subsPath, f.Name()))
+ } else if f.IsDir() && recursive && strings.HasPrefix(f.Name(), baseName) {
+ subsDir := path.Join(subsPath, f.Name())
+ i.loadSubtitles(subsDir, baseName, false)
+ }
+ }
+ }
+}
+
+func IsSupportedSubtitle(fileName string) bool {
+ return reSubExts.MatchString(fileName)
+}
+
+func isSubtitleMatch(f fs.FileInfo, baseName string) bool {
+ return !f.IsDir() && strings.HasPrefix(f.Name(), baseName) &&
+ IsSupportedSubtitle(f.Name())
+}
+
+func (i *FileInfo) addSubtitle(path_ string) {
+ i.Subtitles = append(i.Subtitles, path_)
+}
+
func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
afs := &afero.Afero{Fs: i.Fs}
dir, err := afs.ReadDir(i.Path)
diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json
index 743f1a01..db4f52bc 100644
--- a/frontend/.eslintrc.json
+++ b/frontend/.eslintrc.json
@@ -4,14 +4,21 @@
"node": true
},
"extends": [
- "plugin:vue/essential",
+ "plugin:vue/vue3-essential",
"eslint:recommended",
+ "@vue/eslint-config-typescript",
"@vue/eslint-config-prettier"
],
"rules": {
"vue/multi-word-component-names": "off",
- "vue/no-reserved-component-names": "warn",
- "vue/no-mutating-props": "warn"
+ "vue/no-mutating-props": [
+ "error",
+ {
+ "shallowOnly": true
+ }
+ ]
+ // no-undef is already included in
+ // @vue/eslint-config-typescript
},
"parserOptions": {
"ecmaVersion": "latest",
diff --git a/frontend/index.html b/frontend/index.html
index 7d0b8bc9..02c303ae 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -187,6 +187,6 @@
-
+