playlist 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cbc27126439999045463cfa6de6ba50f75eb1864
4
+ data.tar.gz: 03110b81cb035d0b4b2685a64bc1fd41c8d4f414
5
+ SHA512:
6
+ metadata.gz: 56fe2477c106da8550f666694fc90185c496ad74eb42ff2ab03f695e4f8063b150ae31c8019fc1f11bdee0b192fbbbcbadd555137037df74c47c38399260c09a
7
+ data.tar.gz: 77d78fb8dd7f5d802407ed1058f54de4264c18549278d7c91b426981319b6f6b63a06ea2f83dff37f34b13bde2d51c8f4ea123b3ca754797e23a56b76b1fef61
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /.ruby-version
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
data/.rubocop.yml ADDED
@@ -0,0 +1,29 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.0
3
+ Exclude:
4
+ - 'vendor/**/*'
5
+
6
+ Style/GuardClause:
7
+ Enabled: false
8
+
9
+ Style/HashSyntax:
10
+ EnforcedStyle: hash_rockets
11
+
12
+ Style/MutableConstant:
13
+ Enabled: false
14
+
15
+ Style/SymbolArray:
16
+ EnforcedStyle: brackets
17
+
18
+ Style/ClassAndModuleChildren:
19
+ EnforcedStyle: compact
20
+
21
+ MethodLength:
22
+ Max: 20
23
+
24
+ Metrics/AbcSize:
25
+ Max: 20
26
+
27
+ # Allow long blocks in rspec
28
+ Metrics/BlockLength:
29
+ ExcludedMethods: ['describe', 'context']
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.2.2
5
+ before_install: gem install bundler -v 1.13.7
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in playlist.gemspec
4
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Nicholas J Humfrey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,90 @@
1
+ [![Build Status](https://travis-ci.org/njh/ruby-playlist.svg)](https://travis-ci.org/njh/ruby-playlist)
2
+
3
+ # Playlist
4
+
5
+ This ruby gem allows you to create and manipulate playlists of musical tracks.
6
+
7
+ It supports parsing and generating playlists in the following formats:
8
+
9
+ * M3U
10
+ * XSPF
11
+ * A simple human readable format
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ ```ruby
18
+ gem 'playlist'
19
+ ```
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+ Or install it yourself as:
26
+
27
+ $ gem install playlist
28
+
29
+ ## Usage
30
+
31
+ For full details, please see the [API Documentation](https://www.rubydoc.info/gems/playlist/).
32
+
33
+ ### Creating a Playlist
34
+
35
+ The `playlist` gem provides data model classes for creating and manipulating
36
+ playlists and music metadata in ruby.
37
+
38
+ Playlists can be constructed by passing a list of attributes to the constructor or by setting the attributes directly:
39
+
40
+ ```ruby
41
+ playlist = Playlist.new(:title => "My awesome playlist")
42
+ playlist.annotation = "Each week I add best tracks I can think of."
43
+ playlist.add_track(
44
+ :performer => "Jon Hopkins",
45
+ :title => "Everything Connected"
46
+ )
47
+
48
+ track = Playlist::Track.new(:title => "Get Your Shirt")
49
+ track.add_contributor(:name => "Underworld", :role => :performer)
50
+ track.add_contributor(:name => "Iggy Pop", :role => :performer)
51
+ playlist.add_track(track)
52
+ ```
53
+
54
+ ### Parsing a playlist file
55
+
56
+ The `playlist` gem supports a number of different playlist file formats.
57
+ Here is an example of parsing a M3U file:
58
+
59
+ ```ruby
60
+ File.open("playlist.m3u") do |file|
61
+ playlist = Playlist::Format::M3U.parse(file)
62
+ puts "The playlist contains #{playlist.tracks.count} tracks"
63
+ end
64
+ ```
65
+
66
+ ### Generating a playlist file
67
+
68
+ Having created a `Playlist` object, it can be converted to a playlist file using:
69
+
70
+ ```ruby
71
+ File.open("playlist.m3u", "wb") do |file|
72
+ file.write Playlist::Format::M3U.generate(playlist)
73
+ end
74
+ ```
75
+
76
+
77
+ ## Development
78
+
79
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `rake spec` to run the tests.
80
+
81
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
82
+
83
+ ## Contributing
84
+
85
+ Bug reports and pull requests are welcome on GitHub at https://github.com/njh/ruby-playlist.
86
+
87
+
88
+ ## License
89
+
90
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'bundler/gem_tasks'
3
+ require 'rubocop/rake_task'
4
+ require 'yard'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+ RuboCop::RakeTask.new
8
+ YARD::Rake::YardocTask.new
9
+
10
+ task :default => [:spec, :rubocop, :yard]
@@ -0,0 +1,31 @@
1
+ # Data model class that represents a contributor to a track.
2
+ # A Contrutor can be a person, or a organisation
3
+ # (such an band, choir or orchestra).
4
+ class Playlist::Contributor
5
+ # The name of the contributor
6
+ # @return [String]
7
+ attr_accessor :name
8
+
9
+ # The role of the contribrition to the track
10
+ #
11
+ # Recommended values for role:
12
+ # :performer
13
+ # :composer
14
+ # :arranger
15
+ # :lyricist
16
+ # @return [Symbol]
17
+ attr_accessor :role
18
+
19
+ # Create a new Contributor
20
+ def initialize(attr = nil)
21
+ if attr.is_a?(Hash)
22
+ attr.each_pair do |key, value|
23
+ send("#{key}=", value)
24
+ end
25
+ elsif attr.is_a?(String)
26
+ self.name = attr
27
+ end
28
+
29
+ yield(self) if block_given?
30
+ end
31
+ end
@@ -0,0 +1,39 @@
1
+ # Module to parse and generate M3U playlists
2
+ module Playlist::Format::M3U
3
+ class << self
4
+ # Parse a M3U file into a [Playlist]
5
+ # @param input any object that responds to #each_line
6
+ # (either a String or a IO object)
7
+ # @return [Playlist] a new Playlist object
8
+ def parse(input)
9
+ Playlist.new do |playlist|
10
+ track = Playlist::Track.new
11
+ input.each_line do |line|
12
+ if line =~ /^#EXTINF:(-?\d+),\s*(.+?)\s*-\s*(.+?)\s*$/
13
+ track.duration = Regexp.last_match(1)
14
+ track.creator = Regexp.last_match(2)
15
+ track.title = Regexp.last_match(3)
16
+ else
17
+ if line =~ /^[^#]\S+.+\s*$/
18
+ track.location = line.strip
19
+ playlist.add_track(track)
20
+ end
21
+ track = Playlist::Track.new
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ # Generate a M3U file from a [Playlist]
28
+ # @param playlist [Playlist] the playlist to be converted to M3U
29
+ # @return [String] M3U as a string
30
+ def generate(playlist)
31
+ text = "#EXTM3U\n"
32
+ playlist.tracks.each do |t|
33
+ text += "#EXTINF:#{t.duration.to_i},#{t.artist} - #{t.title}\n"
34
+ text += "#{t.location}\n"
35
+ end
36
+ text
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,39 @@
1
+ # Module to parse and generate playlists with one line per track
2
+ module Playlist::Format::SimpleText
3
+ class << self
4
+ # Parse a human readable list of tracks into a Playlist
5
+ # @param input any object that responds to #each_line
6
+ # (either a String or a IO object)
7
+ # @return [Playlist] a new Playlist object
8
+ def parse(input)
9
+ Playlist.new do |playlist|
10
+ input.each_line do |line|
11
+ next if line =~ /^#/
12
+
13
+ track = parse_line(line.strip)
14
+ playlist.tracks << track unless track.nil?
15
+ end
16
+ end
17
+ end
18
+
19
+ # Parse a single line from a playlist into a Track obect
20
+ # @param line [String] any object that responds to #each_line
21
+ # @return [Track] a new Track object
22
+ def parse_line(line)
23
+ if line =~ /^(\d{1,2}[:.]\d{1,2}([:.]\d{1,2})?)?\s*(.+?) - (.+?)$/
24
+ Playlist::Track.new(
25
+ :start_time => Regexp.last_match(1),
26
+ :creator => Regexp.last_match(3).strip,
27
+ :title => Regexp.last_match(4).strip
28
+ )
29
+ end
30
+ end
31
+
32
+ # Generate a human readable list of tracks from a Playlist
33
+ # @param playlist [Playlist] the playlist
34
+ # @return [String] the playlist with one line per track
35
+ def generate(playlist)
36
+ playlist.tracks.map { |t| "#{t.artist} - #{t.title}" }.join("\n") + "\n"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,62 @@
1
+ require 'nokogiri'
2
+
3
+ # Module to parse and generate XSPF playlists
4
+ module Playlist::Format::XSPF
5
+ class << self
6
+ # Parse a XSPF file into a new Playlist object
7
+ # @param input [String, IO] the source of the XSPF file
8
+ # @return [Playlist] a new Playlist object
9
+ def parse(input)
10
+ Playlist.new do |playlist|
11
+ doc = Nokogiri::XML(input)
12
+ playlist.title = inner_text_or_nil(doc, '/xmlns:playlist/xmlns:title')
13
+ doc.xpath('/xmlns:playlist/xmlns:trackList/xmlns:track').each do |track|
14
+ playlist.tracks << parse_track(track)
15
+ end
16
+ end
17
+ end
18
+
19
+ # Generate a XSPF file from a Playlist object
20
+ # @param playlist [Playlist] the playlist
21
+ # @return [String] the XSPF playlist as a String
22
+ def generate(playlist)
23
+ builder = Nokogiri::XML::Builder.new do |xml|
24
+ xml.playlist(:version => 1, :xmlns => 'http://xspf.org/ns/0/') do
25
+ xml.title(playlist.title) unless playlist.title.nil?
26
+ xml.trackList do
27
+ playlist.tracks.each { |track| generate_track(xml, track) }
28
+ end
29
+ end
30
+ end
31
+ builder.to_xml
32
+ end
33
+
34
+ protected
35
+
36
+ def parse_track(doc)
37
+ Playlist::Track.new do |track|
38
+ track.creator = inner_text_or_nil(doc, 'xmlns:creator')
39
+ track.title = inner_text_or_nil(doc, 'xmlns:title')
40
+ track.location = inner_text_or_nil(doc, 'xmlns:location')
41
+ if (duration = inner_text_or_nil(doc, 'xmlns:duration'))
42
+ track.duration = duration.to_f / 1000
43
+ end
44
+ end
45
+ end
46
+
47
+ def generate_track(xml, track)
48
+ xml.track do
49
+ xml.location(track.location) unless track.location.nil?
50
+ xml.title(track.title) unless track.title.nil?
51
+ xml.creator(track.creator) unless track.creator.nil?
52
+ xml.duration((track.duration * 1000).to_i) unless track.duration.nil?
53
+ end
54
+ end
55
+
56
+ ## FIXME: how to do this better?
57
+ def inner_text_or_nil(doc, path)
58
+ element = doc.at_xpath(path)
59
+ element.inner_text unless element.nil?
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,6 @@
1
+ # Base module for the various playlist formats
2
+ module Playlist::Format
3
+ autoload :M3U, 'playlist/format/m3u'
4
+ autoload :SimpleText, 'playlist/format/simple_text'
5
+ autoload :XSPF, 'playlist/format/xspf'
6
+ end
@@ -0,0 +1,147 @@
1
+ # Data model class that represents a single track.
2
+ class Playlist::Track
3
+ # A URI (or filename) to the location of the file
4
+ attr_accessor :location
5
+
6
+ # The title of the track
7
+ # @return [String]
8
+ attr_accessor :title
9
+
10
+ # The name of the album that the track came from
11
+ # @return [String]
12
+ attr_accessor :album
13
+
14
+ # The number of the track on the album it came from
15
+ # @return [Integer]
16
+ attr_accessor :track_number
17
+
18
+ # The side of disc if the track came from a vinyl album (eg A/B)
19
+ # @return [String]
20
+ attr_accessor :side
21
+
22
+ # The name of the record label that published the track/album
23
+ # @return [String]
24
+ attr_accessor :record_label
25
+
26
+ # The name of the publisher that published the score/lyrics of the song
27
+ # @return [String]
28
+ attr_accessor :publisher
29
+
30
+ # The time a track starts playing at, in seconds.
31
+ # May be a Float to include fractions of a second.
32
+ # @return [Integer, Float]
33
+ attr_accessor :start_time
34
+
35
+ # The duration of the track in seconds.
36
+ # May be a Float to include fractions of a second.
37
+ # @return [Integer, Float]
38
+ attr_reader :duration
39
+
40
+ # Get the array of the contributors to this Track
41
+ # @return [Array<Contributor>] an array of tracks in the playlist
42
+ attr_reader :contributors
43
+
44
+ # Create a new Track
45
+ # @param attr [Hash] a hash of attibute values to set
46
+ def initialize(attr = {})
47
+ @contributors = []
48
+ attr.each_pair do |key, value|
49
+ send("#{key}=", value)
50
+ end
51
+
52
+ yield(self) if block_given?
53
+ end
54
+
55
+ # Get a concatinated list of contributors names with no role
56
+ # @return [String]
57
+ def creator
58
+ contributor_names(nil)
59
+ end
60
+
61
+ # Set the name of the contributor with no role
62
+ # Removes any existing contributors with no role
63
+ # @param name [String] the name the contributor
64
+ def creator=(name)
65
+ replace_contributor(nil, name)
66
+ end
67
+
68
+ # Get a conactinated list of performers for this track
69
+ # If there are no performers, return contributors with no role
70
+ # @return [String]
71
+ def performer
72
+ contributor_names(:performer) || contributor_names(nil)
73
+ end
74
+ alias artist performer
75
+
76
+ # Set the name of the track performer
77
+ # Removes any existing performers
78
+ # @param name [String] the name the performer
79
+ def performer=(name)
80
+ replace_contributor(:performer, name)
81
+ end
82
+ alias artist= performer=
83
+
84
+ # Set the duration of the track
85
+ # If the duration is 0 or -1, then the duration is set to nil
86
+ # @param seconds [Numeric] the duration of the track in seconds
87
+ def duration=(seconds)
88
+ if seconds.is_a?(Numeric)
89
+ @duration = seconds
90
+ else
91
+ seconds = seconds.to_s
92
+ @duration = if seconds =~ /\./
93
+ seconds.to_f
94
+ else
95
+ seconds.to_i
96
+ end
97
+ end
98
+ @duration = nil if [0, -1].include?(@duration)
99
+ end
100
+
101
+ # Add a contributor to the Track
102
+ # @param args [Contributor, Hash] either a Contributor object or
103
+ # a Hash of attributes to creatre a new contributor
104
+ def add_contributor(args)
105
+ @contributors << if args.is_a?(Playlist::Contributor)
106
+ args
107
+ else
108
+ Playlist::Contributor.new(args)
109
+ end
110
+ end
111
+
112
+ # First deletes any contribitors with same role, then adds a new contributor
113
+ # @param role [Symbol] the role of the new contributor
114
+ # @param name [String] the name of the new contributor
115
+ def replace_contributor(role, name)
116
+ @contributors.delete_if { |c| c.role == role }
117
+ add_contributor(:role => role, :name => name)
118
+ end
119
+
120
+ # Get a concatinated list of contributor names for a specific role
121
+ # @param role [Symbol] the role of the new contributor
122
+ # Use :any to concatinate all contributor names
123
+ # @return [String]
124
+ def contributor_names(role = :any)
125
+ filtered = if role == :any
126
+ @contributors
127
+ else
128
+ @contributors.find_all { |c| c.role == role }
129
+ end
130
+ if filtered.count == 1
131
+ filtered.first.name
132
+ elsif filtered.count >= 2
133
+ filtered[0..-2].map(&:name).join(', ') +
134
+ ' & ' + filtered.last.name
135
+ end
136
+ end
137
+
138
+ # Get all the attributes of the track object as a Hash
139
+ # @return [Hash]
140
+ def to_h
141
+ Hash[
142
+ instance_variables.map do |v|
143
+ [v.to_s[1..-1].to_sym, instance_variable_get(v)]
144
+ end
145
+ ]
146
+ end
147
+ end
@@ -0,0 +1,4 @@
1
+ class Playlist
2
+ # The version number of the Playlist Ruby gem
3
+ VERSION = '0.1.0'
4
+ end
data/lib/playlist.rb ADDED
@@ -0,0 +1,57 @@
1
+ require 'playlist/version'
2
+
3
+ # Data model class that for a single playlist.
4
+ class Playlist
5
+ # The title of the playlist
6
+ # @return [String]
7
+ attr_accessor :title
8
+
9
+ # The name of the creator of this playlist
10
+ # @return [String]
11
+ attr_accessor :creator
12
+
13
+ # A description/synopsis of the playlist
14
+ # @return [String]
15
+ attr_accessor :annotation
16
+
17
+ # A URL to get more inforamtion about this playlist
18
+ # @return [String]
19
+ attr_accessor :info_url
20
+
21
+ # A link to the license / terms of use for this playlist
22
+ # @return [String]
23
+ attr_accessor :license
24
+
25
+ # Get the array that contains the list of track for this playlist
26
+ # @return [Array<Track>] an array of tracks in the playlist
27
+ attr_reader :tracks
28
+
29
+ # Create a new Playlist
30
+ # @param attr [Hash] a hash of attibute values to set
31
+ def initialize(attr = {})
32
+ @tracks = []
33
+ attr.each_pair do |key, value|
34
+ send("#{key}=", value)
35
+ end
36
+
37
+ yield(self) if block_given?
38
+ end
39
+
40
+ # Add a track to the playlist
41
+ # @param args [Track, Hash] either a Track object or
42
+ # a Hash of attributes to creatre a new Track
43
+ def add_track(args)
44
+ @tracks << (args.is_a?(Track) ? args : Track.new(args))
45
+ end
46
+
47
+ # Get the total duration of this playlist
48
+ # If any tracks on the playlist don't have a duation, then they are ignored
49
+ # @return [Integer, Float]
50
+ def duration
51
+ @tracks.map(&:duration).compact.inject(:+)
52
+ end
53
+
54
+ autoload :Contributor, 'playlist/contributor'
55
+ autoload :Track, 'playlist/track'
56
+ autoload :Format, 'playlist/format'
57
+ end
data/playlist.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ $LOAD_PATH.unshift File.expand_path('lib', __dir__)
2
+ require 'playlist/version'
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = 'playlist'
6
+ spec.version = Playlist::VERSION
7
+ spec.authors = ['Nicholas Humfrey']
8
+ spec.email = ['njh@aelius.com']
9
+
10
+ spec.summary = 'Convert playlists between different formats'
11
+ spec.homepage = 'https://github.com/njh/ruby-playlist'
12
+ spec.license = 'MIT'
13
+
14
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
15
+ f.match(%r{^(test|spec|features)/})
16
+ end
17
+ spec.bindir = 'bin'
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '> 1.13'
22
+ spec.add_development_dependency 'equivalent-xml', '~> 0.6.0'
23
+ spec.add_development_dependency 'rake', '~> 10.2.2'
24
+ spec.add_development_dependency 'rspec', '~> 3.7.0'
25
+ spec.add_development_dependency 'rubocop', '~> 0.50.0'
26
+ spec.add_development_dependency 'simplecov', '~> 0.9.2'
27
+ spec.add_development_dependency 'yard', '~> 0.9.14'
28
+ end
metadata ADDED
@@ -0,0 +1,158 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: playlist
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nicholas Humfrey
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-07-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: equivalent-xml
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.6.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.6.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 10.2.2
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 10.2.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 3.7.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 3.7.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.50.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.50.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.9.2
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.9.2
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.9.14
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.9.14
111
+ description:
112
+ email:
113
+ - njh@aelius.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rubocop.yml"
120
+ - ".travis.yml"
121
+ - Gemfile
122
+ - LICENSE.md
123
+ - README.md
124
+ - Rakefile
125
+ - lib/playlist.rb
126
+ - lib/playlist/contributor.rb
127
+ - lib/playlist/format.rb
128
+ - lib/playlist/format/m3u.rb
129
+ - lib/playlist/format/simple_text.rb
130
+ - lib/playlist/format/xspf.rb
131
+ - lib/playlist/track.rb
132
+ - lib/playlist/version.rb
133
+ - playlist.gemspec
134
+ homepage: https://github.com/njh/ruby-playlist
135
+ licenses:
136
+ - MIT
137
+ metadata: {}
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubyforge_project:
154
+ rubygems_version: 2.4.5
155
+ signing_key:
156
+ specification_version: 4
157
+ summary: Convert playlists between different formats
158
+ test_files: []