-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathvideo.go
146 lines (121 loc) · 3.73 KB
/
video.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
package goutube
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"regexp"
)
const (
youtubeWatchBaseURL = "https://www.youtube.com/watch?v="
youtubeExistsBaseURL = "https://www.youtube.com/oembed?url=http://www.youtube.com/watch?v="
)
var (
youtubeId = regexp.MustCompile(`^[\w\-]{11}$`)
youtubeLink = regexp.MustCompile(`^((?:https?:)?//)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(/(?:[\w\-]+\?v=|embed/|v/)?)([\w\-]+)(\S+)?$`)
)
var (
ErrSource = errors.New("invalid YouTube video link or id")
ErrIp = errors.New("invalid source ip")
ErrEmptyVideo = errors.New("no video specified")
ErrStreamPocketApi = errors.New("cannot reach remote api")
ErrStreamPocketResponse = errors.New("invalid remote api response")
)
// Video represents a YouTube video.
// To add video id and source IP address, use the AddVideoLink, AddSourceIp safe methods.
type Video struct {
video string
sourceIp string
}
type streamPocketResponse struct {
Recorded string
Filename string
}
// AddVideoLink parse, check and add the source of a YouTube video.
// video may be a 11 long video id string, or a YouTube video link.
// Return nil if successful or ErrSource on failure.
func (v *Video) AddVideoLink(video string) error {
if youtubeId.MatchString(video) {
v.video = video
return nil
}
matches := youtubeLink.FindStringSubmatch(video)
if matches != nil {
v.video = matches[5]
return nil
}
return ErrSource
}
// AddSourceIp parse, check and add the source IP address. Used for youtube-dl command.
// ip is the string representation of the IP address.
// Return nil if successful or ErrIp on failure.
func (v *Video) AddSourceIp(ip string) error {
if net.ParseIP(ip) == nil {
return ErrIp
}
v.sourceIp = ip
return nil
}
// Exists checks if the video exists using a YouTube API call.
// Return true, nil if the video exists, or false with an optional error on failure.
func (v *Video) Exists() (bool, error) {
if v.video == "" {
return false, nil
}
requestUrl := fmt.Sprintf("%s%s", youtubeExistsBaseURL, v.video)
resp, err := http.Get(requestUrl)
if err != nil {
return false, err
}
return resp.StatusCode == 200, nil
}
// YoutubeDlLink returns the direct link of the best quality mp4 video using the youtube-dl command.
// Returns the direct link of the video and no error on success, or ("", error) on failure.
func (v *Video) YoutubeDlLink() (string, error) {
if v.video == "" {
return "", ErrEmptyVideo
}
if v.sourceIp == "" {
return bestVideoLink(v.video)
} else {
return bestVideoLinkWithIp(v.video, v.sourceIp)
}
}
// StreamPocketLink returns the direct link of the best quality mp4 video using the streampocket.net API.
// Returns the direct link of the video and no error on success, or ("", error) on failure.
func (v *Video) StreamPocketLink() (string, error) {
if v.video == "" {
return "", ErrEmptyVideo
}
requestUrl := fmt.Sprintf("http://streampocket.net/json2?stream=%s%s", youtubeWatchBaseURL, v.video)
res, err := http.Get(requestUrl)
if err != nil {
return "", ErrStreamPocketApi
}
data, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", ErrStreamPocketApi
}
err = res.Body.Close()
if err != nil {
return "", ErrStreamPocketApi
}
var response streamPocketResponse
err = json.Unmarshal(data, &response)
if err != nil {
return "", ErrStreamPocketResponse
}
return response.Recorded, nil
}
// Stream writes the content of the video on a writer using the youtube-dl command.
// wr is the writer where the video data will be written.
// Returns no error on success or an ErrEmptyVideo on failure.
func (v *Video) Stream(wr io.Writer) error {
if v.video == "" {
return ErrEmptyVideo
}
return streamBestVideo(v.video, wr)
}