284 lines
7.7 KiB
Go
284 lines
7.7 KiB
Go
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)
|
|
}
|
|
}
|