yt 0.5.4 → 0.5.5

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