-
Notifications
You must be signed in to change notification settings - Fork 0
/
video_download.go
139 lines (124 loc) · 3.11 KB
/
video_download.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
package main
import (
"bytes"
"fmt"
"github.com/moezakura/youlive-capture/model"
"golang.org/x/xerrors"
"io"
"log"
"os"
"os/exec"
"strings"
"syscall"
"time"
)
type VideoDownload struct {
CancelTick chan *model.CancelReason
CompleteTick chan struct{}
Status model.DownloadStatus
targetVideoID chan string
startTicker *time.Ticker
}
func NewVideoDownload() *VideoDownload {
return &VideoDownload{
CancelTick: make(chan *model.CancelReason, 1),
CompleteTick: make(chan struct{}, 1),
targetVideoID: make(chan string, 1),
startTicker: nil,
}
}
func (v *VideoDownload) SetData(videoID string, startTime time.Time) {
duration := startTime.Sub(time.Now().In(time.UTC))
if duration.Seconds() < 120 {
v.startTicker = time.NewTicker(2 * time.Second)
} else {
duration = time.Duration(duration.Seconds()-30) * time.Second
v.startTicker = time.NewTicker(duration)
}
v.targetVideoID <- videoID
}
func (v *VideoDownload) Run() {
v.Status = model.DownloadStatusNotYet
defer func() {
v.Status = model.DownloadStatusCompleted
}()
videoID := ""
select {
case reason := <-v.CancelTick:
log.Printf("run cancel: %s", reason.Reason)
return
case videoID = <-v.targetVideoID:
}
select {
case reason := <-v.CancelTick:
v.startTicker.Stop()
log.Printf("run cancel: %s", reason.Reason)
return
case <-v.startTicker.C:
v.startTicker.Stop()
}
log.Printf("Download start live stream now!")
t := time.NewTicker(time.Second)
defer func() {
t.Stop()
}()
downloadURL := fmt.Sprintf("https://www.youtube.com/watch?v=%s", videoID)
v.Status = model.DownloadStatusDownloading
for {
<-t.C
err := v.download(downloadURL)
if err != nil {
switch {
case xerrors.Is(err, LiveNotStarted):
log.Printf("live is not started: %+v", err)
continue
case xerrors.Is(err, AlreadyDownloaded):
v.CompleteTick <- struct{}{}
return
}
log.Printf("download failed: %+v", err)
continue
}
}
}
func (v *VideoDownload) download(url string) error {
var (
stdOutBuffer bytes.Buffer
errOutBuffer bytes.Buffer
)
cmd := exec.Command("youtube-dl", "--hls-use-mpegts", url)
stdOutBufferMultiWriter := io.MultiWriter(&stdOutBuffer, os.Stdout)
errOutBufferMultiWriter := io.MultiWriter(&errOutBuffer, os.Stderr)
cmd.Stdout = stdOutBufferMultiWriter
cmd.Stderr = errOutBufferMultiWriter
cancelTickCancel := make(chan struct{}, 1)
defer func() {
close(cancelTickCancel)
}()
go func() {
select {
case reason := <-v.CancelTick:
log.Printf("download cancel: %s", reason.Reason)
case <-cancelTickCancel:
}
process := cmd.Process
err := process.Signal(syscall.SIGINT)
if err != nil {
log.Printf("fatal download quit")
}
log.Printf("download quit")
}()
err := cmd.Run()
if err != nil {
return xerrors.Errorf("VideoDownload.downloadWithAnyRetry exec error: %w", err)
}
out := fmt.Sprintf("%s\n%s", stdOutBuffer.String(), errOutBuffer.String())
if strings.Contains(out, "This live event will begin in") {
return LiveNotStarted
}
if strings.Contains(out, "already been downloaded and merged") {
return AlreadyDownloaded
}
cancelTickCancel <- struct{}{}
return nil
}