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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +1 -1
- data/lib/yt/associations/has_reports.rb +69 -10
- data/lib/yt/associations/has_viewer_percentages.rb +2 -2
- data/lib/yt/collections/resumable_sessions.rb +1 -1
- data/lib/yt/models/advertising_options_set.rb +0 -5
- data/lib/yt/models/content_detail.rb +4 -18
- data/lib/yt/models/file_detail.rb +0 -6
- data/lib/yt/models/live_streaming_detail.rb +0 -19
- data/lib/yt/models/player.rb +0 -2
- data/lib/yt/models/resource.rb +6 -3
- data/lib/yt/models/snippet.rb +6 -35
- data/lib/yt/models/status.rb +3 -270
- data/lib/yt/models/video.rb +467 -172
- data/lib/yt/request.rb +2 -4
- data/lib/yt/version.rb +1 -1
- data/spec/models/content_detail_spec.rb +0 -48
- data/spec/models/file_detail_spec.rb +0 -21
- data/spec/models/live_streaming_detail_spec.rb +0 -80
- data/spec/models/player_spec.rb +0 -8
- data/spec/models/statistics_set_spec.rb +1 -0
- data/spec/models/status_spec.rb +0 -302
- data/spec/models/video_spec.rb +494 -0
- data/spec/requests/as_account/account_spec.rb +1 -1
- metadata +2 -2
data/lib/yt/models/video.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
#
|
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]
|
27
|
-
#
|
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
|
-
|
32
|
-
|
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
|
-
#
|
39
|
-
#
|
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
|
-
#
|
44
|
-
|
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
|
-
|
47
|
-
|
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
|
-
|
54
|
-
has_report :earnings
|
381
|
+
### ANALYTICS ###
|
55
382
|
|
56
|
-
# @macro
|
383
|
+
# @macro views_report
|
57
384
|
has_report :views
|
58
385
|
|
59
|
-
# @macro
|
386
|
+
# @macro demographics_report
|
387
|
+
has_report :viewer_percentage
|
388
|
+
|
389
|
+
# @macro daily_report
|
60
390
|
has_report :comments
|
61
391
|
|
62
|
-
# @macro
|
392
|
+
# @macro daily_report
|
63
393
|
has_report :likes
|
64
394
|
|
65
|
-
# @macro
|
395
|
+
# @macro daily_report
|
66
396
|
has_report :dislikes
|
67
397
|
|
68
|
-
# @macro
|
398
|
+
# @macro daily_report
|
69
399
|
has_report :shares
|
70
400
|
|
71
|
-
# @
|
72
|
-
#
|
73
|
-
#
|
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
|
-
# @
|
77
|
-
#
|
78
|
-
#
|
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
|
411
|
+
# @macro daily_report
|
82
412
|
has_report :favorites_added
|
83
413
|
|
84
|
-
# @macro
|
414
|
+
# @macro daily_report
|
85
415
|
has_report :favorites_removed
|
86
416
|
|
87
|
-
# @macro
|
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
|
420
|
+
# @macro daily_report
|
94
421
|
has_report :average_view_percentage
|
95
422
|
|
96
|
-
# @macro
|
97
|
-
has_report :
|
98
|
-
|
99
|
-
# @macro has_report
|
100
|
-
has_report :monetized_playbacks
|
423
|
+
# @macro daily_report
|
424
|
+
has_report :estimated_minutes_watched
|
101
425
|
|
102
|
-
# @macro
|
426
|
+
# @macro daily_report
|
103
427
|
has_report :annotation_clicks
|
104
428
|
|
105
|
-
# @macro
|
429
|
+
# @macro daily_report
|
106
430
|
has_report :annotation_click_through_rate
|
107
431
|
|
108
|
-
# @macro
|
432
|
+
# @macro daily_report
|
109
433
|
has_report :annotation_close_rate
|
110
434
|
|
111
|
-
# @macro
|
112
|
-
has_report :
|
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
|
-
|
119
|
-
|
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]
|
125
|
-
#
|
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
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
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
|