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.
Files changed (6) hide show
  1. checksums.yaml +7 -0
  2. data/lib/rutube.rb +7 -0
  3. data/lib/util.rb +111 -0
  4. data/lib/video.rb +41 -0
  5. data/lib/youtube.rb +105 -0
  6. metadata +48 -0
@@ -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
@@ -0,0 +1,7 @@
1
+ module Rutube
2
+
3
+ require 'youtube'
4
+ require 'video'
5
+ require 'util'
6
+
7
+ end
@@ -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
@@ -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
@@ -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: []