-
Notifications
You must be signed in to change notification settings - Fork 0
/
path_pattern.go
183 lines (161 loc) · 5.38 KB
/
path_pattern.go
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package audiobooker
import (
"errors"
"fmt"
log "github.com/sirupsen/logrus"
"github.com/vjeantet/grok"
"os"
"path/filepath"
"strconv"
"strings"
)
// path templates
const (
AudioFile = "%f"
Author = "%a"
Genre = "%g"
Narrator = "%n"
ReleaseDate = "%y"
Series = "%s"
SeriesPart = "%p"
Title = "%t"
)
// pattern Groks
const (
AuthorGrok = "%{GREEDYDATA:author}"
AudioFileGrok = "%{AUDIO_FILE:audio_file}"
GenreGrok = "%{GREEDYDATA:genre}"
NarratorGrok = "%{GREEDYDATA:narrator}"
ReleaseDateGrok = "%{NUMBER:release_date}"
SeriesGrok = "%{GREEDYDATA:series}"
SeriesPartGrok = "%{NUMBER:series_part}"
TitleGrok = "%{GREEDYDATA:title}"
)
// ParsePathTags takes in file path and pattern string returning a map of the pattern matched values
func ParsePathTags(path, pathPattern string) (map[string]string, error) {
// This _should_ allow for both Unix(like) and Windows directory pathing to be used.
// sanitize path pattern to remove trailing / or \ (platform dependant)
pathPattern = strings.TrimSuffix(pathPattern, string(os.PathSeparator))
// split pattern by directory slashes
parserPatterns := strings.Split(pathPattern, string(os.PathSeparator))
// TODO if this fails try `\` for Windows?
log.Debugln("parser pattern tags:", parserPatterns)
// create list for patterns
parserPatternsList := make([]any, len(parserPatterns))
pathTemplate := ""
// loop through each parser pattern and associate the correct Grok
for i := 0; i < len(parserPatterns); i++ {
switch parserPatterns[i] {
case AudioFile:
parserPatternsList[i] = AudioFileGrok
case Author:
parserPatternsList[i] = AuthorGrok
case Genre:
parserPatternsList[i] = GenreGrok
case Narrator:
parserPatternsList[i] = NarratorGrok
case ReleaseDate:
parserPatternsList[i] = ReleaseDateGrok
case Series:
parserPatternsList[i] = SeriesGrok
case SeriesPart:
parserPatternsList[i] = SeriesPartGrok
case Title:
parserPatternsList[i] = TitleGrok
default:
log.Debugf("couldn't match %s, adding it as a litteral", parserPatterns[i])
parserPatternsList[i] = parserPatterns[i]
}
pathTemplate += "%s/"
}
pathTemplate = strings.TrimSuffix(pathTemplate, "/")
// combine template with list of Groks
pattern := fmt.Sprintf(pathTemplate, parserPatternsList...)
log.Debugln("parser pattern:", pattern)
patterns := make(map[string]string)
patterns["AUDIO_FILE"] = `%{GREEDYDATA}\.(:?flac|mp3|m4a|m4b|ogg|opus)`
patterns["NUMBER"] = `\d+`
// create grok from defined patterns only returning named captures
g, err := grok.NewWithConfig(&grok.Config{Patterns: patterns, NamedCapturesOnly: true})
if err != nil {
return nil, err
}
// parse out path values
values, err := g.Parse(pattern, path)
if err != nil {
return nil, err
}
// if no tags were parsed, error out since something wasn't right
if len(values) == 0 {
errStr := fmt.Sprintf(`no tags were parsed from the path template
Parsed patterns were: %s
Parsed path would have been: %s
Input path was: %s
`, parserPatterns, filepath.Join(parserPatterns...), path)
return nil, errors.New(errStr)
}
return values, nil
}
// OutputPathPattern renders directory structure based on path pattern and Book data
func OutputPathPattern(book Book, pathPattern string) (string, error) {
// sanitize path pattern to remove trailing /
pathPattern = strings.TrimSuffix(pathPattern, "/")
// split pattern by directory slashes
parserPatterns := strings.Split(pathPattern, "/")
// TODO if this fails try `\` for Windows?
log.Debugln("parser pattern tags:", parserPatterns)
// TODO rethink this if default dir can be used?
if len(parserPatterns) == 1 {
return "", errors.New("path parser is invalid")
}
// create list for patterns
outputAttributes := make([]string, len(parserPatterns))
for i := 0; i < len(outputAttributes); i++ {
switch parserPatterns[i] {
case Author:
outputAttributes[i] = book.Author
case Genre:
outputAttributes[i] = *book.Genre
case Narrator:
outputAttributes[i] = *book.Narrator
case ReleaseDate:
outputAttributes[i] = *book.Date
case Series:
outputAttributes[i] = *book.seriesName
case SeriesPart:
outputAttributes[i] = strconv.Itoa(*book.seriesPart)
case Title:
outputAttributes[i] = book.Title
default:
log.Debugf("couldn't match %s, adding it as a litteral", parserPatterns[i])
outputAttributes[i] = parserPatterns[i]
}
}
// TODO maybe use filepath.join?
outputPath := strings.Join(outputAttributes, "/")
return outputPath, nil
}
// OutputFilePattern renders filename based on path pattern and Book data
func OutputFilePattern(book Book, pathPattern string) string {
if pathPattern == "" {
return fmt.Sprintf("%s - %s.m4b", book.Author, book.Title)
}
pathPattern = strings.ReplaceAll(pathPattern, Author, book.Author)
if book.Genre != nil {
pathPattern = strings.ReplaceAll(pathPattern, Genre, *book.Genre)
}
if book.Narrator != nil {
pathPattern = strings.ReplaceAll(pathPattern, Narrator, *book.Narrator)
}
if book.Date != nil {
pathPattern = strings.ReplaceAll(pathPattern, ReleaseDate, *book.Date)
}
if book.seriesName != nil {
pathPattern = strings.ReplaceAll(pathPattern, Series, *book.seriesName)
}
if book.seriesPart != nil {
pathPattern = strings.ReplaceAll(pathPattern, SeriesPart, strconv.Itoa(*book.seriesPart))
}
pathPattern = strings.ReplaceAll(pathPattern, Title, book.Title)
return fmt.Sprintf("%s.m4b", pathPattern)
}