-
Notifications
You must be signed in to change notification settings - Fork 2
/
gondola.go
151 lines (136 loc) · 4.57 KB
/
gondola.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
package main
import (
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"strings"
"time"
)
const (
validExtensions = "mp4 mkv vob avi mpg m4v"
imageFilename = "image.jpg"
imageBackdropFilename = "backdrop.jpg"
metadataFilename = "metadata.json"
hlsFilename = "hls.m3u8" // This is only a few lines, so the framerate can be set.
hlsSegmentsFilename = "seg.m3u8" // This is the main shebang.
)
// Returns true if it's an extension we're interested in.
func isValidExtension(extension string) bool {
lowerExtension := strings.ToLower(extension)
extensions := strings.Split(validExtensions, " ")
for _, e := range extensions {
if "."+e == lowerExtension {
return true
}
}
return false
}
// Scans the new paths, looking for any media files we're interested in.
func scanNewPaths(paths Paths, config Config) {
scanNewPath(paths.NewMovies, true, paths, config)
scanNewPath(paths.NewTV, false, paths, config)
}
func scanNewPath(whichPath string, isMovies bool, paths Paths, config Config) {
files, err := ioutil.ReadDir(whichPath)
if err != nil {
log.Println("Couldn't scan path, error: ", err)
return
}
for _, file := range files {
if !strings.HasPrefix(file.Name(), ".") { // Ignore hidden files.
if !file.IsDir() {
ext := path.Ext(file.Name())
if isValidExtension(ext) {
log.Println("Found file", file.Name())
tryProcess(whichPath, file.Name(), isMovies, paths, config)
} else {
log.Println("Ignoring file with unexpected extension", file.Name())
}
} else {
log.Println("Unexpected, found a directory", file.Name())
}
}
}
}
// Tries processing a file. Doesn't worry if it can't, eg if the file is half-copied, as the completion of the copy will trigger another scan.
func tryProcess(folder string, file string, isMovies bool, paths Paths, config Config) {
source := filepath.Join(folder, file)
if canGetExclusiveAccessToFile(source) {
if isMovies {
processMovie(folder, file, paths, config)
} else {
processTV(folder, file, paths, config)
}
// Re-generate.
generateMetadata(paths)
} else {
log.Println("Couldn't get exclusive access to", file, "might be still copying")
}
}
// Keeps track of where all the paths are.
type Paths struct {
Root string // Config.Root (expanded path, no tilde)
NewBase string // Root/New
NewMovies string // Root/New/Movies
NewTV string // Root/New/TV
Staging string // Root/Staging
MoviesRelativeToRoot string // Movies
Movies string // Root/Movies
TVRelativeToRoot string // TV
TV string // Root/TV
Failed string // Root/Failed
}
func exists(path string) bool {
_, err := os.Stat(path)
if os.IsNotExist(err) {
return false
}
return true
}
func main() {
config, configErr := loadConfig()
if configErr != nil {
log.Fatal(configErr)
}
log.Println("Config loaded:")
log.Printf("%+v\n", config)
// Waiting for the folder to mount.
for i := 0; i < 60; i++ {
if exists(config.Root) { break }
log.Println("Waiting for folder to become available...")
time.Sleep(time.Second)
}
// Figure out all the folders.
var paths Paths
paths.Root = expandTilde(config.Root)
paths.NewBase = filepath.Join(paths.Root, "New")
paths.NewMovies = filepath.Join(paths.NewBase, "Movies")
paths.NewTV = filepath.Join(paths.NewBase, "TV")
paths.Staging = filepath.Join(paths.Root, "Staging")
paths.MoviesRelativeToRoot = "Movies"
paths.Movies = filepath.Join(paths.Root, paths.MoviesRelativeToRoot)
paths.TVRelativeToRoot = "TV"
paths.TV = filepath.Join(paths.Root, paths.TVRelativeToRoot)
paths.Failed = filepath.Join(paths.Root, "Failed")
os.MkdirAll(paths.Root, os.ModePerm) // This will cause permission issues on a non-FAT mount eg local drive.
os.MkdirAll(paths.NewMovies, os.ModePerm)
os.MkdirAll(paths.NewTV, os.ModePerm)
os.RemoveAll(paths.Staging) // Clear the staging folder on startup. Warning - this'll remove any in-progress source files. The idea is that those files vanish when complete anyway so this should be fine.
os.MkdirAll(paths.Staging, os.ModePerm)
os.MkdirAll(paths.Movies, os.ModePerm)
os.MkdirAll(paths.TV, os.ModePerm)
os.MkdirAll(paths.Failed, os.ModePerm)
// When starting, re-gen metadata in case user manually moved stuff, and scan for new files.
generateMetadata(paths)
scanNewPaths(paths, config)
// Listen for changes on the folder.
folders := []string{paths.NewMovies, paths.NewTV}
changes := watch(folders)
log.Println("Watching for changes in " + paths.NewBase)
for {
<-changes
scanNewPaths(paths, config)
}
}