viddl-rb 0.84 → 0.85
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 +3 -1
- data/README.md +2 -2
- data/lib/viddl-rb.rb +1 -0
- data/plugins/arte_plus_seven.rb +37 -0
- data/plugins/youtube.rb +124 -105
- metadata +23 -9
- data/TODO.txt +0 -3
data/Gemfile.lock
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
viddl-rb (0.
|
4
|
+
viddl-rb (0.84)
|
5
5
|
mechanize
|
6
|
+
multi_json
|
6
7
|
nokogiri (~> 1.5.0)
|
7
8
|
progressbar
|
8
9
|
|
@@ -24,6 +25,7 @@ GEM
|
|
24
25
|
webrobots (>= 0.0.9, < 0.2)
|
25
26
|
mime-types (1.24)
|
26
27
|
minitest (5.0.6)
|
28
|
+
multi_json (1.8.0)
|
27
29
|
net-http-digest_auth (1.4)
|
28
30
|
net-http-persistent (2.9)
|
29
31
|
nokogiri (1.5.10)
|
data/README.md
CHANGED
@@ -115,12 +115,12 @@ __Requirements:__
|
|
115
115
|
* [Mechanize](http://mechanize.rubyforge.org/)
|
116
116
|
* ffmpeg if you want to extract audio tracks from the videos
|
117
117
|
|
118
|
-
__Co
|
118
|
+
__Co Maintainers:__
|
119
119
|
* [kl](https://github.com/kl): Windows support (who knew!), bug fixes, veoh plugin, metacafe plugin, refactoring it into a library, ...
|
120
|
+
* [farleyknight](https://github.com/farleyknight): Various small fixes and improvements
|
120
121
|
|
121
122
|
__Contributors:__
|
122
123
|
* [divout](https://github.com/divout) aka Ivan K: blip.tv plugin, bugfixes
|
123
124
|
* Sniper: bugfixes
|
124
125
|
* [Serabe](https://github.com/Serabe) aka Sergio Arbeo: packaging viddl as a binary
|
125
126
|
* [laserlemon](https://github.com/laserlemon): Adding gemnasium images to readme
|
126
|
-
* [farleyknight](https://github.com/farleyknight): Various small fixes and improvements
|
data/lib/viddl-rb.rb
CHANGED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
class ArtePlusSeven < PluginBase
|
4
|
+
# this will be called by the main app to check whether this plugin is responsible for the url passed
|
5
|
+
def self.matches_provider?(url)
|
6
|
+
url.include?("arte.tv")
|
7
|
+
end
|
8
|
+
|
9
|
+
# return the url for original video file and title
|
10
|
+
def self.get_urls_and_filenames(url, options = {})
|
11
|
+
id = self.to_id(url)
|
12
|
+
country = self.extract_country(url)
|
13
|
+
json_url = "http://arte.tv/papi/tvguide/videos/stream/player/#{country}/#{id}_PLUS7-#{country.upcase}/ALL/ALL.json"
|
14
|
+
doc = MultiJson.load(open(json_url))['videoJsonPlayer']
|
15
|
+
# This can be improved a lot,
|
16
|
+
# check the results on http://floriancrouzat.net/arte/
|
17
|
+
first_http_key = doc['VSR'].keys.find{|k| k.start_with?('HTTP')}
|
18
|
+
download_url = doc['VSR'][first_http_key]['url']
|
19
|
+
title = doc['VTI']
|
20
|
+
file_name = PluginBase.make_filename_safe(title) + ".mp4"
|
21
|
+
[{:url => download_url, :name => file_name}]
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.to_id(url)
|
25
|
+
url[/([\d-]+)/,1]
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.extract_country(url)
|
29
|
+
url_country = url[/\/guide\/(..)\//,1]
|
30
|
+
mapping = {
|
31
|
+
'de' => 'D',
|
32
|
+
'fr' => 'F'
|
33
|
+
}
|
34
|
+
mapping[url_country]
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
data/plugins/youtube.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
1
2
|
|
2
3
|
class Youtube < PluginBase
|
3
4
|
|
@@ -34,9 +35,9 @@ class Youtube < PluginBase
|
|
34
35
|
return_vals = []
|
35
36
|
|
36
37
|
if playlist_urls = parser.get_playlist_urls(url, filter)
|
37
|
-
playlist_urls.each { |url| return_vals << grab_single_url_filename(url) }
|
38
|
+
playlist_urls.each { |url| return_vals << grab_single_url_filename(url, options) }
|
38
39
|
else
|
39
|
-
return_vals << grab_single_url_filename(url)
|
40
|
+
return_vals << grab_single_url_filename(url, options)
|
40
41
|
end
|
41
42
|
|
42
43
|
clean_return_values(return_vals)
|
@@ -52,128 +53,150 @@ class Youtube < PluginBase
|
|
52
53
|
end
|
53
54
|
end
|
54
55
|
|
55
|
-
def self.grab_single_url_filename(url)
|
56
|
-
|
56
|
+
def self.grab_single_url_filename(url, options)
|
57
|
+
UrlGrabber.new(url, self, options).process
|
57
58
|
end
|
58
59
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
60
|
+
class UrlGrabber
|
61
|
+
attr_accessor :url, :options, :plugin, :quality
|
62
|
+
|
63
|
+
def initialize(url, plugin, options)
|
64
|
+
@url = url
|
65
|
+
@plugin = plugin
|
66
|
+
@options = options
|
67
|
+
@quality = options[:quality]
|
65
68
|
end
|
66
69
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
file_name = PluginBase.make_filename_safe(title) + "." + VIDEO_FORMATS[selected_format][:extension]
|
70
|
+
def process
|
71
|
+
grab_url_embeddable(url) || grab_url_non_embeddable(url)
|
72
|
+
end
|
71
73
|
|
72
|
-
|
73
|
-
|
74
|
+
# VEVO video: http://www.youtube.com/watch?v=A_J7kEhY9sM
|
75
|
+
# Non-VEVO video: http://www.youtube.com/watch?v=WkkC9cK8Hz0
|
74
76
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
urls_formats = parse_stream_map(url_decode(stream_map))
|
79
|
-
selected_format = choose_format(urls_formats)
|
80
|
-
title = video_info[/<meta name="title" content="([^"]*)">/, 1]
|
81
|
-
file_name = PluginBase.make_filename_safe(title) + "." + VIDEO_FORMATS[selected_format][:extension]
|
82
|
-
|
83
|
-
# cleaning
|
84
|
-
clean_url = urls_formats[selected_format].gsub(/\\u0026[^&]*/, "").split(',type=video').first
|
85
|
-
{:url => clean_url, :name => file_name}
|
86
|
-
end
|
77
|
+
def grab_url_embeddable(url)
|
78
|
+
video_info = get_video_info(url)
|
79
|
+
video_params = extract_video_parameters(video_info)
|
87
80
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
end
|
81
|
+
unless video_params[:embeddable]
|
82
|
+
Youtube.notify("VIDEO IS NOT EMBEDDABLE")
|
83
|
+
return false
|
84
|
+
end
|
93
85
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
url = open(url).base_uri.to_s if url.include?("youtu.be")
|
99
|
-
video_id = url[/(v|embed)[=\/]([^\/\?\&]*)/, 2]
|
86
|
+
urls_formats = extract_urls_formats(video_info)
|
87
|
+
selected_format = choose_format(urls_formats)
|
88
|
+
title = video_params[:title]
|
89
|
+
file_name = PluginBase.make_filename_safe(title) + "." + VIDEO_FORMATS[selected_format][:extension]
|
100
90
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
91
|
+
{:url => urls_formats[selected_format], :name => file_name}
|
92
|
+
end
|
93
|
+
|
94
|
+
def grab_url_non_embeddable(url)
|
95
|
+
video_info = open(url).read
|
96
|
+
stream_map = video_info[/url_encoded_fmt_stream_map\" *: *\"([^\"]+)\"/,1]
|
97
|
+
urls_formats = parse_stream_map(url_decode(stream_map))
|
98
|
+
selected_format = choose_format(urls_formats)
|
99
|
+
title = video_info[/<meta name="title" content="([^"]*)">/, 1]
|
100
|
+
file_name = PluginBase.make_filename_safe(title) + "." + VIDEO_FORMATS[selected_format][:extension]
|
101
|
+
|
102
|
+
# cleaning
|
103
|
+
clean_url = urls_formats[selected_format].gsub(/\\u0026[^&]*/, "").split(',type=video').first
|
104
|
+
{:url => clean_url, :name => file_name}
|
106
105
|
end
|
107
|
-
end
|
108
106
|
|
109
|
-
|
110
|
-
|
107
|
+
def get_video_info(url)
|
108
|
+
id = extract_video_id(url)
|
109
|
+
request_url = VIDEO_INFO_URL + id + VIDEO_INFO_PARMS
|
110
|
+
open(request_url).read
|
111
|
+
end
|
111
112
|
|
112
|
-
|
113
|
-
|
114
|
-
:author => decoded[/author=(.+?)(?:&|$)/, 1],
|
115
|
-
:embeddable => !decoded.include?("status=fail")}
|
116
|
-
end
|
113
|
+
def extract_video_parameters(video_info)
|
114
|
+
video_params = CGI.parse(url_decode(video_info))
|
117
115
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
116
|
+
{
|
117
|
+
:title => video_params["title"].first,
|
118
|
+
:length_sec => video_params["length_seconds"].first,
|
119
|
+
:author => video_params["author"].first,
|
120
|
+
:embeddable => (video_params["status"].first != "fail")
|
121
|
+
}
|
122
|
+
end
|
122
123
|
|
123
|
-
|
124
|
-
|
125
|
-
|
124
|
+
def extract_video_id(url)
|
125
|
+
# the youtube video ID looks like this: [...]v=abc5a5_afe5agae6g&[...], we only want the ID (the \w in the brackets)
|
126
|
+
# addition: might also look like this /v/abc5-a5afe5agae6g
|
127
|
+
# alternative: video_id = url[/v[\/=]([\w-]*)&?/, 1]
|
128
|
+
url = open(url).base_uri.to_s if url.include?("youtu.be")
|
129
|
+
video_id = url[/(v|embed)[=\/]([^\/\?\&]*)/, 2]
|
126
130
|
|
127
|
-
|
128
|
-
|
129
|
-
|
131
|
+
if video_id
|
132
|
+
Youtube.notify("ID FOUND: #{video_id}")
|
133
|
+
video_id
|
134
|
+
else
|
135
|
+
Youtube.download_error("No video id found.")
|
136
|
+
end
|
130
137
|
end
|
131
138
|
|
132
|
-
|
133
|
-
|
139
|
+
def extract_urls_formats(video_info)
|
140
|
+
stream_map = video_info[/url_encoded_fmt_stream_map=(.+?)(?:&|$)/, 1]
|
141
|
+
parse_stream_map(stream_map)
|
142
|
+
end
|
134
143
|
|
135
|
-
|
136
|
-
|
137
|
-
|
144
|
+
def choose_format(urls_formats)
|
145
|
+
available_formats = urls_formats.keys
|
146
|
+
|
147
|
+
if @quality #if the user specified a format
|
148
|
+
ext = @quality[:extension]
|
149
|
+
res = @quality[:resolution]
|
150
|
+
#gets a nested array with all the formats of the same res as the user wanted
|
151
|
+
requested = VIDEO_FORMATS.select { |id, format| format[:name].include?(res) }.to_a
|
152
|
+
|
153
|
+
if requested.empty?
|
154
|
+
Youtube.notify "Requested format \"#{res}:#{ext}\" not found. Downloading default format."
|
155
|
+
get_default_format(available_formats)
|
156
|
+
else
|
157
|
+
pick = requested.find { |format| format[1][:extension] == ext } # get requsted extension if possible
|
158
|
+
pick ? pick.first : get_default_format(requested.map { |req| req.first }) # else return the default format
|
159
|
+
end
|
160
|
+
else
|
161
|
+
get_default_format(available_formats)
|
162
|
+
end
|
163
|
+
end
|
138
164
|
|
139
|
-
|
140
|
-
|
141
|
-
|
165
|
+
def parse_stream_map(stream_map)
|
166
|
+
urls = extract_download_urls(stream_map)
|
167
|
+
formats_urls = {}
|
142
168
|
|
143
|
-
|
169
|
+
urls.each do |url|
|
170
|
+
format = url[/itag=(\d+)/, 1]
|
171
|
+
formats_urls[format] = url
|
172
|
+
end
|
173
|
+
|
174
|
+
formats_urls
|
144
175
|
end
|
145
|
-
end
|
146
176
|
|
147
|
-
|
148
|
-
|
177
|
+
def extract_download_urls(stream_map)
|
178
|
+
entries = stream_map.split("%2C")
|
179
|
+
decoded = entries.map { |entry| url_decode(entry) }
|
149
180
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
#gets a nested array with all the formats of the same res as the user wanted
|
154
|
-
requested = VIDEO_FORMATS.select { |id, format| format[:name].include?(res) }.to_a
|
181
|
+
decoded.map do |entry|
|
182
|
+
url = entry[/url=(.*?itag=.+?)(?:itag=|;|$)/, 1]
|
183
|
+
sig = entry[/sig=(.+?)(?:&|$)/, 1]
|
155
184
|
|
156
|
-
|
157
|
-
notify "Requested format \"#{res}:#{ext}\" not found. Downloading default format."
|
158
|
-
get_default_format(available_formats)
|
159
|
-
else
|
160
|
-
pick = requested.find { |format| format[1][:extension] == ext } # get requsted extension if possible
|
161
|
-
pick ? pick.first : get_default_format(requested.map { |req| req.first }) # else return the default format
|
185
|
+
url + "&signature=#{sig}"
|
162
186
|
end
|
163
|
-
else
|
164
|
-
get_default_format(available_formats)
|
165
187
|
end
|
166
|
-
end
|
167
188
|
|
168
|
-
|
169
|
-
|
170
|
-
|
189
|
+
def get_default_format(available)
|
190
|
+
DEFAULT_FORMAT_ORDER.find { |default| available.include?(default) }
|
191
|
+
end
|
171
192
|
|
172
|
-
|
173
|
-
|
174
|
-
|
193
|
+
def url_decode(text)
|
194
|
+
while text != (decoded = CGI::unescape(text)) do
|
195
|
+
text = decoded
|
196
|
+
end
|
197
|
+
text
|
175
198
|
end
|
176
|
-
|
199
|
+
|
177
200
|
end
|
178
201
|
|
179
202
|
def self.notify(message)
|
@@ -211,24 +234,24 @@ class Youtube < PluginBase
|
|
211
234
|
#http://www.youtube.com/watch?v=Tk78sr5JMIU&videos=jKY836_WMhE
|
212
235
|
|
213
236
|
playlist_ID = url[/(?:list=PL|p=)(.+?)(?:&|\/|$)/, 1]
|
214
|
-
notify "Playlist ID: #{playlist_ID}"
|
237
|
+
Youtube.notify "Playlist ID: #{playlist_ID}"
|
215
238
|
feed_url = PLAYLIST_FEED % playlist_ID
|
216
239
|
url_array = get_video_urls(feed_url)
|
217
|
-
notify "#{url_array.size} links found!"
|
240
|
+
Youtube.notify "#{url_array.size} links found!"
|
218
241
|
url_array
|
219
242
|
end
|
220
243
|
|
221
244
|
def parse_user(username)
|
222
|
-
notify "User: #{username}"
|
245
|
+
Youtube.notify "User: #{username}"
|
223
246
|
feed_url = USER_FEED % username
|
224
247
|
url_array = get_video_urls(feed_url)
|
225
|
-
notify "#{url_array.size} links found!"
|
248
|
+
Youtube.notify "#{url_array.size} links found!"
|
226
249
|
url_array
|
227
250
|
end
|
228
251
|
|
229
252
|
#get all videos and return their urls in an array
|
230
253
|
def get_video_urls(feed_url)
|
231
|
-
notify "Retrieving videos..."
|
254
|
+
Youtube.notify "Retrieving videos..."
|
232
255
|
urls_titles = {}
|
233
256
|
result_feed = Nokogiri::XML(open(feed_url))
|
234
257
|
urls_titles.merge!(grab_urls_and_titles(result_feed))
|
@@ -255,16 +278,12 @@ class Youtube < PluginBase
|
|
255
278
|
#returns only the urls that match the --filter argument regex (if present)
|
256
279
|
def filter_urls(url_hash)
|
257
280
|
if @filter
|
258
|
-
notify "Using filter: #{@filter}"
|
281
|
+
Youtube.notify "Using filter: #{@filter}"
|
259
282
|
filtered = url_hash.select { |url, title| title =~ @filter }
|
260
283
|
filtered.keys
|
261
284
|
else
|
262
285
|
url_hash.keys
|
263
286
|
end
|
264
287
|
end
|
265
|
-
|
266
|
-
def notify(message)
|
267
|
-
Youtube.notify(message)
|
268
|
-
end
|
269
288
|
end
|
270
289
|
end
|
metadata
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: viddl-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 161
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: "0.
|
8
|
+
- 85
|
9
|
+
version: "0.85"
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Marc Seeger
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2013-
|
17
|
+
date: 2013-10-05 00:00:00 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: nokogiri
|
@@ -61,7 +61,7 @@ dependencies:
|
|
61
61
|
type: :runtime
|
62
62
|
version_requirements: *id003
|
63
63
|
- !ruby/object:Gem::Dependency
|
64
|
-
name:
|
64
|
+
name: multi_json
|
65
65
|
prerelease: false
|
66
66
|
requirement: &id004 !ruby/object:Gem::Requirement
|
67
67
|
none: false
|
@@ -72,10 +72,10 @@ dependencies:
|
|
72
72
|
segments:
|
73
73
|
- 0
|
74
74
|
version: "0"
|
75
|
-
type: :
|
75
|
+
type: :runtime
|
76
76
|
version_requirements: *id004
|
77
77
|
- !ruby/object:Gem::Dependency
|
78
|
-
name:
|
78
|
+
name: rake
|
79
79
|
prerelease: false
|
80
80
|
requirement: &id005 !ruby/object:Gem::Requirement
|
81
81
|
none: false
|
@@ -89,7 +89,7 @@ dependencies:
|
|
89
89
|
type: :development
|
90
90
|
version_requirements: *id005
|
91
91
|
- !ruby/object:Gem::Dependency
|
92
|
-
name:
|
92
|
+
name: rest-client
|
93
93
|
prerelease: false
|
94
94
|
requirement: &id006 !ruby/object:Gem::Requirement
|
95
95
|
none: false
|
@@ -102,6 +102,20 @@ dependencies:
|
|
102
102
|
version: "0"
|
103
103
|
type: :development
|
104
104
|
version_requirements: *id006
|
105
|
+
- !ruby/object:Gem::Dependency
|
106
|
+
name: minitest
|
107
|
+
prerelease: false
|
108
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
hash: 3
|
114
|
+
segments:
|
115
|
+
- 0
|
116
|
+
version: "0"
|
117
|
+
type: :development
|
118
|
+
version_requirements: *id007
|
105
119
|
description: An extendable commandline video downloader for flash video sites. Includes plugins for vimeo, youtube, dailymotion and more
|
106
120
|
email: mail@marc-seeger.de
|
107
121
|
executables:
|
@@ -120,6 +134,7 @@ files:
|
|
120
134
|
- helper/download-helper.rb
|
121
135
|
- helper/plugin-helper.rb
|
122
136
|
- helper/utility-helper.rb
|
137
|
+
- plugins/arte_plus_seven.rb
|
123
138
|
- plugins/blip.rb
|
124
139
|
- plugins/dailymotion.rb
|
125
140
|
- plugins/metacafe.rb
|
@@ -131,7 +146,6 @@ files:
|
|
131
146
|
- Gemfile.lock
|
132
147
|
- Rakefile
|
133
148
|
- README.md
|
134
|
-
- TODO.txt
|
135
149
|
homepage: https://github.com/rb2k/viddl-rb
|
136
150
|
licenses: []
|
137
151
|
|
data/TODO.txt
DELETED