forked from kkdai/youtube
-
Notifications
You must be signed in to change notification settings - Fork 0
/
client.go
119 lines (96 loc) · 3.01 KB
/
client.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
package youtube
import (
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
)
// Client offers methods to download video metadata and video streams.
type Client struct {
// Debug enables debugging output through log package
Debug bool
// HTTPClient can be used to set a custom HTTP client.
// If not set, http.DefaultClient will be used
HTTPClient *http.Client
}
// GetVideo fetches video metadata
func (c *Client) GetVideo(url string) (*Video, error) {
return c.GetVideoContext(context.Background(), url)
}
// GetVideoContext fetches video metadata with a context
func (c *Client) GetVideoContext(ctx context.Context, url string) (*Video, error) {
id, err := extractVideoID(url)
if err != nil {
return nil, fmt.Errorf("extractVideoID failed: %w", err)
}
// Circumvent age restriction to pretend access through googleapis.com
eurl := "https://youtube.googleapis.com/v/" + id
body, err := c.httpGetBodyBytes(ctx, "https://youtube.com/get_video_info?video_id="+id+"&eurl="+eurl)
if err != nil {
return nil, err
}
v := &Video{
ID: id,
}
return v, v.parseVideoInfo(string(body))
}
// GetStream returns the HTTP response for a specific format
func (c *Client) GetStream(video *Video, format *Format) (*http.Response, error) {
return c.GetStreamContext(context.Background(), video, format)
}
// GetStreamContext returns the HTTP response for a specific format with a context
func (c *Client) GetStreamContext(ctx context.Context, video *Video, format *Format) (*http.Response, error) {
url, err := c.GetStreamURLContext(ctx, video, format)
if err != nil {
return nil, err
}
return c.httpGet(ctx, url)
}
// GetStreamURL returns the url for a specific format
func (c *Client) GetStreamURL(video *Video, format *Format) (string, error) {
return c.GetStreamURLContext(context.Background(), video, format)
}
// GetStreamURL returns the url for a specific format with a context
func (c *Client) GetStreamURLContext(ctx context.Context, video *Video, format *Format) (string, error) {
if format.URL != "" {
return format.URL, nil
}
cipher := format.Cipher
if cipher == "" {
return "", ErrCipherNotFound
}
return c.decipherURL(ctx, video.ID, cipher)
}
// httpGet does a HTTP GET request, checks the response to be a 200 OK and returns it
func (c *Client) httpGet(ctx context.Context, url string) (resp *http.Response, err error) {
client := c.HTTPClient
if client == nil {
client = http.DefaultClient
}
if c.Debug {
log.Println("GET", url)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
resp, err = client.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, ErrUnexpectedStatusCode(resp.StatusCode)
}
return
}
// httpGetBodyBytes reads the whole HTTP body and returns it
func (c *Client) httpGetBodyBytes(ctx context.Context, url string) ([]byte, error) {
resp, err := c.httpGet(ctx, url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}