rutube 0.3
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 +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: []
|