viddl-rb 0.4.1
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/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: []
|