rutube 0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/rutube.rb +7 -0
- data/lib/util.rb +111 -0
- data/lib/video.rb +41 -0
- data/lib/youtube.rb +105 -0
- metadata +48 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dcbfc16dadfbb4964e347092236508a5678aeffb
|
4
|
+
data.tar.gz: 7db89d0aa31c0f1a2b31fa993ede058ce63662f7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 355babb9207902c387b7f011ffd470e0b48f90da3abf3f8e7f25fc6a4558aad12c1009bbc0ecd9bf0638d657af251f990f2f95dc4c07334f4e75c413332b733d
|
7
|
+
data.tar.gz: 76c69493473b225d0717c5622b565a3234dcd9fdd18fb3ec6f82a101a3df9d4d3f6674d84fa3a012648cf9c174028c6539d51e2621e1c8819dcc6b47db0d20ab
|
data/lib/rutube.rb
ADDED
data/lib/util.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'json'
|
3
|
+
require 'openssl'
|
4
|
+
require 'video'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
class Array
|
8
|
+
def iterate!(code)
|
9
|
+
self.each_with_index do |n, i|
|
10
|
+
self[i] = code.call(n)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module Util
|
16
|
+
|
17
|
+
YT_BASE_URL = 'http://www.youtube.com/get_video_info'
|
18
|
+
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
|
19
|
+
|
20
|
+
# YouTube quality and codecs id map.
|
21
|
+
# source: http://en.wikipedia.org/wiki/YouTube#Quality_and_codecs
|
22
|
+
YT_ENCODING = {
|
23
|
+
# Flash Video
|
24
|
+
5 => ["flv", "240p", "Sorenson H.263", "N/A", "0.25", "MP3", "64"],
|
25
|
+
6 => ["flv", "270p", "Sorenson H.263", "N/A", "0.8", "MP3", "64"],
|
26
|
+
34 => ["flv", "360p", "H.264", "Main", "0.5", "AAC", "128"],
|
27
|
+
35 => ["flv", "480p", "H.264", "Main", "0.8-1", "AAC", "128"],
|
28
|
+
|
29
|
+
# 3GP
|
30
|
+
36 => ["3gp", "240p", "MPEG-4 Visual", "Simple", "0.17", "AAC", "38"],
|
31
|
+
13 => ["3gp", "N/A", "MPEG-4 Visual", "N/A", "0.5", "AAC", "N/A"],
|
32
|
+
17 => ["3gp", "144p", "MPEG-4 Visual", "Simple", "0.05", "AAC", "24"],
|
33
|
+
|
34
|
+
# MPEG-4
|
35
|
+
18 => ["mp4", "360p", "H.264", "Baseline", "0.5", "AAC", "96"],
|
36
|
+
22 => ["mp4", "720p", "H.264", "High", "2-2.9", "AAC", "192"],
|
37
|
+
37 => ["mp4", "1080p", "H.264", "High", "3-4.3", "AAC", "192"],
|
38
|
+
38 => ["mp4", "3072p", "H.264", "High", "3.5-5", "AAC", "192"],
|
39
|
+
82 => ["mp4", "360p", "H.264", "3D", "0.5", "AAC", "96"],
|
40
|
+
83 => ["mp4", "240p", "H.264", "3D", "0.5", "AAC", "96"],
|
41
|
+
84 => ["mp4", "720p", "H.264", "3D", "2-2.9", "AAC", "152"],
|
42
|
+
85 => ["mp4", "1080p", "H.264", "3D", "2-2.9", "AAC", "152"],
|
43
|
+
|
44
|
+
# WebM
|
45
|
+
43 => ["webm", "360p", "VP8", "N/A", "0.5", "Vorbis", "128"],
|
46
|
+
44 => ["webm", "480p", "VP8", "N/A", "1", "Vorbis", "128"],
|
47
|
+
45 => ["webm", "720p", "VP8", "N/A", "2", "Vorbis", "192"],
|
48
|
+
46 => ["webm", "1080p", "VP8", "N/A", "N/A", "Vorbis", "192"],
|
49
|
+
100 => ["webm", "360p", "VP8", "3D", "N/A", "Vorbis", "128"],
|
50
|
+
101 => ["webm", "360p", "VP8", "3D", "N/A", "Vorbis", "192"],
|
51
|
+
102 => ["webm", "720p", "VP8", "3D", "N/A", "Vorbis", "192"]
|
52
|
+
}
|
53
|
+
|
54
|
+
# The keys corresponding to the quality/codec map above.
|
55
|
+
YT_ENCODING_KEYS = [
|
56
|
+
'extension',
|
57
|
+
'resolution',
|
58
|
+
'video_codec',
|
59
|
+
'profile',
|
60
|
+
'video_bitrate',
|
61
|
+
'audio_codec',
|
62
|
+
'audio_bitrate'
|
63
|
+
]
|
64
|
+
|
65
|
+
def parse_stream_map(text)
|
66
|
+
videoinfo = {
|
67
|
+
"itag" => [],
|
68
|
+
"url" => [],
|
69
|
+
"quality" => [],
|
70
|
+
"fallback_host" => [],
|
71
|
+
"s" => [],
|
72
|
+
"type" => []
|
73
|
+
}
|
74
|
+
|
75
|
+
videos = text.split(",")
|
76
|
+
videos.iterate!(lambda { |video| video.split("&") })
|
77
|
+
for video in videos
|
78
|
+
for kv in video
|
79
|
+
key, value = kv.split("=")[0], URI.unescape(kv.split("=")[1])
|
80
|
+
videoinfo[key].index(value) == nil ? videoinfo[key].push(value) : ""
|
81
|
+
end
|
82
|
+
end
|
83
|
+
return videoinfo
|
84
|
+
end
|
85
|
+
|
86
|
+
module_function :parse_stream_map
|
87
|
+
|
88
|
+
def _extract_fmt(url)
|
89
|
+
fmt_data = Hash.new
|
90
|
+
if itag = url.match('itag=\d+').to_s.match('\d+').to_s.to_i
|
91
|
+
attr = YT_ENCODING[itag]
|
92
|
+
if attr == nil
|
93
|
+
return itag, fmt_data
|
94
|
+
end
|
95
|
+
lambda { |x, y| x.each_with_index { |i, j| fmt_data[i] = y[j] } }.call(YT_ENCODING_KEYS, attr)
|
96
|
+
return itag, fmt_data
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
module_function :_extract_fmt
|
101
|
+
|
102
|
+
def sanitize_filename(file_name)
|
103
|
+
file_name.gsub(/[^\w\.\-]/,"_")
|
104
|
+
end
|
105
|
+
|
106
|
+
module_function :sanitize_filename
|
107
|
+
|
108
|
+
def _cipher(s, url)
|
109
|
+
# TODO need to handle this by invoking js from ruby
|
110
|
+
end
|
111
|
+
end
|
data/lib/video.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
|
5
|
+
|
6
|
+
class Video
|
7
|
+
attr_accessor :url, :filename, :fmt_data
|
8
|
+
|
9
|
+
def initialize(url, filename, fmt_data)
|
10
|
+
@url, @filename, @fmt_data = url, filename, fmt_data
|
11
|
+
end
|
12
|
+
|
13
|
+
def download(dir=Dir.pwd)
|
14
|
+
Thread.new(dir){_download(dir)}
|
15
|
+
end
|
16
|
+
|
17
|
+
def _download(dir)
|
18
|
+
if !Dir.exist?(dir)
|
19
|
+
raise "Directory doesn't exist"
|
20
|
+
end
|
21
|
+
|
22
|
+
file_name = File.join(dir, @filename+"."+@fmt_data["extension"])
|
23
|
+
|
24
|
+
if (File.exist?(file_name))
|
25
|
+
raise "File Already Exists"
|
26
|
+
end
|
27
|
+
|
28
|
+
uri = URI(@url)
|
29
|
+
begin
|
30
|
+
Net::HTTP.get_response(uri) { |res|
|
31
|
+
if res != nil && res.is_a?(Net::HTTPSuccess)
|
32
|
+
f = File.new(file_name, "wb")
|
33
|
+
res.read_body do |chunk|
|
34
|
+
f.write(chunk)
|
35
|
+
end
|
36
|
+
f.close()
|
37
|
+
end
|
38
|
+
}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/youtube.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'util'
|
2
|
+
|
3
|
+
class Youtube
|
4
|
+
attr_accessor :file_name, :video_url, :video_id, :fmt_values, :videos, :title
|
5
|
+
|
6
|
+
def initialize(video_url)
|
7
|
+
@video_url = video_url
|
8
|
+
initialize_video_info(self)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize_video_info(yt)
|
12
|
+
yt.title = ""
|
13
|
+
yt.videos = []
|
14
|
+
uri = URI(yt.video_url)
|
15
|
+
response = Net::HTTP.get_response(uri)
|
16
|
+
|
17
|
+
if response.is_a?(Net::HTTPMovedPermanently) && uri.scheme == "http"
|
18
|
+
puts "Changing the url scheme to https since redirections are not handled by ruby"
|
19
|
+
uri.scheme = "https"
|
20
|
+
uri.port = 443
|
21
|
+
response = Net::HTTP.get_response(uri)
|
22
|
+
end
|
23
|
+
|
24
|
+
if response != nil && response.is_a?(Net::HTTPSuccess)
|
25
|
+
content = response.body
|
26
|
+
player_conf = content[17 + content.index("ytplayer.config = ")..content.length]
|
27
|
+
bracket_count = 0
|
28
|
+
index = 0
|
29
|
+
player_conf.each_char.with_index { |char, i|
|
30
|
+
index = i
|
31
|
+
if char == "{"
|
32
|
+
bracket_count += 1
|
33
|
+
elsif char == "}"
|
34
|
+
bracket_count -= 1
|
35
|
+
if bracket_count == 0
|
36
|
+
break
|
37
|
+
end
|
38
|
+
end
|
39
|
+
}
|
40
|
+
|
41
|
+
index = index+1
|
42
|
+
data = JSON.parse(player_conf[0, index])
|
43
|
+
|
44
|
+
is_vevo = false
|
45
|
+
if ['vevo', 'dashmpd'].include?(data["args"]["pk"])
|
46
|
+
is_vevo = true
|
47
|
+
end
|
48
|
+
|
49
|
+
stream_map = Util.parse_stream_map(data["args"]["url_encoded_fmt_stream_map"])
|
50
|
+
yt.title = data["args"]["title"]
|
51
|
+
js_url = "http:" + data["assets"]["js"]
|
52
|
+
video_urls = stream_map["url"]
|
53
|
+
self.file_name = Util.sanitize_filename(yt.title)
|
54
|
+
|
55
|
+
yt.videos, yt.fmt_values = [], []
|
56
|
+
|
57
|
+
video_urls.each_with_index { |url, index|
|
58
|
+
begin
|
59
|
+
fmt, fmt_data = Util._extract_fmt(url)
|
60
|
+
rescue StandardError
|
61
|
+
puts $!, $@
|
62
|
+
end
|
63
|
+
|
64
|
+
if url.index('signature=') == nil
|
65
|
+
if is_vevo
|
66
|
+
## TODO : need to handle it differently
|
67
|
+
else
|
68
|
+
signature = _cipher(stream_map["s"][i], js_url)
|
69
|
+
url = "%url&signature=%sig".gsub('url', url).gsub('sig', signature)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
yt.videos.push(Video.new(url, yt.file_name, fmt_data))
|
74
|
+
yt.fmt_values.push(fmt)
|
75
|
+
}
|
76
|
+
end
|
77
|
+
return yt
|
78
|
+
rescue JSON::JSONError
|
79
|
+
puts "cannot decode json"
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def get(type, resoln=nil)
|
84
|
+
return_videos = []
|
85
|
+
for video in @videos
|
86
|
+
if video.fmt_data["extension"] == type && (resoln == nil || (resoln != nil && resoln == video.fmt_data["resolution"]))
|
87
|
+
return_videos.push(video)
|
88
|
+
break
|
89
|
+
end
|
90
|
+
end
|
91
|
+
if return_videos.length == 1
|
92
|
+
return return_videos[0]
|
93
|
+
else
|
94
|
+
puts "no video present for the given format #{type}"
|
95
|
+
return nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def list_formats
|
100
|
+
for video in @videos
|
101
|
+
puts "Video : #{video.fmt_data["video_codec"]} (.#{video.fmt_data["extension"]}) - #{video.fmt_data["resolution"]}"
|
102
|
+
end
|
103
|
+
return nil
|
104
|
+
end
|
105
|
+
end
|
metadata
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rutube
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.3'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Vidyuth Dandu
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-06-30 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Ruby port of pytube for downloading youtube videos
|
14
|
+
email:
|
15
|
+
- vidyuth.bitsgoa@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/rutube.rb
|
21
|
+
- lib/util.rb
|
22
|
+
- lib/video.rb
|
23
|
+
- lib/youtube.rb
|
24
|
+
homepage: http://github.com
|
25
|
+
licenses:
|
26
|
+
- MIT
|
27
|
+
metadata: {}
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 2.0.0
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 1.3.6
|
42
|
+
requirements: []
|
43
|
+
rubyforge_project:
|
44
|
+
rubygems_version: 2.4.5
|
45
|
+
signing_key:
|
46
|
+
specification_version: 4
|
47
|
+
summary: Download youtube videos using rutube
|
48
|
+
test_files: []
|