yt 0.5.4 → 0.5.5
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/Gemfile.lock +1 -1
- data/HISTORY.md +3 -0
- data/README.md +2 -4
- data/bin/yt +0 -7
- data/lib/yt/actions/delete.rb +3 -3
- data/lib/yt/actions/insert.rb +3 -3
- data/lib/yt/actions/list.rb +4 -6
- data/lib/yt/actions/update.rb +7 -5
- data/lib/yt/associations/authentications.rb +114 -0
- data/lib/yt/associations.rb +1 -0
- data/lib/yt/collections/annotations.rb +2 -2
- data/lib/yt/collections/authentications.rb +42 -0
- data/lib/yt/collections/base.rb +1 -1
- data/lib/yt/collections/playlist_items.rb +1 -1
- data/lib/yt/collections/snippets.rb +1 -2
- data/lib/yt/collections/subscriptions.rb +1 -1
- data/lib/yt/collections/user_infos.rb +2 -2
- data/lib/yt/config.rb +0 -2
- data/lib/yt/errors/missing_auth.rb +50 -0
- data/lib/yt/errors/no_items.rb +5 -9
- data/lib/yt/errors/request_error.rb +52 -0
- data/lib/yt/models/account.rb +17 -44
- data/lib/yt/models/annotation.rb +117 -115
- data/lib/yt/models/authentication.rb +27 -0
- data/lib/yt/models/base.rb +9 -5
- data/lib/yt/models/channel.rb +6 -4
- data/lib/yt/models/configuration.rb +27 -35
- data/lib/yt/models/description.rb +61 -59
- data/lib/yt/models/details_set.rb +26 -24
- data/lib/yt/models/id.rb +3 -1
- data/lib/yt/models/playlist.rb +38 -36
- data/lib/yt/models/playlist_item.rb +29 -27
- data/lib/yt/models/rating.rb +18 -16
- data/lib/yt/models/request.rb +95 -73
- data/lib/yt/models/resource.rb +19 -17
- data/lib/yt/models/snippet.rb +39 -37
- data/lib/yt/models/status.rb +20 -18
- data/lib/yt/models/subscription.rb +24 -22
- data/lib/yt/models/url.rb +68 -68
- data/lib/yt/models/user_info.rb +51 -49
- data/lib/yt/models/video.rb +6 -4
- data/lib/yt/version.rb +1 -1
- data/spec/associations/device_auth/authentications_spec.rb +78 -0
- data/spec/associations/device_auth/channels_spec.rb +2 -4
- data/spec/associations/device_auth/details_sets_spec.rb +4 -5
- data/spec/associations/device_auth/ids_spec.rb +2 -3
- data/spec/associations/device_auth/playlist_items_spec.rb +3 -4
- data/spec/associations/device_auth/playlists_spec.rb +14 -15
- data/spec/associations/device_auth/ratings_spec.rb +2 -4
- data/spec/associations/device_auth/snippets_spec.rb +5 -7
- data/spec/associations/device_auth/subscriptions_spec.rb +2 -4
- data/spec/associations/device_auth/user_infos_spec.rb +2 -5
- data/spec/associations/device_auth/videos_spec.rb +3 -5
- data/spec/associations/server_auth/details_sets_spec.rb +1 -1
- data/spec/associations/server_auth/ids_spec.rb +1 -1
- data/spec/associations/server_auth/playlist_items_spec.rb +1 -1
- data/spec/associations/server_auth/playlists_spec.rb +1 -1
- data/spec/associations/server_auth/snippets_spec.rb +1 -1
- data/spec/associations/server_auth/videos_spec.rb +1 -1
- data/spec/collections/playlist_items_spec.rb +3 -4
- data/spec/collections/subscriptions_spec.rb +2 -3
- data/spec/errors/missing_auth_spec.rb +10 -0
- data/spec/errors/no_items_spec.rb +2 -1
- data/spec/errors/request_error_spec.rb +18 -0
- data/spec/models/configuration_spec.rb +0 -17
- data/spec/models/description_spec.rb +5 -5
- data/spec/models/request_spec.rb +1 -7
- data/spec/models/subscription_spec.rb +2 -3
- data/spec/models/url_spec.rb +6 -6
- data/spec/support/fail_matcher.rb +1 -1
- data/spec/support/global_hooks.rb +33 -0
- metadata +15 -14
- data/lib/yt/errors/base.rb +0 -43
- data/lib/yt/errors/error.rb +0 -8
- data/lib/yt/errors/failed.rb +0 -17
- data/lib/yt/errors/unauthenticated.rb +0 -34
- data/spec/errors/failed_spec.rb +0 -9
- data/spec/errors/unauthenticated_spec.rb +0 -9
- data/spec/support/device_app.rb +0 -15
- data/spec/support/server_app.rb +0 -10
data/lib/yt/models/annotation.rb
CHANGED
@@ -1,142 +1,144 @@
|
|
1
1
|
module Yt
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
2
|
+
module Models
|
3
|
+
# Provides methods to access and analyze a single YouTube annotation.
|
4
|
+
class Annotation
|
5
|
+
# Instantiate an Annotation object from its YouTube XML representation.
|
6
|
+
#
|
7
|
+
# @note There is no documented way to access annotations through API.
|
8
|
+
# There is an endpoint that returns an XML in an undocumented format,
|
9
|
+
# which is here parsed into a comprehensible set of attributes.
|
10
|
+
#
|
11
|
+
# @param [String] xml_data The YouTube XML representation of an annotation
|
12
|
+
def initialize(options = {})
|
13
|
+
@data = options[:data]
|
14
|
+
end
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
# Checks whether the entire annotation box remains above y
|
17
|
+
#
|
18
|
+
# @param [Integer] y Vertical position in the Youtube video (0 to 100)
|
19
|
+
#
|
20
|
+
# @return [Boolean] Whether the box remains above y
|
21
|
+
def above?(y)
|
22
|
+
top && top < y
|
23
|
+
end
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
25
|
+
# Checks whether the entire annotation box remains below y
|
26
|
+
#
|
27
|
+
# @param [Integer] y Vertical position in the Youtube video (0 to 100)
|
28
|
+
#
|
29
|
+
# @return [Boolean] Whether the box remains below y
|
30
|
+
def below?(y)
|
31
|
+
bottom && bottom > y
|
32
|
+
end
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
34
|
+
# Checks whether there is a link to subscribe.
|
35
|
+
# Should a branding watermark also counts, because it links to the channel?
|
36
|
+
#
|
37
|
+
# @return [Boolean] Whether there is a link to subscribe in the annotation
|
38
|
+
def has_link_to_subscribe?(options = {}) # TODO: options for which videos
|
39
|
+
link_class == '5'
|
40
|
+
end
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
42
|
+
# Checks whether there is a link to a video.
|
43
|
+
# An Invideo featured video also counts
|
44
|
+
#
|
45
|
+
# @return [Boolean] Whether there is a link to a video in the annotation
|
46
|
+
def has_link_to_video?(options = {}) # TODO: options for which videos
|
47
|
+
link_class == '1' || type == 'promotion'
|
48
|
+
end
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
50
|
+
# Checks whether there is a link to a playlist.
|
51
|
+
# A link to a video with the playlist in the URL also counts
|
52
|
+
#
|
53
|
+
# @return [Boolean] Whether there is a link to a playlist in the annotation
|
54
|
+
def has_link_to_playlist?
|
55
|
+
link_class == '2' || text.include?('&list=')
|
56
|
+
end
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
58
|
+
# Checks whether the link opens in the same window.
|
59
|
+
#
|
60
|
+
# @return [Boolean] Whether the link opens in the same window
|
61
|
+
def has_link_to_same_window?
|
62
|
+
link_target == 'current'
|
63
|
+
end
|
63
64
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
65
|
+
# Checks whether the annotation comes from InVideo Programming
|
66
|
+
#
|
67
|
+
# @return [Boolean] Whether the annotation comes from InVideo Programming
|
68
|
+
def has_invideo_programming?
|
69
|
+
type == 'promotion' || type == 'branding'
|
70
|
+
end
|
70
71
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
72
|
+
# @return [Boolean] Whether the annotation starts after the number of seconds
|
73
|
+
# @note This is broken for invideo programming, because they do not
|
74
|
+
# have the timestamp in the region, but in the "data" field
|
75
|
+
def starts_after?(seconds)
|
76
|
+
timestamps.first > seconds if timestamps.any?
|
77
|
+
end
|
77
78
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
79
|
+
# @return [Boolean] Whether the annotation starts before the number of seconds
|
80
|
+
# @note This is broken for invideo programming, because they do not
|
81
|
+
# have the timestamp in the region, but in the "data" field
|
82
|
+
def starts_before?(seconds)
|
83
|
+
timestamps.first < seconds if timestamps.any?
|
84
|
+
end
|
84
85
|
|
85
|
-
|
86
|
+
private
|
86
87
|
|
87
|
-
|
88
|
-
|
89
|
-
|
88
|
+
def text
|
89
|
+
@text ||= @data.fetch 'TEXT', ''
|
90
|
+
end
|
90
91
|
|
91
|
-
|
92
|
-
|
93
|
-
|
92
|
+
def type
|
93
|
+
@type ||= @data.fetch 'type', ''
|
94
|
+
end
|
94
95
|
|
95
|
-
|
96
|
-
|
97
|
-
|
96
|
+
def link_class
|
97
|
+
@link_class ||= url['link_class']
|
98
|
+
end
|
98
99
|
|
99
|
-
|
100
|
-
|
101
|
-
|
100
|
+
def link_target
|
101
|
+
@link_target ||= url['target']
|
102
|
+
end
|
102
103
|
|
103
|
-
|
104
|
-
|
105
|
-
|
104
|
+
def url
|
105
|
+
@url ||= action.fetch 'url', {}
|
106
|
+
end
|
106
107
|
|
107
|
-
|
108
|
-
|
109
|
-
|
108
|
+
def action
|
109
|
+
@action ||= @data.fetch 'action', {}
|
110
|
+
end
|
110
111
|
|
111
|
-
|
112
|
-
|
113
|
-
|
112
|
+
def top
|
113
|
+
@top ||= positions.map{|pos| pos['y'].to_f}.max
|
114
|
+
end
|
114
115
|
|
115
|
-
|
116
|
-
|
117
|
-
|
116
|
+
def bottom
|
117
|
+
@bottom ||= positions.map{|pos| pos['y'].to_f + pos['h'].to_f}.max
|
118
|
+
end
|
118
119
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
120
|
+
def timestamps
|
121
|
+
@timestamps ||= positions.map do |pos|
|
122
|
+
regex = %r{(?:|(?<hours>\d*):)(?:|(?<min>\d*):)(?<sec>\d*)\.(?<ms>\d*)}
|
123
|
+
match = pos['t'].match regex
|
124
|
+
hours = (match[:hours] || '0').to_i
|
125
|
+
minutes = (match[:min] || '0').to_i
|
126
|
+
seconds = (match[:sec]).to_i
|
127
|
+
(hours * 60 + minutes) * 60 + seconds
|
128
|
+
end
|
127
129
|
end
|
128
|
-
end
|
129
130
|
|
130
|
-
|
131
|
-
|
132
|
-
|
131
|
+
def positions
|
132
|
+
@positions ||= region['rectRegion'] || region['anchoredRegion'] || []
|
133
|
+
end
|
133
134
|
|
134
|
-
|
135
|
-
|
136
|
-
|
135
|
+
def region
|
136
|
+
@region ||= segment.fetch 'movingRegion', {}
|
137
|
+
end
|
137
138
|
|
138
|
-
|
139
|
-
|
139
|
+
def segment
|
140
|
+
@segment ||= (@data['segment'] || {})
|
141
|
+
end
|
140
142
|
end
|
141
143
|
end
|
142
144
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Yt
|
2
|
+
module Models
|
3
|
+
class Authentication
|
4
|
+
attr_reader :access_token, :refresh_token, :expires_at
|
5
|
+
|
6
|
+
def initialize(data = {})
|
7
|
+
@access_token = data['access_token']
|
8
|
+
@refresh_token = data['refresh_token']
|
9
|
+
@expires_at = expiration_date data.slice('expires_at', 'expires_in')
|
10
|
+
end
|
11
|
+
|
12
|
+
def expired?
|
13
|
+
@expires_at && @expires_at.past?
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def expiration_date(options = {})
|
19
|
+
if options['expires_in']
|
20
|
+
Time.now + options['expires_in'].seconds
|
21
|
+
else
|
22
|
+
Time.parse options['expires_at'] rescue nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/yt/models/base.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
require 'yt/associations'
|
2
2
|
require 'yt/actions/delete'
|
3
3
|
require 'yt/actions/update'
|
4
|
-
require 'yt/errors/
|
4
|
+
require 'yt/errors/request_error'
|
5
5
|
|
6
6
|
module Yt
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
module Models
|
8
|
+
class Base
|
9
|
+
extend Associations
|
10
|
+
include Actions::Delete
|
11
|
+
include Actions::Update
|
12
|
+
end
|
11
13
|
end
|
14
|
+
|
15
|
+
include Models
|
12
16
|
end
|
data/lib/yt/models/channel.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
require 'yt/models/resource'
|
2
2
|
|
3
3
|
module Yt
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
module Models
|
5
|
+
class Channel < Resource
|
6
|
+
has_many :subscriptions
|
7
|
+
has_many :videos
|
8
|
+
has_many :playlists
|
9
|
+
end
|
8
10
|
end
|
9
11
|
end
|
@@ -1,40 +1,32 @@
|
|
1
1
|
module Yt
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
2
|
+
module Models
|
3
|
+
# Stores runtime configuration information.
|
4
|
+
#
|
5
|
+
# Configuration options are loaded from `~/.yt`, `.yt`, command line
|
6
|
+
# switches, and the `YT_OPTS` environment variable (listed in lowest to
|
7
|
+
# highest precedence).
|
8
|
+
#
|
9
|
+
# @example A server-to-server YouTube client app
|
10
|
+
#
|
11
|
+
# Yt.configure do |config|
|
12
|
+
# config.api_key = 'ABCDEFGHIJ1234567890'
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# @example A web YouTube client app
|
16
|
+
#
|
17
|
+
# Yt.configure do |config|
|
18
|
+
# config.client_id = 'ABCDEFGHIJ1234567890'
|
19
|
+
# config.client_secret = 'ABCDEFGHIJ1234567890'
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
class Configuration
|
23
|
+
attr_accessor :api_key, :client_id, :client_secret
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def set_scenario(value)
|
35
|
-
valid_scenarios = [:web_app, :device_app, :server_app]
|
36
|
-
scenario = valid_scenarios.find{|scenario| scenario.to_s == value}
|
37
|
-
scenario || :web_app
|
25
|
+
def initialize
|
26
|
+
@client_id = ENV['YT_CLIENT_ID']
|
27
|
+
@client_secret = ENV['YT_CLIENT_SECRET']
|
28
|
+
@api_key = ENV['YT_API_KEY']
|
29
|
+
end
|
38
30
|
end
|
39
31
|
end
|
40
32
|
end
|
@@ -1,74 +1,76 @@
|
|
1
1
|
require 'yt/models/url'
|
2
2
|
|
3
3
|
module Yt
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
# @example
|
8
|
-
#
|
9
|
-
# description = Yt::Description.new 'Fullscreen provides a suite of end-to-end YouTube tools and services to many of the world’s leading brands and media companies.'
|
10
|
-
# description.to_s.slice(0,19) # => 'Fullscreen provides'
|
11
|
-
# description.length # => 127
|
12
|
-
#
|
13
|
-
class Description < String
|
14
|
-
# Returns whether the description includes a YouTube video URL
|
4
|
+
module Models
|
5
|
+
# Provides read-only access to the description of a YouTube resource.
|
6
|
+
# Resources with descriptions are: videos and channels.
|
15
7
|
#
|
16
8
|
# @example
|
17
9
|
#
|
18
|
-
# description = Yt::Description.new '
|
19
|
-
# description.
|
10
|
+
# description = Yt::Description.new 'Fullscreen provides a suite of end-to-end YouTube tools and services to many of the world’s leading brands and media companies.'
|
11
|
+
# description.to_s.slice(0,19) # => 'Fullscreen provides'
|
12
|
+
# description.length # => 127
|
20
13
|
#
|
21
|
-
|
22
|
-
|
23
|
-
#
|
24
|
-
#
|
25
|
-
|
26
|
-
|
14
|
+
class Description < String
|
15
|
+
# Returns whether the description includes a YouTube video URL
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
#
|
19
|
+
# description = Yt::Description.new 'Link to video: youtube.com/watch?v=MESycYJytkU'
|
20
|
+
# description.has_link_to_video? #=> true
|
21
|
+
#
|
22
|
+
# @return [Boolean] Whether the description includes a link to a video
|
23
|
+
def has_link_to_video?
|
24
|
+
# TODO: might take as an option WHICH video to link to
|
25
|
+
# in order to check if it's my own video
|
26
|
+
links.any?{|link| link.kind == :video}
|
27
|
+
end
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
29
|
+
# Returns whether the description includes a YouTube channel URL
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
#
|
33
|
+
# description = Yt::Description.new 'Link to channel: youtube.com/fullscreen'
|
34
|
+
# description.has_link_to_channel? #=> true
|
35
|
+
#
|
36
|
+
# @return [Boolean] Whether the description includes a link to a channel
|
37
|
+
def has_link_to_channel?(options = {}) # TODO: which channel
|
38
|
+
# TODO: might take as an option WHICH channel to link to
|
39
|
+
# in order to check if it's my own channel
|
40
|
+
links.any?{|link| link.kind == :channel}
|
41
|
+
end
|
41
42
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
43
|
+
# Returns whether the description includes a YouTube subscription URL
|
44
|
+
#
|
45
|
+
# @example
|
46
|
+
#
|
47
|
+
# description = Yt::Description.new 'Link to subscribe: youtube.com/subscription_center?add_user=fullscreen'
|
48
|
+
# description.has_link_to_subscribe? #=> true
|
49
|
+
#
|
50
|
+
# @return [Boolean] Whether the description includes a link to subscribe
|
51
|
+
def has_link_to_subscribe?(options = {}) # TODO: which channel
|
52
|
+
# TODO: might take as an option WHICH channel to subscribe to
|
53
|
+
# in order to check if it's my own channel
|
54
|
+
links.any?{|link| link.kind == :subscription}
|
55
|
+
end
|
55
56
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
57
|
+
# Returns whether the description includes a YouTube playlist URL
|
58
|
+
#
|
59
|
+
# @example
|
60
|
+
#
|
61
|
+
# description = Yt::Description.new 'Link to playlist: youtube.com/playlist?list=LLxO1tY8h1AhOz0T4ENwmpow'
|
62
|
+
# description.has_link_to_playlist? #=> true
|
63
|
+
#
|
64
|
+
# @return [Boolean] Whether the description includes a link to a playlist
|
65
|
+
def has_link_to_playlist?
|
66
|
+
links.any?{|link| link.kind == :playlist}
|
67
|
+
end
|
67
68
|
|
68
|
-
|
69
|
+
private
|
69
70
|
|
70
|
-
|
71
|
-
|
71
|
+
def links
|
72
|
+
@links ||= self.split(' ').map{|word| URL.new word}
|
73
|
+
end
|
72
74
|
end
|
73
75
|
end
|
74
76
|
end
|
@@ -1,34 +1,36 @@
|
|
1
1
|
require 'yt/models/base'
|
2
2
|
|
3
3
|
module Yt
|
4
|
-
|
4
|
+
module Models
|
5
|
+
class DetailsSet < Base
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
def initialize(options = {})
|
8
|
+
@data = options[:data]
|
9
|
+
end
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
# Return the duration of the YouTube video.
|
12
|
+
#
|
13
|
+
# @return [Integer] Duration in seconds of the YouTube video
|
14
|
+
def duration
|
15
|
+
@duration = to_seconds @data.fetch('duration', 0)
|
16
|
+
end
|
17
|
+
# also available: dimension, definition, caption, licensed_content?
|
17
18
|
|
18
|
-
|
19
|
+
private
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
21
|
+
# The length of the video. The tag value is an ISO 8601 duration in the format PT#M#S,
|
22
|
+
# in which the letters PT indicate that the value specifies a period of time, and
|
23
|
+
# the letters M and S refer to length in minutes and seconds, respectively. The #
|
24
|
+
# characters preceding the M and S letters are both integers that specify the number
|
25
|
+
# of minutes (or seconds) of the video. For example, a value of PT15M51S indicates
|
26
|
+
# that the video is 15 minutes and 51 seconds long.
|
27
|
+
def to_seconds(iso8601_duration)
|
28
|
+
match = iso8601_duration.match %r{^PT(?:|(?<hours>\d*?)H)(?:|(?<min>\d*?)M)(?:|(?<sec>\d*?)S)$}
|
29
|
+
hours = (match[:hours] || '0').to_i
|
30
|
+
minutes = (match[:min] || '0').to_i
|
31
|
+
seconds = (match[:sec]).to_i
|
32
|
+
(hours * 60 + minutes) * 60 + seconds
|
33
|
+
end
|
32
34
|
end
|
33
35
|
end
|
34
36
|
end
|