rubytube 0.5.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE.txt +21 -21
- data/README.md +91 -28
- data/lib/rubytube/channel.rb +82 -0
- data/lib/rubytube/innertube.rb +83 -105
- data/lib/rubytube/playlist.rb +111 -0
- data/lib/rubytube/stream.rb +126 -95
- data/lib/rubytube/stream_query.rb +56 -61
- data/lib/rubytube/video_item.rb +28 -0
- data/lib/rubytube/youtube.rb +95 -0
- data/lib/rubytube.rb +25 -27
- metadata +12 -58
- data/.rspec +0 -3
- data/.standard.yml +0 -3
- data/CHANGELOG.md +0 -5
- data/CODE_OF_CONDUCT.md +0 -84
- data/Gemfile +0 -12
- data/Rakefile +0 -10
- data/lib/rubytube/cipher.rb +0 -370
- data/lib/rubytube/client.rb +0 -173
- data/lib/rubytube/error.rb +0 -49
- data/lib/rubytube/extractor.rb +0 -171
- data/lib/rubytube/monostate.rb +0 -5
- data/lib/rubytube/parser.rb +0 -159
- data/lib/rubytube/request.rb +0 -73
- data/lib/rubytube/stream_format.rb +0 -152
- data/lib/rubytube/utils.rb +0 -24
- data/lib/rubytube/version.rb +0 -5
- data/sig/rubytube.rbs +0 -4
data/lib/rubytube/extractor.rb
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
module RubyTube
|
|
2
|
-
class Extractor
|
|
3
|
-
class << self
|
|
4
|
-
def playability_status(watch_html)
|
|
5
|
-
player_response = initial_player_response(watch_html)
|
|
6
|
-
player_response = JSON.parse(player_response)
|
|
7
|
-
status_obj = player_response["playabilityStatus"] || {}
|
|
8
|
-
|
|
9
|
-
if status_obj.has_key?("liveStreamability")
|
|
10
|
-
return ["LIVE_STREAM", "Video is a live stream."]
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
if status_obj.has_key?("status")
|
|
14
|
-
if status_obj.has_key?("reason")
|
|
15
|
-
return [status_obj["status"], [status_obj["reason"]]]
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
if status_obj.has_key?("messages")
|
|
19
|
-
return [status_obj["status"], status_obj["messages"]]
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
[nil, [nil]]
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def video_id(url)
|
|
27
|
-
Utils.regex_search(/(?:v=|\/)([0-9A-Za-z_-]{11}).*/, url, 1)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def js_url(html)
|
|
31
|
-
begin
|
|
32
|
-
base_js = get_ytplayer_config(html)["assets"]["js"]
|
|
33
|
-
rescue RegexMatchError, NoMethodError
|
|
34
|
-
base_js = get_ytplayer_js(html)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
"https://youtube.com#{base_js}"
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def mime_type_codec(mime_type_codec)
|
|
41
|
-
pattern = %r{(\w+/\w+);\scodecs="([a-zA-Z\-0-9.,\s]*)"}
|
|
42
|
-
results = mime_type_codec.match(pattern)
|
|
43
|
-
|
|
44
|
-
raise RegexMatchError.new("mime_type_codec, pattern=#{pattern}") if results.nil?
|
|
45
|
-
|
|
46
|
-
mime_type, codecs = results.captures
|
|
47
|
-
[mime_type, codecs.split(",").map(&:strip)]
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def get_ytplayer_js(html)
|
|
51
|
-
js_url_patterns = [
|
|
52
|
-
%r{(/s/player/[\w\d]+/[\w\d_/.]+/base\.js)}
|
|
53
|
-
]
|
|
54
|
-
|
|
55
|
-
js_url_patterns.each do |pattern|
|
|
56
|
-
function_match = html.match(pattern)
|
|
57
|
-
if function_match
|
|
58
|
-
return function_match[1]
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
raise RegexMatchError.new("get_ytplayer_js", "js_url_patterns")
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def get_ytplayer_config(html)
|
|
66
|
-
config_patterns = [
|
|
67
|
-
/ytplayer\.config\s*=\s*/,
|
|
68
|
-
/ytInitialPlayerResponse\s*=\s*/
|
|
69
|
-
]
|
|
70
|
-
|
|
71
|
-
config_patterns.each do |pattern|
|
|
72
|
-
return Parser.parse_for_object(html, pattern)
|
|
73
|
-
rescue HTMLParseError => e
|
|
74
|
-
next
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
setconfig_patterns = [
|
|
78
|
-
/yt\.setConfig\(.*['\"]PLAYER_CONFIG['\"]:\s*/
|
|
79
|
-
]
|
|
80
|
-
|
|
81
|
-
setconfig_patterns.each do |pattern|
|
|
82
|
-
return Parser.parse_for_object(html, pattern)
|
|
83
|
-
rescue HTMLParseError => e
|
|
84
|
-
next
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
raise RegexMatchError.new("get_ytplayer_config", "config_patterns, setconfig_patterns")
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def apply_signature(stream_manifest, vid_info, js)
|
|
91
|
-
cipher = Cipher.new(js)
|
|
92
|
-
|
|
93
|
-
stream_manifest.each_with_index do |stream, i|
|
|
94
|
-
begin
|
|
95
|
-
url = stream["url"]
|
|
96
|
-
rescue NoMethodError
|
|
97
|
-
live_stream = vid_info.fetch("playabilityStatus", {})["liveStreamability"]
|
|
98
|
-
if live_stream
|
|
99
|
-
raise LiveStreamError.new("UNKNOWN")
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
if url.include?("signature") ||
|
|
104
|
-
(!stream.key?("s") && (url.include?("&sig=") || url.include?("&lsig=")))
|
|
105
|
-
# For certain videos, YouTube will just provide them pre-signed, in
|
|
106
|
-
# which case there's no real magic to download them and we can skip
|
|
107
|
-
# the whole signature descrambling entirely.
|
|
108
|
-
next
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
signature = cipher.get_signature(stream["s"])
|
|
112
|
-
|
|
113
|
-
parsed_url = URI.parse(url)
|
|
114
|
-
|
|
115
|
-
query_params = CGI.parse(parsed_url.query)
|
|
116
|
-
query_params.transform_values!(&:first)
|
|
117
|
-
query_params["sig"] = signature
|
|
118
|
-
unless query_params.key?("ratebypass")
|
|
119
|
-
initial_n = query_params["n"].chars
|
|
120
|
-
new_n = cipher.calculate_n(initial_n)
|
|
121
|
-
query_params["n"] = new_n
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
url = "#{parsed_url.scheme}://#{parsed_url.host}#{parsed_url.path}?#{URI.encode_www_form(query_params)}"
|
|
125
|
-
|
|
126
|
-
stream_manifest[i]["url"] = url
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def apply_descrambler(stream_data)
|
|
131
|
-
return if stream_data.has_key?("url")
|
|
132
|
-
|
|
133
|
-
# Merge formats and adaptiveFormats into a single array
|
|
134
|
-
formats = []
|
|
135
|
-
formats += stream_data["formats"] if stream_data.has_key?("formats")
|
|
136
|
-
formats += stream_data["adaptiveFormats"] if stream_data.has_key?("adaptiveFormats")
|
|
137
|
-
|
|
138
|
-
# Extract url and s from signatureCiphers as necessary
|
|
139
|
-
formats.each do |data|
|
|
140
|
-
unless data.has_key?("url")
|
|
141
|
-
if data.has_key?("signatureCipher")
|
|
142
|
-
cipher_url = URI.decode_www_form(data["signatureCipher"]).to_h
|
|
143
|
-
data["url"] = cipher_url["url"]
|
|
144
|
-
data["s"] = cipher_url["s"]
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
data["is_otf"] = data["type"] == "FORMAT_STREAM_TYPE_OTF"
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
formats
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
private
|
|
154
|
-
|
|
155
|
-
def initial_player_response(watch_html)
|
|
156
|
-
patterns = [
|
|
157
|
-
"window\\[['\"]ytInitialPlayerResponse['\"]\\]\\s*=\\s*",
|
|
158
|
-
"ytInitialPlayerResponse\\s*=\\s*"
|
|
159
|
-
]
|
|
160
|
-
|
|
161
|
-
patterns.each do |pattern|
|
|
162
|
-
return Parser.parse_for_object(watch_html, pattern)
|
|
163
|
-
rescue HTMLParseError
|
|
164
|
-
next
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
raise RegexMatchError.new("initial_player_response", "initial_player_response_pattern")
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
end
|
data/lib/rubytube/monostate.rb
DELETED
data/lib/rubytube/parser.rb
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
module RubyTube
|
|
2
|
-
module Parser
|
|
3
|
-
module_function
|
|
4
|
-
|
|
5
|
-
def parse_for_object(html, preceding_regex)
|
|
6
|
-
regex = Regexp.new(preceding_regex)
|
|
7
|
-
result = regex.match(html)
|
|
8
|
-
|
|
9
|
-
if result.nil?
|
|
10
|
-
raise HTMLParseError, "No matches for regex #{preceding_regex}"
|
|
11
|
-
end
|
|
12
|
-
start_index = result.end(0)
|
|
13
|
-
|
|
14
|
-
parse_for_object_from_startpoint(html, start_index)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def find_object_from_startpoint(html, start_point)
|
|
18
|
-
html = html[start_point..-1]
|
|
19
|
-
unless ["{", "["].include?(html[0])
|
|
20
|
-
raise HTMLParseError, "Invalid start point. Start of HTML:\n#{html[0..19]}"
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
last_char = "{"
|
|
24
|
-
curr_char = nil
|
|
25
|
-
stack = [html[0]]
|
|
26
|
-
i = 1
|
|
27
|
-
|
|
28
|
-
context_closers = {
|
|
29
|
-
"{" => "}",
|
|
30
|
-
"[" => "]",
|
|
31
|
-
'"' => '"',
|
|
32
|
-
"/" => "/"
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
while i < html.length
|
|
36
|
-
break if stack.empty?
|
|
37
|
-
|
|
38
|
-
last_char = curr_char unless [" ", '\n'].include?(curr_char)
|
|
39
|
-
curr_char = html[i]
|
|
40
|
-
curr_context = stack.last
|
|
41
|
-
|
|
42
|
-
if curr_char == context_closers[curr_context]
|
|
43
|
-
stack.pop
|
|
44
|
-
i += 1
|
|
45
|
-
next
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
if ['"', "/"].include?(curr_context)
|
|
49
|
-
if curr_char == "\\"
|
|
50
|
-
i += 2
|
|
51
|
-
next
|
|
52
|
-
end
|
|
53
|
-
elsif context_closers.keys.include?(curr_char)
|
|
54
|
-
unless curr_char == "/" && !["(", ",", "=", ":", "[", "!", "&", "|", "?", "{", "}", ";"].include?(last_char)
|
|
55
|
-
stack.push(curr_char)
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
i += 1
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
html[0...i]
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def parse_for_object_from_startpoint(html, start_point)
|
|
66
|
-
html = html[start_point..-1]
|
|
67
|
-
|
|
68
|
-
unless ["{", "["].include?(html[0])
|
|
69
|
-
raise HTMLParseError, "Invalid start point. Start of HTML:\n#{html[0..19]}"
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# First letter MUST be an open brace, so we put that in the stack,
|
|
73
|
-
# and skip the first character.
|
|
74
|
-
last_char = "{"
|
|
75
|
-
curr_char = nil
|
|
76
|
-
stack = [html[0]]
|
|
77
|
-
i = 1
|
|
78
|
-
|
|
79
|
-
context_closers = {
|
|
80
|
-
"{" => "}",
|
|
81
|
-
"[" => "]",
|
|
82
|
-
'"' => '"',
|
|
83
|
-
:"'" => "'",
|
|
84
|
-
"/" => "/" # JavaScript regex
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
while i < html.length
|
|
88
|
-
break if stack.empty?
|
|
89
|
-
|
|
90
|
-
last_char = curr_char unless [" ", '\n'].include?(curr_char)
|
|
91
|
-
curr_char = html[i]
|
|
92
|
-
curr_context = stack.last
|
|
93
|
-
|
|
94
|
-
# If we've reached a context closer, we can remove an element off the stack
|
|
95
|
-
if curr_char == context_closers[curr_context]
|
|
96
|
-
stack.pop
|
|
97
|
-
i += 1
|
|
98
|
-
next
|
|
99
|
-
end
|
|
100
|
-
# Strings and regex expressions require special context handling because they can contain
|
|
101
|
-
# context openers *and* closers
|
|
102
|
-
if ['"', "/"].include?(curr_context)
|
|
103
|
-
# If there's a backslash in a string or regex expression, we skip a character
|
|
104
|
-
if curr_char == "\\"
|
|
105
|
-
i += 2
|
|
106
|
-
next
|
|
107
|
-
end
|
|
108
|
-
elsif context_closers.keys.include?(curr_char)
|
|
109
|
-
# Non-string contexts are when we need to look for context openers.
|
|
110
|
-
unless curr_char == "/" && ["(", ",", "=", ":", "[", "!", "&", "|", "?", "{", "}", ";"].include?(last_char)
|
|
111
|
-
stack << curr_char
|
|
112
|
-
end
|
|
113
|
-
# Slash starts a regular expression depending on context
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
i += 1
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
html[0..(i - 1)]
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def throttling_array_split(js_array)
|
|
123
|
-
results = []
|
|
124
|
-
curr_substring = js_array[1..-1]
|
|
125
|
-
|
|
126
|
-
comma_regex = /,/
|
|
127
|
-
func_regex = /function\([^)]*\)/
|
|
128
|
-
|
|
129
|
-
until curr_substring.empty?
|
|
130
|
-
if curr_substring.start_with?("function")
|
|
131
|
-
match = func_regex.match(curr_substring)
|
|
132
|
-
match_start = match.begin(0)
|
|
133
|
-
match_end = match.end(0)
|
|
134
|
-
|
|
135
|
-
function_text = find_object_from_startpoint(curr_substring, match_end)
|
|
136
|
-
full_function_def = curr_substring[0, match_end + function_text.length]
|
|
137
|
-
results << full_function_def
|
|
138
|
-
curr_substring = curr_substring[full_function_def.length + 1..-1]
|
|
139
|
-
else
|
|
140
|
-
match = comma_regex.match(curr_substring)
|
|
141
|
-
|
|
142
|
-
begin
|
|
143
|
-
match_start = match.begin(0)
|
|
144
|
-
match_end = match.end(0)
|
|
145
|
-
rescue NoMethodError
|
|
146
|
-
match_start = curr_substring.length - 1
|
|
147
|
-
match_end = match_start + 1
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
curr_el = curr_substring[0, match_start]
|
|
151
|
-
results << curr_el
|
|
152
|
-
curr_substring = curr_substring[match_end..-1]
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
results
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
end
|
data/lib/rubytube/request.rb
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
module RubyTube
|
|
2
|
-
module Request
|
|
3
|
-
module_function
|
|
4
|
-
|
|
5
|
-
DEFAULT_RANGE_SIZE = 9437184
|
|
6
|
-
|
|
7
|
-
def get(url, options = {})
|
|
8
|
-
send(:get, url, options).body
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def post(url, options = {})
|
|
12
|
-
send(:post, url, options).body
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def head(url, options = {})
|
|
16
|
-
send(:head, url, options).headers
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def stream(url, timeout: 60, max_retries: 0)
|
|
20
|
-
file_size = DEFAULT_RANGE_SIZE
|
|
21
|
-
downloaded = 0
|
|
22
|
-
|
|
23
|
-
while downloaded < file_size
|
|
24
|
-
stop_pos = [downloaded + DEFAULT_RANGE_SIZE, file_size].min - 1
|
|
25
|
-
range_header = "bytes=#{downloaded}-#{stop_pos}"
|
|
26
|
-
tries = 0
|
|
27
|
-
|
|
28
|
-
while true
|
|
29
|
-
begin
|
|
30
|
-
if tries >= 1 + max_retries
|
|
31
|
-
raise MaxRetriesExceeded
|
|
32
|
-
end
|
|
33
|
-
response = send(:get, "#{url}&range=#{downloaded}-#{stop_pos}")
|
|
34
|
-
break
|
|
35
|
-
rescue Faraday::TimeoutError
|
|
36
|
-
rescue Faraday::ClientError => e
|
|
37
|
-
raise e
|
|
38
|
-
end
|
|
39
|
-
tries += 1
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
if file_size == DEFAULT_RANGE_SIZE
|
|
43
|
-
begin
|
|
44
|
-
resp = send(:get, "#{url}&range=0-99999999999")
|
|
45
|
-
content_range = resp.headers["Content-Length"]
|
|
46
|
-
file_size = content_range.to_i
|
|
47
|
-
rescue KeyError, IndexError, StandardError => e
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
response.body.each_char do |chunk|
|
|
52
|
-
downloaded += chunk.length
|
|
53
|
-
yield chunk
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def send(method, url, options = {})
|
|
59
|
-
headers = {"Content-Type": "text/html"}
|
|
60
|
-
options[:headers] && headers.merge!(options[:headers])
|
|
61
|
-
|
|
62
|
-
connection = Faraday.new(url: url) do |faraday|
|
|
63
|
-
faraday.response :follow_redirects
|
|
64
|
-
faraday.adapter Faraday.default_adapter
|
|
65
|
-
end
|
|
66
|
-
connection.send(method) do |req|
|
|
67
|
-
req.headers = headers
|
|
68
|
-
options[:query] && req.params = options[:query]
|
|
69
|
-
options[:data] && req.body = JSON.dump(options[:data])
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
module RubyTube
|
|
2
|
-
class StreamFormat
|
|
3
|
-
PROGRESSIVE_VIDEO = {
|
|
4
|
-
5 => ["240p", "64kbps"],
|
|
5
|
-
6 => ["270p", "64kbps"],
|
|
6
|
-
13 => ["144p", nil],
|
|
7
|
-
17 => ["144p", "24kbps"],
|
|
8
|
-
18 => ["360p", "96kbps"],
|
|
9
|
-
22 => ["720p", "192kbps"],
|
|
10
|
-
34 => ["360p", "128kbps"],
|
|
11
|
-
35 => ["480p", "128kbps"],
|
|
12
|
-
36 => ["240p", nil],
|
|
13
|
-
37 => ["1080p", "192kbps"],
|
|
14
|
-
38 => ["3072p", "192kbps"],
|
|
15
|
-
43 => ["360p", "128kbps"],
|
|
16
|
-
44 => ["480p", "128kbps"],
|
|
17
|
-
45 => ["720p", "192kbps"],
|
|
18
|
-
46 => ["1080p", "192kbps"],
|
|
19
|
-
59 => ["480p", "128kbps"],
|
|
20
|
-
78 => ["480p", "128kbps"],
|
|
21
|
-
82 => ["360p", "128kbps"],
|
|
22
|
-
83 => ["480p", "128kbps"],
|
|
23
|
-
84 => ["720p", "192kbps"],
|
|
24
|
-
85 => ["1080p", "192kbps"],
|
|
25
|
-
91 => ["144p", "48kbps"],
|
|
26
|
-
92 => ["240p", "48kbps"],
|
|
27
|
-
93 => ["360p", "128kbps"],
|
|
28
|
-
94 => ["480p", "128kbps"],
|
|
29
|
-
95 => ["720p", "256kbps"],
|
|
30
|
-
96 => ["1080p", "256kbps"],
|
|
31
|
-
100 => ["360p", "128kbps"],
|
|
32
|
-
101 => ["480p", "192kbps"],
|
|
33
|
-
102 => ["720p", "192kbps"],
|
|
34
|
-
132 => ["240p", "48kbps"],
|
|
35
|
-
151 => ["720p", "24kbps"],
|
|
36
|
-
300 => ["720p", "128kbps"],
|
|
37
|
-
301 => ["1080p", "128kbps"]
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
DASH_VIDEO = {
|
|
41
|
-
133 => ["240p", nil],
|
|
42
|
-
134 => ["360p", nil],
|
|
43
|
-
135 => ["480p", nil],
|
|
44
|
-
136 => ["720p", nil],
|
|
45
|
-
137 => ["1080p", nil],
|
|
46
|
-
138 => ["2160p", nil],
|
|
47
|
-
160 => ["144p", nil],
|
|
48
|
-
167 => ["360p", nil],
|
|
49
|
-
168 => ["480p", nil],
|
|
50
|
-
169 => ["720p", nil],
|
|
51
|
-
170 => ["1080p", nil],
|
|
52
|
-
212 => ["480p", nil],
|
|
53
|
-
218 => ["480p", nil],
|
|
54
|
-
219 => ["480p", nil],
|
|
55
|
-
242 => ["240p", nil],
|
|
56
|
-
243 => ["360p", nil],
|
|
57
|
-
244 => ["480p", nil],
|
|
58
|
-
245 => ["480p", nil],
|
|
59
|
-
246 => ["480p", nil],
|
|
60
|
-
247 => ["720p", nil],
|
|
61
|
-
248 => ["1080p", nil],
|
|
62
|
-
264 => ["1440p", nil],
|
|
63
|
-
266 => ["2160p", nil],
|
|
64
|
-
271 => ["1440p", nil],
|
|
65
|
-
272 => ["4320p", nil],
|
|
66
|
-
278 => ["144p", nil],
|
|
67
|
-
298 => ["720p", nil],
|
|
68
|
-
299 => ["1080p", nil],
|
|
69
|
-
302 => ["720p", nil],
|
|
70
|
-
303 => ["1080p", nil],
|
|
71
|
-
308 => ["1440p", nil],
|
|
72
|
-
313 => ["2160p", nil],
|
|
73
|
-
315 => ["2160p", nil],
|
|
74
|
-
330 => ["144p", nil],
|
|
75
|
-
331 => ["240p", nil],
|
|
76
|
-
332 => ["360p", nil],
|
|
77
|
-
333 => ["480p", nil],
|
|
78
|
-
334 => ["720p", nil],
|
|
79
|
-
335 => ["1080p", nil],
|
|
80
|
-
336 => ["1440p", nil],
|
|
81
|
-
337 => ["2160p", nil],
|
|
82
|
-
394 => ["144p", nil],
|
|
83
|
-
395 => ["240p", nil],
|
|
84
|
-
396 => ["360p", nil],
|
|
85
|
-
397 => ["480p", nil],
|
|
86
|
-
398 => ["720p", nil],
|
|
87
|
-
399 => ["1080p", nil],
|
|
88
|
-
400 => ["1440p", nil],
|
|
89
|
-
401 => ["2160p", nil],
|
|
90
|
-
402 => ["4320p", nil],
|
|
91
|
-
571 => ["4320p", nil],
|
|
92
|
-
694 => ["144p", nil],
|
|
93
|
-
695 => ["240p", nil],
|
|
94
|
-
696 => ["360p", nil],
|
|
95
|
-
697 => ["480p", nil],
|
|
96
|
-
698 => ["720p", nil],
|
|
97
|
-
699 => ["1080p", nil],
|
|
98
|
-
700 => ["1440p", nil],
|
|
99
|
-
701 => ["2160p", nil],
|
|
100
|
-
702 => ["4320p", nil]
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
DASH_AUDIO = {
|
|
104
|
-
139 => [nil, "48kbps"],
|
|
105
|
-
140 => [nil, "128kbps"],
|
|
106
|
-
141 => [nil, "256kbps"],
|
|
107
|
-
171 => [nil, "128kbps"],
|
|
108
|
-
172 => [nil, "256kbps"],
|
|
109
|
-
249 => [nil, "50kbps"],
|
|
110
|
-
250 => [nil, "70kbps"],
|
|
111
|
-
251 => [nil, "160kbps"],
|
|
112
|
-
256 => [nil, "192kbps"],
|
|
113
|
-
258 => [nil, "384kbps"],
|
|
114
|
-
325 => [nil, nil],
|
|
115
|
-
328 => [nil, nil]
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
ITAGS = {
|
|
119
|
-
**PROGRESSIVE_VIDEO,
|
|
120
|
-
**DASH_VIDEO,
|
|
121
|
-
**DASH_AUDIO
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
HDR = [330, 331, 332, 333, 334, 335, 336, 337]
|
|
125
|
-
FORMAT_3D = [82, 83, 84, 85, 100, 101, 102]
|
|
126
|
-
LIVE = [91, 92, 93, 94, 95, 96, 132, 151]
|
|
127
|
-
|
|
128
|
-
attr_reader :itag, :resolution, :abr
|
|
129
|
-
|
|
130
|
-
def initialize(_itag)
|
|
131
|
-
@itag = _itag
|
|
132
|
-
|
|
133
|
-
@resolution, @abr = ITAGS.fetch(itag)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def is_live?
|
|
137
|
-
!!LIVE[itag]
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def is_3d?
|
|
141
|
-
!!FORMAT_3D[itag]
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def is_hdr?
|
|
145
|
-
!!HDR[itag]
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def is_dash?
|
|
149
|
-
!!DASH_AUDIO[itag] || !!DASH_VIDEO[itag]
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
end
|
data/lib/rubytube/utils.rb
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
module RubyTube
|
|
2
|
-
module Utils
|
|
3
|
-
module_function
|
|
4
|
-
|
|
5
|
-
def regex_search(pattern, string, group)
|
|
6
|
-
match = string.match(pattern)
|
|
7
|
-
if match
|
|
8
|
-
return match[group]
|
|
9
|
-
end
|
|
10
|
-
nil
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def target_directory(output_path = nil)
|
|
14
|
-
if output_path
|
|
15
|
-
result = File.join(Dir.pwd, output_path) unless File.absolute_path?(output_path)
|
|
16
|
-
else
|
|
17
|
-
result = Dir.pwd
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
FileUtils.mkdir_p(result) unless Dir.exist?(result)
|
|
21
|
-
result
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
data/lib/rubytube/version.rb
DELETED
data/sig/rubytube.rbs
DELETED