tangled
alpha
login
or
join now
arimelody.space
/
vodular
0
fork
atom
Helper tool for stitching together livestream VOD segments and uploading them to YouTube!
0
fork
atom
overview
issues
pulls
pipelines
check if fullvod exists; store config in UserConfigDir
arimelody.space
1 month ago
aa28ffed
c3dc6f09
+58
-13
3 changed files
expand all
collapse all
unified
split
config
config.go
main.go
scanner
scanner.go
+1
-1
config/config.go
···
33
},
34
}
35
36
-
const CONFIG_FILENAME = "config.toml"
37
38
func ReadConfig(filename string) (*Config, error) {
39
cfgBytes, err := os.ReadFile(filename)
···
33
},
34
}
35
36
+
var CONFIG_FILENAME string = "config.toml"
37
38
func ReadConfig(filename string) (*Config, error) {
39
cfgBytes, err := os.ReadFile(filename)
+35
-12
main.go
···
28
const segmentExtension = "mkv"
29
30
func showHelp() {
31
-
execSplits := strings.Split(os.Args[0], "/")
32
-
execName := execSplits[len(execSplits) - 1]
33
-
fmt.Printf(helpText, execName)
34
}
35
36
func main() {
37
// config
0
0
0
0
0
0
38
cfg, err := config.ReadConfig(config.CONFIG_FILENAME)
39
if err != nil {
40
log.Fatalf("Failed to read config: %v", err)
41
os.Exit(1)
42
}
43
if cfg == nil {
0
0
0
0
0
44
err = config.GenerateConfig(config.CONFIG_FILENAME)
45
if err != nil {
46
log.Fatalf("Failed to generate config: %v", err)
···
57
// arguments
58
if len(os.Args) < 2 || os.Args[1] == "--help" || os.Args[1] == "-h" {
59
showHelp()
60
-
os.Exit(0)
61
}
62
63
var verbose bool = false
···
76
fallthrough
77
case "--help":
78
showHelp()
79
-
os.Exit(0)
80
81
case "-v":
82
fallthrough
···
228
os.Exit(1)
229
}
230
fmt.Printf(
231
-
"\n================================\n" +
232
-
"TITLE:\n%s\n\n" +
233
-
"DESCRIPTION:\n%s\n" +
234
"\n================================\n",
235
title, description,
236
)
237
}
238
239
// concatenate VOD segments into full VOD
240
-
video.SizeBytes, err = vid.ConcatVideo(video, vodFiles, verbose)
241
-
if err != nil {
242
-
log.Fatalf("Failed to concatenate VOD segments: %v", err)
243
-
os.Exit(1)
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
244
}
245
246
// youtube oauth flow
···
28
const segmentExtension = "mkv"
29
30
func showHelp() {
31
+
fmt.Println(helpText)
32
+
os.Exit(0)
0
33
}
34
35
func main() {
36
// config
37
+
userConfigDir, err := os.UserConfigDir()
38
+
if err != nil {
39
+
log.Fatalf("Could not determine user configuration directory: %v", err)
40
+
os.Exit(1)
41
+
}
42
+
config.CONFIG_FILENAME = path.Join(userConfigDir, "vodular", "config.toml")
43
cfg, err := config.ReadConfig(config.CONFIG_FILENAME)
44
if err != nil {
45
log.Fatalf("Failed to read config: %v", err)
46
os.Exit(1)
47
}
48
if cfg == nil {
49
+
err = os.MkdirAll(path.Dir(config.CONFIG_FILENAME), 0750)
50
+
if err != nil {
51
+
log.Fatalf("Failed to create config directory: %v", err)
52
+
os.Exit(1)
53
+
}
54
err = config.GenerateConfig(config.CONFIG_FILENAME)
55
if err != nil {
56
log.Fatalf("Failed to generate config: %v", err)
···
67
// arguments
68
if len(os.Args) < 2 || os.Args[1] == "--help" || os.Args[1] == "-h" {
69
showHelp()
0
70
}
71
72
var verbose bool = false
···
85
fallthrough
86
case "--help":
87
showHelp()
0
88
89
case "-v":
90
fallthrough
···
236
os.Exit(1)
237
}
238
fmt.Printf(
239
+
"\n================================\n\n" +
240
+
"< TITLE >\n%s\n\n" +
241
+
"< DESCRIPTION >\n%s\n" +
242
"\n================================\n",
243
title, description,
244
)
245
}
246
247
// concatenate VOD segments into full VOD
248
+
fullVodExists := func () bool {
249
+
// check if full VOD already exists with expected duration
250
+
if fullVodProbe, err := scanner.ProbeSegment(video.Filename); err != nil {
251
+
var totalLength float64 = 0
252
+
for _, filename := range vodFiles {
253
+
probe, err := scanner.ProbeSegment(filename)
254
+
if err != nil { continue }
255
+
totalLength += probe.Format.Duration
256
+
}
257
+
return fullVodProbe.Format.Duration == totalLength
258
+
}
259
+
return false
260
+
}()
261
+
if !fullVodExists {
262
+
video.SizeBytes, err = vid.ConcatVideo(video, vodFiles, verbose)
263
+
if err != nil {
264
+
log.Fatalf("Failed to concatenate VOD segments: %v", err)
265
+
os.Exit(1)
266
+
}
267
}
268
269
// youtube oauth flow
+22
scanner/scanner.go
···
1
package scanner
2
3
import (
0
4
"os"
5
"path"
6
"strings"
7
"time"
8
9
"github.com/pelletier/go-toml/v2"
0
10
)
11
12
type (
···
24
FootageDir string `toml:"footage_dir"`
25
Uploaded bool `toml:"uploaded"`
26
Category *Category `toml:"category" comment:"(Optional) Category details, for additional credits."`
0
0
0
0
0
0
0
0
0
27
}
28
)
29
···
45
}
46
47
return files, nil
0
0
0
0
0
0
0
0
0
0
0
48
}
49
50
func ReadMetadata(directory string) (*Metadata, error) {
···
1
package scanner
2
3
import (
4
+
"encoding/json"
5
"os"
6
"path"
7
"strings"
8
"time"
9
10
"github.com/pelletier/go-toml/v2"
11
+
ffmpeg_go "github.com/u2takey/ffmpeg-go"
12
)
13
14
type (
···
26
FootageDir string `toml:"footage_dir"`
27
Uploaded bool `toml:"uploaded"`
28
Category *Category `toml:"category" comment:"(Optional) Category details, for additional credits."`
29
+
}
30
+
31
+
ffprobeFormat struct {
32
+
Duration float64 `json:"duration"`
33
+
Size int64 `json:"size"`
34
+
}
35
+
36
+
ffprobeOutput struct {
37
+
Format ffprobeFormat `json:"format"`
38
}
39
)
40
···
56
}
57
58
return files, nil
59
+
}
60
+
61
+
func ProbeSegment(filename string) (*ffprobeOutput, error) {
62
+
out, err := ffmpeg_go.Probe(filename)
63
+
if err != nil { return nil, err }
64
+
65
+
probe := ffprobeOutput{}
66
+
err = json.Unmarshal([]byte(out), probe)
67
+
if err != nil { return nil, err }
68
+
69
+
return &probe, nil
70
}
71
72
func ReadMetadata(directory string) (*Metadata, error) {