-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 80c58a6
Showing
5 changed files
with
378 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Binaries for programs and plugins | ||
*.exe | ||
*.dll | ||
*.so | ||
*.dylib | ||
*.mp4 | ||
*.webm | ||
*.3gpp | ||
|
||
# Test binary, build with `go test -c` | ||
*.test | ||
|
||
# Output of the go coverage tool, specifically when used with LiteIDE | ||
*.out | ||
|
||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 | ||
.glide/ | ||
pkg |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"github.com/noelyahan/gonyvido" | ||
) | ||
|
||
var url string | ||
var savePath string | ||
var quality string | ||
|
||
func init() { | ||
const ( | ||
defaultUrl = "https://www.youtube.com/watch?v=PIyrV8UmNqg" | ||
defaultSavePath = "./" | ||
defaultQuality = "high" // medium, low | ||
) | ||
// url flags | ||
flag.StringVar(&url, "url", defaultUrl, "Video download url") | ||
flag.StringVar(&url, "u", defaultUrl, "Video download url") | ||
|
||
// save path flags | ||
flag.StringVar(&savePath, "path", defaultSavePath, "Video save path") | ||
flag.StringVar(&savePath, "p", defaultSavePath, "Video save path") | ||
|
||
// quaity flags | ||
flag.StringVar(&quality, "quality", defaultQuality, "Video quality") | ||
flag.StringVar(&quality, "q", defaultQuality, "Video quality") | ||
} | ||
|
||
func main() { | ||
|
||
flag.Parse() | ||
|
||
switch quality { | ||
case "high": | ||
gonyvido.GetHQVideo(url).SetSavePath(savePath).Download().ShowProgress() | ||
case "medium": | ||
gonyvido.GetMQVideo(url).SetSavePath(savePath).Download().ShowProgress() | ||
case "low": | ||
gonyvido.GetLQVideo(url).SetSavePath(savePath).Download().ShowProgress() | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
go install | ||
gonyvido -u "https://youtu.be/hPsaAFBsFJQ" -q high -p ./downloads | ||
gonyvido -u "https://youtu.be/hPsaAFBsFJQ?list=RDMMhPsaAFBsFJQ" -q low -p ./downloads | ||
gonyvido -u "https://youtu.be/hPsaAFBsFJQ?list=RDMMhPsaAFBsFJQ" -q medium -p ./downloads | ||
gonyvido -u "https://www.youtube.com/watch?v=ar-3chBG4NU&list=RDMMhPsaAFBsFJQ&index=3" -q high -p ./downloads | ||
gonyvido -u "https://www.youtube.com/watch?v=ar-3chBG4NU&list=RDMMhPsaAFBsFJQ&index=3" -q low -p ./downloads | ||
gonyvido -u "https://www.youtube.com/watch?v=ar-3chBG4NU&list=RDMMhPsaAFBsFJQ&index=3" -q medium -p ./downloads | ||
gonyvido -u "https://www.youtube.com/watch?v=285VO0GHFFg" -q high -p ./downloads | ||
gonyvido -u "https://www.youtube.com/watch?v=285VO0GHFFg" -q low -p ./downloads | ||
gonyvido -u "https://www.youtube.com/watch?v=285VO0GHFFg" -q medium -p ./downloads | ||
gonyvido -url "https://www.youtube.com/watch?v=kvbZyeDipvs" -quality high -path ./downloads | ||
gonyvido -url "https://www.youtube.com/watch?v=kvbZyeDipvs" -quality low -path ./downloads | ||
gonyvido -url "https://www.youtube.com/watch?v=kvbZyeDipvs" -quality medium -path ./downloads |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package gonyvido | ||
|
||
import ( | ||
"fmt" | ||
"github.com/gosuri/uiprogress" | ||
s "strings" | ||
) | ||
|
||
func Find(url string) []Video { | ||
videos, err := GetYoutubeVideos(url) | ||
if err != nil { | ||
fmt.Println(err) | ||
return nil | ||
} | ||
return videos | ||
} | ||
|
||
func GetHQVideo(url string) *Video { | ||
return getFilteredVideo(url, "hd") | ||
} | ||
|
||
func GetMQVideo(url string) *Video { | ||
return getFilteredVideo(url, "medium") | ||
} | ||
|
||
func GetLQVideo(url string) *Video { | ||
return getFilteredVideo(url, "small") | ||
} | ||
|
||
func (v Video) ShowProgress() { | ||
fmt.Println("Downloading: " + v.GetTitle() + " " + v.GetQuality()) | ||
uiprogress.Start() | ||
bar := uiprogress.AddBar(100) | ||
bar.AppendCompleted() | ||
bar.PrependElapsed() | ||
pVal := 0 | ||
for p := range v.DownloadP { | ||
if pVal != p { | ||
diff := p - pVal | ||
for i := 0; i < diff; i++ { | ||
bar.Incr() | ||
} | ||
pVal = p | ||
} | ||
} | ||
fmt.Println("Finished: " + v.GetTitle() + " quality: " + v.GetQuality()) | ||
} | ||
|
||
func getFilteredVideo(url, vtype string) *Video { | ||
videos := Find(url) | ||
for _, video := range videos { | ||
if s.Contains(video.GetQuality(), vtype) { | ||
return &video | ||
} | ||
} | ||
return &Video{"", "", "", "", "", 0, 0, nil, "./"} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
package gonyvido | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"regexp" | ||
s "strings" | ||
) | ||
|
||
const ( | ||
YoutubeVideoInfoApi = "http://youtube.com/get_video_info?video_id=" | ||
YoutubeWatchUrl = "//www.youtube.com/watch?v" | ||
YoutubeShortUrl = "youtu.be" | ||
) | ||
|
||
type Video struct { | ||
title string | ||
author string | ||
quality string | ||
videoType string | ||
url string | ||
|
||
videoLength int | ||
downloadedLength int | ||
DownloadP chan int | ||
savePath string | ||
} | ||
|
||
func (v *Video) getUrl() string { | ||
return v.url | ||
} | ||
|
||
func (v Video) getExt() string { | ||
ext := s.Split(v.videoType, ";")[0] | ||
ext = s.Split(ext, "/")[1] | ||
return "." + ext | ||
} | ||
|
||
func (v Video) getFileName() string { | ||
fileName := s.Replace(v.title, "|", "", -1) | ||
return fileName | ||
} | ||
|
||
func (v Video) getSavePath() string { | ||
return v.savePath | ||
} | ||
|
||
func (v Video) GetType() string { | ||
return v.videoType | ||
} | ||
|
||
func (v Video) GetTitle() string { | ||
return v.title | ||
} | ||
|
||
func (v Video) GetAuthor() string { | ||
return v.author | ||
} | ||
|
||
func (v Video) GetQuality() string { | ||
return v.quality | ||
} | ||
|
||
func (v *Video) SetSavePath(savePath string) *Video { | ||
v.savePath = savePath | ||
if string(savePath[len(savePath)-1]) != "/" { | ||
v.savePath += "/" | ||
} | ||
return v | ||
} | ||
|
||
func GetYoutubeVideos(url string) ([]Video, error) { | ||
var videoId string | ||
splitted := s.Split(url, "=") | ||
if len(splitted) > 1 { | ||
videoId = splitted[1] | ||
} | ||
if s.Contains(url, YoutubeShortUrl) { | ||
splitted = s.Split(url, "/") | ||
if len(splitted) > 3 { | ||
videoId = splitted[3] | ||
} | ||
} | ||
if s.Contains(videoId, "&") { | ||
videoId = s.Split(videoId, "&")[0] | ||
} | ||
infoApiUrl := YoutubeVideoInfoApi + videoId | ||
defaultApiUrl := YoutubeWatchUrl + "=" + videoId | ||
|
||
videos, err := getVideoInfo(infoApiUrl) | ||
if err != nil { | ||
videos, err = getVideoInfo(defaultApiUrl) | ||
if err != nil { | ||
fmt.Println("Sorry this video is cannot be download.", err) | ||
return nil, err | ||
} | ||
return videos, nil | ||
} | ||
return videos, nil | ||
} | ||
|
||
func (v *Video) Write(b []byte) (n int, err error) { | ||
v.downloadedLength = v.downloadedLength + len(b) | ||
p := (100 / (v.videoLength / v.downloadedLength)) | ||
v.DownloadP <- p | ||
if p == 100 && v.videoLength <= v.downloadedLength { | ||
close(v.DownloadP) | ||
} | ||
return len(b), nil | ||
} | ||
|
||
func (v *Video) Download() Video { | ||
go func() { | ||
resp, err := http.Get(v.getUrl()) | ||
if err != nil { | ||
fmt.Println(err) | ||
} | ||
defer resp.Body.Close() | ||
if resp.StatusCode != 200 { | ||
fmt.Println(err) | ||
} | ||
v.videoLength = int(resp.ContentLength) | ||
|
||
out, err := os.Create(v.getSavePath() + v.getFileName() + v.getExt()) | ||
if err != nil { | ||
fmt.Println(err) | ||
} | ||
mw := io.MultiWriter(out, v) | ||
_, err = io.Copy(mw, resp.Body) | ||
if err != nil { | ||
fmt.Println(err) | ||
} | ||
}() | ||
return Video{ | ||
v.title, | ||
v.author, | ||
v.quality, | ||
v.videoType, | ||
v.url, | ||
v.videoLength, | ||
v.downloadedLength, | ||
v.DownloadP, | ||
"./", | ||
} | ||
} | ||
|
||
func getVideoInfo(url string) ([]Video, error) { | ||
resp, err := http.Get(url) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer resp.Body.Close() | ||
if resp.StatusCode != 200 { | ||
return nil, err | ||
} | ||
body, err := ioutil.ReadAll(resp.Body) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Stratergy pattern | ||
var title, author, streamMapStr string | ||
if s.Contains(url, YoutubeVideoInfoApi) { | ||
title, author, streamMapStr, err = extractFromInfoApi(string(body)) | ||
} else { | ||
title, author, streamMapStr, err = extractFromDefault(string(body)) | ||
} | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return constructVideos(title, author, streamMapStr) | ||
} | ||
|
||
func extractFromDefault(strBody string) (string, string, string, error) { | ||
streamMapFilter := "url_encoded_fmt_stream_map\":" | ||
streamMapRegexp, err := regexp.Compile(streamMapFilter + "(.*?)\",") | ||
if err != nil { | ||
return "", "", "", err | ||
} | ||
titleRegexp, err := regexp.Compile("<title>(.*?)<\\/title>") | ||
if err != nil { | ||
return "", "", "", err | ||
} | ||
authorRegexp, err := regexp.Compile("author\":\"(.*?)\",") | ||
if err != nil { | ||
return "", "", "", err | ||
} | ||
title := titleRegexp.FindString(strBody) | ||
author := authorRegexp.FindString(strBody) | ||
title = title[len("<title>") : len(title)-len("</title>")] | ||
author = author[len("author:\"")+1 : len(author)-2] | ||
|
||
streamMapStr := streamMapRegexp.FindString(strBody) | ||
streamMapStr = streamMapStr[len(streamMapFilter)+1 : len(streamMapStr)-2] | ||
streamMapStr = s.Replace(streamMapStr, "\\u0026", "&", -1) | ||
|
||
return title, author, streamMapStr, err | ||
} | ||
|
||
func extractFromInfoApi(strBody string) (string, string, string, error) { | ||
bodyMap, err := url.ParseQuery(strBody) | ||
if err != nil { | ||
return "", "", "", err | ||
} | ||
if bodyMap["status"][0] == "fail" { | ||
return "", "", "", errors.New(bodyMap["reason"][0]) | ||
} | ||
var title, author string | ||
if _, exist := bodyMap["title"]; exist { | ||
title = bodyMap["title"][0] | ||
} | ||
if _, exist := bodyMap["author"]; exist { | ||
author = bodyMap["author"][0] | ||
} | ||
streamMapStr := bodyMap["url_encoded_fmt_stream_map"][0] | ||
return title, author, streamMapStr, nil | ||
} | ||
|
||
func constructVideos(title, author, streamMapStr string) ([]Video, error) { | ||
streamList := s.Split(streamMapStr, ",") | ||
videos := make([]Video, 0) | ||
for _, streamItem := range streamList { | ||
stream, err := url.ParseQuery(streamItem) | ||
if err != nil { | ||
return nil, err | ||
} | ||
videos = append(videos, Video{ | ||
title, | ||
author, | ||
stream["quality"][0], | ||
stream["type"][0], | ||
stream["url"][0], | ||
0, | ||
0, | ||
make(chan int), | ||
"./", | ||
}) | ||
} | ||
return videos, nil | ||
} |