yt 0.15.2 → 0.15.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fe92325f41cffdb8e81da539742c539cc3280fd0
4
- data.tar.gz: 40ccb712b549661f2cd905dfed5870cf14d77f4e
3
+ metadata.gz: 955b88eeaa4654c03880228bf322546eb0fba3c3
4
+ data.tar.gz: 8b5bbaf09793909b2230180a49107a58f0f69ed7
5
5
  SHA512:
6
- metadata.gz: 2567686bcaae799d503506e063ec171e16fa7ed975e9fb78d053c971c1aad144c0d3bdabc10daf7ad69b30045ef8c8edb945c37d0b19ed007cb5d664b774e9c4
7
- data.tar.gz: 6283dfcb5b3288c2bf1f66bd15833a5bd073982f253b759c3fceb665c6c29ce7e373073aa0a9e1243b82167e5b58f9e7d1ae8f02f90a2764ac77289279398e0c
6
+ metadata.gz: 6715a5ff1ec202aa3f1d678aea12baa21f87daf15f1234c3b24e4e0be298b62db322ea1c6ea8e948fd4345a49ca15d27aa3b03237f0f8249d0fea67f371cbcc8
7
+ data.tar.gz: eac8f4472b3c814986f3c41469357043019648e523edfef4aafdf4ef3003a4d4700e5c3b2d6eb17df799ed750ebd0372057ecfa2946b3196e0dc43e1df944b60
@@ -7,6 +7,12 @@ For more information about changelogs, check
7
7
  [Vandamme](http://tech-angels.github.io/vandamme).
8
8
 
9
9
 
10
+ ## 0.15.3 - 2015-04-27
11
+
12
+ * [FEATURE] New `file_size`, `file_type`, `container` methods for Yt::Video.
13
+ * [BUGFIX] Retrieve `category_id` also for videos obtained through a search.
14
+ * [FEATURE] Add .includes(:category) to .videos in order to eager-load category title and ID of a collection of videos
15
+
10
16
  ## 0.15.2 - 2015-04-27
11
17
 
12
18
  * [FEATURE] New `embed_html` method for Yt::Video.
data/README.md CHANGED
@@ -41,7 +41,7 @@ To install on your system, run
41
41
 
42
42
  To use inside a bundled Ruby project, add this line to the Gemfile:
43
43
 
44
- gem 'yt', '~> 0.15.2'
44
+ gem 'yt', '~> 0.15.3'
45
45
 
46
46
  Since the gem follows [Semantic Versioning](http://semver.org),
47
47
  indicating the full version in your Gemfile (~> *major*.*minor*.*patch*)
@@ -310,6 +310,8 @@ Use [Yt::Video](http://rubydoc.info/github/Fullscreen/yt/master/Yt/Models/Video)
310
310
  * retrieve data about live-streaming videos
311
311
  * retrieve the advertising options of a video
312
312
  * retrieve the HTML code of the video player
313
+ * retrieve the file details of a video
314
+ * retrieve the category title of a video
313
315
 
314
316
  ```ruby
315
317
  # Videos can be initialized with ID or URL
@@ -368,6 +370,12 @@ video.concurrent_viewers #=> 0
368
370
 
369
371
  video.embed_html #=> "<iframe type='text/html' src='http://www.youtube.com/embed/BPNYv0vd78A' width='640' height='360' frameborder='0' allowfullscreen='true'/>"
370
372
 
373
+ video.file_size #=> 8000000
374
+ video.file_type #=> "video"
375
+ video.container #=> "mov"
376
+
377
+ video.category_title #=> "People & Blogs"
378
+
371
379
  video.annotations.count #=> 1
372
380
  video.annotations.first #=> #<Yt::Models::Annotation @id=...>
373
381
  ```
@@ -913,9 +921,3 @@ the [YouTube Analytics API](https://developers.google.com/youtube/analytics).
913
921
  If you find that a method is missing, fork the project, add the missing code,
914
922
  write the appropriate tests, then submit a pull request, and it will gladly
915
923
  be merged!
916
-
917
- Don’t hesitate to send code comments, issues or pull requests through GitHub
918
- and to spread the love on Twitter by following [@ytgem](https://twitter.com/intent/user?screen_name=ytgem) – 
919
- all feedback is appreciated.
920
-
921
- ![yt4](https://cloud.githubusercontent.com/assets/7408595/3638115/0ef9d6bc-1037-11e4-831a-787204e9b979.png)
@@ -0,0 +1,29 @@
1
+ require 'yt/collections/base'
2
+ require 'yt/models/file_detail'
3
+
4
+ module Yt
5
+ module Collections
6
+ class FileDetails < Base
7
+
8
+ private
9
+
10
+ def attributes_for_new_item(data)
11
+ {data: data['fileDetails']}
12
+ end
13
+
14
+ # @return [Hash] the parameters to submit to YouTube to get the
15
+ # file detail of a video.
16
+ # @see https://developers.google.com/youtube/v3/docs/videos#fileDetails
17
+ def list_params
18
+ super.tap do |params|
19
+ params[:params] = file_details_params
20
+ params[:path] = '/youtube/v3/videos'
21
+ end
22
+ end
23
+
24
+ def file_details_params
25
+ {max_results: 50, part: 'fileDetails', id: @parent.id}
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,35 @@
1
+ require 'yt/collections/base'
2
+ require 'yt/models/video_category'
3
+
4
+ module Yt
5
+ module Collections
6
+ class VideoCategories < Base
7
+
8
+ private
9
+
10
+ def attributes_for_new_item(data)
11
+ {}.tap do |attributes|
12
+ attributes[:id] = data['id']
13
+ attributes[:snippet] = data['snippet']
14
+ attributes[:auth] = @auth
15
+ end
16
+ end
17
+
18
+ # @return [Hash] the parameters to submit to YouTube to list video categories.
19
+ # @see https://developers.google.com/youtube/v3/docs/videoCategories/list
20
+ def list_params
21
+ super.tap do |params|
22
+ params[:params] = video_categories_params
23
+ end
24
+ end
25
+
26
+ def video_categories_params
27
+ {}.tap do |params|
28
+ params[:part] = 'snippet'
29
+ params[:id] = @parent.category_id if @parent
30
+ apply_where_params! params
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -24,12 +24,16 @@ module Yt
24
24
  attributes[:status] = data['status']
25
25
  attributes[:content_details] = data['contentDetails']
26
26
  attributes[:statistics] = data['statistics']
27
+ attributes[:video_category] = data['videoCategory']
27
28
  attributes[:auth] = @auth
28
29
  end
29
30
  end
30
31
 
31
32
  def eager_load_items_from(items)
32
33
  if included_relationships.any?
34
+ include_category = included_relationships.delete(:category)
35
+ included_relationships.append(:snippet).uniq! if include_category
36
+
33
37
  ids = items.map{|item| item['id']['videoId']}
34
38
  parts = included_relationships.map{|r| r.to_s.camelize(:lower)}
35
39
  conditions = {id: ids.join(','), part: parts.join(',')}
@@ -46,6 +50,17 @@ module Yt
46
50
  end
47
51
  end
48
52
  end
53
+
54
+ if include_category
55
+ category_ids = items.map{|item| item['snippet']['categoryId']}.uniq
56
+ conditions = {id: category_ids.join(',')}
57
+ video_categories = Collections::VideoCategories.new(auth: @auth).where conditions
58
+
59
+ items.each do |item|
60
+ video_category = video_categories.find{|v| v.id == item['snippet']['categoryId']}
61
+ item['videoCategory'] = video_category.data
62
+ end
63
+ end
49
64
  end
50
65
  super
51
66
  end
@@ -0,0 +1,26 @@
1
+ require 'yt/models/base'
2
+
3
+ module Yt
4
+ module Models
5
+ # Encapsulates basic information about the video file itself,
6
+ # including the file name, size, type, and container.
7
+ # @see https://developers.google.com/youtube/v3/docs/videos#fileDetails
8
+ class FileDetail < Base
9
+ attr_reader :data
10
+
11
+ def initialize(options = {})
12
+ @data = options[:data] || {}
13
+ end
14
+
15
+ # @return [Integer] the file size of the video (in bytes).
16
+ has_attribute :file_size, type: Integer
17
+
18
+ # @return [String] the file type of the file. May be one of:
19
+ # archive, audio, document, image, other, project, or video.
20
+ has_attribute :file_type
21
+
22
+ # @return [String] the container of the video (e.g. 'mov').
23
+ has_attribute :container
24
+ end
25
+ end
26
+ end
@@ -147,9 +147,9 @@ module Yt
147
147
  end
148
148
 
149
149
  # Returns whether YouTube API includes all attributes in this snippet.
150
- # For instance, YouTube API only returns tags on Videos#list, not on
151
- # Videos#search. And returns position on PlaylistItems#list, not on
152
- # PlaylistItems#insert.
150
+ # For instance, YouTube API only returns tags and categoryId on
151
+ # Videos#list, not on Videos#search. And returns position on
152
+ # PlaylistItems#list, not on PlaylistItems#insert.
153
153
  # This method is used to guarantee that, when a video was instantiated
154
154
  # by one of the methods above, an additional call to is made to retrieve
155
155
  # the full snippet in case an attribute is missing.
@@ -23,6 +23,11 @@ module Yt
23
23
  delegate :duration, :hd?, :stereoscopic?, :captioned?, :licensed?,
24
24
  to: :content_detail
25
25
 
26
+ # @!attribute [r] file_detail
27
+ # @return [Yt::Models::FileDetail] the video’s file details.
28
+ has_one :file_detail
29
+ delegate :file_size, :file_type, :container, to: :file_detail
30
+
26
31
  has_one :advertising_options_set
27
32
  delegate :ad_formats, to: :advertising_options_set
28
33
 
@@ -30,6 +35,11 @@ module Yt
30
35
  # @return [Yt::Models::Rating] the video’s rating.
31
36
  has_one :rating
32
37
 
38
+ # @!attribute [r] video_category
39
+ # @return [Yt::Models::VideoCategory] the video’s category.
40
+ has_one :video_category
41
+ delegate :title, to: :video_category, prefix: :category
42
+
33
43
  # @!attribute [r] live_streaming_detail
34
44
  # @return [Yt::Models::LiveStreamingDetail] live streaming detail.
35
45
  has_one :live_streaming_detail
@@ -131,6 +141,12 @@ module Yt
131
141
  if options[:content_details]
132
142
  @content_detail = ContentDetail.new data: options[:content_details]
133
143
  end
144
+ if options[:file_details]
145
+ @file_detail = FileDetail.new data: options[:file_details]
146
+ end
147
+ if options[:video_category]
148
+ @video_category = VideoCategory.new data: options[:video_category]
149
+ end
134
150
  end
135
151
 
136
152
  # Returns the list of keyword tags associated with the video.
@@ -148,6 +164,20 @@ module Yt
148
164
  snippet.tags
149
165
  end
150
166
 
167
+ # Returns the category ID associated with the video.
168
+ # Since YouTube API only returns categoryID on Videos#list, the memoized
169
+ # @snippet is erased if the video was instantiated through Video#search
170
+ # (e.g., by calling account.videos or channel.videos), so that the full
171
+ # snippet (with categoryID) is loaded, rather than the partial one.
172
+ # @see https://developers.google.com/youtube/v3/docs/videos
173
+ # @return [String] ID of the YouTube category associated with the video.
174
+ def category_id
175
+ unless snippet.category_id.present? || snippet.complete?
176
+ @snippet = nil
177
+ end
178
+ snippet.category_id
179
+ end
180
+
151
181
  # Deletes the video.
152
182
  #
153
183
  # This method requires {Resource#auth auth} to return an authenticated
@@ -273,4 +303,4 @@ module Yt
273
303
  end
274
304
  end
275
305
  end
276
- end
306
+ end
@@ -0,0 +1,11 @@
1
+ require 'yt/models/resource'
2
+
3
+ module Yt
4
+ module Models
5
+ # Provides methods to interact with YouTube video categories.
6
+ # @see https://developers.google.com/youtube/v3/docs/videoCategories
7
+ class VideoCategory < Resource
8
+ delegate :data, :title, to: :snippet
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module Yt
2
- VERSION = '0.15.2'
2
+ VERSION = '0.15.3'
3
3
  end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+ require 'yt/models/file_detail'
3
+
4
+ describe Yt::FileDetail do
5
+ subject(:file_detail) { Yt::FileDetail.new data: data }
6
+
7
+ describe '#data' do
8
+ let(:data) { {"key"=>"value"} }
9
+ specify 'returns the data the file detail was initialized with' do
10
+ expect(file_detail.data).to eq data
11
+ end
12
+ end
13
+
14
+ describe '#file_size' do
15
+ context 'given a video with fileSize' do
16
+ let(:data) { {"fileSize"=>"8000000"} }
17
+ it { expect(file_detail.file_size).to be 8_000_000 }
18
+ end
19
+ end
20
+
21
+ describe '#file_type' do
22
+ context 'given a video with fileType' do
23
+ let(:data) { {"fileType"=>"video"} }
24
+ it { expect(file_detail.file_type).to eq 'video' }
25
+ end
26
+ end
27
+
28
+ describe '#container' do
29
+ context 'given a video with container' do
30
+ let(:data) { {"container"=>"mov"} }
31
+ it { expect(file_detail.container).to eq 'mov' }
32
+ end
33
+ end
34
+ end
@@ -7,7 +7,7 @@ describe Yt::Player do
7
7
 
8
8
  describe '#data' do
9
9
  let(:data) { {"key"=>"value"} }
10
- specify 'returns the data the statistics set was initialized with' do
10
+ specify 'returns the data the player was initialized with' do
11
11
  expect(player.data).to eq data
12
12
  end
13
13
  end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+ require 'yt/models/video_category'
3
+
4
+ describe Yt::VideoCategory do
5
+ subject(:video_category) { Yt::VideoCategory.new attrs }
6
+
7
+ describe '#id' do
8
+ context 'given fetching a video category returns an id' do
9
+ let(:attrs) { {id: "22"} }
10
+ it { expect(video_category.id).to eq '22' }
11
+ end
12
+ end
13
+
14
+ describe '#snippet' do
15
+ context 'given fetching a video category returns a snippet' do
16
+ let(:attrs) { {snippet: {"title": "People & Blogs", "assignable": true}} }
17
+
18
+ it { expect(video_category.snippet).to be_a Yt::Snippet }
19
+ end
20
+ end
21
+ end
@@ -21,9 +21,10 @@ describe Yt::Account, :device_app do
21
21
  describe '.videos' do
22
22
  let(:video) { $account.videos.where(order: 'viewCount').first }
23
23
 
24
- specify 'returns the videos uploaded by the account with their tags' do
24
+ specify 'returns the videos uploaded by the account with their tags and category ID' do
25
25
  expect(video).to be_a Yt::Video
26
26
  expect(video.tags).not_to be_empty
27
+ expect(video.category_id).not_to be_nil
27
28
  end
28
29
 
29
30
  describe '.where(q: query_string)' do
@@ -24,7 +24,7 @@ describe Yt::Channel, :device_app do
24
24
  describe '.videos' do
25
25
  let(:video) { channel.videos.first }
26
26
 
27
- specify 'returns the videos in the channel without their tags' do
27
+ specify 'returns the videos in the channel without tags or category ID' do
28
28
  expect(video).to be_a Yt::Video
29
29
  expect(video.snippet).not_to be_complete
30
30
  end
@@ -62,6 +62,14 @@ describe Yt::Channel, :device_app do
62
62
  end
63
63
  end
64
64
 
65
+ describe '.includes(:category)' do
66
+ let(:video) { channel.videos.includes(:category, :status).first }
67
+
68
+ specify 'eager-loads the category (id and title) of each video' do
69
+ expect(video.instance_variable_defined? :@snippet).to be true
70
+ expect(video.instance_variable_defined? :@video_category).to be true
71
+ end
72
+ end
65
73
 
66
74
  describe 'when the channel has more than 500 videos' do
67
75
  let(:id) { 'UCsmvakQZlvGsyjyOhmhvOsw' }
@@ -64,6 +64,7 @@ describe Yt::Video, :device_app do
64
64
  expect(video.scheduled_end_time).to be_nil
65
65
  expect(video.concurrent_viewers).to be_nil
66
66
  expect(video.embed_html).to be_a String
67
+ expect(video.category_title).to be_a String
67
68
  end
68
69
 
69
70
  it { expect{video.update}.to fail }
@@ -119,6 +120,7 @@ describe Yt::Video, :device_app do
119
120
  it { expect{video.rating}.to raise_error Yt::Errors::NoItems }
120
121
  it { expect{video.status}.to raise_error Yt::Errors::NoItems }
121
122
  it { expect{video.statistics_set}.to raise_error Yt::Errors::NoItems }
123
+ it { expect{video.file_detail}.to raise_error Yt::Errors::NoItems }
122
124
  end
123
125
 
124
126
  context 'given one of my own videos that I want to delete' do
@@ -280,7 +282,7 @@ describe Yt::Video, :device_app do
280
282
 
281
283
  it 'returns valid reports for video-related metrics' do
282
284
  # Some reports are only available to Content Owners.
283
- # See content ownere test for more details about what the methods return.
285
+ # See content owner test for more details about what the methods return.
284
286
  expect{video.views}.not_to raise_error
285
287
  expect{video.comments}.not_to raise_error
286
288
  expect{video.likes}.not_to raise_error
@@ -389,4 +391,22 @@ describe Yt::Video, :device_app do
389
391
  it { expect{update}.to raise_error Yt::Errors::RequestError }
390
392
  end
391
393
  end
392
- end
394
+
395
+ # @note: This test is separated from the block above because YouTube only
396
+ # returns file details for *some videos*: "The fileDetails object will
397
+ # only be returned if the processingDetails.fileAvailability property
398
+ # has a value of available.". Therefore, just to test fileDetails, we use a
399
+ # different video that (for some unknown reason) is marked as 'available'.
400
+ # Also note that I was not able to find a single video returning fileName,
401
+ # therefore video.file_name is not returned by Yt, until it can be tested.
402
+ # @see https://developers.google.com/youtube/v3/docs/videos#processingDetails.fileDetailsAvailability
403
+ context 'given one of my own *available* videos' do
404
+ let(:id) { 'yCmaOvUFhlI' }
405
+
406
+ it 'returns valid file details' do
407
+ expect(video.file_size).to be_an Integer
408
+ expect(video.file_type).to be_a String
409
+ expect(video.container).to be_a String
410
+ end
411
+ end
412
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.2
4
+ version: 0.15.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Claudio Baccigalupo
@@ -142,6 +142,7 @@ files:
142
142
  - lib/yt/collections/content_owner_details.rb
143
143
  - lib/yt/collections/content_owners.rb
144
144
  - lib/yt/collections/device_flows.rb
145
+ - lib/yt/collections/file_details.rb
145
146
  - lib/yt/collections/ids.rb
146
147
  - lib/yt/collections/live_streaming_details.rb
147
148
  - lib/yt/collections/ownerships.rb
@@ -162,6 +163,7 @@ files:
162
163
  - lib/yt/collections/subscribers.rb
163
164
  - lib/yt/collections/subscriptions.rb
164
165
  - lib/yt/collections/user_infos.rb
166
+ - lib/yt/collections/video_categories.rb
165
167
  - lib/yt/collections/videos.rb
166
168
  - lib/yt/collections/viewer_percentages.rb
167
169
  - lib/yt/config.rb
@@ -189,6 +191,7 @@ files:
189
191
  - lib/yt/models/content_owner_detail.rb
190
192
  - lib/yt/models/description.rb
191
193
  - lib/yt/models/device_flow.rb
194
+ - lib/yt/models/file_detail.rb
192
195
  - lib/yt/models/id.rb
193
196
  - lib/yt/models/iterator.rb
194
197
  - lib/yt/models/live_streaming_detail.rb
@@ -212,6 +215,7 @@ files:
212
215
  - lib/yt/models/url.rb
213
216
  - lib/yt/models/user_info.rb
214
217
  - lib/yt/models/video.rb
218
+ - lib/yt/models/video_category.rb
215
219
  - lib/yt/request.rb
216
220
  - lib/yt/version.rb
217
221
  - spec/collections/claims_spec.rb
@@ -239,6 +243,7 @@ files:
239
243
  - spec/models/content_detail_spec.rb
240
244
  - spec/models/content_owner_detail_spec.rb
241
245
  - spec/models/description_spec.rb
246
+ - spec/models/file_detail_spec.rb
242
247
  - spec/models/live_streaming_detail_spec.rb
243
248
  - spec/models/ownership_spec.rb
244
249
  - spec/models/player_spec.rb
@@ -257,6 +262,7 @@ files:
257
262
  - spec/models/subscription_spec.rb
258
263
  - spec/models/url_spec.rb
259
264
  - spec/models/user_info_spec.rb
265
+ - spec/models/video_category_spec.rb
260
266
  - spec/models/video_spec.rb
261
267
  - spec/requests/as_account/account_spec.rb
262
268
  - spec/requests/as_account/authentications_spec.rb
@@ -341,6 +347,7 @@ test_files:
341
347
  - spec/models/content_detail_spec.rb
342
348
  - spec/models/content_owner_detail_spec.rb
343
349
  - spec/models/description_spec.rb
350
+ - spec/models/file_detail_spec.rb
344
351
  - spec/models/live_streaming_detail_spec.rb
345
352
  - spec/models/ownership_spec.rb
346
353
  - spec/models/player_spec.rb
@@ -359,6 +366,7 @@ test_files:
359
366
  - spec/models/subscription_spec.rb
360
367
  - spec/models/url_spec.rb
361
368
  - spec/models/user_info_spec.rb
369
+ - spec/models/video_category_spec.rb
362
370
  - spec/models/video_spec.rb
363
371
  - spec/requests/as_account/account_spec.rb
364
372
  - spec/requests/as_account/authentications_spec.rb