Skip to content

Instantly share code, notes, and snippets.

@creative-link
Created December 9, 2016 10:20
Show Gist options
  • Save creative-link/e9c86c6a950f4c28f602d09599cf350d to your computer and use it in GitHub Desktop.
Save creative-link/e9c86c6a950f4c28f602d09599cf350d to your computer and use it in GitHub Desktop.
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