video_info 2.5.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,135 @@
1
+ require 'oga'
2
+ require 'open-uri'
3
+ require 'json'
4
+
5
+ class VideoInfo
6
+ module Providers
7
+ module VimeoScraper
8
+ def author
9
+ json_info['author']['name']
10
+ end
11
+
12
+ def author_thumbnail
13
+ d = data.css('script')[5].text.split('window.vimeo.clip_page_config =')[1]
14
+ JSON.parse(d.split(";\n")[0])['owner']['portrait']['src']
15
+ end
16
+
17
+ def available?
18
+ is_available = super
19
+
20
+ if is_available
21
+ page_header = data.css('#page_header')
22
+
23
+ if page_header.text == "\n Private Video\n "
24
+ is_available = false
25
+ end
26
+ end
27
+
28
+ is_available
29
+ end
30
+
31
+ def title
32
+ meta_node_value('og:title')
33
+ end
34
+
35
+ def description
36
+ meta_node_value('og:description')
37
+ end
38
+
39
+ def date
40
+ upload_date = json_info['uploadDate']
41
+ ISO8601::DateTime.new(upload_date).to_time
42
+ end
43
+
44
+ def duration
45
+ duration = json_info['duration']
46
+ ISO8601::Duration.new(duration).to_seconds.to_i
47
+ end
48
+
49
+ def keywords
50
+ keywords_str = ''
51
+
52
+ json_info['keywords'].each { |keyword| keywords_str << keyword << ", " }
53
+
54
+ keywords_str.chop.chop # remove trailing ", "
55
+ end
56
+
57
+ def height
58
+ meta_node_value('og:video:height').to_i
59
+ end
60
+
61
+ def width
62
+ meta_node_value('og:video:width').to_i
63
+ end
64
+
65
+ def thumbnail_small
66
+ thumbnail_url.split('_')[0] + '_100x75.jpg'
67
+ end
68
+
69
+ def thumbnail_medium
70
+ thumbnail_url.split('_')[0] + '_200x150.jpg'
71
+ end
72
+
73
+ def thumbnail_large
74
+ thumbnail_url.split('_')[0] + '_640.jpg'
75
+ end
76
+
77
+ def view_count
78
+ json_info['interactionCount']
79
+ end
80
+
81
+ private
82
+
83
+ def json_info
84
+ @json_info ||= JSON.parse(data.css('script').detect do |n|
85
+ type = n.attr('type')
86
+
87
+ if type.nil?
88
+ false
89
+ else
90
+ type.value == 'application/ld+json'
91
+ end
92
+ end.text)[0]
93
+ end
94
+
95
+ def thumbnail_url
96
+ @thumbnail_url ||= json_info['thumbnailUrl']
97
+ end
98
+
99
+ def meta_nodes
100
+ @meta_nodes ||= data.css('meta')
101
+ end
102
+
103
+ def meta_node_value(name)
104
+ if available?
105
+ node = meta_nodes.detect do |n|
106
+ property = n.attr('property')
107
+
108
+ if property.nil?
109
+ false
110
+ else
111
+ property.value == name
112
+ end
113
+ end
114
+
115
+ node.attr('content').value
116
+ end
117
+ end
118
+
119
+ def _set_data_from_api_impl(api_url)
120
+ Oga.parse_html(open(api_url.to_s, allow_redirections: :safe).read)
121
+ end
122
+
123
+ def _api_url
124
+ uri = URI.parse(@url)
125
+ uri.scheme = 'https'
126
+ uri.to_s
127
+ end
128
+
129
+ def _api_path
130
+ _api_url
131
+ end
132
+ end
133
+ end
134
+ end
135
+
@@ -50,17 +50,21 @@ class VideoInfo
50
50
  end
51
51
 
52
52
  def embed_url
53
- youtube = data[
54
- /iframe\ id=\"video_player\".*src=\"(.*)\"\ frameborder=/, 1
53
+ iframe_src = data[
54
+ /iframe\ id=\"video_player\".*src=\"([^\"]*)\".*frameborder=/, 1
55
55
  ]
56
- if youtube
56
+ if iframe_src
57
+ # it may be youtube video
57
58
  VideoInfo::Providers::Youtube.new(
58
- URI.unescape(youtube.gsub(/\\/, ''))
59
+ URI.unescape(iframe_src.gsub(/\\/, ''))
59
60
  ).embed_url
60
61
  else
61
62
  "//vk.com/video_ext.php?oid=#{video_owner}" +
62
63
  "&id=#{video_id}&hash=#{_data_hash}"
63
64
  end
65
+ rescue
66
+ # or rutube video
67
+ iframe_src
64
68
  end
65
69
 
66
70
  def duration
@@ -82,15 +86,14 @@ class VideoInfo
82
86
  resp.body.force_encoding('cp1251').encode('UTF-8', undef: :replace)
83
87
  end
84
88
 
85
- def _set_data_from_api
86
- url = URI('https://vk.com/al_video.php')
89
+ def _set_data_from_api_impl(api_url)
87
90
  options['act'] = 'show'
88
91
  options['al'] = '1'
89
92
  options['video'] = "#{@video_owner}_#{@video_id}"
90
- data = _make_request(url, options)
93
+ data = _make_request(api_url, options)
91
94
  if data.index('Ошибка доступа')
92
95
  # try second time
93
- _make_request(url, options)
96
+ _make_request(api_url, options)
94
97
  else
95
98
  data
96
99
  end
@@ -158,6 +161,10 @@ class VideoInfo
158
161
  def _default_url_attributes
159
162
  {}
160
163
  end
164
+
165
+ def _api_url
166
+ URI('https://vk.com/al_video.php')
167
+ end
161
168
  end
162
169
  end
163
170
  end
@@ -32,18 +32,15 @@ class VideoInfo
32
32
  end
33
33
 
34
34
  def thumbnail_small
35
- return "https://i.ytimg.com/vi/#{video_id}/default.jpg" unless _video_snippet['thumbnails']
36
- _video_snippet['thumbnails']['default']['url']
35
+ "https://i.ytimg.com/vi/#{video_id}/default.jpg"
37
36
  end
38
37
 
39
38
  def thumbnail_medium
40
- return "https://i.ytimg.com/vi/#{video_id}/mqdefault.jpg" unless _video_snippet['thumbnails']
41
- _video_snippet['thumbnails']['medium']['url']
39
+ "https://i.ytimg.com/vi/#{video_id}/mqdefault.jpg"
42
40
  end
43
41
 
44
42
  def thumbnail_large
45
- return "https://i.ytimg.com/vi/#{video_id}/hqdefault.jpg" unless _video_snippet['thumbnails']
46
- _video_snippet['thumbnails']['high']['url']
43
+ "https://i.ytimg.com/vi/#{video_id}/hqdefault.jpg"
47
44
  end
48
45
 
49
46
  private
@@ -34,7 +34,9 @@ class VideoInfo
34
34
  private
35
35
 
36
36
  def available?
37
- data['items'].size > 0 rescue false
37
+ data['items'].size > 0
38
+ rescue VideoInfo::HttpError
39
+ false
38
40
  end
39
41
 
40
42
  def _api_base
@@ -1,5 +1,6 @@
1
1
  require 'oga'
2
2
  require 'open-uri'
3
+ require 'net_http_timeout_errors'
3
4
 
4
5
  class VideoInfo
5
6
  module Providers
@@ -34,20 +35,8 @@ class VideoInfo
34
35
  meta_node_value('title')
35
36
  end
36
37
 
37
- def thumbnail_small
38
- "https://i.ytimg.com/vi/#{video_id}/default.jpg"
39
- end
40
-
41
- def thumbnail_medium
42
- "https://i.ytimg.com/vi/#{video_id}/mqdefault.jpg"
43
- end
44
-
45
- def thumbnail_large
46
- "https://i.ytimg.com/vi/#{video_id}/hqdefault.jpg"
47
- end
48
-
49
38
  def view_count
50
- data.css('div.watch-view-count').text.gsub(',', '').to_i
39
+ data.css('div.watch-view-count').text.gsub(/\D/, '').to_i
51
40
  end
52
41
 
53
42
  private
@@ -80,7 +69,7 @@ class VideoInfo
80
69
  data.css('div#unavailable-submessage').text.strip.empty?
81
70
  end
82
71
 
83
- def _set_data_from_api(api_url = _api_url)
72
+ def _set_data_from_api_impl(api_url)
84
73
  uri = URI(api_url)
85
74
 
86
75
  unless uri.scheme
@@ -88,7 +77,14 @@ class VideoInfo
88
77
  uri.scheme = 'http'
89
78
  end
90
79
 
91
- Oga.parse_html(open(uri.to_s, allow_redirections: :safe).read)
80
+ # handle fullscreen video URLs
81
+ if url.include?('.com/v/')
82
+ video_id = url.split('/v/')[1].split('?')[0]
83
+ new_url = 'https://www.youtube.com/watch?v=' + video_id
84
+ Oga.parse_html(open(new_url).read)
85
+ else
86
+ Oga.parse_html(open(uri.to_s, allow_redirections: :safe).read)
87
+ end
92
88
  end
93
89
 
94
90
  def _api_url
@@ -48,7 +48,7 @@ class VideoInfo
48
48
  private
49
49
 
50
50
  def _url_regex
51
- /youtube.com\/playlist\?p=(\S*)|youtube.com\/embed\/videoseries\?list=([a-zA-Z0-9-]*)/
51
+ /youtube.com\/playlist\?p=(\S*)|youtube.com\/playlist\?list=(\S*)|youtube.com\/embed\/videoseries\?list=([a-zA-Z0-9-]*)/
52
52
  end
53
53
  end
54
54
  end
@@ -22,6 +22,18 @@ class VideoInfo
22
22
  nil
23
23
  end
24
24
 
25
+ def thumbnail_small
26
+ _video_snippet['thumbnails']['default']['url']
27
+ end
28
+
29
+ def thumbnail_medium
30
+ _video_snippet['thumbnails']['medium']['url']
31
+ end
32
+
33
+ def thumbnail_large
34
+ _video_snippet['thumbnails']['high']['url']
35
+ end
36
+
25
37
  private
26
38
 
27
39
  def _playlist_entry
@@ -1,3 +1,3 @@
1
1
  class VideoInfo
2
- VERSION = '2.5.0'
2
+ VERSION = '2.6.0'
3
3
  end
@@ -0,0 +1,242 @@
1
+ require 'spec_helper'
2
+
3
+ describe VideoInfo::Providers::Vimeo do
4
+ before(:all) do
5
+ VideoInfo.provider_api_keys = { vimeo: '6b66b015a3504793b4f541d878f46ff6' }
6
+ end
7
+
8
+ describe '.usable?' do
9
+ subject { VideoInfo::Providers::Vimeo.usable?(url) }
10
+
11
+ context 'with vimeo url' do
12
+ let(:url) { 'http://www.vimeo.com/898029' }
13
+ it { is_expected.to be_truthy }
14
+ end
15
+
16
+ context 'with Vimeo OnDemand url' do
17
+ let(:url) { 'https://vimeo.com/ondemand/less/101677664' }
18
+ it { is_expected.to be_truthy }
19
+ end
20
+
21
+ context 'with Vimeo Channels url' do
22
+ let(:url) { 'https://vimeo.com/channels/any_channel/111431415' }
23
+ it { is_expected.to be_truthy }
24
+ end
25
+
26
+ context "with Vimeo Review url" do
27
+ let(:url) { 'https://vimeo.com/user39798190/review/126641548/8a56234e32' }
28
+ it { is_expected.to be_truthy }
29
+ end
30
+
31
+ context 'with vimeo album url' do
32
+ let(:url) { 'http://vimeo.com/album/2755718' }
33
+ it { is_expected.to be_falsey }
34
+ end
35
+
36
+ context 'with vimeo hubnub embed url' do
37
+ let(:url) { 'http://player.vimeo.com/hubnut/album/2755718' }
38
+ it { is_expected.to be_falsey }
39
+ end
40
+
41
+ context 'with other url' do
42
+ let(:url) { 'http://www.youtube.com/898029' }
43
+ it { is_expected.to be_falsey }
44
+ end
45
+ end
46
+
47
+ describe '#available?' do
48
+ context 'with valid video', :vcr do
49
+ subject { VideoInfo.new('http://www.vimeo.com/898029') }
50
+
51
+ describe '#available?' do
52
+ it { is_expected.to be_available }
53
+ end
54
+ end
55
+
56
+ context "with 'this video does not exist' video", :vcr do
57
+ subject { VideoInfo.new('http://vimeo.com/59312311') }
58
+
59
+ describe '#available?' do
60
+ it { is_expected.to_not be_available }
61
+ end
62
+ end
63
+
64
+ context "with 'password required' video", :vcr do
65
+ subject { VideoInfo.new('http://vimeo.com/74636562') }
66
+
67
+ describe '#available?' do
68
+ it { is_expected.to_not be_available }
69
+ end
70
+ end
71
+ end
72
+
73
+ context 'with video 898029', :vcr do
74
+ subject { VideoInfo.new('http://www.vimeo.com/898029') }
75
+
76
+ describe '#provider' do
77
+ subject { super().provider }
78
+ it { is_expected.to eq 'Vimeo' }
79
+ end
80
+
81
+ describe '#video_id' do
82
+ subject { super().video_id }
83
+ it { is_expected.to eq '898029' }
84
+ end
85
+
86
+ describe '#url' do
87
+ subject { super().url }
88
+ it { is_expected.to eq 'http://www.vimeo.com/898029' }
89
+ end
90
+
91
+ describe '#embed_url' do
92
+ subject { super().embed_url }
93
+ it { is_expected.to eq '//player.vimeo.com/video/898029' }
94
+ end
95
+
96
+ describe '#embed_code' do
97
+ subject { super().embed_code }
98
+ it { is_expected.to eq '<iframe src="//player.vimeo.com/video/898029?autoplay=0&byline=0&portrait=0&title=0" frameborder="0"></iframe>' }
99
+ end
100
+
101
+ describe '#title' do
102
+ subject { super().title }
103
+ it { is_expected.to eq 'Cherry Bloom - King Of The Knife' }
104
+ end
105
+
106
+ describe '#description' do
107
+ subject { super().description }
108
+ it { is_expected.to eq 'The first video from the upcoming album Secret Sounds, to download in-stores April 14. Checkout http://www.cherrybloom.net' }
109
+ end
110
+
111
+ describe '#keywords' do
112
+ subject { super().keywords }
113
+ it { is_expected.to eq 'cherry bloom, secret sounds, king of the knife, rock, alternative' }
114
+ end
115
+
116
+ describe '#duration' do
117
+ subject { super().duration }
118
+ it { is_expected.to eq 175 }
119
+ end
120
+
121
+ describe '#width' do
122
+ subject { super().width }
123
+ it { is_expected.to eq 640 }
124
+ end
125
+
126
+ describe '#height' do
127
+ subject { super().height }
128
+ it { is_expected.to eq 360 }
129
+ end
130
+
131
+ describe '#date' do
132
+ subject { super().date }
133
+ it { is_expected.to eq Time.parse('2008-04-14T17:10:39+00:00', Time.now.utc).utc }
134
+ end
135
+
136
+ describe '#thumbnail_small' do
137
+ subject { super().thumbnail_small }
138
+ it { is_expected.to eq 'https://i.vimeocdn.com/video/34373130_100x75.jpg' }
139
+ end
140
+
141
+ describe '#thumbnail_medium' do
142
+ subject { super().thumbnail_medium }
143
+ it { is_expected.to eq 'https://i.vimeocdn.com/video/34373130_200x150.jpg' }
144
+ end
145
+
146
+ describe '#thumbnail_large' do
147
+ subject { super().thumbnail_large }
148
+ it { is_expected.to eq 'https://i.vimeocdn.com/video/34373130_640.jpg' }
149
+ end
150
+
151
+ describe '#author_thumbnail' do
152
+ subject { super().author_thumbnail }
153
+ it { is_expected.to eq 'https://i.vimeocdn.com/portrait/2577152_75x75.jpg' }
154
+ end
155
+
156
+ describe '#author' do
157
+ subject { super().author }
158
+ it { is_expected.to eq 'Octave Zangs' }
159
+ end
160
+
161
+ describe '#view_count' do
162
+ subject { super().view_count }
163
+ it { is_expected.to be > 4000 }
164
+ end
165
+ end
166
+
167
+ context 'with video 898029 and url_attributes', :vcr do
168
+ subject { VideoInfo.new('http://www.vimeo.com/898029') }
169
+
170
+ it { expect(subject.embed_code(url_attributes: { autoplay: 1 })).to match(/autoplay=1/) }
171
+ end
172
+
173
+ context 'with video 898029 and iframe_attributes', :vcr do
174
+ subject { VideoInfo.new('http://www.vimeo.com/898029') }
175
+
176
+ it { expect(subject.embed_code(iframe_attributes: { width: 800, height: 600 })).to match(/width="800"/) }
177
+ it { expect(subject.embed_code(iframe_attributes: { width: 800, height: 600 })).to match(/height="600"/) }
178
+ end
179
+
180
+ context 'with video 898029 in /group/ url', :vcr do
181
+ subject { VideoInfo.new('http://vimeo.com/groups/1234/videos/898029') }
182
+
183
+ describe '#provider' do
184
+ subject { super().provider }
185
+ it { is_expected.to eq 'Vimeo' }
186
+ end
187
+
188
+ describe '#video_id' do
189
+ subject { super().video_id }
190
+ it { is_expected.to eq '898029' }
191
+ end
192
+ end
193
+
194
+ context 'with video 898029 in /group/ url', :vcr do
195
+ subject { VideoInfo.new('http://player.vimeo.com/video/898029') }
196
+
197
+ describe '#provider' do
198
+ subject { super().provider }
199
+ it { is_expected.to eq 'Vimeo' }
200
+ end
201
+
202
+ describe '#video_id' do
203
+ subject { super().video_id }
204
+ it { is_expected.to eq '898029' }
205
+ end
206
+ end
207
+
208
+ context 'with video 898029 in text', :vcr do
209
+ subject { VideoInfo.new('<a href="http://www.vimeo.com/898029">http://www.vimeo.com/898029</a>') }
210
+
211
+ describe '#provider' do
212
+ subject { super().provider }
213
+ it { is_expected.to eq 'Vimeo' }
214
+ end
215
+
216
+ describe '#video_id' do
217
+ subject { super().video_id }
218
+ it { is_expected.to eq '898029' }
219
+ end
220
+ end
221
+
222
+ context 'with video 101677664 in /ondemand/ url', :vcr do
223
+ subject { VideoInfo.new('https://vimeo.com/ondemand/less/101677664') }
224
+
225
+ its(:provider) { should eq 'Vimeo' }
226
+ its(:video_id) { should eq '101677664' }
227
+ end
228
+
229
+ context 'with video 111431415 in /channels/*/ url', :vcr do
230
+ subject { VideoInfo.new('https://vimeo.com/channels/some_channel1/111431415') }
231
+
232
+ its(:provider) { should eq 'Vimeo' }
233
+ its(:video_id) { should eq '111431415' }
234
+ end
235
+
236
+ context "with video 126641548 in /user*/review/126641548/* url", :vcr do
237
+ subject { VideoInfo.new('http://www.vimeo.com/user39798190/review/126641548/8a56234e32') }
238
+
239
+ its(:provider) { should eq 'Vimeo' }
240
+ its(:video_id) { should eq '126641548' }
241
+ end
242
+ end