This commit is contained in:
2018-11-04 15:58:15 +01:00
commit f956bcee28
1178 changed files with 584552 additions and 0 deletions

12
vendor/github.com/rylio/ytdl/Dockerfile generated vendored Normal file
View File

@@ -0,0 +1,12 @@
FROM golang:alpine
COPY . $GOPATH/src/github.com/rylio/ytdl/
RUN apk update && apk upgrade && \
apk add --no-cache git
RUN go get -d github.com/rylio/ytdl/cmd/ytdl/
RUN apk del git
WORKDIR $GOPATH/src/github.com/rylio/ytdl/cmd/ytdl/
RUN go build -o /go/bin/ytdl
WORKDIR /ytdl/
ENTRYPOINT ["/go/bin/ytdl"]

25
vendor/github.com/rylio/ytdl/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,25 @@
Copyright (c) <2015> <Ryan Coffman>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

120
vendor/github.com/rylio/ytdl/README.md generated vendored Normal file
View File

@@ -0,0 +1,120 @@
# ytdl [![Build Status](https://travis-ci.org/rylio/ytdl.svg)](https://travis-ci.org/rylio/ytdl) [![GoDoc](https://godoc.org/github.com/rylio/ytdl?status.svg)](https://godoc.org/github.com/rylio/ytdl)
Go library for downloading YouTube videos
[Documentation: https://godoc.org/github.com/rylio/ytdl](https://godoc.org/github.com/rylio/ytdl "ytdl")
## Example
```go
import (
"github.com/otium/ytdl"
"os"
)
vid, err := ytdl.GetVideoInfo("https://www.youtube.com/watch?v=1rZ-JorHJEY")
file, _ = os.Create(vid.Title + ".mp4")
defer file.Close()
vid.Download(file)
```
## ytdl CLI
- To install: `go get -u github.com/rylio/ytdl/...`
- Or use Docker image `docker pull brucwangno1/ytdl:1.0`
### Usage
- `ytdl [global options] [youtube url or video id]`
- Or using Docker: `docker run -it --rm -v /directory/you/want/to/save/the/download/:/ytdl/ brucewangno1/ytdl:1.0 [global options] "[youtube url or video id]"`
### Options
- `--help, -h` - show help
- `--filter, -f` - Filter out formats
- Syntax: `-f key:value1,value2,...,valueN`
- Shortcuts for best/worst(e.g. `-f best`)
- `best`/`worst` - best/worst video and audio
- `best-video`/`worst-video` - best/worst video
- `best-fps`/`worst-fps` - best/worst video with fps as the first priority
- `best-audio`/`worst-audio` - best/worst audio
- To exclude: -f !key:value1,...
- Available keys (See format.go for available values):
- `ext` - extension of video
- `res` - resolution of video
- `videnc` - video encoding
- `audenc` - audio encoding
- `prof` - youtube video profile
- `audbr` - audio bitrate
- Default filters
- `ext:mp4`
- `!videnc:`
- `!audenc:`
- `best`
- `--output, -o` - Output to specific path
- Supports templates, ex: {{.Title}}.{{.Ext}}
- Defaults to `{{.Title}}.{{.Ext}}`
- Supported template variables are Title, Ext, DatePublished, Resolution
- Pass - to output to stdout, former stdout output is redirected to stderr
- `--info, -i` - Just gets video info, outputs to stdout
- `--no-progress` - Disables the progress bar
- `--silent, -s` - Disables all output, except for fatal errors
- `--debug, -d` - Output debug logs
- `--append, -a` - append to output file, instead of truncating
- `--range, -r` - specify a range of bytes, placed in http range header, ex: 0-100
- `--download-url, -u` - just print download url to, don't do anything else
- `--version, -v` - print out ytdl cli version
- `--start-offset` - offset the beginning of the video by a duration of time(e.g. 20s or 1m)
- `--download-option, -p` - Print video and audio download options and accept input interactively
### Examples
Download content based on itag
```bash
ytdl -f itag:22 'https://www.youtube.com/watch?v=9bZkp7q19f0'
```
Download content with the best fps
```bash
ytdl -f best-fps 'https://www.youtube.com/watch?v=9bZkp7q19f0'
```
Get all download formats (Requires [jq](https://github.com/stedolan/jq) to be installed)
```bash
./ytdl -j 'http://youtube.com/watch?v=9bZkp7q19f0' | jq ".formats"
```
Extract title of the video (Requires [jq](https://github.com/stedolan/jq) to be installed)
```bash
ytdl -j 'http://youtube.com/watch?v=9bZkp7q19f0' | jq ".title"
```
Print download url without downloading the content
```bash
ytdl -f itag:22 --download-url 'https://www.youtube.com/watch?v=9bZkp7q19f0'
```
Print video and audio download options and accept input interactively
```bash
ytdl -p 'https://www.youtube.com/watch?v=9bZkp7q19f0'
```
## Contributing
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Added some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request
## License
ytdl is released under the MIT License, see LICENSE for more details.

583
vendor/github.com/rylio/ytdl/format.go generated vendored Normal file
View File

@@ -0,0 +1,583 @@
package ytdl
import "strconv"
// FormatKey is a string type containing a key in a video format map
type FormatKey string
// Available format Keys
const (
FormatExtensionKey FormatKey = "ext"
FormatResolutionKey FormatKey = "res"
FormatVideoEncodingKey FormatKey = "videnc"
FormatAudioEncodingKey FormatKey = "audenc"
FormatItagKey FormatKey = "itag"
FormatAudioBitrateKey FormatKey = "audbr"
FormatFPSKey FormatKey = "fps"
)
// Format is a youtube is a static youtube video format
type Format struct {
Itag int `json:"itag"`
Extension string `json:"extension"`
Resolution string `json:"resolution"`
VideoEncoding string `json:"videoEncoding"`
AudioEncoding string `json:"audioEncoding"`
AudioBitrate int `json:"audioBitrate"`
meta map[string]interface{}
}
func newFormat(itag int) (Format, bool) {
if f, ok := FORMATS[itag]; ok {
f.meta = make(map[string]interface{})
return f, true
}
return Format{}, false
}
// ValueForKey gets the format value for a format key, used for filtering
func (f Format) ValueForKey(key FormatKey) interface{} {
switch key {
case FormatItagKey:
return f.Itag
case FormatExtensionKey:
return f.Extension
case FormatResolutionKey:
return f.Resolution
case FormatVideoEncodingKey:
return f.VideoEncoding
case FormatAudioEncodingKey:
return f.AudioEncoding
case FormatAudioBitrateKey:
return f.AudioBitrate
default:
if f.meta != nil {
return f.meta[string(key)]
}
return nil
}
}
func (f Format) CompareKey(other Format, key FormatKey) int {
switch key {
case FormatResolutionKey:
res := f.ValueForKey(key).(string)
res1, res2 := 0, 0
if res != "" {
res1, _ = strconv.Atoi(res[0 : len(res)-2])
}
res = other.ValueForKey(key).(string)
if res != "" {
res2, _ = strconv.Atoi(res[0 : len(res)-2])
}
return res1 - res2
case FormatAudioBitrateKey:
return f.ValueForKey(key).(int) - other.ValueForKey(key).(int)
case FormatFPSKey:
if f.ValueForKey(key) == nil {
return -1
} else if other.ValueForKey(key) == nil {
return 1
} else {
a, _ := strconv.Atoi(f.ValueForKey(key).(string))
b, _ := strconv.Atoi(other.ValueForKey(key).(string))
return a - b
}
default:
return 0
}
}
// FORMATS is a map of all itags and their formats
var FORMATS = map[int]Format{
5: Format{
Extension: "flv",
Resolution: "240p",
VideoEncoding: "Sorenson H.283",
AudioEncoding: "mp3",
Itag: 5,
AudioBitrate: 64,
},
6: Format{
Extension: "flv",
Resolution: "270p",
VideoEncoding: "Sorenson H.263",
AudioEncoding: "mp3",
Itag: 6,
AudioBitrate: 64,
},
13: Format{
Extension: "3gp",
Resolution: "",
VideoEncoding: "MPEG-4 Visual",
AudioEncoding: "aac",
Itag: 13,
AudioBitrate: 0,
},
17: Format{
Extension: "3gp",
Resolution: "144p",
VideoEncoding: "MPEG-4 Visual",
AudioEncoding: "aac",
Itag: 17,
AudioBitrate: 24,
},
18: Format{
Extension: "mp4",
Resolution: "360p",
VideoEncoding: "H.264",
AudioEncoding: "aac",
Itag: 18,
AudioBitrate: 96,
},
22: Format{
Extension: "mp4",
Resolution: "720p",
VideoEncoding: "H.264",
AudioEncoding: "aac",
Itag: 22,
AudioBitrate: 192,
},
34: Format{
Extension: "flv",
Resolution: "480p",
VideoEncoding: "H.264",
AudioEncoding: "aac",
Itag: 34,
AudioBitrate: 128,
},
35: Format{
Extension: "flv",
Resolution: "360p",
VideoEncoding: "H.264",
AudioEncoding: "aac",
Itag: 35,
AudioBitrate: 128,
},
36: Format{
Extension: "3gp",
Resolution: "240p",
VideoEncoding: "MPEG-4 Visual",
AudioEncoding: "aac",
Itag: 36,
AudioBitrate: 36,
},
37: Format{
Extension: "mp4",
Resolution: "1080p",
VideoEncoding: "H.264",
AudioEncoding: "aac",
Itag: 37,
AudioBitrate: 192,
},
38: Format{
Extension: "mp4",
Resolution: "3072p",
VideoEncoding: "H.264",
AudioEncoding: "aac",
Itag: 38,
AudioBitrate: 192,
},
43: Format{
Extension: "webm",
Resolution: "360p",
VideoEncoding: "VP8",
AudioEncoding: "vorbis",
Itag: 43,
AudioBitrate: 128,
},
44: Format{
Extension: "webm",
Resolution: "480p",
VideoEncoding: "VP8",
AudioEncoding: "vorbis",
Itag: 44,
AudioBitrate: 128,
},
45: Format{
Extension: "webm",
Resolution: "720p",
VideoEncoding: "VP8",
AudioEncoding: "vorbis",
Itag: 45,
AudioBitrate: 192,
},
46: Format{
Extension: "webm",
Resolution: "1080p",
VideoEncoding: "VP8",
AudioEncoding: "vorbis",
Itag: 46,
AudioBitrate: 192,
},
82: Format{
Extension: "mp4",
Resolution: "360p",
VideoEncoding: "H.264",
Itag: 82,
AudioBitrate: 96,
},
83: Format{
Extension: "mp4",
Resolution: "240p",
VideoEncoding: "H.264",
AudioEncoding: "aac",
Itag: 83,
AudioBitrate: 96,
},
84: Format{
Extension: "mp4",
Resolution: "720p",
VideoEncoding: "H.264",
AudioEncoding: "aac",
Itag: 84,
AudioBitrate: 192,
},
85: Format{
Extension: "mp4",
Resolution: "1080p",
VideoEncoding: "H.264",
AudioEncoding: "aac",
Itag: 85,
AudioBitrate: 192,
},
100: Format{
Extension: "webm",
Resolution: "360p",
VideoEncoding: "VP8",
AudioEncoding: "vorbis",
Itag: 100,
AudioBitrate: 128,
},
101: Format{
Extension: "webm",
Resolution: "360p",
VideoEncoding: "VP8",
AudioEncoding: "vorbis",
Itag: 101,
AudioBitrate: 192,
},
102: Format{
Extension: "webm",
Resolution: "720p",
VideoEncoding: "VP8",
AudioEncoding: "vorbis",
Itag: 102,
AudioBitrate: 192,
},
// DASH (video only)
133: Format{
Extension: "mp4",
Resolution: "240p",
VideoEncoding: "H.264",
AudioEncoding: "",
Itag: 133,
AudioBitrate: 0,
},
134: Format{
Extension: "mp4",
Resolution: "360p",
VideoEncoding: "H.264",
AudioEncoding: "",
Itag: 134,
AudioBitrate: 0,
},
135: Format{
Extension: "mp4",
Resolution: "480p",
VideoEncoding: "H.264",
AudioEncoding: "",
Itag: 135,
AudioBitrate: 0,
},
136: Format{
Extension: "mp4",
Resolution: "720p",
VideoEncoding: "H.264",
AudioEncoding: "",
Itag: 136,
AudioBitrate: 0,
},
137: Format{
Extension: "mp4",
Resolution: "1080p",
VideoEncoding: "H.264",
AudioEncoding: "",
Itag: 137,
AudioBitrate: 0,
},
138: Format{
Extension: "mp4",
Resolution: "2160p",
VideoEncoding: "H.264",
AudioEncoding: "",
Itag: 138,
AudioBitrate: 0,
},
160: Format{
Extension: "mp4",
Resolution: "144p",
VideoEncoding: "H.264",
AudioEncoding: "",
Itag: 160,
AudioBitrate: 0,
},
242: Format{
Extension: "webm",
Resolution: "240p",
VideoEncoding: "VP9",
AudioEncoding: "",
Itag: 242,
AudioBitrate: 0,
},
243: Format{
Extension: "webm",
Resolution: "360p",
VideoEncoding: "VP9",
AudioEncoding: "",
Itag: 243,
AudioBitrate: 0,
},
244: Format{
Extension: "webm",
Resolution: "480p",
VideoEncoding: "VP9",
AudioEncoding: "",
Itag: 244,
AudioBitrate: 0,
},
247: Format{
Extension: "webm",
Resolution: "720p",
VideoEncoding: "VP9",
AudioEncoding: "",
Itag: 247,
AudioBitrate: 0,
},
248: Format{
Extension: "webm",
Resolution: "1080p",
VideoEncoding: "VP9",
AudioEncoding: "",
Itag: 248,
AudioBitrate: 9,
},
264: Format{
Extension: "mp4",
Resolution: "1440p",
VideoEncoding: "H.264",
AudioEncoding: "",
Itag: 264,
AudioBitrate: 0,
},
266: Format{
Extension: "mp4",
Resolution: "2160p",
VideoEncoding: "H.264",
AudioEncoding: "",
Itag: 266,
AudioBitrate: 0,
},
271: Format{
Extension: "webm",
Resolution: "1440p",
VideoEncoding: "VP9",
AudioEncoding: "",
Itag: 271,
AudioBitrate: 0,
},
272: Format{
Extension: "webm",
Resolution: "2160p",
VideoEncoding: "VP9",
AudioEncoding: "",
Itag: 272,
AudioBitrate: 0,
},
278: Format{
Extension: "webm",
Resolution: "144p",
VideoEncoding: "VP9",
AudioEncoding: "",
Itag: 278,
AudioBitrate: 0,
},
298: Format{
Extension: "mp4",
Resolution: "720p",
VideoEncoding: "H.264",
AudioEncoding: "",
Itag: 298,
AudioBitrate: 0,
},
299: Format{
Extension: "mp4",
Resolution: "1080p",
VideoEncoding: "H.264",
AudioEncoding: "",
Itag: 299,
AudioBitrate: 0,
},
302: Format{
Extension: "webm",
Resolution: "720p",
VideoEncoding: "VP9",
AudioEncoding: "",
Itag: 302,
AudioBitrate: 0,
},
303: Format{
Extension: "webm",
Resolution: "1080p",
VideoEncoding: "VP9",
AudioEncoding: "",
Itag: 303,
AudioBitrate: 0,
},
// DASH (audio only)
139: Format{
Extension: "mp4",
Resolution: "",
VideoEncoding: "",
AudioEncoding: "aac",
Itag: 139,
AudioBitrate: 48,
},
140: Format{
Extension: "mp4",
Resolution: "",
VideoEncoding: "",
AudioEncoding: "aac",
Itag: 140,
AudioBitrate: 128,
},
141: Format{
Extension: "mp4",
Resolution: "",
VideoEncoding: "",
AudioEncoding: "aac",
Itag: 141,
AudioBitrate: 256,
},
171: Format{
Extension: "webm",
Resolution: "",
VideoEncoding: "",
AudioEncoding: "vorbis",
Itag: 171,
AudioBitrate: 128,
},
172: Format{
Extension: "webm",
Resolution: "",
VideoEncoding: "",
AudioEncoding: "vorbis",
Itag: 172,
AudioBitrate: 192,
},
249: Format{
Extension: "webm",
Resolution: "",
VideoEncoding: "",
AudioEncoding: "opus",
Itag: 249,
AudioBitrate: 50,
},
250: Format{
Extension: "webm",
Resolution: "",
VideoEncoding: "",
AudioEncoding: "opus",
Itag: 250,
AudioBitrate: 70,
},
251: Format{
Extension: "webm",
Resolution: "",
VideoEncoding: "",
AudioEncoding: "opus",
Itag: 251,
AudioBitrate: 160,
},
// Live streaming
92: Format{
Extension: "ts",
Resolution: "240p",
VideoEncoding: "H.264",
AudioEncoding: "aac",
Itag: 92,
AudioBitrate: 48,
},
93: Format{
Extension: "ts",
Resolution: "480p",
VideoEncoding: "H.264",
AudioEncoding: "aac",
Itag: 93,
AudioBitrate: 128,
},
94: Format{
Extension: "ts",
Resolution: "720p",
VideoEncoding: "H.264",
AudioEncoding: "aac",
Itag: 94,
AudioBitrate: 128,
},
95: Format{
Extension: "ts",
Resolution: "1080p",
VideoEncoding: "H.264",
AudioEncoding: "aac",
Itag: 95,
AudioBitrate: 256,
},
96: Format{
Extension: "ts",
Resolution: "720p",
VideoEncoding: "H.264",
AudioEncoding: "aac",
Itag: 96,
AudioBitrate: 256,
},
120: Format{
Extension: "flv",
Resolution: "720p",
VideoEncoding: "H.264",
AudioEncoding: "aac",
Itag: 120,
AudioBitrate: 128,
},
127: Format{
Extension: "ts",
Resolution: "",
VideoEncoding: "",
AudioEncoding: "aac",
Itag: 127,
AudioBitrate: 96,
},
128: Format{
Extension: "ts",
Resolution: "",
VideoEncoding: "",
AudioEncoding: "aac",
Itag: 128,
AudioBitrate: 96,
},
132: Format{
Extension: "ts",
Resolution: "240p",
VideoEncoding: "H.264",
AudioEncoding: "aac",
Itag: 132,
AudioBitrate: 48,
},
151: Format{
Extension: "ts",
Resolution: "720p",
VideoEncoding: "H.264",
AudioEncoding: "aac",
Itag: 151,
AudioBitrate: 24,
},
}

93
vendor/github.com/rylio/ytdl/format_list.go generated vendored Normal file
View File

@@ -0,0 +1,93 @@
package ytdl
import "sort"
// FormatList is a slice of formats with filtering functionality
type FormatList []Format
func (formats FormatList) Filter(key FormatKey, values []interface{}) FormatList {
var dst FormatList
for _, v := range values {
for _, f := range formats {
if interfaceToString(f.ValueForKey(key)) == interfaceToString(v) {
dst = append(dst, f)
}
}
}
return dst
}
func (formats FormatList) Extremes(key FormatKey, best bool) FormatList {
dst := formats.Copy()
if len(dst) > 1 {
dst.Sort(key, best)
first := dst[0]
var i int
for i = 0; i < len(dst)-1; i++ {
if first.CompareKey(dst[i+1], key) != 0 {
break
}
}
i++
dst = dst[0:i]
}
return dst
}
func (formats FormatList) Best(key FormatKey) FormatList {
return formats.Extremes(key, true)
}
func (formats FormatList) Worst(key FormatKey) FormatList {
return formats.Extremes(key, false)
}
func (formats FormatList) Sort(key FormatKey, reverse bool) {
wrapper := formatsSortWrapper{formats, key}
if !reverse {
sort.Stable(wrapper)
} else {
sort.Stable(sort.Reverse(wrapper))
}
}
func (formats FormatList) Subtract(other FormatList) FormatList {
var dst FormatList
for _, f := range formats {
include := true
for _, f2 := range other {
if f2.Itag == f.Itag {
include = false
break
}
}
if include {
dst = append(dst, f)
}
}
return dst
}
func (formats FormatList) Copy() FormatList {
dst := make(FormatList, len(formats))
copy(dst, formats)
return dst
}
type formatsSortWrapper struct {
formats FormatList
key FormatKey
}
func (s formatsSortWrapper) Len() int {
return len(s.formats)
}
func (s formatsSortWrapper) Less(i, j int) bool {
return s.formats[i].CompareKey(s.formats[j], s.key) < 0
}
func (s formatsSortWrapper) Swap(i, j int) {
s.formats[i], s.formats[j] = s.formats[j], s.formats[i]
}

9
vendor/github.com/rylio/ytdl/goreleaser.yml generated vendored Normal file
View File

@@ -0,0 +1,9 @@
builds:
- main: ./cmd/ytdl/main.go
binary: ytdl
goos:
- windows
- darwin
- linux
goarch:
- amd64

183
vendor/github.com/rylio/ytdl/signature.go generated vendored Normal file
View File

@@ -0,0 +1,183 @@
package ytdl
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
)
func getDownloadURL(format Format, htmlPlayerFile string) (*url.URL, error) {
var sig string
if s, ok := format.meta["s"]; ok && len(s.(string)) > 0 {
tokens, err := getSigTokens(htmlPlayerFile)
if err != nil {
return nil, err
}
sig = decipherTokens(tokens, s.(string))
} else {
if s, ok := format.meta["sig"]; ok {
sig = s.(string)
}
}
var urlString string
if s, ok := format.meta["url"]; ok {
urlString = s.(string)
} else if s, ok := format.meta["stream"]; ok {
if c, ok := format.meta["conn"]; ok {
urlString = c.(string)
if urlString[len(urlString)-1] != '/' {
urlString += "/"
}
}
urlString += s.(string)
} else {
return nil, fmt.Errorf("Couldn't extract url from format")
}
urlString, err := url.QueryUnescape(urlString)
if err != nil {
return nil, err
}
u, err := url.Parse(urlString)
if err != nil {
return nil, err
}
query := u.Query()
query.Set("ratebypass", "yes")
if len(sig) > 0 {
query.Set("signature", sig)
}
u.RawQuery = query.Encode()
return u, nil
}
func decipherTokens(tokens []string, sig string) string {
var pos int
sigSplit := strings.Split(sig, "")
for i, l := 0, len(tokens); i < l; i++ {
tok := tokens[i]
if len(tok) > 1 {
pos, _ = strconv.Atoi(string(tok[1:]))
pos = ^^pos
}
switch string(tok[0]) {
case "r":
reverseStringSlice(sigSplit)
case "w":
s := sigSplit[0]
sigSplit[0] = sigSplit[pos]
sigSplit[pos] = s
case "s":
sigSplit = sigSplit[pos:]
case "p":
sigSplit = sigSplit[pos:]
}
}
return strings.Join(sigSplit, "")
}
const (
jsvarStr = "[a-zA-Z_\\$][a-zA-Z_0-9]*"
reverseStr = ":function\\(a\\)\\{" +
"(?:return )?a\\.reverse\\(\\)" +
"\\}"
sliceStr = ":function\\(a,b\\)\\{" +
"return a\\.slice\\(b\\)" +
"\\}"
spliceStr = ":function\\(a,b\\)\\{" +
"a\\.splice\\(0,b\\)" +
"\\}"
swapStr = ":function\\(a,b\\)\\{" +
"var c=a\\[0\\];a\\[0\\]=a\\[b(?:%a\\.length)?\\];a\\[b(?:%a\\.length)?\\]=c(?:;return a)?" +
"\\}"
)
var actionsObjRegexp = regexp.MustCompile(fmt.Sprintf(
"var (%s)=\\{((?:(?:%s%s|%s%s|%s%s|%s%s),?\\n?)+)\\};", jsvarStr, jsvarStr, reverseStr, jsvarStr, sliceStr, jsvarStr, spliceStr, jsvarStr, swapStr))
var actionsFuncRegexp = regexp.MustCompile(fmt.Sprintf(
"function(?: %s)?\\(a\\)\\{"+
"a=a\\.split\\(\"\"\\);\\s*"+
"((?:(?:a=)?%s\\.%s\\(a,\\d+\\);)+)"+
"return a\\.join\\(\"\"\\)"+
"\\}", jsvarStr, jsvarStr, jsvarStr))
var reverseRegexp = regexp.MustCompile(fmt.Sprintf(
"(?m)(?:^|,)(%s)%s", jsvarStr, reverseStr))
var sliceRegexp = regexp.MustCompile(fmt.Sprintf(
"(?m)(?:^|,)(%s)%s", jsvarStr, sliceStr))
var spliceRegexp = regexp.MustCompile(fmt.Sprintf(
"(?m)(?:^|,)(%s)%s", jsvarStr, spliceStr))
var swapRegexp = regexp.MustCompile(fmt.Sprintf(
"(?m)(?:^|,)(%s)%s", jsvarStr, swapStr))
func getSigTokens(htmlPlayerFile string) ([]string, error) {
u, _ := url.Parse(youtubeBaseURL)
p, err := url.Parse(htmlPlayerFile)
if err != nil {
return nil, err
}
resp, err := http.Get(u.ResolveReference(p).String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Error fetching signature tokens, status code %d", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
bodyString := string(body)
if err != nil {
return nil, err
}
objResult := actionsObjRegexp.FindStringSubmatch(bodyString)
funcResult := actionsFuncRegexp.FindStringSubmatch(bodyString)
if len(objResult) < 3 || len(funcResult) < 2 {
return nil, fmt.Errorf("Error parsing signature tokens")
}
obj := strings.Replace(objResult[1], "$", "\\$", -1)
objBody := strings.Replace(objResult[2], "$", "\\$", -1)
funcBody := strings.Replace(funcResult[1], "$", "\\$", -1)
var reverseKey, sliceKey, spliceKey, swapKey string
var result []string
if result = reverseRegexp.FindStringSubmatch(objBody); len(result) > 1 {
reverseKey = strings.Replace(result[1], "$", "\\$", -1)
}
if result = sliceRegexp.FindStringSubmatch(objBody); len(result) > 1 {
sliceKey = strings.Replace(result[1], "$", "\\$", -1)
}
if result = spliceRegexp.FindStringSubmatch(objBody); len(result) > 1 {
spliceKey = strings.Replace(result[1], "$", "\\$", -1)
}
if result = swapRegexp.FindStringSubmatch(objBody); len(result) > 1 {
swapKey = strings.Replace(result[1], "$", "\\$", -1)
}
keys := []string{reverseKey, sliceKey, spliceKey, swapKey}
regex, err := regexp.Compile(fmt.Sprintf("(?:a=)?%s\\.(%s)\\(a,(\\d+)\\)", obj, strings.Join(keys, "|")))
if err != nil {
return nil, err
}
results := regex.FindAllStringSubmatch(funcBody, -1)
var tokens []string
for _, s := range results {
switch s[1] {
case swapKey:
tokens = append(tokens, "w"+s[2])
case reverseKey:
tokens = append(tokens, "r")
case sliceKey:
tokens = append(tokens, "s"+s[2])
case spliceKey:
tokens = append(tokens, "p"+s[2])
}
}
return tokens, nil
}

21
vendor/github.com/rylio/ytdl/thumbnail.go generated vendored Normal file
View File

@@ -0,0 +1,21 @@
package ytdl
// From http://stackoverflow.com/questions/2068344/how-do-i-get-a-youtube-video-thumbnail-from-the-youtube-api
// ThumbnailQuality is a youtube thumbnail quality option
type ThumbnailQuality string
// ThumbnailQualityHigh is the high quality thumbnail jpg
const ThumbnailQualityHigh ThumbnailQuality = "hqdefault"
// ThumbnailQualityDefault is the default quality thumbnail jpg
const ThumbnailQualityDefault ThumbnailQuality = "default"
// ThumbnailQualityMedium is the medium quality thumbnail jpg
const ThumbnailQualityMedium ThumbnailQuality = "mqdefault"
// ThumbnailQualitySD is the standard def quality thumbnail jpg
const ThumbnailQualitySD ThumbnailQuality = "sddefault"
// ThumbnailQualityMaxRes is the maximum resolution quality jpg
const ThumbnailQualityMaxRes ThumbnailQuality = "maxresdefault"

13
vendor/github.com/rylio/ytdl/utils.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
package ytdl
import "fmt"
func reverseStringSlice(s []string) {
for i, j := 0, len(s)-1; i < len(s)/2; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}
func interfaceToString(val interface{}) string {
return fmt.Sprintf("%v", val)
}

402
vendor/github.com/rylio/ytdl/video_info.go generated vendored Normal file
View File

@@ -0,0 +1,402 @@
package ytdl
import (
"bytes"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
"github.com/PuerkitoBio/goquery"
log "github.com/sirupsen/logrus"
)
const youtubeBaseURL = "https://www.youtube.com/watch"
const youtubeEmbededBaseURL = "https://www.youtube.com/embed/"
const youtubeVideoEURL = "https://youtube.googleapis.com/v/"
const youtubeVideoInfoURL = "https://www.youtube.com/get_video_info"
const youtubeDateFormat = "2006-01-02"
// VideoInfo contains the info a youtube video
type VideoInfo struct {
// The video ID
ID string `json:"id"`
// The video title
Title string `json:"title"`
// The video description
Description string `json:"description"`
// The date the video was published
DatePublished time.Time `json:"datePublished"`
// Formats the video is available in
Formats FormatList `json:"formats"`
// List of keywords associated with the video
Keywords []string `json:"keywords"`
// Author of the video
Author string `json:"author"`
// Duration of the video
Duration time.Duration
htmlPlayerFile string
}
// GetVideoInfo fetches info from a url string, url object, or a url string
func GetVideoInfo(value interface{}) (*VideoInfo, error) {
switch t := value.(type) {
case *url.URL:
return GetVideoInfoFromURL(t)
case string:
u, err := url.ParseRequestURI(t)
if err != nil {
return GetVideoInfoFromID(t)
}
if u.Host == "youtu.be" {
return GetVideoInfoFromShortURL(u)
}
return GetVideoInfoFromURL(u)
default:
return nil, fmt.Errorf("Identifier type must be a string, *url.URL, or []byte")
}
}
// GetVideoInfoFromURL fetches video info from a youtube url
func GetVideoInfoFromURL(u *url.URL) (*VideoInfo, error) {
videoID := u.Query().Get("v")
if len(videoID) == 0 {
return nil, fmt.Errorf("Invalid youtube url, no video id")
}
return GetVideoInfoFromID(videoID)
}
// GetVideoInfoFromShortURL fetches video info from a short youtube url
func GetVideoInfoFromShortURL(u *url.URL) (*VideoInfo, error) {
if len(u.Path) >= 1 {
if path := u.Path[1:]; path != "" {
return GetVideoInfoFromID(path)
}
}
return nil, errors.New("Could not parse short URL")
}
// GetVideoInfoFromID fetches video info from a youtube video id
func GetVideoInfoFromID(id string) (*VideoInfo, error) {
u, _ := url.ParseRequestURI(youtubeBaseURL)
values := u.Query()
values.Set("v", id)
u.RawQuery = values.Encode()
resp, err := http.Get(u.String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Invalid status code: %d", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return getVideoInfoFromHTML(id, body)
}
// GetDownloadURL gets the download url for a format
func (info *VideoInfo) GetDownloadURL(format Format) (*url.URL, error) {
return getDownloadURL(format, info.htmlPlayerFile)
}
// GetThumbnailURL returns a url for the thumbnail image
// with the given quality
func (info *VideoInfo) GetThumbnailURL(quality ThumbnailQuality) *url.URL {
u, _ := url.Parse(fmt.Sprintf("http://img.youtube.com/vi/%s/%s.jpg",
info.ID, quality))
return u
}
// Download is a convenience method to download a format to an io.Writer
func (info *VideoInfo) Download(format Format, dest io.Writer) error {
u, err := info.GetDownloadURL(format)
if err != nil {
return err
}
resp, err := http.Get(u.String())
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode > 299 {
return fmt.Errorf("Invalid status code: %d", resp.StatusCode)
}
_, err = io.Copy(dest, resp.Body)
return err
}
func getVideoInfoFromHTML(id string, html []byte) (*VideoInfo, error) {
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(html))
if err != nil {
return nil, err
}
info := &VideoInfo{}
// extract description and title
info.Description = strings.TrimSpace(doc.Find("#eow-description").Text())
info.Title = strings.TrimSpace(doc.Find("#eow-title").Text())
info.ID = id
dateStr, ok := doc.Find("meta[itemprop=\"datePublished\"]").Attr("content")
if !ok {
log.Debug("Unable to extract date published")
} else {
date, err := time.Parse(youtubeDateFormat, dateStr)
if err == nil {
info.DatePublished = date
} else {
log.Debug("Unable to parse date published", err.Error())
}
}
// match json in javascript
re := regexp.MustCompile("ytplayer.config = (.*?);ytplayer.load")
matches := re.FindSubmatch(html)
var jsonConfig map[string]interface{}
if len(matches) > 1 {
err = json.Unmarshal(matches[1], &jsonConfig)
if err != nil {
return nil, err
}
} else {
log.Debug("Unable to extract json from default url, trying embedded url")
var resp *http.Response
resp, err = http.Get(youtubeEmbededBaseURL + id)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Embeded url request returned status code %d ", resp.StatusCode)
}
html, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
// re = regexp.MustCompile("\"sts\"\\s*:\\s*(\\d+)")
re = regexp.MustCompile("yt.setConfig\\('PLAYER_CONFIG', (.*?)\\);</script>")
matches := re.FindSubmatch(html)
if len(matches) < 2 {
return nil, fmt.Errorf("Error extracting sts from embedded url response")
}
dec := json.NewDecoder(bytes.NewBuffer(matches[1]))
err = dec.Decode(&jsonConfig)
if err != nil {
return nil, fmt.Errorf("Unable to extract json from embedded url: %s", err.Error())
}
query := url.Values{
"sts": []string{strconv.Itoa(int(jsonConfig["sts"].(float64)))},
"video_id": []string{id},
"eurl": []string{youtubeVideoEURL + id},
}
resp, err = http.Get(youtubeVideoInfoURL + "?" + query.Encode())
if err != nil {
return nil, fmt.Errorf("Error fetching video info: %s", err.Error())
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Video info response invalid status code")
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("Unable to read video info response body: %s", err.Error())
}
query, err = url.ParseQuery(string(body))
if err != nil {
return nil, fmt.Errorf("Unable to parse video info data: %s", err.Error())
}
args := make(map[string]interface{})
for k, v := range query {
if len(v) > 0 {
args[k] = v[0]
}
}
jsonConfig["args"] = args
}
inf := jsonConfig["args"].(map[string]interface{})
if status, ok := inf["status"].(string); ok && status == "fail" {
return nil, fmt.Errorf("Error %d:%s", inf["errorcode"], inf["reason"])
}
if a, ok := inf["author"].(string); ok {
info.Author = a
} else {
log.Debug("Unable to extract author")
}
if length, ok := inf["length_seconds"].(string); ok {
if duration, err := strconv.ParseInt(length, 10, 64); err == nil {
info.Duration = time.Second * time.Duration(duration)
} else {
log.Debug("Unable to parse duration string: ", length)
}
} else {
log.Debug("Unable to extract duration")
}
// For the future maybe
parseKey := func(key string) []string {
val, ok := inf[key].(string)
if !ok {
return nil
}
vals := []string{}
split := strings.Split(val, ",")
for _, v := range split {
if v != "" {
vals = append(vals, v)
}
}
return vals
}
info.Keywords = parseKey("keywords")
info.htmlPlayerFile = jsonConfig["assets"].(map[string]interface{})["js"].(string)
/*
fmtList := parseKey("fmt_list")
fexp := parseKey("fexp")
watermark := parseKey("watermark")
if len(fmtList) != 0 {
vals := []string{}
for _, v := range fmtList {
vals = append(vals, strings.Split(v, "/")...)
} else {
info["fmt_list"] = []string{}
}
videoVerticals := []string{}
if videoVertsStr, ok := inf["video_verticals"].(string); ok {
videoVertsStr = string([]byte(videoVertsStr)[1 : len(videoVertsStr)-2])
videoVertsSplit := strings.Split(videoVertsStr, ", ")
for _, v := range videoVertsSplit {
if v != "" {
videoVerticals = append(videoVerticals, v)
}
}
}
*/
var formatStrings []string
if fmtStreamMap, ok := inf["url_encoded_fmt_stream_map"].(string); ok {
formatStrings = append(formatStrings, strings.Split(fmtStreamMap, ",")...)
}
if adaptiveFormats, ok := inf["adaptive_fmts"].(string); ok {
formatStrings = append(formatStrings, strings.Split(adaptiveFormats, ",")...)
}
var formats FormatList
for _, v := range formatStrings {
query, err := url.ParseQuery(v)
if err == nil {
itag, _ := strconv.Atoi(query.Get("itag"))
if format, ok := newFormat(itag); ok {
if strings.HasPrefix(query.Get("conn"), "rtmp") {
format.meta["rtmp"] = true
}
for k, v := range query {
if len(v) == 1 {
format.meta[k] = v[0]
} else {
format.meta[k] = v
}
}
formats = append(formats, format)
} else {
log.Debug("No metadata found for itag: ", itag, ", skipping...")
}
} else {
log.Debug("Unable to format string", err.Error())
}
}
if dashManifestURL, ok := inf["dashmpd"].(string); ok {
tokens, err := getSigTokens(info.htmlPlayerFile)
if err != nil {
return nil, fmt.Errorf("Unable to extract signature tokens: %s", err.Error())
}
regex := regexp.MustCompile("\\/s\\/([a-fA-F0-9\\.]+)")
regexSub := regexp.MustCompile("([a-fA-F0-9\\.]+)")
dashManifestURL = regex.ReplaceAllStringFunc(dashManifestURL, func(str string) string {
return "/signature/" + decipherTokens(tokens, regexSub.FindString(str))
})
dashFormats, err := getDashManifest(dashManifestURL)
if err != nil {
return nil, fmt.Errorf("Unable to extract dash manifest: %s", err.Error())
}
for _, dashFormat := range dashFormats {
added := false
for j, format := range formats {
if dashFormat.Itag == format.Itag {
formats[j] = dashFormat
added = true
break
}
}
if !added {
formats = append(formats, dashFormat)
}
}
}
info.Formats = formats
return info, nil
}
type representation struct {
Itag int `xml:"id,attr"`
Height int `xml:"height,attr"`
URL string `xml:"BaseURL"`
}
func getDashManifest(urlString string) (formats []Format, err error) {
resp, err := http.Get(urlString)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Invalid status code %d", resp.StatusCode)
}
dec := xml.NewDecoder(resp.Body)
var token xml.Token
for ; err == nil; token, err = dec.Token() {
if el, ok := token.(xml.StartElement); ok && el.Name.Local == "Representation" {
var rep representation
err = dec.DecodeElement(&rep, &el)
if err != nil {
break
}
if format, ok := newFormat(rep.Itag); ok {
format.meta["url"] = rep.URL
if rep.Height != 0 {
format.Resolution = strconv.Itoa(rep.Height) + "p"
} else {
format.Resolution = ""
}
formats = append(formats, format)
} else {
log.Debug("No metadata found for itag: ", rep.Itag, ", skipping...")
}
}
}
if err != io.EOF {
return nil, err
}
return formats, nil
}