aboutsummaryrefslogtreecommitdiffstats
path: root/parser/parser.go
blob: 621992d2ed25488384ba7a0dae2cdac74fea55a9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package parser

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"text/template"
)

var (
	moviePattern    = regexp.MustCompile(`(.*?)\.(\d{4})`)
	episodePatterns = [4]*regexp.Regexp{
		regexp.MustCompile(`^(?P<name>.+?)\.S(?P<season>\d{2})(?:E(?P<episode>\d{2}))?`), // S01, S01E04
		regexp.MustCompile(`^(?P<name>.+?)\.E(?P<episode>\d{2})`),                        // E04
		regexp.MustCompile(`^(?P<name>.+?)\.(?P<season>\d{1,2})x(?P<episode>\d{2})`),     // 1x04, 01x04
		regexp.MustCompile(`^(?P<name>.+?)\.Part\.?(?P<episode>\d{1,2})`),                // Part4, Part11, Part.4, Part.11
	}
)

type Parser func(s string) (Media, error)

type Media struct {
	Release string
	Name    string
	Year    int
	Season  int
	Episode int
}

func (m *Media) IsEmpty() bool {
	return m.Name == ""
}

func (m *Media) ReplaceName(re *regexp.Regexp, repl string) {
	m.Name = re.ReplaceAllString(m.Name, repl)
}

func (m *Media) Equal(o Media) bool {
	if m.IsEmpty() {
		return false
	}
	return m.Name == o.Name && m.Season == o.Season && m.Episode == o.Episode && m.Year == o.Year
}

func (m *Media) PathIn(dir *template.Template) (string, error) {
	var b bytes.Buffer
	if err := dir.Execute(&b, m); err != nil {
		return "", err
	}
	path := b.String()
	// When path has a trailing slash, the actual destination path will be a directory inside LocalPath (same
	// behaviour as rsync)
	if strings.HasSuffix(path, string(os.PathSeparator)) {
		path = filepath.Join(path, m.Release)
	}
	return path, nil
}

func Default(s string) (Media, error) {
	return Media{Release: s}, nil
}

func Movie(s string) (Media, error) {
	matches := moviePattern.FindStringSubmatch(s)
	if len(matches) < 3 {
		return Media{}, fmt.Errorf("invalid input: %q", s)
	}
	name := matches[1]
	year, err := strconv.Atoi(matches[2])
	if err != nil {
		return Media{}, fmt.Errorf("invalid input: %q: %s", s, err)
	}
	return Media{
		Release: s,
		Name:    name,
		Year:    year,
	}, nil
}

func Show(s string) (Media, error) {
	for _, p := range episodePatterns {
		matches := p.FindStringSubmatch(s)
		if len(matches) == 0 {
			continue
		}
		groupNames := p.SubexpNames()
		var (
			name    string
			season  = 1
			episode = 0
			err     error
		)
		for i, group := range matches {
			if group == "" {
				continue
			}
			switch groupNames[i] {
			case "name":
				name = group
			case "season":
				season, err = strconv.Atoi(group)
				if err != nil {
					return Media{}, fmt.Errorf("invalid input: %q: %s", s, err)
				}
			case "episode":
				episode, err = strconv.Atoi(group)
				if err != nil {
					return Media{}, fmt.Errorf("invalid input: %q: %s", s, err)
				}
			}
		}
		return Media{
			Release: s,
			Name:    name,
			Season:  season,
			Episode: episode,
		}, nil
	}
	return Media{}, fmt.Errorf("invalid input: %q", s)
}