Created
December 9, 2016 10:20
-
-
Save creative-link/e9c86c6a950f4c28f602d09599cf350d to your computer and use it in GitHub Desktop.
This file contains hidden or 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
package main | |
import ( | |
"fmt" | |
"io" | |
"io/ioutil" | |
"bytes" | |
"time" | |
"errors" | |
"net/url" | |
"net/http" | |
. "strings" | |
"encoding/json" | |
) | |
type Video struct { | |
Title string | |
url string | |
quality string | |
extension string | |
} | |
func main() { | |
http.HandleFunc("/getById", retrieveHandler) | |
http.ListenAndServe(":1488", nil) | |
} | |
func retrieveHandler(w http.ResponseWriter, r *http.Request) { | |
id := r.URL.Query().Get("id") | |
dl := r.URL.Query().Get("dl") | |
rnd := r.URL.Query().Get("r") | |
url := fmt.Sprintf("https://www.youtube.com/watch?v=%s", id) | |
// Получаем страницу с видео | |
videoPage := GetHttpFromUrl(url) | |
// Получаем json-данные со страницы | |
jsonData, err := GetJsonFromHttp(videoPage) | |
fmt.Printf("Error: %v \n", err) | |
// Получаем ссылку на видео-файл в mp4 | |
videoLink := GetVideoListFromJson(jsonData) | |
// Скачиваем | |
videoData := GetHttpFromUrl(videoLink) | |
// Запихиваем его в выходной поток | |
fileBytes := bytes.NewReader(videoData) | |
if dl != "" && rnd != "" { | |
w.Header().Set("Content-Disposition", "attachment; filename="+dl+".mp3") | |
w.Header().Set("Content-Type", r.Header.Get("Content-Type")) | |
io.Copy(w, fileBytes) | |
} else { | |
http.ServeContent(w, r, "song.mp3", time.Now(), fileBytes) | |
} | |
} | |
func GetJsonFromHttp(httpData []byte) (map[string]interface{}, error) { | |
//Find out if this page is age-restricted | |
if bytes.Index(httpData, []byte("og:restrictions:age")) != -1 { | |
return nil, errors.New("this page is age-restricted") | |
} | |
//Find begining of json data | |
jsonBeg := "ytplayer.config = {" | |
beg := bytes.Index(httpData, []byte(jsonBeg)) | |
if beg == -1 { //pattern not found | |
return nil, errors.New("Not found") | |
} | |
beg += len(jsonBeg) //len(jsonBeg) returns the number of bytes in jsonBeg | |
//Find offset of json data | |
unmatchedBrackets := 1 | |
offset := 0 | |
for unmatchedBrackets > 0 { | |
nextRight := bytes.Index(httpData[beg+offset:], []byte("}")) | |
if nextRight == -1 { | |
return nil, errors.New("unmatched brackets") | |
} | |
unmatchedBrackets -= 1 | |
unmatchedBrackets += bytes.Count(httpData[beg+offset:beg+offset+nextRight], []byte("{")) | |
offset += nextRight + 1 | |
} | |
//Load json data | |
var f interface{} | |
err := json.Unmarshal(httpData[beg-1:beg+offset], &f) | |
if err != nil { | |
return nil, err | |
} | |
return f.(map[string]interface{}), nil | |
} | |
func GetVideoListFromJson(jsonData map[string]interface{}) (string) { | |
args := jsonData["args"].(map[string]interface{}) | |
encodedStreamMap := args["url_encoded_fmt_stream_map"].(string) | |
//Videos are seperated by "," | |
videoListStr := Split(encodedStreamMap, ",") | |
for _, videoStr := range videoListStr { | |
//Parameters of a video are seperated by "&" | |
videoParams := Split(videoStr, "&") | |
var video Video | |
for _, param := range videoParams { | |
/*Unescape the url encoding characters. | |
Only do it after seperation because | |
there are "," and "&" escaped in url*/ | |
param, err := url.QueryUnescape(param) | |
if err != nil { | |
return "" | |
} | |
switch { | |
case HasPrefix(param, "quality"): | |
video.quality = param[8:] | |
case HasPrefix(param, "type"): | |
//type and codecs are seperated by ";" | |
video.extension = Split(param, ";")[0][5:] | |
case HasPrefix(param, "url"): | |
video.url = param[4:] | |
} | |
} | |
fmt.Printf("Video: %v => %v\n\n", video.extension, video.url) | |
if (video.extension == "video/mp4") { | |
return video.url | |
} | |
} | |
return "" | |
} | |
func GetHttpFromUrl(url string) (body []byte) { | |
resp, err := http.Get(url) | |
if err != nil { | |
fmt.Printf("error: %v \n", err) | |
return nil | |
} | |
defer resp.Body.Close() | |
body, err = ioutil.ReadAll(resp.Body) | |
if err != nil { | |
fmt.Printf("error: %v \n", err) | |
body = nil | |
} | |
return body | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment