viddl-rb 0.99 → 1.0.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/Rakefile +13 -4
- data/bin/helper/downloader.rb +6 -4
- data/bin/viddl-rb +3 -3
- data/helper/utility-helper.rb +10 -9
- data/plugins/blip.rb +0 -1
- data/plugins/vimeo.rb +0 -1
- data/plugins/youtube.rb +23 -8
- data/plugins/youtube/cipher_guesser.rb +114 -0
- data/plugins/youtube/{cipher_loader.rb → cipher_io.rb} +13 -8
- data/plugins/youtube/ciphers.yml +13 -0
- data/plugins/youtube/decipher_coordinator.rb +28 -0
- data/plugins/youtube/decipherer.rb +18 -4
- data/plugins/youtube/format_picker.rb +1 -1
- data/plugins/youtube/video_resolver.rb +35 -20
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aff24988a745ce18c4928aca3c4e50fb47232f1d
|
4
|
+
data.tar.gz: c0fff623ca863b24999582c94d582a2de1288c1f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04af8ad42b49b38d890fb2e1536a2e8de0573c6a419c984bb3135078c18cc98a1f3fd9c760d885c540372ebdbf303d164147f240e3ca760edf0161bac16042b7
|
7
|
+
data.tar.gz: 9a7b77633e663298a5ea5872afef78491de249906ff598d73401c15e9f2db4ba99635118e83e74fcebb61ca65da30d59b0955b03846595fd9720c4b70d357b63
|
data/Rakefile
CHANGED
@@ -2,12 +2,13 @@ require 'rubygems'
|
|
2
2
|
require 'bundler/setup'
|
3
3
|
require 'rake/testtask'
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
SKIPPED_INTEGRATION = FileList["spec/integration/youtube/cipher_guesser_spec.rb"]
|
6
|
+
ALL_INTEGRATION = FileList["spec/integration/*.rb", "spec/integration/*/*.rb"] - SKIPPED_INTEGRATION
|
7
|
+
ALL_UNIT = FileList["spec/unit/*.rb", "spec/unit/*/*.rb"]
|
7
8
|
|
8
|
-
task :default => [:
|
9
|
+
task :default => [:test_all]
|
9
10
|
|
10
|
-
Rake::TestTask.new(:
|
11
|
+
Rake::TestTask.new(:test_all) do |t|
|
11
12
|
t.test_files = ALL_INTEGRATION + ALL_UNIT
|
12
13
|
end
|
13
14
|
|
@@ -34,3 +35,11 @@ end
|
|
34
35
|
Rake::TestTask.new(:test_cipher_loader) do |t|
|
35
36
|
t.test_files = FileList["spec/integration/youtube/cipher_loader_spec.rb"]
|
36
37
|
end
|
38
|
+
|
39
|
+
Rake::TestTask.new(:test_cipher_guesser) do |t|
|
40
|
+
t.test_files = FileList["spec/unit/youtube/cipher_guesser_spec.rb"]
|
41
|
+
end
|
42
|
+
|
43
|
+
Rake::TestTask.new(:test_decipherer) do |t|
|
44
|
+
t.test_files = FileList["spec/unit/youtube/decipherer_spec.rb"]
|
45
|
+
end
|
data/bin/helper/downloader.rb
CHANGED
@@ -15,15 +15,17 @@ class Downloader
|
|
15
15
|
name,
|
16
16
|
:save_dir => params[:save_dir],
|
17
17
|
:tool => params[:tool] && params[:tool].to_sym
|
18
|
-
|
18
|
+
if result
|
19
|
+
puts "Download for #{name} successful."
|
20
|
+
url_name[:on_downloaded].call(true) if url_name[:on_downloaded]
|
21
|
+
ViddlRb::AudioHelper.extract(name, params[:save_dir]) if params[:extract_audio]
|
22
|
+
else
|
23
|
+
url_name[:on_downloaded].call(false) if url_name[:on_downloaded]
|
19
24
|
if params[:abort_on_failure]
|
20
25
|
raise DownloadFailedError, "Download for #{name} failed."
|
21
26
|
else
|
22
27
|
puts "Download for #{name} failed. Moving onto next file."
|
23
28
|
end
|
24
|
-
else
|
25
|
-
puts "Download for #{name} successful."
|
26
|
-
ViddlRb::AudioHelper.extract(name, params[:save_dir]) if params[:extract_audio]
|
27
29
|
end
|
28
30
|
end
|
29
31
|
end
|
data/bin/viddl-rb
CHANGED
@@ -28,14 +28,14 @@ begin
|
|
28
28
|
puts "Will try to extract audio: #{params[:extract_audio] == true}."
|
29
29
|
puts "Analyzing URL: #{params[:url]}"
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
driver = Driver.new(params)
|
32
|
+
driver.start # starts the download process
|
33
33
|
|
34
34
|
rescue OptionParser::ParseError, ViddlRb::RequirementError => e
|
35
35
|
puts "Error: #{e.message}"
|
36
36
|
exit(1)
|
37
37
|
|
38
|
-
rescue
|
38
|
+
rescue => e
|
39
39
|
puts "Error: #{e.message}"
|
40
40
|
puts "\nBacktrace:"
|
41
41
|
puts e.backtrace
|
data/helper/utility-helper.rb
CHANGED
@@ -52,27 +52,28 @@ module ViddlRb
|
|
52
52
|
File.join(File.dirname(File.expand_path(__FILE__)), "..")
|
53
53
|
end
|
54
54
|
|
55
|
-
#checks to see whether the os has a certain utility like wget or curl
|
56
|
-
|
57
|
-
#system returns the exit code of the process
|
55
|
+
# checks to see whether the os has a certain utility like wget or curl
|
56
|
+
# `` returns the standard output of the process
|
57
|
+
# system returns the exit code of the process
|
58
58
|
def self.os_has?(utility)
|
59
|
-
|
60
|
-
unless windows?
|
61
|
-
`which #{utility}`.include?(utility.to_s)
|
62
|
-
else
|
59
|
+
if windows?
|
63
60
|
if !system("where /q where").nil? #if Windows has the where utility
|
64
61
|
system("where /q #{utility}") #/q is the quiet mode flag
|
65
62
|
else
|
66
|
-
begin
|
63
|
+
begin #as a fallback we just run the utility itself
|
67
64
|
system(utility)
|
68
65
|
rescue Errno::ENOENT
|
69
66
|
false
|
70
67
|
end
|
71
68
|
end
|
69
|
+
else
|
70
|
+
# This might work in windows too... I am not quite sure :-/
|
71
|
+
ENV['PATH'].split(':').any?{|dir| File.exist?( File.join(dir, utility.to_s) ) }
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
|
-
#recursively get the final location (after following all redirects)
|
75
|
+
# recursively get the final location (after following all redirects)
|
76
|
+
# for an url.
|
76
77
|
def self.get_final_location(url)
|
77
78
|
Net::HTTP.get_response(URI.parse(url)) do |res|
|
78
79
|
location = res["location"]
|
data/plugins/blip.rb
CHANGED
data/plugins/vimeo.rb
CHANGED
data/plugins/youtube.rb
CHANGED
@@ -1,18 +1,16 @@
|
|
1
|
-
|
2
1
|
class Youtube < PluginBase
|
3
2
|
|
3
|
+
#TODO: TEST THIS: https://www.youtube.com/watch?v=Qapou-3-fM8&list=PL_Z529zmzNGcOVBJA0MgjjQoKiBcmMQWh
|
4
|
+
|
4
5
|
# this will be called by the main app to check whether this plugin is responsible for the url passed
|
5
6
|
def self.matches_provider?(url)
|
6
7
|
url.include?("youtube.com") || url.include?("youtu.be")
|
7
8
|
end
|
8
9
|
|
9
10
|
def self.get_urls_and_filenames(url, options = {})
|
11
|
+
initialize_components(options)
|
10
12
|
|
11
|
-
|
12
|
-
@video_resolver = VideoResolver.new(Decipherer.new(CipherLoader.new))
|
13
|
-
@format_picker = FormatPicker.new(options)
|
14
|
-
|
15
|
-
urls = @url_resolver.get_all_urls(url, options[:filter])
|
13
|
+
urls = @url_resolver.get_all_urls(url, options[:filter])
|
16
14
|
videos = get_videos(urls)
|
17
15
|
|
18
16
|
return_value = videos.map do |video|
|
@@ -23,6 +21,14 @@ class Youtube < PluginBase
|
|
23
21
|
return_value.empty? ? download_error("No videos could be downloaded.") : return_value
|
24
22
|
end
|
25
23
|
|
24
|
+
def self.initialize_components(options)
|
25
|
+
@cipher_io = CipherIO.new
|
26
|
+
coordinator = DecipherCoordinator.new(Decipherer.new(@cipher_io), CipherGuesser.new)
|
27
|
+
@video_resolver = VideoResolver.new(coordinator)
|
28
|
+
@url_resolver = UrlResolver.new
|
29
|
+
@format_picker = FormatPicker.new(options)
|
30
|
+
end
|
31
|
+
|
26
32
|
def self.notify(message)
|
27
33
|
puts "[YOUTUBE] #{message}"
|
28
34
|
end
|
@@ -37,17 +43,26 @@ class Youtube < PluginBase
|
|
37
43
|
@video_resolver.get_video(url)
|
38
44
|
rescue VideoResolver::VideoRemovedError
|
39
45
|
notify "The video #{url} has been removed."
|
46
|
+
nil
|
40
47
|
rescue => e
|
41
48
|
notify "Error getting the video: #{e.message}"
|
49
|
+
nil
|
42
50
|
end
|
43
51
|
end
|
44
|
-
|
45
52
|
videos.reject(&:nil?)
|
46
53
|
end
|
47
54
|
|
48
55
|
def self.make_url_filname_hash(video, format)
|
49
56
|
url = video.get_download_url(format.itag)
|
50
57
|
name = PluginBase.make_filename_safe(video.title) + ".#{format.extension}"
|
51
|
-
{url: url, name: name}
|
58
|
+
{url: url, name: name, on_downloaded: make_downloaded_callback(video)}
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.make_downloaded_callback(video)
|
62
|
+
return nil unless video.signature_guess?
|
63
|
+
|
64
|
+
lambda do |success|
|
65
|
+
@cipher_io.add_cipher(video.cipher_version, video.cipher_operations) if success
|
66
|
+
end
|
52
67
|
end
|
53
68
|
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
class CipherGuesser
|
4
|
+
class CipherGuessError < StandardError; end
|
5
|
+
|
6
|
+
JS_URL = "http://s.ytimg.com/yts/jsbin/html5player-%s.js"
|
7
|
+
|
8
|
+
def guess(cipher_version)
|
9
|
+
js = download_player_javascript(cipher_version)
|
10
|
+
body = extract_decipher_function_body(js)
|
11
|
+
|
12
|
+
parse_function_body(body)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def download_player_javascript(cipher_version)
|
18
|
+
open(JS_URL % cipher_version).read
|
19
|
+
end
|
20
|
+
|
21
|
+
def extract_decipher_function_body(js)
|
22
|
+
function_name = js[decipher_function_name_regex, 1]
|
23
|
+
function_regex = get_function_regex(function_name)
|
24
|
+
match = function_regex.match(js)
|
25
|
+
|
26
|
+
raise(CipherGuessError, "Could not extract the decipher function") unless match
|
27
|
+
match[:brace]
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_function_body(body)
|
31
|
+
lines = body.split(";")
|
32
|
+
|
33
|
+
remove_non_decipher_lines!(lines)
|
34
|
+
do_pre_transformations!(lines)
|
35
|
+
|
36
|
+
lines.map do |line|
|
37
|
+
if /\(\w+,(?<index>\d+)\)/ =~ line # calling a two argument function (swap)
|
38
|
+
"w#{index}"
|
39
|
+
elsif /slice\((?<index>\d+)\)/ =~ line # calling slice
|
40
|
+
"s#{index}"
|
41
|
+
elsif /reverse\(\)/ =~ line # calling reverse
|
42
|
+
"r"
|
43
|
+
else
|
44
|
+
raise "Cannot parse line: #{line}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def remove_non_decipher_lines!(lines)
|
50
|
+
# The first line splits the string into an array and the last joins and returns
|
51
|
+
lines.delete_at(0)
|
52
|
+
lines.delete_at(-1)
|
53
|
+
end
|
54
|
+
|
55
|
+
def do_pre_transformations!(lines)
|
56
|
+
change_inline_swap_to_function_call(lines) if inline_swap?(lines)
|
57
|
+
end
|
58
|
+
|
59
|
+
def inline_swap?(lines)
|
60
|
+
# Defining a variable = inline swap function
|
61
|
+
lines.any? { |line| line.include?("var ") }
|
62
|
+
end
|
63
|
+
|
64
|
+
def change_inline_swap_to_function_call(lines)
|
65
|
+
start_index = lines.find_index { |line| line.include?("var ") }
|
66
|
+
swap_lines = lines.slice!(start_index, 3) # inline swap is 3 lines long
|
67
|
+
i1, i2 = get_swap_indices(swap_lines)
|
68
|
+
|
69
|
+
lines.insert(start_index, "swap(#{i1},#{i2})")
|
70
|
+
lines
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_swap_indices(lines)
|
74
|
+
i1 = lines.first[/(\d+)/, 1]
|
75
|
+
i2 = lines.last[/(\d+)/, 1]
|
76
|
+
[i1, i2]
|
77
|
+
end
|
78
|
+
|
79
|
+
def decipher_function_name_regex
|
80
|
+
# Find "C" in this: var A = B.sig || C (B.s)
|
81
|
+
/
|
82
|
+
\.sig
|
83
|
+
\s*
|
84
|
+
\|\|
|
85
|
+
(\w+)
|
86
|
+
\(
|
87
|
+
/x
|
88
|
+
end
|
89
|
+
|
90
|
+
def get_function_regex(function_name)
|
91
|
+
# Match the function function_name (that has one argument)
|
92
|
+
/
|
93
|
+
#{function_name}
|
94
|
+
\(
|
95
|
+
\w+
|
96
|
+
\)
|
97
|
+
#{function_body_regex}
|
98
|
+
/x
|
99
|
+
end
|
100
|
+
|
101
|
+
def function_body_regex
|
102
|
+
# Match nested braces
|
103
|
+
/
|
104
|
+
(?<brace>
|
105
|
+
{
|
106
|
+
(
|
107
|
+
[^{}]
|
108
|
+
| \g<brace>
|
109
|
+
)*
|
110
|
+
}
|
111
|
+
)
|
112
|
+
/x
|
113
|
+
end
|
114
|
+
end
|
@@ -4,7 +4,7 @@ require 'net/http'
|
|
4
4
|
require 'openssl'
|
5
5
|
require 'yaml'
|
6
6
|
|
7
|
-
class
|
7
|
+
class CipherIO
|
8
8
|
|
9
9
|
CIPHER_YAML_URL = "https://raw.github.com/rb2k/viddl-rb/master/plugins/youtube/ciphers.yml"
|
10
10
|
CIPHER_YAML_PATH = File.join(ViddlRb::UtilityHelper.base_path, "plugins/youtube/ciphers.yml")
|
@@ -23,24 +23,29 @@ class CipherLoader
|
|
23
23
|
@ciphers.dup
|
24
24
|
end
|
25
25
|
|
26
|
+
def add_cipher(version, operations)
|
27
|
+
File.open(CIPHER_YAML_PATH, "a") do |file|
|
28
|
+
file.puts("#{version}: #{operations}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
26
32
|
private
|
27
33
|
|
28
34
|
def update_ciphers
|
29
|
-
|
35
|
+
server_etag = get_server_etag
|
36
|
+
return if server_etag == @ciphers["ETag"]
|
30
37
|
|
31
38
|
@ciphers.merge!(download_server_ciphers)
|
39
|
+
@ciphers["ETag"] = server_etag
|
32
40
|
save_local_ciphers(@ciphers)
|
33
41
|
end
|
34
42
|
|
35
|
-
def
|
36
|
-
File.size(CIPHER_YAML_PATH)
|
37
|
-
end
|
38
|
-
|
39
|
-
def get_server_file_size
|
43
|
+
def get_server_etag
|
40
44
|
uri = URI.parse(CIPHER_YAML_URL)
|
41
45
|
http = make_http(uri)
|
42
46
|
head = Net::HTTP::Head.new(uri.request_uri)
|
43
|
-
http.request(head)["
|
47
|
+
etag = http.request(head)["ETag"]
|
48
|
+
etag.gsub('"', '') # remove leading and trailing quotes
|
44
49
|
end
|
45
50
|
|
46
51
|
def make_http(uri)
|
data/plugins/youtube/ciphers.yml
CHANGED
@@ -84,3 +84,16 @@ ima-vflxBu-5R: w40 w62 r s2 w21 s3 r w7 s3
|
|
84
84
|
ima-vflrGwWV9: w36 w45 r s2 r
|
85
85
|
ima-vflCME3y0: w8 s2 r w52
|
86
86
|
ima-vfl1LZyZ5: w8 s2 r w52
|
87
|
+
ima-vfl4_saJa: r s1 w19 w9 w57 w38 s3 r s2
|
88
|
+
ima-en_US-vflP9269H: r w63 w37 s3 r w14 r
|
89
|
+
ima-en_US-vflkClbFb: s1 w12 w24 s1 w52 w70 s2
|
90
|
+
ima-en_US-vflYhChiG: w27 r s3
|
91
|
+
ima-en_US-vflWnCYSF: r s1 r s3 w19 r w35 w61 s2
|
92
|
+
en_US-vflbT9-GA: w51 w15 s1 w22 s1 w41 r w43 r
|
93
|
+
en_US-vflAYBrl7: s2 r w39 w43
|
94
|
+
en_US-vflS1POwl: w48 s2 r s1 w4 w35
|
95
|
+
en_US-vflLMtkhg: w30 r w30 w39
|
96
|
+
en_US-vflbJnZqE: w26 s1 w15 w3 w62 w54 w22
|
97
|
+
en_US-vflgd5txb: w26 s1 w15 w3 w62 w54 w22
|
98
|
+
en_US-vflTm330y: w26 s1 w15 w3 w62 w54 w22
|
99
|
+
en_US-vflnwMARr: s3 r w24 s2
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
class DecipherCoordinator
|
3
|
+
|
4
|
+
def initialize(decipherer, cipher_guesser)
|
5
|
+
@decipherer = decipherer
|
6
|
+
@cipher_guesser = cipher_guesser
|
7
|
+
end
|
8
|
+
|
9
|
+
def get_decipher_data(cipher_version)
|
10
|
+
ops = @decipherer.get_operations(cipher_version)
|
11
|
+
Youtube.notify "Cipher guess: no"
|
12
|
+
{version: cipher_version, operations: ops.join(" "), guess?: false}
|
13
|
+
|
14
|
+
rescue Decipherer::UnknownCipherVersionError => e
|
15
|
+
ops = @cipher_guesser.guess(cipher_version)
|
16
|
+
Youtube.notify "Cipher guess: yes"
|
17
|
+
{version: cipher_version, operations: ops.join(" "), guess?: true}
|
18
|
+
|
19
|
+
rescue Decipherer::UnknownCipherOperationError => e
|
20
|
+
Youtube.notify "Failed to parse the cipher from the Youtube player version #{cipher_version}\n" +
|
21
|
+
"Please submit a bug report at https://github.com/rb2k/viddl-rb"
|
22
|
+
raise e
|
23
|
+
end
|
24
|
+
|
25
|
+
def decipher_with_operations(cipher, operations)
|
26
|
+
@decipherer.decipher_with_operations(cipher, operations)
|
27
|
+
end
|
28
|
+
end
|
@@ -1,22 +1,36 @@
|
|
1
1
|
|
2
2
|
class Decipherer
|
3
3
|
|
4
|
-
class UnknownCipherVersionError < StandardError; end
|
5
4
|
class UnknownCipherOperationError < StandardError; end
|
6
5
|
|
6
|
+
class UnknownCipherVersionError < StandardError
|
7
|
+
|
8
|
+
attr_reader :cipher_version
|
9
|
+
|
10
|
+
def initialize(cipher_version)
|
11
|
+
super("Unknown cipher version: #{cipher_version}")
|
12
|
+
@cipher_version = cipher_version
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
7
16
|
def initialize(loader)
|
8
17
|
@ciphers = loader.load_ciphers
|
9
18
|
end
|
10
19
|
|
11
20
|
def decipher_with_version(cipher, cipher_version)
|
12
|
-
|
13
|
-
|
21
|
+
ops = get_operations(cipher_version)
|
22
|
+
decipher_with_operations(cipher, ops)
|
23
|
+
end
|
14
24
|
|
15
|
-
|
25
|
+
def get_operations(cipher_version)
|
26
|
+
operations = @ciphers[cipher_version]
|
27
|
+
raise UnknownCipherVersionError.new(cipher_version) unless operations
|
28
|
+
operations.split
|
16
29
|
end
|
17
30
|
|
18
31
|
def decipher_with_operations(cipher, operations)
|
19
32
|
cipher = cipher.dup
|
33
|
+
operations = operations.split if operations.is_a?(String)
|
20
34
|
|
21
35
|
operations.each do |op|
|
22
36
|
cipher = apply_operation(cipher, op)
|
@@ -11,8 +11,13 @@ class VideoResolver
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def get_video(url)
|
14
|
-
@json
|
15
|
-
|
14
|
+
@json = load_json(url)
|
15
|
+
decipher_data = @decipherer.get_decipher_data(get_html5player_version)
|
16
|
+
url_data = parse_stream_map(get_stream_map)
|
17
|
+
|
18
|
+
decipher_signatures!(url_data, decipher_data)
|
19
|
+
|
20
|
+
Video.new(get_title, url_data, decipher_data)
|
16
21
|
end
|
17
22
|
|
18
23
|
private
|
@@ -47,10 +52,7 @@ class VideoResolver
|
|
47
52
|
#
|
48
53
|
def parse_stream_map(stream_map)
|
49
54
|
entries = stream_map.split(",")
|
50
|
-
|
51
|
-
parsed = entries.map { |entry| parse_stream_map_entry(entry) }
|
52
|
-
parsed.each { |entry| apply_signature!(entry) if entry[:sig] }
|
53
|
-
parsed
|
55
|
+
entries.map { |entry| parse_stream_map_entry(entry) }
|
54
56
|
end
|
55
57
|
|
56
58
|
def parse_stream_map_entry(entry)
|
@@ -65,7 +67,7 @@ class VideoResolver
|
|
65
67
|
end
|
66
68
|
|
67
69
|
# The signature key can be either "sig" or "s".
|
68
|
-
# Very rarely there is no "s" or "sig"
|
70
|
+
# Very rarely there is no "s" or "sig" parameter. In this case the signature is already
|
69
71
|
# applied and the the video can be downloaded directly.
|
70
72
|
def fetch_signature(params)
|
71
73
|
sig = params.fetch("sig", nil) || params.fetch("s", nil)
|
@@ -79,32 +81,45 @@ class VideoResolver
|
|
79
81
|
text
|
80
82
|
end
|
81
83
|
|
82
|
-
def
|
83
|
-
|
84
|
-
|
85
|
-
entry.delete(:sig)
|
86
|
-
end
|
84
|
+
def decipher_signatures!(url_data, decipher_data)
|
85
|
+
url_data.each do |entry|
|
86
|
+
next unless entry[:sig]
|
87
87
|
|
88
|
-
|
89
|
-
|
90
|
-
|
88
|
+
sig = @decipherer.decipher_with_operations(entry[:sig], decipher_data[:operations])
|
89
|
+
entry[:url] << "&#{SIGNATURE_URL_PARAMETER}=#{sig}"
|
90
|
+
entry.delete(:sig)
|
91
|
+
end
|
91
92
|
end
|
92
93
|
|
94
|
+
|
93
95
|
class Video
|
94
96
|
attr_reader :title
|
95
97
|
|
96
|
-
def initialize(title,
|
98
|
+
def initialize(title, url_data, decipher_data)
|
97
99
|
@title = title
|
98
|
-
@
|
100
|
+
@url_data = url_data
|
101
|
+
@decipher_data = decipher_data
|
99
102
|
end
|
100
103
|
|
101
104
|
def available_itags
|
102
|
-
@
|
105
|
+
@url_data.map { |entry| entry[:itag] }
|
103
106
|
end
|
104
107
|
|
105
108
|
def get_download_url(itag)
|
106
|
-
|
107
|
-
|
109
|
+
entry = @url_data.find { |entry| entry[:itag] == itag }
|
110
|
+
entry[:url] if entry
|
111
|
+
end
|
112
|
+
|
113
|
+
def signature_guess?
|
114
|
+
@decipher_data[:guess?]
|
115
|
+
end
|
116
|
+
|
117
|
+
def cipher_operations
|
118
|
+
@decipher_data[:operations]
|
119
|
+
end
|
120
|
+
|
121
|
+
def cipher_version
|
122
|
+
@decipher_data[:version]
|
108
123
|
end
|
109
124
|
end
|
110
125
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: viddl-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marc Seeger
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-04-07 00:00:00 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mime-types
|
@@ -95,8 +95,10 @@ files:
|
|
95
95
|
- plugins/soundcloud.rb
|
96
96
|
- plugins/veoh.rb
|
97
97
|
- plugins/vimeo.rb
|
98
|
-
- plugins/youtube/
|
98
|
+
- plugins/youtube/cipher_guesser.rb
|
99
|
+
- plugins/youtube/cipher_io.rb
|
99
100
|
- plugins/youtube/ciphers.yml
|
101
|
+
- plugins/youtube/decipher_coordinator.rb
|
100
102
|
- plugins/youtube/decipherer.rb
|
101
103
|
- plugins/youtube/format_picker.rb
|
102
104
|
- plugins/youtube/url_resolver.rb
|