viddl-rb 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +12 -0
- data/README.txt +13 -0
- data/bin/viddl-rb +46 -0
- data/helper/download-helper.rb +44 -0
- data/helper/plugin-helper.rb +10 -0
- data/plugins/megavideo.rb +100 -0
- data/plugins/vimeo.rb +28 -0
- data/plugins/youtube.rb +118 -0
- metadata +65 -0
data/CHANGELOG.txt
ADDED
data/README.txt
ADDED
data/bin/viddl-rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'helper')
|
3
|
+
|
4
|
+
require "download-helper.rb"
|
5
|
+
require "plugin-helper.rb"
|
6
|
+
|
7
|
+
|
8
|
+
if ARGV.first.nil?
|
9
|
+
puts "Usage: viddl-rb [URL]!"
|
10
|
+
exit
|
11
|
+
end
|
12
|
+
|
13
|
+
puts "Loading Plugins"
|
14
|
+
Dir[File.join(File.dirname(__FILE__),"../plugins/*.rb")].each do |plugin|
|
15
|
+
load plugin
|
16
|
+
end
|
17
|
+
|
18
|
+
puts "Plugins loaded: #{PluginBase.registered_plugins.inspect}"
|
19
|
+
|
20
|
+
|
21
|
+
url = ARGV.first
|
22
|
+
puts "Analyzing URL: #{url}"
|
23
|
+
PluginBase.registered_plugins.each do |plugin|
|
24
|
+
if plugin.matches_provider?(url)
|
25
|
+
puts "#{plugin}: true"
|
26
|
+
begin
|
27
|
+
download_queue = plugin.get_urls_and_filenames(url)
|
28
|
+
rescue StandardError => e
|
29
|
+
puts "Error while running the #{plugin.name.inspect} plugin. Maybe it has to be updated?"
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
download_queue.each do |url_name|
|
33
|
+
result = DownloadHelper.save_file(url_name[:url], url_name[:name])
|
34
|
+
if result
|
35
|
+
puts "Download for #{url_name[:name]} successful."
|
36
|
+
else
|
37
|
+
puts "Download for #{url_name[:name]} failed."
|
38
|
+
end
|
39
|
+
end
|
40
|
+
exit
|
41
|
+
else
|
42
|
+
puts "#{plugin}: false"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
puts "No plugin seems to feel responsible for this URL."
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class DownloadHelper
|
2
|
+
#usually not called directly
|
3
|
+
def self.fetch_file(uri)
|
4
|
+
|
5
|
+
begin
|
6
|
+
require "progressbar" #http://github.com/nex3/ruby-progressbar
|
7
|
+
rescue LoadError
|
8
|
+
puts "ERROR: You don't seem to have curl or wget on your system. In this case you'll need to install the 'progressbar' gem."
|
9
|
+
exit
|
10
|
+
end
|
11
|
+
progress_bar = nil
|
12
|
+
open(uri, :proxy => nil,
|
13
|
+
:content_length_proc => lambda { |length|
|
14
|
+
if length && 0 < length
|
15
|
+
progress_bar = ProgressBar.new(uri.to_s, length)
|
16
|
+
end
|
17
|
+
},
|
18
|
+
:progress_proc => lambda { |progress|
|
19
|
+
progress_bar.set(progress) if progress_bar
|
20
|
+
}) {|file| return file.read}
|
21
|
+
end
|
22
|
+
|
23
|
+
#simple helper that will save a file from the web and save it with a progress bar
|
24
|
+
def self.save_file(file_uri, file_name)
|
25
|
+
unescaped_uri = CGI::unescape(file_uri)
|
26
|
+
result = false
|
27
|
+
if `which wget`.include?("wget")
|
28
|
+
puts "using wget"
|
29
|
+
IO.popen("wget \"#{unescaped_uri}\" -O #{file_name}", "r") { |pipe| pipe.each {|line| print line}}
|
30
|
+
result = ($?.exitstatus == 0)
|
31
|
+
elsif `which curl`.include?("curl")
|
32
|
+
puts "using curl"
|
33
|
+
#-L means: follow redirects, We set an agent because Vimeo seems to want one
|
34
|
+
IO.popen("curl -A 'Mozilla/2.02 (OS/2; U)' -L \"#{unescaped_uri}\" -o #{file_name}", "r") { |pipe| pipe.each {|line| print line }}
|
35
|
+
result = ($?.exitstatus == 0)
|
36
|
+
else
|
37
|
+
open(file_name, 'wb') { |file|
|
38
|
+
file.write(fetch_file(unescaped_uri)); puts
|
39
|
+
}
|
40
|
+
result = true
|
41
|
+
end
|
42
|
+
result
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class PluginBase
|
2
|
+
#some static stuff
|
3
|
+
class << self; attr_reader :registered_plugins end
|
4
|
+
@registered_plugins = []
|
5
|
+
|
6
|
+
#if you inherit from this class, the child gets added to the "registered plugins" array
|
7
|
+
def self.inherited(child)
|
8
|
+
PluginBase.registered_plugins << child
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
class Megavideo < PluginBase
|
2
|
+
require "cgi"
|
3
|
+
require "open-uri"
|
4
|
+
require "nokogiri"
|
5
|
+
|
6
|
+
#this will be called by the main app to check weather this plugin is responsible for the url passed
|
7
|
+
def self.matches_provider?(url)
|
8
|
+
url.include?("megavideo.com")
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.decrypt(un,k1,k2)
|
12
|
+
#thanks to http://userscripts.org/scripts/review/42944
|
13
|
+
k1 = k1.to_i
|
14
|
+
k2 = k2.to_i
|
15
|
+
|
16
|
+
#convert the hex "un" to binary
|
17
|
+
location1 = Array.new
|
18
|
+
un.each_char do |char|
|
19
|
+
#puts "#{char} => #{char.to_i(16).to_s(2)}"
|
20
|
+
location1 << ("000" + char.to_i(16).to_s(2))[-4,4]
|
21
|
+
end
|
22
|
+
|
23
|
+
location1 = location1.join("").split("")
|
24
|
+
|
25
|
+
location6 = Array.new
|
26
|
+
0.upto(383) do |n|
|
27
|
+
k1 = (k1 * 11 + 77213) % 81371
|
28
|
+
k2 = (k2 * 17 + 92717) % 192811
|
29
|
+
location6[n] = (k1 + k2) % 128
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
location3 = Array.new
|
35
|
+
location4 = Array.new
|
36
|
+
location5 = Array.new
|
37
|
+
location8 = Array.new
|
38
|
+
256.downto(0) do |n|
|
39
|
+
location5 = location6[n]
|
40
|
+
location4 = n % 128
|
41
|
+
location8 = location1[location5]
|
42
|
+
location1[location5] = location1[location4]
|
43
|
+
location1[location4] = location8
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
0.upto(127) do |n|
|
48
|
+
location1[n] = location1[n].to_i ^ location6[n+256] & 1
|
49
|
+
end
|
50
|
+
|
51
|
+
location12 = location1.join("")
|
52
|
+
location7 = Array.new
|
53
|
+
|
54
|
+
n = 0
|
55
|
+
while (n < location12.length) do
|
56
|
+
location9 = location12[n,4]
|
57
|
+
location7 << location9
|
58
|
+
n+=4
|
59
|
+
end
|
60
|
+
|
61
|
+
result = ""
|
62
|
+
location7.each do |bin|
|
63
|
+
result = result + bin.to_i(2).to_s(16)
|
64
|
+
end
|
65
|
+
result
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def self.get_urls_and_filenames(url)
|
70
|
+
#the megavideo video ID looks like this: http://www.megavideo.com/?v=ABCDEF72 , we only want the ID (the \w in the brackets)
|
71
|
+
video_id = url[/v[\/=](\w*)&?/, 1]
|
72
|
+
puts "[MEGAVIDEO] ID FOUND: " + video_id
|
73
|
+
video_page = Nokogiri::XML(open("http://www.megavideo.com/xml/videolink.php?v=#{video_id}"))
|
74
|
+
info = video_page.at("//ROWS/ROW")
|
75
|
+
title = info["title"]
|
76
|
+
puts "[MEGAVIDEO] title: #{title}"
|
77
|
+
runtime = info["runtimehms"]
|
78
|
+
puts "[MEGAVIDEO] runtime: #{runtime}"
|
79
|
+
size = info["size"].to_i / 1024 / 1024
|
80
|
+
puts "[MEGAVIDEO] size: #{size} MB"
|
81
|
+
#lame crypto stuff
|
82
|
+
key_s = info["s"]
|
83
|
+
key_un = info["un"]
|
84
|
+
key_k1 = info["k1"]
|
85
|
+
key_k2 = info["k2"]
|
86
|
+
puts "[MEGAVIDEO] lame pseudo crypto keys:"
|
87
|
+
puts "[MEGAVIDEO] s=#{key_s}"
|
88
|
+
puts "[MEGAVIDEO] un=#{key_un}"
|
89
|
+
puts "[MEGAVIDEO] k1=#{key_k1}"
|
90
|
+
puts "[MEGAVIDEO] k2=#{key_k2}"
|
91
|
+
puts "decrypting"
|
92
|
+
download_url = "http://www#{key_s}.megavideo.com/files/#{decrypt(key_un,key_k1,key_k2)}/#{title}.flv"
|
93
|
+
puts download_url
|
94
|
+
puts "done decrypting"
|
95
|
+
file_name = title + ".flv"
|
96
|
+
puts "downloading to " + file_name
|
97
|
+
[{:url => download_url, :name => file_name}]
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
data/plugins/vimeo.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
class Vimeo < PluginBase
|
2
|
+
require "nokogiri"
|
3
|
+
require "open-uri"
|
4
|
+
|
5
|
+
#this will be called by the main app to check weather this plugin is responsible for the url passed
|
6
|
+
def self.matches_provider?(url)
|
7
|
+
url.include?("vimeo.com")
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.get_urls_and_filenames(url)
|
11
|
+
#the vimeo ID consists of 7 decimal numbers in the URL
|
12
|
+
vimeo_id = url[/\d{7,8}/]
|
13
|
+
doc = Nokogiri::XML(open("http://www.vimeo.com/moogaloop/load/clip:#{vimeo_id}"))
|
14
|
+
title = doc.at("//video/caption").inner_text
|
15
|
+
puts "[VIMEO] Title: #{title}"
|
16
|
+
request_signature = doc.at("//request_signature").inner_text
|
17
|
+
request_signature_expires = doc.at("//request_signature_expires").inner_text
|
18
|
+
|
19
|
+
|
20
|
+
puts "[VIMEO] Request Signature: #{request_signature} expires: #{request_signature_expires}"
|
21
|
+
|
22
|
+
download_url = "http://www.vimeo.com/moogaloop/play/clip:#{vimeo_id}/#{request_signature}/#{request_signature_expires}/?q=hd"
|
23
|
+
#todo: put the filename cleaning stuff into a seperate helper
|
24
|
+
file_name = title.delete("\"'").gsub(/[^0-9A-Za-z]/, '_') + ".flv"
|
25
|
+
puts "downloading to " + file_name
|
26
|
+
[{:url => download_url, :name => file_name}]
|
27
|
+
end
|
28
|
+
end
|
data/plugins/youtube.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
class Youtube < PluginBase
|
2
|
+
require "cgi"
|
3
|
+
require "open-uri"
|
4
|
+
require "nokogiri"
|
5
|
+
|
6
|
+
#this will be called by the main app to check weather this plugin is responsible for the url passed
|
7
|
+
def self.matches_provider?(url)
|
8
|
+
url.include?("youtube.com")
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.parse_playlist(url)
|
12
|
+
#http://www.youtube.com/view_play_list?p=F96B063007B44E1E&search_query=welt+auf+schwäbisch
|
13
|
+
#http://www.youtube.com/watch?v=9WEP5nCxkEY&videos=jKY836_WMhE&playnext_from=TL&playnext=1
|
14
|
+
#http://www.youtube.com/watch?v=Tk78sr5JMIU&videos=jKY836_WMhE
|
15
|
+
|
16
|
+
playlist_ID = url[/p=(\w{16})&?/,1]
|
17
|
+
puts "[YOUTUBE] Playlist ID: #{playlist_ID}"
|
18
|
+
url_array = Array.new
|
19
|
+
video_info = Nokogiri::HTML(open("http://gdata.youtube.com/feeds/api/playlists/#{playlist_ID}?v=2"))
|
20
|
+
video_info.search("//content").each do |video|
|
21
|
+
url_array << video["url"] if video["url"].include?("http://www.youtube.com/v/") #filters out rtsp links
|
22
|
+
end
|
23
|
+
|
24
|
+
puts "[YOUTUBE] #{url_array.size} links found!"
|
25
|
+
url_array
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def self.get_urls_and_filenames(url)
|
30
|
+
return_values = []
|
31
|
+
if url.include?("view_play_list")
|
32
|
+
puts "[YOUTUBE] playlist found! analyzing..."
|
33
|
+
files = self.parse_playlist(url)
|
34
|
+
puts "[YOUTUBE] Starting playlist download"
|
35
|
+
files.each do |file|
|
36
|
+
puts "[YOUTUBE] Downloading next movie on the playlist (#{file})"
|
37
|
+
return_values << self.grab_single_url_filename(url)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
return_values << self.grab_single_url_filename(url)
|
41
|
+
end
|
42
|
+
return_values
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.grab_single_url_filename(url)
|
46
|
+
#the youtube video ID looks like this: [...]v=abc5a5_afe5agae6g&[...], we only want the ID (the \w in the brackets)
|
47
|
+
#addition: might also look like this /v/abc5-a5afe5agae6g
|
48
|
+
# alternative: video_id = url[/v[\/=]([\w-]*)&?/, 1]
|
49
|
+
video_id = url[/(v|embed)[\/=]([^\/\?\&]*)/,2]
|
50
|
+
if video_id.nil?
|
51
|
+
puts "no video id found."
|
52
|
+
exit
|
53
|
+
else
|
54
|
+
puts "[YOUTUBE] ID FOUND: #{video_id}"
|
55
|
+
end
|
56
|
+
#let's get some infos about the video. data is urlencoded
|
57
|
+
video_info = open("http://youtube.com/get_video_info?video_id=#{video_id}").read
|
58
|
+
|
59
|
+
#converting the huge infostring into a hash. simply by splitting it at the & and then splitting it into key and value arround the =
|
60
|
+
#[...]blabla=blubb&narf=poit&marc=awesome[...]
|
61
|
+
video_info_hash = Hash[*video_info.split("&").collect { |v|
|
62
|
+
key, value = v.split "="
|
63
|
+
value = CGI::unescape(value) if value
|
64
|
+
if key =~ /_map/
|
65
|
+
value = value.split(",")
|
66
|
+
value = if key == "fmt_map"
|
67
|
+
Hash[*value.collect{ |v|
|
68
|
+
k2, *v2 = v.split("/")
|
69
|
+
[k2, v2]
|
70
|
+
}.flatten(1)]
|
71
|
+
elsif key == "fmt_url_map" || key == "fmt_stream_map"
|
72
|
+
Hash[*value.collect { |v| v.split("|")}.flatten]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
[key, value]
|
76
|
+
}.flatten]
|
77
|
+
|
78
|
+
if video_info_hash["status"] == "fail"
|
79
|
+
puts "Error: embedding disabled, no video info found"
|
80
|
+
return false
|
81
|
+
end
|
82
|
+
|
83
|
+
title = video_info_hash["title"]
|
84
|
+
length_s = video_info_hash["length_seconds"]
|
85
|
+
token = video_info_hash["token"]
|
86
|
+
|
87
|
+
|
88
|
+
#Standard = 34 <- flv
|
89
|
+
#Medium = 18 <- mp4
|
90
|
+
#High = 35 <- flv
|
91
|
+
#720p = 22 <- mp4
|
92
|
+
#1080p = 37 <- mp4
|
93
|
+
#mobile = 17 <- 3gp
|
94
|
+
# --> 37 > 22 > 35 > 18 > 34 > 17
|
95
|
+
formats = video_info_hash["fmt_map"].keys
|
96
|
+
|
97
|
+
format_ext = {}
|
98
|
+
format_ext["37"] = ["mp4", "MP4 Highest Quality 1920x1080"]
|
99
|
+
format_ext["22"] = ["mp4", "MP4 1280x720"]
|
100
|
+
format_ext["35"] = ["flv", "FLV 854x480"]
|
101
|
+
format_ext["34"] = ["flv", "FLV 640x360"]
|
102
|
+
format_ext["18"] = ["mp4", "MP4 480x270"]
|
103
|
+
format_ext["17"] = ["3gp", "3gp"]
|
104
|
+
format_ext["5"] = ["flv", "old default?"]
|
105
|
+
|
106
|
+
puts "[YOUTUBE] Title: #{title}"
|
107
|
+
puts "[YOUTUBE] Length: #{length_s} s"
|
108
|
+
puts "[YOUTUBE] t-parameter: #{token}"
|
109
|
+
#best quality seems always to be firsts
|
110
|
+
puts "[YOUTUBE] formats available: #{formats} (downloading ##{formats.first} -> #{format_ext[formats.first].last})"
|
111
|
+
|
112
|
+
|
113
|
+
download_url = video_info_hash["fmt_url_map"][formats.first]
|
114
|
+
file_name = title.delete("\"'").gsub(/[^0-9A-Za-z]/, '_') + "." + format_ext[formats.first].first
|
115
|
+
puts "downloading to " + file_name
|
116
|
+
{:url => download_url, :name => file_name}
|
117
|
+
end
|
118
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: viddl-rb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Marc Seeger
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-07-16 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: nokogiri
|
16
|
+
requirement: &70145693005120 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70145693005120
|
25
|
+
description: An extendable commandline video downloader for flash video sites. Includes
|
26
|
+
plugins for vimeo, youtube and megavideo
|
27
|
+
email: mail@marc-seeger.de
|
28
|
+
executables:
|
29
|
+
- viddl-rb
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- bin/viddl-rb
|
34
|
+
- helper/download-helper.rb
|
35
|
+
- helper/plugin-helper.rb
|
36
|
+
- plugins/megavideo.rb
|
37
|
+
- plugins/vimeo.rb
|
38
|
+
- plugins/youtube.rb
|
39
|
+
- CHANGELOG.txt
|
40
|
+
- README.txt
|
41
|
+
homepage: https://github.com/rb2k/viddl-rb
|
42
|
+
licenses: []
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ! '>='
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: 1.3.4
|
59
|
+
requirements: []
|
60
|
+
rubyforge_project: viddl-rb
|
61
|
+
rubygems_version: 1.8.5
|
62
|
+
signing_key:
|
63
|
+
specification_version: 3
|
64
|
+
summary: An extendable commandline video downloader for flash video sites.
|
65
|
+
test_files: []
|