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