yt 0.16.0 → 0.17.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.
@@ -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