video_info 2.5.0 → 2.6.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.
@@ -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