commit f621612340841117a9ccf372dbb83c1875fac9c6 Author: Roy Olav Purser Date: Tue Mar 23 09:48:02 2021 +0100 first commit diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b96e4be --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.purser.it/roypur/envconf + +go 1.16 diff --git a/main.go b/main.go new file mode 100644 index 0000000..3edca52 --- /dev/null +++ b/main.go @@ -0,0 +1,283 @@ +package main + +import ("regexp" + "fmt" + "strconv" + "strings" + "time" + "path/filepath" + "io/fs" + "os" + "github.com/fsnotify/fsnotify") + +type SeasonInfo struct { + Name string + Season int + IsSeries bool + IncludeSeriesName bool +} + +type Config struct { + apikey string + ignore string // Ignore files with this file extension (case insensitive) + size int64 // Minimum file size for files to be moved + src string + dst string + req int64 // Number of requests per second to API +} + +var seriesMatcher []*regexp.Regexp +var moviesMatcher *regexp.Regexp + +func getEnvString(key string)(val string, ok bool) { + val, ok = os.LookupEnv(key) + if ok { + val = strings.TrimSuffix(val, "/") + } else { + fmt.Printf("Environment variable missing: %s\n", key) + } + return +} + +func getEnvBool(key string)(val bool, ok bool) { + var str string + var err error + str, ok = getEnvString(key) + if ok { + val, err = strconv.ParseBool(str) + if err != nil { + ok = false + fmt.Printf("Failed to parse environment variable as bool: %s\n", key) + } + } + return +} +func getEnvInt(key string)(val int64, ok bool) { + var str string + var err error + str, ok = getEnvString(key) + if ok { + upper := strings.ToUpper(str) + mod := int64(1) + if strings.HasSuffix(upper, "K") { + mod = 1024 + upper = strings.TrimSuffix(upper, "K") + } else if strings.HasSuffix(upper, "M") { + mod = 1024 * 1024 + upper = strings.TrimSuffix(upper, "M") + } else if strings.HasSuffix(upper, "G") { + mod = 1024 * 1024 * 1024 + upper = strings.TrimSuffix(upper, "G") + } + var tmp int64 + tmp, err = strconv.ParseInt(upper, 10, 64) + if err == nil { + val = tmp * mod + } else { + ok = false + fmt.Printf("Failed to parse environment variable as int: %s\n", key) + } + } + return +} + +func main() { + compileRegex() + + anyfail := false + var ok bool + var config Config + var info SeasonInfo + + config.apikey, ok = getEnvString("THEMOVIEDB_API_KEY") + anyfail = !ok || anyfail + config.src, ok = getEnvString("SOURCE_FOLDER") + anyfail = !ok || anyfail + config.dst, ok = getEnvString("DESTINATION_FOLDER") + anyfail = !ok || anyfail + config.ignore, ok = getEnvString("IGNORE_FILE_EXTENSION") + anyfail = !ok || anyfail + config.req, ok = getEnvInt("REQUESTS_PER_SECOND") + anyfail = !ok || anyfail + config.size, ok = getEnvInt("MINIMUM_FILE_SIZE") + anyfail = !ok || anyfail + info.IncludeSeriesName, ok = getEnvBool("INCLUDE_SERIES_IN_FILENAME") + anyfail = !ok || anyfail + + if anyfail { + return + } + setupRequest(time.Duration(int64(time.Second) / int64(config.req))) + watch(config, info) +} + +func watch(config Config, info SeasonInfo) { + watcher, err := fsnotify.NewWatcher() + if err != nil { + printerr(err) + return + } + defer watcher.Close() + + walkDirFunc := func(path string, dir fs.DirEntry, err error) (nerr error) { + if err != nil { + printerr(err) + return + } + if dir.IsDir() { + return watcher.Add(path) + } else { + go move(path, config, info) + } + return + } + filepath.WalkDir(config.src, walkDirFunc) + + for { + event, ok := <-watcher.Events + if ok { + if event.Op & fsnotify.Create == fsnotify.Create { + finfo, err := os.Stat(event.Name) + if err != nil { + printerr(err) + continue + } + if finfo.IsDir() { + watcher.Add(event.Name) + } else { + go move(event.Name, config, info) + } + } else if event.Op & fsnotify.Remove == fsnotify.Remove { + watcher.Remove(event.Name) + } + } + } +} + +func compileRegex() { + seriesMatcherString := [...]string { + "(?:\\[.*\\])?(.*)(?:[Ss])([0-9]{2})(?:[Ee])([0-9]{2})(?:.*)\\.(.+)", + "(?:\\[.*\\])?(.*)(?:[Ss])([0-9]{2})(?:[Ee])([0-9])(?:.*)\\.(.+)", + "(?:\\[.*\\])?(.*)(?:[Ss])([0-9])(?:[Ee])([0-9]{2})(?:.*)\\.(.+)", + "(?:\\[.*\\])?(.*)(?:[Ss])([0-9])(?:[Ee])([0-9])(?:.*)\\.(.+)", + "(?:\\[.*\\])?(.*)([0-9]{2})(?:[Xx])([0-9]{2})(?:.*)\\.(.+)", + "(?:\\[.*\\])?(.*)([0-9]{2})(?:[Xx])([0-9])(?:.*)\\.(.+)", + "(?:\\[.*\\])?(.*)([0-9])(?:[Xx])([0-9]{2})(?:.*)\\.(.+)", + "(?:\\[.*\\])?(.*)([0-9])(?:[Xx])([0-9])(?:.*)\\.(.+)", + } + + /* + SERIES S01E01 + SERIES S01E1 + SERIES S1E01 + SERIES S1E1 + SERIES 01X01 + SERIES 01X1 + SERIES 1X01 + SERIES 1X1 + */ + + seriesMatcher = make([]*regexp.Regexp, len(seriesMatcherString)) + + for k,v := range seriesMatcherString { + seriesMatcher[k] = regexp.MustCompile(v) + } + + moviesMatcher = regexp.MustCompile("(?:\\[.*\\])?(.+)(?:\\(?)((?:18|19|20)(?:[0-9]{2}))(?:\\)?)(?:.*)?\\.(.+)") +} + +func move(name string, config Config, info SeasonInfo) { + fmt.Printf(`------ lookup file: "%s" ------%s`, name, "\n") + config.src = name + finfo, err := os.Lstat(config.src) + if err != nil { + printerr(err) + return + } + lname := strings.ToLower(config.src) + suffix := "." + strings.ToLower(config.ignore) + if strings.HasSuffix(lname, suffix) { + fmt.Printf(`------ file ignored: "%s" ------%s`, config.src, "\n") + return + } + if finfo.Mode().IsRegular() { + config.src = strings.TrimSuffix(strings.TrimSuffix(config.src, finfo.Name()), "/") + if finfo.Size() > config.size { + rename(finfo.Name(), config, info) + } + } +} + +func rename(filename string, config Config, info SeasonInfo){ + var episode int64 + var extension string + + find := func(re *regexp.Regexp)(bool){ + res := re.FindStringSubmatch(filename) + tf := func(r rune) bool { + return r < '0' + } + for k,v := range res { + splitted := strings.Split(v, "(") + if len(splitted) > 1 { + res[k] = strings.TrimFunc(splitted[0], tf) + } else { + res[k] = strings.TrimFunc(v, tf) + } + } + + if len(res) == 5{ + for _,v := range res{ + fmt.Println(v) + } + fmt.Println("") + info.Name = res[1] + + season,_ := strconv.ParseInt(res[2], 10, 64) + episode,_ = strconv.ParseInt(res[3], 10, 64) + info.Season = int(season) + extension = res[4] + info.IsSeries = true + return true + }else{ + return false + } + } + for _,v := range seriesMatcher { + if find(v) { + break + } + } + + if info.IsSeries { + newFilename, success := themoviedb(info.Name, info.Season, int(episode), extension, config.apikey, info.IncludeSeriesName, 0) + if success { + linkFile(config.src + "/" + filename, config.dst + "/" + newFilename) + } + } else { + res := moviesMatcher.FindStringSubmatch(filename) + if len(res) == 4{ + for _,v := range res{ + fmt.Println(v) + } + fmt.Println("") + + year,_ := strconv.ParseInt(res[2], 10, 64) + + if (year < 1800) || (year > 2100){ + year = 0 + } + + newFilename, success := themoviedb(res[1], info.Season, int(episode), res[3], config.apikey, info.IncludeSeriesName, int(year)) + if success { + linkFile(config.src + "/" + filename, config.dst + "/" + newFilename) + } + } + } +} + +func printerr(err error){ + if err != nil { + fmt.Println(err) + } +}