123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- package youtube
- import (
- "encoding/json"
- "errors"
- "fmt"
- "io/ioutil"
- "net/http"
- "net/url"
- "strconv"
- "strings"
- "time"
- "unicode"
- )
- var (
- ErrNoSuchVideo = errors.New("no such video")
- ErrInvalidAPIKey = errors.New("invalid or missing API key")
- ErrInvalidID = errors.New("invalid or missing video ID")
- )
- type VideoInfo struct {
- ID string
- Title string
- Duration time.Duration
- }
- func GetVideoInfo(apiKey, id string) (*VideoInfo, error) {
- const videoURL = "https://www.googleapis.com/youtube/v3/videos?id=%s&part=contentDetails,snippet&key=%s"
- apiKey = strings.TrimSpace(apiKey)
- apiKey = url.QueryEscape(apiKey)
- if len(apiKey) == 0 {
- return nil, ErrInvalidAPIKey
- }
- id = strings.TrimSpace(id)
- id = url.QueryEscape(id)
- if len(id) == 0 {
- return nil, ErrInvalidID
- }
- var resp videoListResponse
- url := fmt.Sprintf(videoURL, id, apiKey)
- err := fetch(url, &resp)
- if err != nil {
- return nil, err
- }
- if len(resp.Items) == 0 {
- return nil, ErrNoSuchVideo
- }
- item := resp.Items[0]
- return &VideoInfo{
- ID: item.ID,
- Title: item.Snippet.Title,
- Duration: parseISO8601(item.ContentDetails.Duration),
- }, nil
- }
- type videoListResponse struct {
- Items []struct {
- ID string `json:"id"`
- Snippet struct {
- Title string `json:"title"`
- } `json:snippet`
- ContentDetails struct {
- Duration string `json:"duration"`
- } `json:"contentDetails"`
- } `json:"items"`
- }
- func fetch(url string, v interface{}) error {
- resp, err := http.Get(url)
- if err != nil {
- return err
- }
- data, err := ioutil.ReadAll(resp.Body)
- resp.Body.Close()
- if err != nil {
- return err
- }
- return json.Unmarshal(data, v)
- }
- func parseISO8601(v string) time.Duration {
- v = strings.ToUpper(v)
- v = strings.TrimSpace(v)
- if len(v) < 3 || v[0] != 'P' {
- return 0
- }
- var err error
- var sum time.Duration
- var inDate bool
- var n int64
- digits := make([]rune, 0, len(v)/2)
- for _, r := range v {
- switch r {
- case 'P':
- inDate = true
- case 'T':
- inDate = false
- case 'Y':
- n, err = strconv.ParseInt(string(digits), 10, 32)
- sum += time.Duration(n) * time.Hour * 8760
- digits = digits[:0]
- case 'M':
- if inDate {
- n, err = strconv.ParseInt(string(digits), 10, 32)
- sum += time.Duration(n) * time.Hour * 730
- digits = digits[:0]
- } else {
- n, err = strconv.ParseInt(string(digits), 10, 32)
- sum += time.Duration(n) * time.Minute
- digits = digits[:0]
- }
- case 'W':
- n, err = strconv.ParseInt(string(digits), 10, 32)
- sum += time.Duration(n) * time.Hour * 168
- digits = digits[:0]
- case 'D':
- n, err = strconv.ParseInt(string(digits), 10, 32)
- sum += time.Duration(n) * time.Hour * 24
- digits = digits[:0]
- case 'H':
- n, err = strconv.ParseInt(string(digits), 10, 32)
- sum += time.Duration(n) * time.Hour
- digits = digits[:0]
- case 'S':
- n, err = strconv.ParseInt(string(digits), 10, 32)
- sum += time.Duration(n) * time.Second
- digits = digits[:0]
- default:
- if unicode.IsDigit(r) {
- digits = append(digits, r)
- } else {
- return 0
- }
- }
- if err != nil {
- return 0
- }
- }
- return sum
- }
|