viddl-rb 0.68 → 0.70
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.
- data/Gemfile.lock +24 -5
- data/README.md +110 -49
- data/Rakefile +18 -8
- data/TODO.txt +3 -0
- data/bin/helper/downloader.rb +25 -0
- data/bin/helper/driver.rb +47 -0
- data/bin/helper/parameter-parser.rb +67 -0
- data/bin/viddl-rb +39 -118
- data/helper/audio-helper.rb +49 -0
- data/helper/download-helper.rb +86 -81
- data/helper/plugin-helper.rb +60 -16
- data/helper/utility-helper.rb +16 -0
- data/lib/viddl-rb.rb +113 -0
- data/plugins/blip.rb +1 -1
- data/plugins/dailymotion.rb +44 -0
- data/plugins/metacafe.rb +4 -6
- data/plugins/soundcloud.rb +3 -2
- data/plugins/veoh.rb +39 -39
- data/plugins/vimeo.rb +5 -7
- data/plugins/youtube.rb +196 -197
- metadata +13 -5
data/plugins/metacafe.rb
CHANGED
@@ -11,7 +11,7 @@ class Metacafe < PluginBase
|
|
11
11
|
url.include?("metacafe.com")
|
12
12
|
end
|
13
13
|
|
14
|
-
def self.get_urls_and_filenames(url)
|
14
|
+
def self.get_urls_and_filenames(url, options = {})
|
15
15
|
video_id = get_video_id(url)
|
16
16
|
info_url = API_BASE + "item/#{video_id}" #use the API to get the full video url
|
17
17
|
info_doc = Nokogiri::XML(open(info_url))
|
@@ -25,16 +25,14 @@ class Metacafe < PluginBase
|
|
25
25
|
file_info = get_file_info(redirect_url, video_id)
|
26
26
|
key_string = get_file_key(redirect_url)
|
27
27
|
file_url_with_key = file_info[:file_url] + "?__gda__=#{key_string}"
|
28
|
-
|
29
|
-
|
30
|
-
[{:url => escaped_url, :name => get_video_name(video_swf_url) + file_info[:extension]}]
|
28
|
+
|
29
|
+
[{:url => file_url_with_key, :name => get_video_name(video_swf_url) + file_info[:extension]}]
|
31
30
|
end
|
32
31
|
|
33
32
|
def self.get_video_id(url)
|
34
33
|
id = url[/watch\/(\d+)/, 1]
|
35
34
|
unless id
|
36
|
-
|
37
|
-
exit
|
35
|
+
raise CouldNotDownloadVideoError, "Can only download videos that has the ID in the URL."
|
38
36
|
end
|
39
37
|
id
|
40
38
|
end
|
data/plugins/soundcloud.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'rest_client'
|
1
2
|
class Soundcloud < PluginBase
|
2
3
|
# this will be called by the main app to check whether this plugin is responsible for the url passed
|
3
4
|
def self.matches_provider?(url)
|
@@ -5,8 +6,8 @@ class Soundcloud < PluginBase
|
|
5
6
|
end
|
6
7
|
|
7
8
|
# return the url for original video file and title
|
8
|
-
def self.get_urls_and_filenames(url)
|
9
|
-
doc = Nokogiri::
|
9
|
+
def self.get_urls_and_filenames(url, options = {})
|
10
|
+
doc = Nokogiri::HTML(RestClient.get(url).body)
|
10
11
|
download_filename = doc.at("#main-content-inner img[class=waveform]").attributes["src"].value.to_s.match(/\.com\/(.+)\_/)[1]
|
11
12
|
download_url = "http://media.soundcloud.com/stream/#{download_filename}"
|
12
13
|
file_name = transliterate("#{doc.at('//h1/em').text.chomp}") + ".mp3"
|
data/plugins/veoh.rb
CHANGED
@@ -1,45 +1,45 @@
|
|
1
1
|
class Veoh < PluginBase
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
2
|
+
VEOH_API_BASE = "http://www.veoh.com/api/"
|
3
|
+
PREFERRED_FORMATS = [:mp4, :flash] # mp4 is preferred because it enables downloading full videos and not just previews
|
4
|
+
|
5
|
+
#this will be called by the main app to check whether this plugin is responsible for the url passed
|
6
|
+
def self.matches_provider?(url)
|
7
|
+
url.include?("veoh.com")
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
def self.get_urls_and_filenames(url, options = {})
|
11
|
+
veoh_id = url[/\/watch\/([\w\d]+)/, 1]
|
12
|
+
info_url = "#{VEOH_API_BASE}findByPermalink?permalink=#{veoh_id}"
|
13
|
+
info_doc = Nokogiri::XML(open(info_url))
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
download_url = get_download_url(info_doc)
|
16
|
+
file_name = get_file_name(info_doc, download_url)
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
18
|
+
[{:url => download_url, :name => file_name}]
|
19
|
+
end
|
20
|
+
|
21
|
+
#returns the first valid download url string, in order of the prefered formats, that is found for the video
|
22
|
+
def self.get_download_url(info_doc)
|
23
|
+
PREFERRED_FORMATS.each do |format|
|
24
|
+
a = get_attribute(format)
|
25
|
+
download_attr = info_doc.xpath('//rsp/videoList/video').first.attributes[a]
|
26
|
+
return(download_attr.content) unless download_attr.nil? || download_attr.content.empty?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
#the file name string is a combination of the video name and the extension
|
31
|
+
def self.get_file_name(info_doc, download_url)
|
32
|
+
name = info_doc.xpath('//rsp/videoList/video').first.attributes['title'].content
|
33
|
+
extension = download_url[/\/[\w\d]+(\.[\w\d]+)\?ct/, 1]
|
34
|
+
PluginBase.make_filename_safe(name) + extension
|
35
|
+
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
37
|
+
def self.get_attribute(format)
|
38
|
+
case format
|
39
|
+
when :mp4
|
40
|
+
"ipodUrl"
|
41
|
+
when :flash
|
42
|
+
"previewUrl"
|
43
|
+
end
|
44
|
+
end
|
45
45
|
end
|
data/plugins/vimeo.rb
CHANGED
@@ -1,12 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
1
|
class Vimeo < PluginBase
|
4
2
|
#this will be called by the main app to check whether this plugin is responsible for the url passed
|
5
3
|
def self.matches_provider?(url)
|
6
4
|
url.include?("vimeo.com")
|
7
5
|
end
|
8
6
|
|
9
|
-
def self.get_urls_and_filenames(url)
|
7
|
+
def self.get_urls_and_filenames(url, options = {})
|
10
8
|
#the vimeo ID consists of 7 decimal numbers in the URL
|
11
9
|
vimeo_id = url[/\d{7,8}/]
|
12
10
|
|
@@ -15,9 +13,9 @@ class Vimeo < PluginBase
|
|
15
13
|
|
16
14
|
video_page = agent.get("http://vimeo.com/#{vimeo_id}")
|
17
15
|
page_html = video_page.root.inner_html
|
18
|
-
|
19
|
-
title =
|
20
|
-
puts "[VIMEO] Title: #{title}"
|
16
|
+
doc = Nokogiri::HTML(page_html)
|
17
|
+
title = doc.at('meta[property="og:title"]').attributes['content'].value
|
18
|
+
puts "[VIMEO] Title: #{title.inspect}"
|
21
19
|
|
22
20
|
#the timestamp and sig info is in the embedded player javascript in the video page
|
23
21
|
timestamp = page_html[/"timestamp":(\d+),/, 1]
|
@@ -31,4 +29,4 @@ class Vimeo < PluginBase
|
|
31
29
|
|
32
30
|
[{:url => download_url, :name => file_name}]
|
33
31
|
end
|
34
|
-
end
|
32
|
+
end
|
data/plugins/youtube.rb
CHANGED
@@ -1,197 +1,196 @@
|
|
1
|
-
|
2
|
-
class Youtube < PluginBase
|
3
|
-
#this will be called by the main app to check whether this plugin is responsible for the url passed
|
4
|
-
def self.matches_provider?(url)
|
5
|
-
url.include?("youtube.com") || url.include?("youtu.be")
|
6
|
-
end
|
7
|
-
|
8
|
-
#get all videos and return their urls in an array
|
9
|
-
def self.get_video_urls(feed_url)
|
10
|
-
puts "[YOUTUBE] Retrieving videos..."
|
11
|
-
urls_titles = Hash.new
|
12
|
-
result_feed = Nokogiri::
|
13
|
-
urls_titles.merge!(grab_ut(result_feed))
|
14
|
-
|
15
|
-
#as long as the feed has a next link we follow it and add the resulting video urls
|
16
|
-
loop do
|
17
|
-
next_link = result_feed.search("//feed/link[@rel='next']").first
|
18
|
-
break if next_link.nil?
|
19
|
-
result_feed = Nokogiri::HTML(open(next_link["href"]))
|
20
|
-
urls_titles.merge!(grab_ut(result_feed))
|
21
|
-
end
|
22
|
-
|
23
|
-
self.filter_urls(urls_titles)
|
24
|
-
end
|
25
|
-
|
26
|
-
#returns only the urls that match the --filter argument regex (if present)
|
27
|
-
def self.filter_urls(url_hash)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
#
|
99
|
-
#
|
100
|
-
#
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
else
|
109
|
-
puts "[YOUTUBE] ID FOUND: #{video_id}"
|
110
|
-
end
|
111
|
-
#let's get some infos about the video. data is urlencoded
|
112
|
-
yt_url = "http://www.youtube.com/get_video_info?video_id=#{video_id}"
|
113
|
-
video_info =
|
114
|
-
#converting the huge infostring into a hash. simply by splitting it at the & and then splitting it into key and value arround the =
|
115
|
-
#[...]blabla=blubb&narf=poit&marc=awesome[...]
|
116
|
-
video_info_hash = Hash[*video_info.split("&").collect { |v|
|
117
|
-
key, encoded_value = v.split("=")
|
118
|
-
if encoded_value.to_s.empty?
|
119
|
-
value = ""
|
120
|
-
else
|
121
|
-
#decode until everything is "normal"
|
122
|
-
while (encoded_value != CGI::unescape(encoded_value)) do
|
123
|
-
#"decoding"
|
124
|
-
encoded_value = CGI::unescape(encoded_value)
|
125
|
-
end
|
126
|
-
value = encoded_value
|
127
|
-
end
|
128
|
-
|
129
|
-
if key =~ /_map/
|
130
|
-
orig_value = value
|
131
|
-
value = value.split(",")
|
132
|
-
if key == "url_encoded_fmt_stream_map"
|
133
|
-
url_array = orig_value.split("url=").map{|url_string| url_string.chomp(",")}
|
134
|
-
result_hash = {}
|
135
|
-
url_array.each do |url|
|
136
|
-
next if url.to_s.empty?
|
137
|
-
format_id = url
|
138
|
-
result_hash[format_id] = url
|
139
|
-
end
|
140
|
-
value = result_hash
|
141
|
-
elsif key == "fmt_map"
|
142
|
-
value = Hash[*value.collect { |v|
|
143
|
-
k2, *v2 = v.split("/")
|
144
|
-
[k2, v2]
|
145
|
-
}.flatten(1)]
|
146
|
-
elsif key == "fmt_url_map" || key == "fmt_stream_map"
|
147
|
-
Hash[*value.collect { |v| v.split("|")}.flatten]
|
148
|
-
end
|
149
|
-
end
|
150
|
-
[key, value]
|
151
|
-
}.flatten]
|
152
|
-
|
153
|
-
if video_info_hash["status"] == "fail"
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
format_ext = {}
|
167
|
-
format_ext["
|
168
|
-
format_ext["
|
169
|
-
format_ext["
|
170
|
-
format_ext["
|
171
|
-
format_ext["
|
172
|
-
format_ext["
|
173
|
-
format_ext["
|
174
|
-
format_ext["
|
175
|
-
format_ext["
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
puts "[YOUTUBE]
|
184
|
-
puts "[YOUTUBE]
|
185
|
-
|
186
|
-
#
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
end
|
1
|
+
|
2
|
+
class Youtube < PluginBase
|
3
|
+
#this will be called by the main app to check whether this plugin is responsible for the url passed
|
4
|
+
def self.matches_provider?(url)
|
5
|
+
url.include?("youtube.com") || url.include?("youtu.be")
|
6
|
+
end
|
7
|
+
|
8
|
+
#get all videos and return their urls in an array
|
9
|
+
def self.get_video_urls(feed_url)
|
10
|
+
puts "[YOUTUBE] Retrieving videos..."
|
11
|
+
urls_titles = Hash.new
|
12
|
+
result_feed = Nokogiri::XML(open(feed_url))
|
13
|
+
urls_titles.merge!(grab_ut(result_feed))
|
14
|
+
|
15
|
+
#as long as the feed has a next link we follow it and add the resulting video urls
|
16
|
+
loop do
|
17
|
+
next_link = result_feed.search("//feed/link[@rel='next']").first
|
18
|
+
break if next_link.nil?
|
19
|
+
result_feed = Nokogiri::HTML(open(next_link["href"]))
|
20
|
+
urls_titles.merge!(grab_ut(result_feed))
|
21
|
+
end
|
22
|
+
|
23
|
+
self.filter_urls(urls_titles)
|
24
|
+
end
|
25
|
+
|
26
|
+
#returns only the urls that match the --filter argument regex (if present)
|
27
|
+
def self.filter_urls(url_hash)
|
28
|
+
if @filter
|
29
|
+
puts "[YOUTUBE] Using filter: #{@filter}"
|
30
|
+
filtered = url_hash.select { |url, title| title =~ @filter }
|
31
|
+
filtered.keys
|
32
|
+
else
|
33
|
+
url_hash.keys
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
#extract all video urls and their titles from a feed and return in a hash
|
38
|
+
def self.grab_ut(feed)
|
39
|
+
feed.remove_namespaces! #so that we can get to the titles easily
|
40
|
+
urls = feed.search("//entry/link[@rel='alternate']").map { |link| link["href"] }
|
41
|
+
titles = feed.search("//entry/group/title").map { |title| title.text }
|
42
|
+
Hash[urls.zip(titles)] #hash like this: url => title
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.parse_playlist(url)
|
46
|
+
#http://www.youtube.com/view_play_list?p=F96B063007B44E1E&search_query=welt+auf+schwäbisch
|
47
|
+
#http://www.youtube.com/watch?v=9WEP5nCxkEY&videos=jKY836_WMhE&playnext_from=TL&playnext=1
|
48
|
+
#http://www.youtube.com/watch?v=Tk78sr5JMIU&videos=jKY836_WMhE
|
49
|
+
|
50
|
+
playlist_ID = url[/(?:list=PL|p=)(\w{16})&?/,1]
|
51
|
+
puts "[YOUTUBE] Playlist ID: #{playlist_ID}"
|
52
|
+
feed_url = "http://gdata.youtube.com/feeds/api/playlists/#{playlist_ID}?&max-results=50&v=2"
|
53
|
+
url_array = self.get_video_urls(feed_url)
|
54
|
+
puts "[YOUTUBE] #{url_array.size} links found!"
|
55
|
+
url_array
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.parse_user(username)
|
59
|
+
puts "[YOUTUBE] User: #{username}"
|
60
|
+
feed_url = "http://gdata.youtube.com/feeds/api/users/#{username}/uploads?&max-results=50&v=2"
|
61
|
+
url_array = get_video_urls(feed_url)
|
62
|
+
puts "[YOUTUBE] #{url_array.size} links found!"
|
63
|
+
url_array
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.get_urls_and_filenames(url, options = {})
|
67
|
+
@filter = options[:playlist_filter] #used to filter a playlist in self.filter_urls
|
68
|
+
return_values = []
|
69
|
+
if url.include?("view_play_list") || url.include?("playlist?list=") #if playlist
|
70
|
+
puts "[YOUTUBE] playlist found! analyzing..."
|
71
|
+
files = self.parse_playlist(url)
|
72
|
+
puts "[YOUTUBE] Starting playlist download"
|
73
|
+
files.each do |file|
|
74
|
+
puts "[YOUTUBE] Downloading next movie on the playlist (#{file})"
|
75
|
+
return_values << self.grab_single_url_filename(file)
|
76
|
+
end
|
77
|
+
elsif match = url.match(/\/user\/([\w\d]+)$/) #if user url, e.g. youtube.com/user/woot
|
78
|
+
username = match[1]
|
79
|
+
video_urls = self.parse_user(username)
|
80
|
+
puts "[YOUTUBE] Starting user videos download"
|
81
|
+
video_urls.each do |url|
|
82
|
+
puts "[YOUTUBE] Downloading next user video (#{url})"
|
83
|
+
return_values << self.grab_single_url_filename(url)
|
84
|
+
end
|
85
|
+
else #if single video
|
86
|
+
return_values << self.grab_single_url_filename(url)
|
87
|
+
end
|
88
|
+
return_values.reject! { |value| value == :no_embed } #remove results that can not be downloaded
|
89
|
+
|
90
|
+
if return_values.empty?
|
91
|
+
raise CouldNotDownloadVideoError, "No videos could be downloaded - embedding disabled."
|
92
|
+
else
|
93
|
+
return_values
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.grab_single_url_filename(url)
|
98
|
+
#the youtube video ID looks like this: [...]v=abc5a5_afe5agae6g&[...], we only want the ID (the \w in the brackets)
|
99
|
+
#addition: might also look like this /v/abc5-a5afe5agae6g
|
100
|
+
# alternative: video_id = url[/v[\/=]([\w-]*)&?/, 1]
|
101
|
+
# First get the redirect
|
102
|
+
if url.include?("youtu.be")
|
103
|
+
url = open(url).base_uri.to_s
|
104
|
+
end
|
105
|
+
video_id = url[/(v|embed)[=\/]([^\/\?\&]*)/,2]
|
106
|
+
if video_id.nil?
|
107
|
+
raise CouldNotDownloadVideoError, "No video id found."
|
108
|
+
else
|
109
|
+
puts "[YOUTUBE] ID FOUND: #{video_id}"
|
110
|
+
end
|
111
|
+
#let's get some infos about the video. data is urlencoded
|
112
|
+
yt_url = "http://www.youtube.com/get_video_info?video_id=#{video_id}"
|
113
|
+
video_info = RestClient.get(yt_url).body
|
114
|
+
#converting the huge infostring into a hash. simply by splitting it at the & and then splitting it into key and value arround the =
|
115
|
+
#[...]blabla=blubb&narf=poit&marc=awesome[...]
|
116
|
+
video_info_hash = Hash[*video_info.split("&").collect { |v|
|
117
|
+
key, encoded_value = v.split("=")
|
118
|
+
if encoded_value.to_s.empty?
|
119
|
+
value = ""
|
120
|
+
else
|
121
|
+
#decode until everything is "normal"
|
122
|
+
while (encoded_value != CGI::unescape(encoded_value)) do
|
123
|
+
#"decoding"
|
124
|
+
encoded_value = CGI::unescape(encoded_value)
|
125
|
+
end
|
126
|
+
value = encoded_value
|
127
|
+
end
|
128
|
+
|
129
|
+
if key =~ /_map/
|
130
|
+
orig_value = value
|
131
|
+
value = value.split(",")
|
132
|
+
if key == "url_encoded_fmt_stream_map"
|
133
|
+
url_array = orig_value.split("url=").map{|url_string| url_string.chomp(",")}
|
134
|
+
result_hash = {}
|
135
|
+
url_array.each do |url|
|
136
|
+
next if url.to_s.empty? || url.to_s.match(/^itag/)
|
137
|
+
format_id = url[/\&itag=(\d+)/, 1]
|
138
|
+
result_hash[format_id] = url
|
139
|
+
end
|
140
|
+
value = result_hash
|
141
|
+
elsif key == "fmt_map"
|
142
|
+
value = Hash[*value.collect { |v|
|
143
|
+
k2, *v2 = v.split("/")
|
144
|
+
[k2, v2]
|
145
|
+
}.flatten(1)]
|
146
|
+
elsif key == "fmt_url_map" || key == "fmt_stream_map"
|
147
|
+
Hash[*value.collect { |v| v.split("|")}.flatten]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
[key, value]
|
151
|
+
}.flatten]
|
152
|
+
|
153
|
+
if video_info_hash["status"] == "fail"
|
154
|
+
return :no_embed
|
155
|
+
end
|
156
|
+
|
157
|
+
title = video_info_hash["title"]
|
158
|
+
length_s = video_info_hash["length_seconds"]
|
159
|
+
token = video_info_hash["token"]
|
160
|
+
|
161
|
+
#for the formats, see: http://en.wikipedia.org/wiki/YouTube#Quality_and_codecs
|
162
|
+
fmt_list = video_info_hash["fmt_list"].split(",")
|
163
|
+
available_formats = fmt_list.map{|format| format.split("/").first}
|
164
|
+
|
165
|
+
format_ext = {}
|
166
|
+
format_ext["38"] = {:extension => "mp4", :name => "MP4 Highest Quality 4096x3027 (H.264, AAC)"}
|
167
|
+
format_ext["37"] = {:extension => "mp4", :name => "MP4 Highest Quality 1920x1080 (H.264, AAC)"}
|
168
|
+
format_ext["22"] = {:extension => "mp4", :name => "MP4 1280x720 (H.264, AAC)"}
|
169
|
+
format_ext["45"] = {:extension => "webm", :name => "WebM 1280x720 (VP8, Vorbis)"}
|
170
|
+
format_ext["44"] = {:extension => "webm", :name => "WebM 854x480 (VP8, Vorbis)"}
|
171
|
+
format_ext["18"] = {:extension => "mp4", :name => "MP4 640x360 (H.264, AAC)"}
|
172
|
+
format_ext["35"] = {:extension => "flv", :name => "FLV 854x480 (H.264, AAC)"}
|
173
|
+
format_ext["34"] = {:extension => "flv", :name => "FLV 640x360 (H.264, AAC)"}
|
174
|
+
format_ext["5"] = {:extension => "flv", :name => "FLV 400x240 (Soerenson H.263)"}
|
175
|
+
format_ext["17"] = {:extension => "3gp", :name => "3gp"}
|
176
|
+
|
177
|
+
#since 1.8 doesn't do ordered hashes
|
178
|
+
prefered_order = ["38","37","22","45","44","18","35","34","5","17"]
|
179
|
+
|
180
|
+
selected_format = prefered_order.select{|possible_format| available_formats.include?(possible_format)}.first
|
181
|
+
|
182
|
+
puts "[YOUTUBE] Title: #{title}"
|
183
|
+
puts "[YOUTUBE] Length: #{length_s} s"
|
184
|
+
puts "[YOUTUBE] t-parameter: #{token}"
|
185
|
+
#best quality seems always to be firsts
|
186
|
+
puts "[YOUTUBE] formats available: #{available_formats.inspect} (downloading format #{selected_format} -> #{format_ext[selected_format][:name]})"
|
187
|
+
|
188
|
+
#video_info_hash.keys.sort.each{|key| puts "#{key} : #{video_info_hash[key]}" }
|
189
|
+
download_url = video_info_hash["url_encoded_fmt_stream_map"][selected_format]
|
190
|
+
#if download url ends with a ';' followed by a codec string remove that part because it stops URI.parse from working
|
191
|
+
download_url = $1 if download_url =~ /(.*?);\scodecs=/
|
192
|
+
file_name = PluginBase.make_filename_safe(title) + "." + format_ext[selected_format][:extension]
|
193
|
+
puts "downloading to " + file_name
|
194
|
+
{:url => download_url, :name => file_name}
|
195
|
+
end
|
196
|
+
end
|