158 lines
3.3 KiB
Go
158 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/signal"
|
|
"path"
|
|
"regexp"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
type Fields struct {
|
|
Title string
|
|
Season string
|
|
Episode string
|
|
}
|
|
|
|
type MvNLn func(string, string) error
|
|
|
|
func main() {
|
|
ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT)
|
|
defer can()
|
|
|
|
if len(os.Args) < 2 {
|
|
if err := Recursive(ctx); err != nil {
|
|
panic(err)
|
|
}
|
|
} else {
|
|
if err := Main(ctx); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Recursive(ctx context.Context) error {
|
|
return io.EOF
|
|
}
|
|
|
|
func Main(ctx context.Context) error {
|
|
flags := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
|
|
overridesS := flags.String("c", `{"title":"","season":"","episode":""}`, "overrides")
|
|
ind := flags.String("i", "/dev/null", "in dir")
|
|
outd := flags.String("o", "/dev/null", "out dir")
|
|
dry := flags.Bool("d", true, "dry run")
|
|
if err := flags.Parse(os.Args[1:]); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
var overrides Fields
|
|
json.Unmarshal([]byte(*overridesS), &overrides)
|
|
|
|
mvNLn := RealMvNLn
|
|
if *dry {
|
|
mvNLn = DryMvNLn()
|
|
}
|
|
|
|
return Run(ctx,
|
|
*outd,
|
|
*ind,
|
|
append(flags.Args(),
|
|
`^\[[^\]]*\] (?P<title>.*) - (?P<episode>[0-9]+) .*`,
|
|
),
|
|
overrides,
|
|
mvNLn,
|
|
)
|
|
}
|
|
|
|
func Run(ctx context.Context, outd, ind string, patterns []string, overrides Fields, mvNLn MvNLn) error {
|
|
entries, err := os.ReadDir(ind)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, entry := range entries {
|
|
if !entry.Type().IsRegular() {
|
|
continue
|
|
}
|
|
if err := one(ctx, outd, path.Join(ind, entry.Name()), patterns, overrides, mvNLn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func one(ctx context.Context, outd, inf string, patterns []string, overrides Fields, mvNLn MvNLn) error {
|
|
f := path.Base(inf)
|
|
for _, pattern := range patterns {
|
|
re := regexp.MustCompile(pattern)
|
|
if !re.MatchString(f) {
|
|
continue
|
|
}
|
|
|
|
found := overrides
|
|
groupNames := re.SubexpNames()
|
|
groups := re.FindStringSubmatch(f)
|
|
for i := 1; i < len(groupNames); i++ {
|
|
v := groups[i]
|
|
switch groupNames[i] {
|
|
case "title":
|
|
found.Title = v
|
|
case "season":
|
|
found.Season = v
|
|
case "episode":
|
|
found.Episode = v
|
|
default:
|
|
return fmt.Errorf("unexpected capture group %q", groupNames[i])
|
|
}
|
|
}
|
|
|
|
if found.Title == "" || found.Season == "" || found.Episode == "" {
|
|
continue
|
|
}
|
|
found.Title = strings.Join(strings.Fields(found.Title), "_")
|
|
|
|
return foundOne(ctx, outd, inf, found, mvNLn)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func foundOne(ctx context.Context, outd, inf string, fields Fields, mvNLn MvNLn) error {
|
|
outf := path.Join(outd, fmt.Sprintf("%s_S%sE%s%s", fields.Title, fields.Season, fields.Episode, path.Ext(inf)))
|
|
return mvNLn(outf, inf)
|
|
}
|
|
|
|
func RealMvNLn(outf, inf string) error {
|
|
if stat, err := os.Stat(inf); err != nil || !stat.Mode().IsRegular() {
|
|
return fmt.Errorf("cannot mv_n_ln(%s): (%v) mode=%v", inf, err, stat.Mode())
|
|
}
|
|
if _, err := os.Stat(outf); err == nil {
|
|
return nil // fmt.Errorf("conflict: %s already exists", path.Base(outf))
|
|
}
|
|
if err := os.Rename(inf, outf); err != nil {
|
|
return err
|
|
}
|
|
return os.Symlink(outf, inf)
|
|
}
|
|
|
|
func DryMvNLn() func(string, string) error {
|
|
outd := map[string]struct{}{}
|
|
return func(outf, inf string) error {
|
|
if _, err := os.Stat(outf); err == nil {
|
|
return nil
|
|
}
|
|
|
|
if _, ok := outd[outf]; ok {
|
|
return nil
|
|
}
|
|
outd[outf] = struct{}{}
|
|
|
|
fmt.Fprintf(os.Stderr, "mv %q %q\n", inf, outf)
|
|
return nil
|
|
}
|
|
}
|