lllibrary 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
@@ -0,0 +1,39 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ lllibrary (0.0.1)
5
+ activerecord
6
+ activesupport
7
+ bundler
8
+ plist
9
+ sqlite3
10
+
11
+ GEM
12
+ remote: http://rubygems.org/
13
+ specs:
14
+ activemodel (3.2.9)
15
+ activesupport (= 3.2.9)
16
+ builder (~> 3.0.0)
17
+ activerecord (3.2.9)
18
+ activemodel (= 3.2.9)
19
+ activesupport (= 3.2.9)
20
+ arel (~> 3.0.2)
21
+ tzinfo (~> 0.3.29)
22
+ activesupport (3.2.9)
23
+ i18n (~> 0.6)
24
+ multi_json (~> 1.0)
25
+ arel (3.0.2)
26
+ builder (3.0.4)
27
+ i18n (0.6.1)
28
+ multi_json (1.5.0)
29
+ plist (3.1.0)
30
+ rake (10.0.3)
31
+ sqlite3 (1.3.6)
32
+ tzinfo (0.3.35)
33
+
34
+ PLATFORMS
35
+ ruby
36
+
37
+ DEPENDENCIES
38
+ lllibrary!
39
+ rake
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2012 Jeremy Ruten
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,85 @@
1
+ #lllibrary
2
+
3
+ `lllibrary` is a Ruby library that manages music libraries. It stores tracks (with metadata) and playlists in a database (using ActiveRecord), and gives you a sort of DSL to build complex playlists with ease.
4
+
5
+ ## Install
6
+
7
+ $ gem install lllibrary
8
+
9
+ ## Usage
10
+
11
+ This gives you an idea. Better documentation coming soon.
12
+
13
+ # Lllibrary
14
+ library = Lllibrary.new(File.join(ENV["HOME"], "music.sqlite3"), "sqlite3") do |t|
15
+ # provide a block like this if the database doesn't exist yet.
16
+ # the location, created_at, and updated_at fields are always
17
+ # created, the rest must be specified like so.
18
+
19
+ # adds database fields for title, artist, etc.
20
+ t.default_metadata
21
+
22
+ # adds all the database fields that iTunes uses
23
+ t.itunes_metadata
24
+
25
+ # you can also specify your own custom fields
26
+ t.string :five_word_review
27
+ t.integer :popularity, null: false, default: 0
28
+ end
29
+ library.tracks
30
+ library.playlists
31
+ library.add(Dir["Music/**/*.{mp3,m4a,ogg}"], &blk)
32
+ library.add_playlist(:favourites, library.select { rating 100 })
33
+ library.import(:itunes, "/path/to/iTunes Music Library.xml", &blk)
34
+ library.clear_playlists
35
+ library.clear_all
36
+
37
+ # Lllibrary::Track
38
+ track.playlists
39
+ track.location
40
+ track.created_at
41
+ track.updated_at
42
+ track.send(metadata_field)
43
+
44
+ # Lllibrary::Playlist
45
+ playlist.name
46
+ playlist.tracks
47
+ playlist.empty?
48
+ playlist.length
49
+ playlist.add(track_or_tracks, index = nil)
50
+ playlist.remove(track_or_tracks)
51
+ playlist.total_length
52
+ playlist.sort(&blk)
53
+ playlist.shuffle
54
+ playlist.playlist_items
55
+ playlist.created_at
56
+ playlist.updated_at
57
+ playlist.clear
58
+
59
+ # Lllibrary::PlaylistItem
60
+ playlist_item.position
61
+ playlist_item.track
62
+ playlist_item.playlist
63
+ playlist_item.created_at
64
+ playlist_item.updated_at
65
+
66
+ # Selectors DSL
67
+ library.select do
68
+ title("blackout") # equivalent to library.tracks.where("title LIKE '%blackout%'").all
69
+ title("blackout", match: :left) # equivalent to library.tracks.where("title LIKE 'blackout%'").all
70
+ title("blackout", match: :exact) # equivalent to library.tracks.where("title LIKE 'blackout'").all
71
+ length("1:00".."2:00") # equivalent to library.tracks.where(length: 60000...120000).all
72
+ rating(gte: 80) # equivalent to library.tracks.where("rating >= 80").all
73
+ none # []
74
+ all # library.tracks.all
75
+ end
76
+
77
+ ## TODO
78
+
79
+ * Better error reporting
80
+ * Fix bug with time selectors
81
+ * Get metadata from tags in audio files without requiring a C library to be installed (like taglib)
82
+ * Allow blocks to be passed to Lllibrary#add and Lllibrary#import
83
+ * Figure out how to handle multiple database connections
84
+ * Tests, documentation, the like
85
+
@@ -0,0 +1,170 @@
1
+ require "active_record"
2
+ require "plist"
3
+
4
+ require "lllibrary/track"
5
+ require "lllibrary/playlist"
6
+ require "lllibrary/playlist_item"
7
+
8
+ require "lllibrary/database"
9
+ require "lllibrary/dsl"
10
+
11
+ class Lllibrary
12
+ def initialize(db_path, db_adapter, &schema_blk)
13
+ @db = Lllibrary::Database.new(db_path, db_adapter)
14
+ if schema_blk
15
+ @db.generate_schema do |t|
16
+ t.string :location
17
+ t.timestamps
18
+
19
+ schema_blk.call(t)
20
+ end
21
+ end
22
+ @dsl = Lllibrary::DSL.new(self)
23
+ end
24
+
25
+ def select(&blk)
26
+ @dsl.instance_eval &blk
27
+ end
28
+
29
+ def tracks
30
+ Lllibrary::Track.scoped
31
+ end
32
+
33
+ def playlists
34
+ Lllibrary::Playlist.scoped
35
+ end
36
+
37
+ def add(paths_to_tracks, &blk)
38
+ Array(paths_to_tracks).each do |path|
39
+ track = tracks.new(location: path)
40
+ track = blk.call(track) if blk
41
+ track.save! if track.is_a?(Track)
42
+ end
43
+ end
44
+
45
+ def add_playlist(name, tracks)
46
+ playlist = playlists.new(name: name)
47
+ playlist.save!
48
+ tracks.each.with_index do |track, i|
49
+ playlist_item = Lllibrary::PlaylistItem.new
50
+ playlist_item.playlist = playlist
51
+ playlist_item.track = track
52
+ playlist_item.position = i
53
+ playlist_item.save!
54
+ end
55
+ end
56
+
57
+ def import(type, path, options = {})
58
+ logger = options[:logger]
59
+ if type == :itunes
60
+ logger.("Parsing XML...") if logger
61
+ data = Plist::parse_xml(path)
62
+
63
+ logger.("Importing #{data['Tracks'].length} tracks...") if logger
64
+ num_tracks = 0
65
+ whitelist = tracks.new.attributes.keys
66
+ data["Tracks"].each do |track_id, row|
67
+ if row["Kind"] !~ /audio/
68
+ logger.("[skipping non-audio file]") if logger
69
+ next
70
+ end
71
+
72
+ # row already contains a hash of attributes almost ready to be passed to
73
+ # ActiveRecord. We just need to modify the keys, e.g. change "Play Count"
74
+ # to "play_count".
75
+ row["Title"] = row.delete("Name")
76
+ row["Play Date"] = row.delete("Play Date UTC")
77
+ row["Original ID"] = row.delete("Track ID")
78
+ attributes = row.inject({}) do |acc, (key, value)|
79
+ attribute = key.gsub(" ", "").underscore
80
+ acc[attribute] = value if whitelist.include? attribute
81
+ acc
82
+ end
83
+
84
+ # change iTunes' URL-style locations into simple paths
85
+ if attributes["location"] && attributes["location"] =~ /^file:\/\//
86
+ attributes["location"].sub! /^file:\/\/localhost/, ""
87
+
88
+ # CGI::unescape changes plus signs to spaces. This is a work around to
89
+ # keep the plus signs.
90
+ attributes["location"].gsub! "+", "%2B"
91
+
92
+ attributes["location"] = CGI::unescape(attributes["location"])
93
+ end
94
+
95
+ track = tracks.new(attributes)
96
+ if track.save
97
+ num_tracks += 1
98
+ end
99
+ end
100
+ logger.("Imported #{num_tracks} tracks successfully.") if logger
101
+
102
+ if tracks.new.attributes.keys.include? "original_id"
103
+ logger.("Importing #{data['Playlists'].length} playlists...") if logger
104
+ num_playlists = 0
105
+ data["Playlists"].each do |playlist_data|
106
+ playlist = []
107
+
108
+ if ["Library", "Music", "Movies", "TV Shows", "iTunes DJ"].include? playlist_data["Name"]
109
+ logger.("[skipping \"#{playlist_data['Name']}\" playlist]") if logger
110
+ elsif playlist_data["Playlist Items"].nil?
111
+ logger.("[skipping \"#{playlist_data['Name']}\" playlist (because it's empty)]") if logger
112
+ else
113
+ playlist_data["Playlist Items"].map(&:values).flatten.each do |original_id|
114
+ playlist << tracks.where(original_id: original_id).first
115
+ end
116
+ playlist.compact!
117
+ add_playlist(playlist_data["Name"], playlist)
118
+ num_playlists += 1
119
+ end
120
+ end
121
+ logger.("Imported #{num_playlists} playlists successfully.") if logger
122
+ else
123
+ logger.("Can't import playlists because tracks table doesn't have an original_id field.") if logger
124
+ end
125
+ end
126
+ end
127
+
128
+ def clear_playlists
129
+ playlists.destroy_all
130
+ end
131
+
132
+ def clear_all
133
+ tracks.destroy_all
134
+ clear_playlists
135
+ end
136
+
137
+ # Helper method to format a number of milliseconds as a string like
138
+ # "1:03:56.555". The only option is :include_milliseconds, true by default. If
139
+ # false, milliseconds won't be included in the formatted string.
140
+ def self.format_time(milliseconds, options = {})
141
+ ms = milliseconds % 1000
142
+ seconds = (milliseconds / 1000) % 60
143
+ minutes = (milliseconds / 60000) % 60
144
+ hours = milliseconds / 3600000
145
+
146
+ if ms.zero? || options[:include_milliseconds] == false
147
+ ms_string = ""
148
+ else
149
+ ms_string = ".%03d" % [ms]
150
+ end
151
+
152
+ if hours > 0
153
+ "%d:%02d:%02d%s" % [hours, minutes, seconds, ms_string]
154
+ else
155
+ "%d:%02d%s" % [minutes, seconds, ms_string]
156
+ end
157
+ end
158
+
159
+ # Helper method to parse a string like "1:03:56.555" and return the number of
160
+ # milliseconds that time length represents.
161
+ def self.parse_time(string)
162
+ parts = string.split(":").map(&:to_f)
163
+ parts = [0] + parts if parts.length == 2
164
+ hours, minutes, seconds = parts
165
+ seconds = hours * 3600 + minutes * 60 + seconds
166
+ milliseconds = seconds * 1000
167
+ milliseconds.to_i
168
+ end
169
+ end
170
+
@@ -0,0 +1,84 @@
1
+ class Lllibrary
2
+ class Database
3
+ def initialize(path, adapter)
4
+ @path, @adapter = path, adapter
5
+ ActiveRecord::Base.establish_connection(adapter: @adapter, database: @path)
6
+ end
7
+
8
+ def disconnect
9
+ ActiveRecord::Base.remove_connection
10
+ end
11
+
12
+ def exists?
13
+ File.exists? @path
14
+ end
15
+
16
+ def delete
17
+ FileUtils.rm @path
18
+ end
19
+
20
+ def connected?
21
+ ActiveRecord::Base.connected?
22
+ end
23
+
24
+ def generate_schema(&blk)
25
+ ActiveRecord::Schema.define do
26
+ create_table :tracks do |t|
27
+ t.string :location
28
+ t.timestamps
29
+
30
+ def t.default_metadata
31
+ string :title
32
+ string :artist
33
+ string :album
34
+ string :genre
35
+ integer :length
36
+ integer :track_number
37
+ integer :year
38
+ end
39
+
40
+ def t.itunes_metadata
41
+ integer :original_id
42
+ string :title
43
+ string :artist
44
+ string :composer
45
+ string :album
46
+ string :album_artist
47
+ string :genre
48
+ integer :total_time
49
+ integer :disc_number
50
+ integer :disc_count
51
+ integer :track_number
52
+ integer :track_count
53
+ integer :year
54
+ datetime :date_modified, null: false
55
+ datetime :date_added, null: false
56
+ integer :bit_rate
57
+ integer :sample_rate
58
+ text :comments
59
+ integer :play_count, null: false, default: 0
60
+ datetime :play_date
61
+ integer :skip_count, null: false, default: 0
62
+ datetime :skip_date
63
+ integer :rating, null: false, default: 0
64
+ end
65
+
66
+ blk.call(t)
67
+ end
68
+
69
+ create_table :playlists do |t|
70
+ t.string :name
71
+ t.datetime :created_at
72
+ t.datetime :updated_at
73
+ end
74
+
75
+ create_table :playlist_items do |t|
76
+ t.references :playlist, null: false
77
+ t.references :track, null: false
78
+ t.integer :position
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+
@@ -0,0 +1,144 @@
1
+ class Lllibrary
2
+ class DSL
3
+ def initialize(library)
4
+ @library = library
5
+ end
6
+
7
+ def method_missing(field, *args)
8
+ if Lllibrary::Track.column_names.include? field.to_s
9
+ type = Lllibrary::Track.columns_hash[field.to_s].type
10
+ case type
11
+ when :integer, :float, :decimal, :boolean, :datetime, :timestamp, :date, :time
12
+ numeric_selector(field, *args)
13
+ when :string, :text
14
+ string_selector(field, *args)
15
+ else
16
+ raise ArgumentError, "lllibrary: selectors aren't supported for #{field}'s type '#{type}'"
17
+ end
18
+ else
19
+ super
20
+ end
21
+ end
22
+
23
+ # Selects all the tracks in the library.
24
+ def all
25
+ @library.tracks.all
26
+ end
27
+
28
+ # Selects no tracks, returning an empty playlist.
29
+ def none
30
+ []
31
+ end
32
+
33
+ # Makes a playlist out of tracks that match the string query on the given
34
+ # column. It's SQL underneath, so you can use % and _ as wildcards in the
35
+ # query. By default, % wildcards are inserted on the left and right of your
36
+ # query. Use the :match option to change this:
37
+ #
38
+ # :match => :middle "%query%" (default)
39
+ # :match => :left "query%"
40
+ # :match => :right "%query"
41
+ # :match => :exact "query"
42
+ #
43
+ def string_selector(column, *queries)
44
+ options = queries.last.is_a?(Hash) ? queries.pop : {}
45
+ options[:match] ||= :middle
46
+
47
+ tracks = @library.tracks.arel_table
48
+ queries.map do |query|
49
+ query = {
50
+ exact: "#{query}",
51
+ left: "#{query}%",
52
+ right: "%#{query}",
53
+ middle: "%#{query}%"
54
+ }[options[:match]]
55
+
56
+ @library.tracks.where(tracks[column].matches(query)).all
57
+ end.flatten
58
+ end
59
+
60
+ # Makes a playlist out of tracks that satisfy certain conditions on the given
61
+ # numeric column. You can pass an exact value to check for equality, a range,
62
+ # or a hash that specifies greater-than and less-than options like this:
63
+ #
64
+ # numeric_selector :year, greater_than: 2000
65
+ #
66
+ # The possible operators, with their shortcuts, are:
67
+ #
68
+ # :greater_than / :gt
69
+ # :less_than / :lt
70
+ # :greater_than_or_equal / :gte
71
+ # :less_than_or_equal / :lte
72
+ # :not_equal / :ne
73
+ #
74
+ # Note: You can only use one of these operators at a time. If you want a
75
+ # range, use a Range.
76
+ #
77
+ def numeric_selector(column, *values_or_hash)
78
+ parse = lambda do |x|
79
+ if x.is_a? String
80
+ ms = Lllibrary.parse_time(x)
81
+ if ms % 1000 == 0
82
+ ms...(ms + 1000)
83
+ else
84
+ ms
85
+ end
86
+ elsif x.is_a? Range
87
+ left = x.begin.is_a?(String) ? Lllibrary.parse_time(x.begin) : x.begin
88
+ right = x.end.is_a?(String) ? Lllibrary.parse_time(x.end) : x.end
89
+ Range.new(left, right, x.exclude_end?)
90
+ else
91
+ x
92
+ end
93
+ end
94
+
95
+ if values_or_hash.last.is_a? Hash
96
+ operator = values_or_hash.last.keys.first.to_sym
97
+ value = parse.(values_or_hash.last.values.first)
98
+ case operator
99
+ when :greater_than, :gt
100
+ op = ">"
101
+ if value.is_a? Range
102
+ op = ">=" if value.exclude_end?
103
+ value = value.end
104
+ end
105
+ @library.tracks.where("tracks.#{column} #{op} ?", value).all
106
+ when :less_than, :lt
107
+ value = value.begin if value.is_a? Range
108
+ @library.tracks.where("tracks.#{column} < ?", value).all
109
+ when :greater_than_or_equal, :gte
110
+ value = value.begin if value.is_a? Range
111
+ @library.tracks.where("tracks.#{column} >= ?", value).all
112
+ when :less_than_or_equal, :lte
113
+ op = "<="
114
+ if value.is_a? Range
115
+ op = "<" if value.exclude_end?
116
+ value = value.end
117
+ end
118
+ @library.tracks.where("tracks.#{column} #{op} ?", value)
119
+ when :not_equal, :ne
120
+ if value.is_a? Range
121
+ op = value.exclude_end? ? ">=" : ">"
122
+ @library.tracks.where("tracks.#{column} < ? AND tracks.#{column} #{op} ?", value.begin, value.end)
123
+ else
124
+ @library.tracks.where("tracks.#{column} != ?", value)
125
+ end
126
+ else
127
+ raise ArgumentError, "'#{operator}' isn't a valid operator"
128
+ end
129
+ else
130
+ @library.tracks.where(column => parse.(values_or_hash)).all
131
+ end
132
+ end
133
+
134
+ def playlist(*names)
135
+ names.map do |name|
136
+ playlists = @library.playlists.arel_table
137
+ if playlist = @library.playlists.where(playlists[:name].matches(name.to_s)).first
138
+ playlist.tracks.all
139
+ end
140
+ end.flatten
141
+ end
142
+ end
143
+ end
144
+
@@ -0,0 +1,62 @@
1
+ class Lllibrary
2
+ class Playlist < ActiveRecord::Base
3
+ has_many :playlist_items, order: "playlist_items.position ASC", dependent: :destroy
4
+ has_many :tracks, through: :playlist_items, order: "playlist_items.position ASC"
5
+
6
+ def add(track_or_tracks, at = nil)
7
+ base_position = nil
8
+ if at
9
+ at_track = playlist_items.offset(at).first
10
+ base_position = at_track.position if at_track
11
+ end
12
+ base_position ||= empty? ? 0 : playlist_items.last.position + 1
13
+
14
+ playlist_items.where("playlist_items.position >= ?", base_position).update_all("position = position + #{Array(track_or_tracks).length}")
15
+
16
+ Array(track_or_tracks).each.with_index do |track, i|
17
+ playlist_item = Lllibrary::PlaylistItem.new
18
+ playlist_item.track = track
19
+ playlist_item.playlist = self
20
+ playlist_item.position = base_position + i
21
+ playlist_item.save!
22
+ end
23
+
24
+ reload
25
+ end
26
+
27
+ def remove(track_or_tracks)
28
+ playlist_items.where(track_id: track_or_tracks).destroy_all
29
+ reload
30
+ end
31
+
32
+ def empty?
33
+ playlist_items.empty?
34
+ end
35
+
36
+ def length
37
+ playlist_items.count
38
+ end
39
+
40
+ def total_length(options = {})
41
+ tracks.sum(options[:field] || :length)
42
+ end
43
+
44
+ def sort(&blk)
45
+ sorted_tracks = tracks.sort(&blk)
46
+ clear
47
+ add(sorted_tracks)
48
+ end
49
+
50
+ def shuffle
51
+ shuffled_tracks = tracks.shuffle
52
+ clear
53
+ add(shuffled_tracks)
54
+ end
55
+
56
+ def clear
57
+ playlist_items.destroy_all
58
+ reload
59
+ end
60
+ end
61
+ end
62
+
@@ -0,0 +1,6 @@
1
+ class Lllibrary
2
+ class PlaylistItem < ActiveRecord::Base
3
+ belongs_to :playlist
4
+ belongs_to :track
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ class Lllibrary
2
+ class Track < ActiveRecord::Base
3
+ has_many :playlist_items, dependent: :destroy
4
+ has_many :playlists, through: :playlist_items
5
+
6
+ validates :location, presence: true, uniqueness: true
7
+ end
8
+ end
@@ -0,0 +1,23 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "lllibrary"
3
+ s.version = "0.0.1"
4
+ s.date = "2012-12-17"
5
+ s.summary = "A library for managing and querying a music library."
6
+ s.description = "lllibrary is a Ruby library for managing and querying a music library."
7
+ s.author = "Jeremy Ruten"
8
+ s.email = "jeremy.ruten@gmail.com"
9
+ s.homepage = "http://github.com/yjerem/lllibrary"
10
+ s.license = "MIT"
11
+ s.required_ruby_version = ">= 1.9.2"
12
+
13
+ s.files = ["Gemfile", "Gemfile.lock", "LICENSE", "lllibrary.gemspec", "README.md"]
14
+ s.files += Dir["lib/**/*.rb"]
15
+
16
+ %w(bundler plist sqlite3 activerecord activesupport).each do |gem_name|
17
+ s.add_runtime_dependency gem_name
18
+ end
19
+
20
+ %w(rake).each do |gem_name|
21
+ s.add_development_dependency gem_name
22
+ end
23
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lllibrary
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jeremy Ruten
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: plist
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: sqlite3
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: activerecord
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: activesupport
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rake
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: lllibrary is a Ruby library for managing and querying a music library.
111
+ email: jeremy.ruten@gmail.com
112
+ executables: []
113
+ extensions: []
114
+ extra_rdoc_files: []
115
+ files:
116
+ - Gemfile
117
+ - Gemfile.lock
118
+ - LICENSE
119
+ - lllibrary.gemspec
120
+ - README.md
121
+ - lib/lllibrary/database.rb
122
+ - lib/lllibrary/dsl.rb
123
+ - lib/lllibrary/playlist.rb
124
+ - lib/lllibrary/playlist_item.rb
125
+ - lib/lllibrary/track.rb
126
+ - lib/lllibrary.rb
127
+ homepage: http://github.com/yjerem/lllibrary
128
+ licenses:
129
+ - MIT
130
+ post_install_message:
131
+ rdoc_options: []
132
+ require_paths:
133
+ - lib
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - ! '>='
138
+ - !ruby/object:Gem::Version
139
+ version: 1.9.2
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ none: false
142
+ requirements:
143
+ - - ! '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 1.8.23
149
+ signing_key:
150
+ specification_version: 3
151
+ summary: A library for managing and querying a music library.
152
+ test_files: []