113 lines
2.7 KiB
Go
113 lines
2.7 KiB
Go
package classifier
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"mediawatcher/internal/config"
|
|
)
|
|
|
|
type Type string
|
|
|
|
const (
|
|
TypeTV Type = "tv"
|
|
TypeMovie Type = "movie"
|
|
TypeMisc Type = "misc"
|
|
TypeUnknown Type = "unknown"
|
|
)
|
|
|
|
type Result struct {
|
|
Type Type
|
|
Title string
|
|
Season int
|
|
Episode int
|
|
Year int
|
|
DestDir string
|
|
DestName string
|
|
SourcePath string
|
|
}
|
|
|
|
// regexes
|
|
var (
|
|
tvPattern = regexp.MustCompile(`(?i)(S?(\d{1,2}))[ ._-]*[Ex](\d{1,3})`)
|
|
yearPattern = regexp.MustCompile(`\b(19\d{2}|20[0-3]\d)\b`)
|
|
)
|
|
|
|
func Classify(path string, cfg *config.Config) Result {
|
|
base := filepath.Base(path)
|
|
nameNoExt := strings.TrimSuffix(base, filepath.Ext(base))
|
|
|
|
// TV first
|
|
if m := tvPattern.FindStringSubmatch(nameNoExt); len(m) == 4 {
|
|
season := atoiSafe(m[2])
|
|
episode := atoiSafe(m[3])
|
|
|
|
cleaned := tvPattern.ReplaceAllString(nameNoExt, "")
|
|
title := cleanupTitle(cleaned)
|
|
|
|
destDir := filepath.Join(cfg.Structure.TVDir, title, fmt.Sprintf("Season %02d", season))
|
|
|
|
return Result{
|
|
Type: TypeTV,
|
|
Title: title,
|
|
Season: season,
|
|
Episode: episode,
|
|
DestDir: destDir,
|
|
DestName: base,
|
|
SourcePath: path,
|
|
}
|
|
}
|
|
|
|
// Movie
|
|
if ym := yearPattern.FindString(nameNoExt); ym != "" {
|
|
year := atoiSafe(ym)
|
|
cleaned := strings.Replace(nameNoExt, ym, "", 1)
|
|
title := cleanupTitle(cleaned)
|
|
destDir := filepath.Join(cfg.Structure.MoviesDir, fmt.Sprintf("%s (%d)", title, year))
|
|
|
|
return Result{
|
|
Type: TypeMovie,
|
|
Title: title,
|
|
Year: year,
|
|
DestDir: destDir,
|
|
DestName: base,
|
|
SourcePath: path,
|
|
}
|
|
}
|
|
|
|
// Misc based on extension
|
|
ext := strings.ToLower(filepath.Ext(base))
|
|
if ext == ".mp3" || ext == ".flac" || ext == ".m4a" || ext == ".aac" {
|
|
return Result{
|
|
Type: TypeMisc,
|
|
DestDir: cfg.Structure.MiscDir,
|
|
DestName: base,
|
|
SourcePath: path,
|
|
}
|
|
}
|
|
|
|
// Unknown
|
|
return Result{
|
|
Type: TypeUnknown,
|
|
DestDir: cfg.Structure.UnknownDir,
|
|
DestName: base,
|
|
SourcePath: path,
|
|
}
|
|
}
|
|
|
|
func cleanupTitle(s string) string {
|
|
s = strings.ReplaceAll(s, ".", " ")
|
|
s = strings.ReplaceAll(s, "_", " ")
|
|
s = strings.ReplaceAll(s, "-", " ")
|
|
s = strings.Join(strings.Fields(s), " ")
|
|
return strings.TrimSpace(s)
|
|
}
|
|
|
|
func atoiSafe(s string) int {
|
|
n, _ := strconv.Atoi(s)
|
|
return n
|
|
}
|