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 }