yt 0.16.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,132 +5,512 @@ module Yt
5
5
  # Provides methods to interact with YouTube videos.
6
6
  # @see https://developers.google.com/youtube/v3/docs/videos
7
7
  class Video < Resource
8
- delegate :channel_id, :channel_title, :category_id,
9
- :live_broadcast_content, to: :snippet
10
-
11
- delegate :deleted?, :failed?, :processed?, :rejected?, :uploaded?,
12
- :uses_unsupported_codec?, :has_failed_conversion?, :empty?, :invalid?,
13
- :too_small?, :aborted?, :claimed?, :infringes_copyright?, :duplicate?,
14
- :scheduled_at, :publish_at, :scheduled?, :too_long?, :violates_terms_of_use?,
15
- :inappropriate?, :infringes_trademark?, :belongs_to_closed_account?,
16
- :belongs_to_suspended_account?, :licensed_as_creative_commons?,
17
- :licensed_as_standard_youtube?, :has_public_stats_viewable?, :uploading?,
18
- :public_stats_viewable, :embeddable, :embeddable?, :license, to: :status
19
-
20
- # @!attribute [r] content_detail
21
- # @return [Yt::Models::ContentDetail] the video’s content details.
8
+
9
+ ### SNIPPET ###
10
+
11
+ # @!attribute [r] title
12
+ # @return [String] the video’s title. Has a maximum of 100 characters and
13
+ # may contain all valid UTF-8 characters except < and >.
14
+ delegate :title, to: :snippet
15
+
16
+ # @!attribute [r] description
17
+ # @return [String] the video’s description. Has a maximum of 5000 bytes
18
+ # and may contain all valid UTF-8 characters except < and >.
19
+ delegate :description, to: :snippet
20
+
21
+ # Return the URL of the video’s thumbnail.
22
+ # @!method thumbnail_url(size = :default)
23
+ # @param [Symbol, String] size The size of the video’s thumbnail.
24
+ # @return [String] if +size+ is +default+, the URL of a 120x90px image.
25
+ # @return [String] if +size+ is +medium+, the URL of a 320x180px image.
26
+ # @return [String] if +size+ is +high+, the URL of a 480x360px image.
27
+ # @return [nil] if the +size+ is not +default+, +medium+ or +high+.
28
+ delegate :thumbnail_url, to: :snippet
29
+
30
+ # @!attribute [r] published_at
31
+ # @return [Time] the date and time that the video was published.
32
+ delegate :published_at, to: :snippet
33
+
34
+ # @!attribute [r] channel_id
35
+ # @return [String] the ID of the channel that the video belongs to.
36
+ delegate :channel_id, to: :snippet
37
+
38
+ # @!attribute [r] channel_title
39
+ # @return [String] the title of the channel that the video belongs to.
40
+ delegate :channel_title, to: :snippet
41
+
42
+ # @!attribute [r] live_broadcast_content
43
+ # @return [String] the type of live broadcast that the video contains.
44
+ # Valid values are: live, none, upcoming.
45
+ delegate :live_broadcast_content, to: :snippet
46
+
47
+ # @return [Array<Yt::Models::Tag>] the list of tags attached to the video.
48
+ # with the video.
49
+ def tags
50
+ ensure_complete_snippet :tags
51
+ end
52
+
53
+ # @return [String] ID of the YouTube category associated with the video.
54
+ def category_id
55
+ ensure_complete_snippet :category_id
56
+ end
57
+
58
+ ### STATUS ###
59
+
60
+ # @return [Boolean] whether the video was deleted by the user.
61
+ def deleted?
62
+ status.upload_status == 'deleted'
63
+ end
64
+
65
+ # @return [Boolean] whether the video failed to upload.
66
+ def failed?
67
+ status.upload_status == 'failed'
68
+ end
69
+
70
+ # @return [Boolean] whether the video has been fully processed by YouTube.
71
+ def processed?
72
+ status.upload_status == 'processed'
73
+ end
74
+
75
+ # @return [Boolean] whether the video was rejected by YouTube.
76
+ def rejected?
77
+ status.upload_status == 'rejected'
78
+ end
79
+
80
+ # @return [Boolean] whether the video is being uploaded to YouTube.
81
+ def uploading?
82
+ status.upload_status == 'uploaded'
83
+ end
84
+
85
+ # @deprecated Use {#uploading?} instead.
86
+ # @return [Boolean] whether the video is being uploaded to YouTube.
87
+ def uploaded?
88
+ uploading?
89
+ end
90
+
91
+ # @return [Boolean] whether the video failed to upload to YouTube because
92
+ # of an unsupported codec.
93
+ # @see https://support.google.com/youtube/answer/1722171
94
+ def uses_unsupported_codec?
95
+ status.failure_reason == 'codec'
96
+ end
97
+
98
+ # @return [Boolean] whether the video failed to upload to YouTube because
99
+ # YouTube was unable to convert the video.
100
+ def has_failed_conversion?
101
+ status.failure_reason == 'conversion'
102
+ end
103
+
104
+ # @return [Boolean] whether the video failed to upload to YouTube because
105
+ # the video file is empty.
106
+ def empty?
107
+ status.failure_reason == 'emptyFile'
108
+ end
109
+
110
+ # @return [Boolean] whether the video failed to upload to YouTube because
111
+ # the video uses an unsupported file format.
112
+ # @see https://support.google.com/youtube/troubleshooter/2888402?hl=en
113
+ def invalid?
114
+ status.failure_reason == 'invalidFile'
115
+ end
116
+
117
+ # @return [Boolean] whether the video failed to upload to YouTube because
118
+ # the video file is too small for YouTube.
119
+ def too_small?
120
+ status.failure_reason == 'tooSmall'
121
+ end
122
+
123
+ # @return [Boolean] whether the video failed to upload to YouTube because
124
+ # the uploading process was aborted.
125
+ def aborted?
126
+ status.failure_reason == 'uploadAborted'
127
+ end
128
+
129
+ # @return [Boolean] whether the video was rejected by YouTube because
130
+ # the video was claimed by a different account.
131
+ def claimed?
132
+ status.rejection_reason == 'claim'
133
+ end
134
+
135
+ # @return [Boolean] whether the video was rejected by YouTube because
136
+ # the video commits a copyright infringement.
137
+ def infringes_copyright?
138
+ status.rejection_reason == 'copyright'
139
+ end
140
+
141
+ # @return [Boolean] whether the video was rejected by YouTube because
142
+ # the video is a duplicate of another video.
143
+ def duplicate?
144
+ status.rejection_reason == 'duplicate'
145
+ end
146
+
147
+ # @return [Boolean] whether the video was rejected by YouTube because
148
+ # the video contains inappropriate content.
149
+ def inappropriate?
150
+ status.rejection_reason == 'inappropriate'
151
+ end
152
+
153
+ # @return [Boolean] whether the video was rejected by YouTube because
154
+ # the video exceeds the maximum duration for YouTube.
155
+ # @see https://support.google.com/youtube/answer/71673?hl=en
156
+ def too_long?
157
+ status.rejection_reason == 'length'
158
+ end
159
+
160
+ # @return [Boolean] whether the video was rejected by YouTube because
161
+ # the video violates the Terms of Use.
162
+ def violates_terms_of_use?
163
+ status.rejection_reason == 'termsOfUse'
164
+ end
165
+
166
+ # @return [Boolean] whether the video was rejected by YouTube because
167
+ # the video infringes a trademark.
168
+ # @see https://support.google.com/youtube/answer/2801979?hl=en
169
+ def infringes_trademark?
170
+ status.rejection_reason == 'trademark'
171
+ end
172
+
173
+ # @return [Boolean] whether the video was rejected by YouTube because
174
+ # the account that uploaded the video has been closed.
175
+ def belongs_to_closed_account?
176
+ status.rejection_reason == 'uploaderAccountClosed'
177
+ end
178
+
179
+ # @return [Boolean] whether the video was rejected by YouTube because
180
+ # the account that uploaded the video has been suspended.
181
+ def belongs_to_suspended_account?
182
+ status.rejection_reason == 'uploaderAccountSuspended'
183
+ end
184
+
185
+ # Returns the time when a video is scheduled to be published.
186
+ # @return [Time] if the video is scheduled to be published, the time
187
+ # when it will become public.
188
+ # @return [nil] if the video is not scheduled to be published.
189
+ def scheduled_at
190
+ status.publish_at if scheduled?
191
+ end
192
+
193
+ # @return [Boolean] whether the video is scheduled to be published.
194
+ def scheduled?
195
+ private? && status.publish_at
196
+ end
197
+
198
+ # @!attribute [r] license
199
+ # @return [String] the video’s license.
200
+ # Valid values are: creativeCommon, youtube.
201
+ delegate :license, to: :status
202
+
203
+ # @return [Boolean] whether the video uses the Standard YouTube license.
204
+ # @see https://www.youtube.com/static?template=terms
205
+ def licensed_as_standard_youtube?
206
+ license == 'youtube'
207
+ end
208
+
209
+ # @return [Boolean] whether the video uses a Creative Commons license.
210
+ # @see https://support.google.com/youtube/answer/2797468?hl=en
211
+ def licensed_as_creative_commons?
212
+ license == 'creativeCommon'
213
+ end
214
+
215
+ # Returns whether the video statistics are publicly viewable.
216
+ # @return [Boolean] if the resource is a video, whether the extended
217
+ # video statistics on the video’s watch page are publicly viewable.
218
+ # By default, those statistics are viewable, and statistics like a
219
+ # video’s viewcount and ratings will still be publicly visible even
220
+ # if this property’s value is set to false.
221
+ def has_public_stats_viewable?
222
+ status.public_stats_viewable
223
+ end
224
+
225
+ # @return [Boolean] whether the video can be embedded on another website.
226
+ def embeddable?
227
+ status.embeddable
228
+ end
229
+
230
+ ### CONTENT DETAILS ###
231
+
22
232
  has_one :content_detail
23
- delegate :duration, :hd?, :stereoscopic?, :captioned?, :licensed?,
24
- to: :content_detail
25
233
 
26
- # @!attribute [r] file_detail
27
- # @return [Yt::Models::FileDetail] the video’s file details.
234
+ # @!attribute [r] duration
235
+ # @return [Integer] the duration of the video (in seconds).
236
+ delegate :duration, to: :content_detail
237
+
238
+ # @return [Boolean] whether the video is available in 3D.
239
+ def stereoscopic?
240
+ content_detail.dimension == '3d'
241
+ end
242
+
243
+ # @return [Boolean] whether the video is available in high definition.
244
+ def hd?
245
+ content_detail.definition == 'hd'
246
+ end
247
+
248
+ # @return [Boolean] whether captions are available for the video.
249
+ def captioned?
250
+ content_detail.caption == 'true'
251
+ end
252
+
253
+ # @return [Boolean] whether the video represents licensed content, which
254
+ # means that the content has been claimed by a YouTube content partner.
255
+ def licensed?
256
+ content_detail.licensed_content || false
257
+ end
258
+
259
+ ### FILE DETAILS ###
260
+
28
261
  has_one :file_detail
29
- delegate :file_size, :file_type, :container, to: :file_detail
30
262
 
31
- has_one :advertising_options_set
32
- delegate :ad_formats, to: :advertising_options_set
263
+ # @!attribute [r] file_size
264
+ # @return [Integer] the size of the uploaded file (in bytes).
265
+ delegate :file_size, to: :file_detail
266
+
267
+ # @!attribute [r] file_type
268
+ # @return [String] the type of file uploaded. May be one of:
269
+ # archive, audio, document, image, other, project, video.
270
+ delegate :file_type, to: :file_detail
271
+
272
+ # @!attribute [r] container
273
+ # @return [String] the video container of the uploaded file. (e.g. 'mov').
274
+ delegate :container, to: :file_detail
275
+
276
+
277
+ ### RATING ###
33
278
 
34
- # @!attribute [r] rating
35
- # @return [Yt::Models::Rating] the video’s rating.
36
279
  has_one :rating
37
280
 
38
- # @!attribute [r] video_category
39
- # @return [Yt::Models::VideoCategory] the video’s category.
281
+ # @return [Boolean] whether the authenticated account likes the video.
282
+ # @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} is not an
283
+ # authenticated Yt::Account.
284
+ def liked?
285
+ rating.rating == :like
286
+ end
287
+
288
+ # Likes the video on behalf of the authenticated account.
289
+ # @return [Boolean] whether the authenticated account likes the video.
290
+ # @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} is not an
291
+ # authenticated Yt::Account.
292
+ def like
293
+ rating.set :like
294
+ liked?
295
+ end
296
+
297
+ # Dislikes the video on behalf of the authenticated account.
298
+ # @return [Boolean] whether the account does not like the video.
299
+ # @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} is not an
300
+ # authenticated Yt::Account.
301
+ def dislike
302
+ rating.set :dislike
303
+ !liked?
304
+ end
305
+
306
+ # Resets the rating of the video on behalf of the authenticated account.
307
+ # @return [Boolean] whether the account does not like the video.
308
+ # @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} is not an
309
+ # authenticated Yt::Account.
310
+ def unlike
311
+ rating.set :none
312
+ !liked?
313
+ end
314
+
315
+ ### VIDEO CATEGORY ###
316
+
40
317
  has_one :video_category
41
- delegate :title, to: :video_category, prefix: :category
42
318
 
43
- # @!attribute [r] live_streaming_detail
44
- # @return [Yt::Models::LiveStreamingDetail] live streaming detail.
319
+ # @return [String] the video category’s title.
320
+ def category_title
321
+ video_category.title
322
+ end
323
+
324
+
325
+ ### ADVERTISING OPTIONS ###
326
+
327
+ has_one :advertising_options_set
328
+
329
+ # @!attribute [r] ad_formats
330
+ # @return [Array<String>] the list of ad formats that the video is
331
+ # allowed to show. Valid values are: long, overlay,
332
+ # standard_instream, third_party, trueview_inslate, trueview_instream.
333
+ delegate :ad_formats, to: :advertising_options_set
334
+
335
+
336
+ ### LIVE STREAMING DETAILS ###
337
+
45
338
  has_one :live_streaming_detail
46
- delegate :actual_start_time, :actual_end_time, :scheduled_start_time,
47
- :scheduled_end_time, :concurrent_viewers, to: :live_streaming_detail
339
+
340
+ # The time when a live broadcast started.
341
+ # @!attribute [r] actual_start_time
342
+ # @return [Time] if the broadcast has begun, the time it actually started.
343
+ # @return [nil] if the broadcast has not begun or video is not live.
344
+ delegate :actual_start_time, to: :live_streaming_detail
345
+
346
+ # The time when a live broadcast ended.
347
+ # @!attribute [r] actual_end_time
348
+ # @return [Time] if the broadcast is over, the time it actually ended.
349
+ # @return [nil] if the broadcast is not over or video is not live.
350
+ delegate :actual_end_time, to: :live_streaming_detail
351
+
352
+ # The time when a live broadcast is scheduled to start.
353
+ # @!attribute [r] scheduled_start_time
354
+ # @return [Time] the time that the broadcast is scheduled to begin.
355
+ # @return [nil] if video is not live.
356
+ delegate :scheduled_start_time, to: :live_streaming_detail
357
+
358
+ # The time when a live broadcast is scheduled to end.
359
+ # @!attribute [r] scheduled_end_time
360
+ # @return [Time] if the broadcast is scheduled to end, the time it is
361
+ # scheduled to end.
362
+ # @return [nil] if the broadcast is scheduled to continue indefinitely
363
+ # or the video is not live.
364
+ delegate :scheduled_end_time, to: :live_streaming_detail
365
+
366
+ # The number of current viewers of a live broadcast.
367
+ # @!attribute [r] concurrent_viewers
368
+ # @return [Integer] if the broadcast has current viewers and the
369
+ # broadcast owner has not hidden the viewcount for the video, the
370
+ # number of viewers currently watching the broadcast.
371
+ # @return [nil] if the broadcast has ended or the broadcast owner has
372
+ # hidden the viewcount for the video or the video is not live.
373
+ delegate :concurrent_viewers, to: :live_streaming_detail
374
+
375
+ ### ANNOTATIONS ###
48
376
 
49
377
  # @!attribute [r] annotations
50
378
  # @return [Yt::Collections::Annotations] the video’s annotations.
51
379
  has_many :annotations
52
380
 
53
- # @macro has_report
54
- has_report :earnings
381
+ ### ANALYTICS ###
55
382
 
56
- # @macro has_report
383
+ # @macro views_report
57
384
  has_report :views
58
385
 
59
- # @macro has_report
386
+ # @macro demographics_report
387
+ has_report :viewer_percentage
388
+
389
+ # @macro daily_report
60
390
  has_report :comments
61
391
 
62
- # @macro has_report
392
+ # @macro daily_report
63
393
  has_report :likes
64
394
 
65
- # @macro has_report
395
+ # @macro daily_report
66
396
  has_report :dislikes
67
397
 
68
- # @macro has_report
398
+ # @macro daily_report
69
399
  has_report :shares
70
400
 
71
- # @macro has_report
72
- # @note: This is not the total number of subscribers gained by the video’s
73
- # channel, but the subscribers gained *from* the video’s page.
401
+ # @note This is not the total number of subscribers gained by the video’s
402
+ # channel, but the subscribers gained *from* the video’s page.
403
+ # @macro daily_report
74
404
  has_report :subscribers_gained
75
405
 
76
- # @macro has_report
77
- # @note: This is not the total number of subscribers lost by the video’s
78
- # channel, but the subscribers lost *from* the video’s page.
406
+ # @note This is not the total number of subscribers lost by the video’s
407
+ # channel, but the subscribers lost *from* the video’s page.
408
+ # @macro daily_report
79
409
  has_report :subscribers_lost
80
410
 
81
- # @macro has_report
411
+ # @macro daily_report
82
412
  has_report :favorites_added
83
413
 
84
- # @macro has_report
414
+ # @macro daily_report
85
415
  has_report :favorites_removed
86
416
 
87
- # @macro has_report
88
- has_report :estimated_minutes_watched
89
-
90
- # @macro has_report
417
+ # @macro daily_report
91
418
  has_report :average_view_duration
92
419
 
93
- # @macro has_report
420
+ # @macro daily_report
94
421
  has_report :average_view_percentage
95
422
 
96
- # @macro has_report
97
- has_report :impressions
98
-
99
- # @macro has_report
100
- has_report :monetized_playbacks
423
+ # @macro daily_report
424
+ has_report :estimated_minutes_watched
101
425
 
102
- # @macro has_report
426
+ # @macro daily_report
103
427
  has_report :annotation_clicks
104
428
 
105
- # @macro has_report
429
+ # @macro daily_report
106
430
  has_report :annotation_click_through_rate
107
431
 
108
- # @macro has_report
432
+ # @macro daily_report
109
433
  has_report :annotation_close_rate
110
434
 
111
- # @macro has_report
112
- has_report :viewer_percentage
435
+ # @macro daily_report
436
+ has_report :earnings
437
+
438
+ # @macro daily_report
439
+ has_report :impressions
440
+
441
+ # @macro daily_report
442
+ has_report :monetized_playbacks
113
443
 
114
444
  # @deprecated Use {#has_report :viewer_percentage}.
115
445
  # @macro has_viewer_percentages
116
446
  has_viewer_percentages
117
447
 
118
- # @!attribute [r] statistics_set
119
- # @return [Yt::Models::StatisticsSet] the statistics for the video.
448
+ ### STATISTICS ###
449
+
120
450
  has_one :statistics_set
121
- delegate :view_count, :like_count, :dislike_count, :favorite_count,
122
- :comment_count, to: :statistics_set
123
451
 
124
- # @!attribute [r] player
125
- # @return [Yt::Models::Player] the player for the video.
452
+ # @!attribute [r] view_count
453
+ # @return [Integer] the number of times the video has been viewed.
454
+ delegate :view_count, to: :statistics_set
455
+
456
+ # @!attribute [r] like_count
457
+ # @return [Integer] the number of users who liked the video.
458
+ delegate :like_count, to: :statistics_set
459
+
460
+ # @!attribute [r] dislike_count
461
+ # @return [Integer] the number of users who disliked the video.
462
+ delegate :dislike_count, to: :statistics_set
463
+
464
+ # @!attribute [r] favorite_count
465
+ # @return [Integer] the number of users who marked the video as favorite.
466
+ delegate :favorite_count, to: :statistics_set
467
+
468
+ # @!attribute [r] dislike_count
469
+ # @return [Integer] the number of comments for the video.
470
+ delegate :comment_count, to: :statistics_set
471
+
472
+ ### PLAYER ###
473
+
126
474
  has_one :player
475
+
476
+ # @!attribute [r] embed_html
477
+ # @return [String] the HTML code of an <iframe> tag that embeds a
478
+ # player that will play the video.
127
479
  delegate :embed_html, to: :player
128
480
 
129
- # @!attribute [r] resumable_sessions
130
- # @return [Yt::Collections::ResumableSessions] the sessions used to
131
- # upload thumbnails using the resumable upload protocol.
481
+
482
+ ### ACTIONS (UPLOAD, UPDATE, DELETE) ###
483
+
132
484
  has_many :resumable_sessions
133
485
 
486
+ # Uploads a thumbnail
487
+ # @param [String] path_or_url the image to upload. Can either be the
488
+ # path of a local file or the URL of a remote file.
489
+ # @return the new thumbnail resource for the given image.
490
+ # @raise [Yt::Errors::RequestError] if path_or_url is not a valid path
491
+ # or URL.
492
+ def upload_thumbnail(path_or_url)
493
+ file = open(path_or_url, 'rb') rescue StringIO.new
494
+ session = resumable_sessions.insert file.size
495
+
496
+ session.update(body: file) do |data|
497
+ snippet.instance_variable_set :@thumbnails, data['items'].first
498
+ end
499
+ end
500
+
501
+ # Deletes the video on behalf of the authenticated account.
502
+ # @return [Boolean] whether the video does not exist anymore.
503
+ # @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} is not an
504
+ # authenticated Yt::Account with permissions to delete the video.
505
+ def delete(options = {})
506
+ do_delete {@id = nil}
507
+ !exists?
508
+ end
509
+
510
+
511
+ ### PRIVATE API ###
512
+
513
+ # @private
134
514
  # Override Resource's new to set statistics and content details as well
135
515
  # if the response includes them
136
516
  def initialize(options = {})
@@ -144,119 +524,22 @@ module Yt
144
524
  if options[:file_details]
145
525
  @file_detail = FileDetail.new data: options[:file_details]
146
526
  end
527
+ if options[:live_streaming_details]
528
+ @live_streaming_detail = LiveStreamingDetail.new data: options[:live_streaming_details]
529
+ end
147
530
  if options[:video_category]
148
531
  @video_category = VideoCategory.new data: options[:video_category]
149
532
  end
150
- end
151
-
152
- # Returns the list of keyword tags associated with the video.
153
- # Since YouTube API only returns tags on Videos#list, the memoized
154
- # @snippet is erased if the video was instantiated through Video#search
155
- # (e.g., by calling account.videos or channel.videos), so that the full
156
- # snippet (with tags) is loaded, rather than the partial one.
157
- # @see https://developers.google.com/youtube/v3/docs/videos
158
- # @return [Array<Yt::Models::Tag>] the list of keyword tags associated
159
- # with the video.
160
- def tags
161
- unless snippet.tags.any? || snippet.complete? || @auth.nil?
162
- @snippet = nil
533
+ if options[:player]
534
+ @player = Player.new data: options[:player]
163
535
  end
164
- snippet.tags
165
- end
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
-
181
- # Deletes the video.
182
- #
183
- # This method requires {Resource#auth auth} to return an authenticated
184
- # instance of {Yt::Account} with permissions to delete the video.
185
- # @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
186
- # return an account with permissions to delete the video.
187
- # @return [Boolean] whether the video does not exist anymore.
188
- def delete(options = {})
189
- do_delete {@id = nil}
190
- !exists?
191
536
  end
192
537
 
538
+ # @private
193
539
  def exists?
194
540
  !@id.nil?
195
541
  end
196
542
 
197
- # Returns whether the authenticated account likes the video.
198
- #
199
- # This method requires {Resource#auth auth} to return an
200
- # authenticated instance of {Yt::Account}.
201
- # @return [Boolean] whether the account likes the video.
202
- # @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
203
- # return an authenticated account.
204
- def liked?
205
- rating.rating == :like
206
- end
207
-
208
- # Likes the video on behalf of the authenticated account.
209
- #
210
- # This method requires {Resource#auth auth} to return an
211
- # authenticated instance of {Yt::Account}.
212
- # @return [Boolean] whether the account likes the video.
213
- # @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
214
- # return an authenticated account.
215
- def like
216
- rating.set :like
217
- liked?
218
- end
219
-
220
- # Dislikes the video on behalf of the authenticated account.
221
- #
222
- # This method requires {Resource#auth auth} to return an
223
- # authenticated instance of {Yt::Account}.
224
- # @return [Boolean] whether the account does not like the video.
225
- # @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
226
- # return an authenticated account.
227
- def dislike
228
- rating.set :dislike
229
- !liked?
230
- end
231
-
232
- # Resets the rating of the video on behalf of the authenticated account.
233
- #
234
- # This method requires {Resource#auth auth} to return an
235
- # authenticated instance of {Yt::Account}.
236
- # @return [Boolean] whether the account does not like the video.
237
- # @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
238
- # return an authenticated account.
239
- def unlike
240
- rating.set :none
241
- !liked?
242
- end
243
-
244
- # Uploads a thumbnail
245
- # @param [String] path_or_url the image to upload. Can either be the
246
- # path of a local file or the URL of a remote file.
247
- # @return the new thumbnail resource for the given image.
248
- # @raise [Yt::Errors::RequestError] if path_or_url is not a valid path
249
- # or URL.
250
- # @see https://developers.google.com/youtube/v3/docs/thumbnails#resource
251
- def upload_thumbnail(path_or_url)
252
- file = open(path_or_url, 'rb') rescue StringIO.new
253
- session = resumable_sessions.insert file.size
254
-
255
- session.update(body: file) do |data|
256
- snippet.instance_variable_set :@thumbnails, data['items'].first
257
- end
258
- end
259
-
260
543
  # @private
261
544
  # Tells `has_reports` to retrieve the reports from YouTube Analytics API
262
545
  # either as a Channel or as a Content Owner.
@@ -277,6 +560,7 @@ module Yt
277
560
  def upload_path
278
561
  '/upload/youtube/v3/thumbnails/set'
279
562
  end
563
+
280
564
  # @private
281
565
  # Tells `has_many :resumable_sessions` what params are set for the object
282
566
  # associated to the uploaded file.
@@ -292,6 +576,17 @@ module Yt
292
576
 
293
577
  private
294
578
 
579
+ # Since YouTube API only returns tags on Videos#list, the memoized
580
+ # `@snippet` is erased if the video was instantiated through Video#search
581
+ # (e.g., by calling account.videos or channel.videos), so that the full
582
+ # snippet (with tags and category) is loaded, rather than the partial one.
583
+ def ensure_complete_snippet(attribute)
584
+ unless snippet.public_send(attribute).present? || snippet.complete?
585
+ @snippet = nil
586
+ end
587
+ snippet.public_send attribute
588
+ end
589
+
295
590
  # @see https://developers.google.com/youtube/v3/docs/videos/update
296
591
  # @todo: Add recording details keys
297
592
  def update_parts