1package ytdl 2 3import ( 4 "fmt" 5 "net/url" 6 "strconv" 7 "strings" 8) 9 10// FormatKey is a string type containing a key in a video format map 11type FormatKey string 12 13// Available format Keys 14const ( 15 FormatExtensionKey FormatKey = "ext" 16 FormatResolutionKey FormatKey = "res" 17 FormatVideoEncodingKey FormatKey = "videnc" 18 FormatAudioEncodingKey FormatKey = "audenc" 19 FormatItagKey FormatKey = "itag" 20 FormatAudioBitrateKey FormatKey = "audbr" 21 FormatFPSKey FormatKey = "fps" 22) 23 24type Range struct { 25 Start string `json:"start"` 26 End string `json:"end"` 27} 28 29type Format struct { 30 Itag 31 Adaptive bool 32 // FromDASH indicates that the stream 33 // was extracted from the DASH manifest file 34 FromDASH bool 35 Index *Range 36 Init *Range 37 url string 38 s string 39 sig string 40 stream string 41 conn string 42 sp string 43} 44 45func parseFormat(queryString string) (*Format, error) { 46 query, err := url.ParseQuery(queryString) 47 if err != nil { 48 return nil, err 49 } 50 51 format := Format{} 52 53 for k, v := range query { 54 switch k { 55 case "itag": 56 i, err := strconv.Atoi(v[0]) 57 if err != nil { 58 return nil, fmt.Errorf("unable to parse itag param: %w", err) 59 } 60 61 itag := getItag(i) 62 if itag == nil { 63 return nil, fmt.Errorf("no metadata found for itag: %v", i) 64 } 65 66 format.Itag = *itag 67 case "url": 68 format.url = v[0] 69 case "s": 70 format.s = v[0] 71 case "sig": 72 format.sig = v[0] 73 case "stream": 74 format.stream = v[0] 75 case "conn": 76 format.conn = v[0] 77 case "sp": 78 format.sp = v[0] 79 case "index": 80 format.Index, err = parseRange(v[0]) 81 if err != nil { 82 return nil, fmt.Errorf("unable to parse index range") 83 } 84 case "init": 85 format.Init, err = parseRange(v[0]) 86 if err != nil { 87 return nil, fmt.Errorf("unable to parse init range") 88 } 89 } 90 } 91 return &format, nil 92} 93 94func parseRange(s string) (*Range, error) { 95 sa := strings.Split(s, "-") 96 if len(sa) != 2 { 97 return nil, fmt.Errorf("Invalid range") 98 } 99 return &Range{Start: sa[0], End: sa[1]}, nil 100} 101 102// ValueForKey gets the format value for a format key, used for filtering 103func (f *Format) ValueForKey(key FormatKey) interface{} { 104 switch key { 105 case FormatItagKey: 106 return f.Itag.Number 107 case FormatExtensionKey: 108 return f.Extension 109 case FormatResolutionKey: 110 return f.Resolution 111 case FormatVideoEncodingKey: 112 return f.VideoEncoding 113 case FormatAudioEncodingKey: 114 return f.AudioEncoding 115 case FormatAudioBitrateKey: 116 return f.AudioBitrate 117 case FormatFPSKey: 118 return f.FPS 119 default: 120 return fmt.Errorf("Unknown format key: %v", key) 121 } 122} 123 124func (f *Format) CompareKey(other *Format, key FormatKey) int { 125 switch key { 126 case FormatResolutionKey: 127 return f.resolution() - other.resolution() 128 case FormatAudioBitrateKey: 129 return f.AudioBitrate - other.AudioBitrate 130 case FormatFPSKey: 131 return f.FPS - other.FPS 132 default: 133 return 0 134 } 135} 136 137// width in pixels 138func (f *Format) resolution() int { 139 res := f.Itag.Resolution 140 if len(res) < 2 { 141 return 0 142 } 143 144 width, _ := strconv.Atoi(res[:len(res)-2]) 145 return width 146} 147