rjl-itunes 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.yardopts +1 -0
- data/Changelog.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +20 -0
- data/README.md +88 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/fix_genres.rb +35 -0
- data/bin/setup +8 -0
- data/lib/rjl/album.rb +83 -0
- data/lib/rjl/itunes.rb +179 -0
- data/lib/rjl/metadata.rb +135 -0
- data/lib/rjl/playlist.rb +43 -0
- data/lib/rjl/track.rb +121 -0
- data/lib/rjl/utils.rb +8 -0
- data/lib/rjl/version.rb +4 -0
- data/lib/rjl.rb +7 -0
- data/rjl-itunes.gemspec +26 -0
- data/spec/album_spec.rb +99 -0
- data/spec/metadata_spec.rb +37 -0
- data/spec/playlist.rb +50 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/track_spec.rb +14 -0
- metadata +177 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4fb59ab8673076305fb0813d5d13f0b075d831c6
|
4
|
+
data.tar.gz: c817756e947c2a24757e8d2a0024d199b188c246
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: db5ad5e51298f53168feac23361abd9e7e75874626675573e6c472ad9f8e56b6140a382d7c904b886e94b147ee4a060835710911122cdf3cd595e67fd1c5491b
|
7
|
+
data.tar.gz: 7a93638d0ecc070c885d70b7977dff897170757dbe2b64fce0eb0ec352763c5f016530ea73ebcd74811d6175121e7d6b179931eb60febaf2a879325e2914b4a3
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
- LICENSE.txt
|
data/Changelog.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) Richard Lyon
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# rjl-itunes 0.1
|
2
|
+
|
3
|
+
`rjl-itunes` is a Ruby client for Apple's iTunes application. It's designed to support utilities that make it both easier and more pleasant to maintain your collection of albums.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'rjl-itunes'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install rjl-itunes
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Getting a list of albums
|
24
|
+
|
25
|
+
require 'rjl/itunes'
|
26
|
+
|
27
|
+
itunes = RJL::Itunes.new
|
28
|
+
|
29
|
+
itunes.albums.each do |album|
|
30
|
+
puts "#{album.album_artist}, '#{album.title}'"
|
31
|
+
album.tracks.each do |track|
|
32
|
+
puts track.title, track.genre
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
### Updating an album's genre
|
37
|
+
|
38
|
+
album = itunes.albums[0]
|
39
|
+
album.genre = "Pop/Rock"
|
40
|
+
|
41
|
+
See `/bin` for more uses. See 'Warning' below.
|
42
|
+
|
43
|
+
## How it works
|
44
|
+
|
45
|
+
`rjl-itunes` uses applescript to interact with your currently active Itunes library. Albums can be manipulated in a straightforward manner. Playlists and Playlist Folders can be created and destroyed, and albums added to them.
|
46
|
+
|
47
|
+
### Genres
|
48
|
+
|
49
|
+
Album genres are sometimes divided into sub-genres. [Allmusic.com](allmusic.com) refers to these sub-genres as 'styles', and can return several genres and styles for an album. iTunes provides the field `genre` for specifying genre and allows albums to be sorted by this field in the 'Albums' view. It also provides the field `grouping`, but only allows albums to be sorted by this via the column Browser in the 'Songs' view. There is no straightforward way of mapping multiple genres and styles into this these fields.
|
50
|
+
|
51
|
+
`rjl-itunes` computes a single string to represents the album's genre from the genres and styles obtained from [Allmusic.com](allmusic.com). The frequency of each genre and style in the whole library is calculated. For each album that has more than one genre or style, the highest frequency genre and style is chosen and combined.
|
52
|
+
|
53
|
+
### Tags
|
54
|
+
|
55
|
+
Tags can be used to control how `rjl-itunes` interacts with your library. Tags are encoded as `[tag1][tag2]` in the track's `groupings` field in iTunes (this is likely to change in future versions).
|
56
|
+
|
57
|
+
Reserved tags are as follows:
|
58
|
+
|
59
|
+
* `[protected]`
|
60
|
+
Track is excluded from processing. Use this if, for example, you have set your own genre and do not want `rjl-itunes` to change it.
|
61
|
+
|
62
|
+
## Warning
|
63
|
+
|
64
|
+
This might wreck your iTunes library in two ways. You might use commands that accidentally hose your library. Or the commands may have unknown side-effects. Always back up your library up first.
|
65
|
+
|
66
|
+
`rjl-itunes` tries to be efficient in the way it interacts with iTunes, but Applescript is something of a dark art (to me). Expect scripts to mysteriously slow down, especially with large changes. [Ruby Progressbar](https://github.com/jfelchner/ruby-progressbar/wiki) is your friend.
|
67
|
+
|
68
|
+
## Testing
|
69
|
+
|
70
|
+
RSpec tests are provided. The tests will not work with your library, but I can't distribute mine because of copyright issues. You can edit the tests to suit your own library.
|
71
|
+
|
72
|
+
## Changes
|
73
|
+
|
74
|
+
### 17 March 2016 -- 0.1
|
75
|
+
Initial release.
|
76
|
+
|
77
|
+
## Acknowledgements
|
78
|
+
|
79
|
+
`rjl-itunes` uses [Brendan Thompson's Ruby fork](https://github.com/BrendanThompson/rb-scpt) of the SF Project [appscript](http://appscript.sourceforge.net/rb-appscript/index.html) for accessing itunes via applescript.
|
80
|
+
|
81
|
+
## Contacting me
|
82
|
+
|
83
|
+
You can contact me at r i c h l y o n @ m a c . c o m
|
84
|
+
|
85
|
+
## Copyright
|
86
|
+
|
87
|
+
Copyright (c) 2016 Richard Lyon. See {file:LICENSE.txt LICENSE} for
|
88
|
+
further details.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "rjl-itunes"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/fix_genres.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# fix_genres.rb
|
4
|
+
|
5
|
+
# Ruby script to classify iTunes track genres. Retrieves genre and style
|
6
|
+
# information from Allmusic.com, computes a genre, and sets track genre to it.
|
7
|
+
# Insert [protected] in Groupings to exclude tracks from classification.
|
8
|
+
|
9
|
+
# (c) Richard Lyon 15 March, 2016
|
10
|
+
|
11
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
12
|
+
require 'rjl/itunes'
|
13
|
+
|
14
|
+
# require 'metadata'
|
15
|
+
require 'ruby-progressbar' # https://github.com/jfelchner/ruby-progressbar/wiki
|
16
|
+
|
17
|
+
itunes = RJL::Itunes.new
|
18
|
+
|
19
|
+
progressbar = ProgressBar.create(
|
20
|
+
:format => '%e |%b>>%i| %p%% %t',
|
21
|
+
:title => "Tracks",
|
22
|
+
:total => itunes.albums.count)
|
23
|
+
|
24
|
+
# main loop - fix album genre unless tagged 'proteected'
|
25
|
+
itunes.albums.each do |album|
|
26
|
+
metadata = RJL::Metadata.new( album: album )
|
27
|
+
progressbar.increment
|
28
|
+
unless album.protected?
|
29
|
+
new_genre = metadata.genre( album )
|
30
|
+
unless new_genre == ""
|
31
|
+
album.genre = metadata.genre( album )
|
32
|
+
progressbar.log "#{album.album_artist}, '#{album.title}' -> #{new_genre}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/bin/setup
ADDED
data/lib/rjl/album.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
module RJL
|
2
|
+
# Represents an album in Apple's 'iTunes' application.
|
3
|
+
class Album
|
4
|
+
|
5
|
+
attr_reader :album_artist
|
6
|
+
attr_reader :title
|
7
|
+
attr_accessor :genre
|
8
|
+
attr_reader :tracks
|
9
|
+
|
10
|
+
# Creates a new RJL::Album. Album properties are derived from the
|
11
|
+
# properties of the supplied tracks.
|
12
|
+
# @param [List of Track] tracks the album tracks
|
13
|
+
def initialize( tracks: [])
|
14
|
+
@tracks = tracks
|
15
|
+
end
|
16
|
+
|
17
|
+
# Album's artist e.g "Simply Red". Returns 'Various Artists' if more than one.
|
18
|
+
# @return [String artist name] the album artist if only one
|
19
|
+
# @return [String 'Various Artists'] if more than one
|
20
|
+
def album_artist
|
21
|
+
album_artist = nil
|
22
|
+
if unique?(@tracks, "artist" )
|
23
|
+
album_artist = @tracks[0].artist
|
24
|
+
else
|
25
|
+
album_artist = 'Various Artists'
|
26
|
+
end
|
27
|
+
return album_artist
|
28
|
+
end
|
29
|
+
|
30
|
+
# Album title e.g "Greatest Hits"
|
31
|
+
# @return [String] album title
|
32
|
+
def title
|
33
|
+
return @tracks[0].album
|
34
|
+
end
|
35
|
+
|
36
|
+
# Album genre e.g "Pop/Rock [Alternative/Indie Rock]"
|
37
|
+
# @return [String] album genre
|
38
|
+
def genre
|
39
|
+
if unique?( @tracks, "genre" )
|
40
|
+
return @tracks[0].genre
|
41
|
+
else
|
42
|
+
return 'mixed'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param [String] album genre
|
47
|
+
def genre=(str)
|
48
|
+
if !self.protected?
|
49
|
+
@tracks.each do |track|
|
50
|
+
track.genre = str
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# The tracks in the album
|
56
|
+
# @return [List of Track] tracks the album tracks
|
57
|
+
def tracks
|
58
|
+
return @tracks
|
59
|
+
end
|
60
|
+
|
61
|
+
# Is the album protected from changes?
|
62
|
+
# @return [Boolean] true if it is
|
63
|
+
def protected?
|
64
|
+
protected = false
|
65
|
+
@tracks.each do |track|
|
66
|
+
if track.tags.include? 'protected'
|
67
|
+
protected = true
|
68
|
+
break
|
69
|
+
end
|
70
|
+
end
|
71
|
+
return protected
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_s
|
75
|
+
puts "-"*50
|
76
|
+
puts "> #{self.album_artist}, '#{self.title}'"
|
77
|
+
@tracks.each do |track|
|
78
|
+
puts " #{track.name}"
|
79
|
+
end
|
80
|
+
puts "="*50
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/rjl/itunes.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'rjl'
|
2
|
+
require 'rb-scpt' # https://github.com/BrendanThompson/rb-scpt
|
3
|
+
include Appscript
|
4
|
+
|
5
|
+
module RJL
|
6
|
+
# RJL_itunes::Itunes contains the public api for Itunes.
|
7
|
+
# @api public
|
8
|
+
# @example itunes = Itunes.new
|
9
|
+
|
10
|
+
class Itunes
|
11
|
+
|
12
|
+
attr_reader :version
|
13
|
+
attr_reader :albums
|
14
|
+
|
15
|
+
# Create a new RJL_itunes::Itunes. Can be called with a Playlist name to only
|
16
|
+
# get the tracks in that Playlist - useful for testing, and may be deprecated
|
17
|
+
# @param [String] playlist optional name of a Playlist to open
|
18
|
+
def initialize( playlist = nil )
|
19
|
+
@tracks = get_tracks( playlist )
|
20
|
+
@albums = get_albums( @tracks )
|
21
|
+
@playlists = fetch_playlists( kind: :none )
|
22
|
+
@playlist_folders = fetch_playlists( kind: :none )
|
23
|
+
end
|
24
|
+
|
25
|
+
# iTunes version number
|
26
|
+
# @return [String] the iTunes application version number.
|
27
|
+
def version
|
28
|
+
return app("iTunes").version.get
|
29
|
+
end
|
30
|
+
|
31
|
+
# The albums, or a single album
|
32
|
+
# @example
|
33
|
+
# albums = itunes.albums
|
34
|
+
# puts albums.count # => 5
|
35
|
+
#
|
36
|
+
# album = itunes.albums(album_artist: "Simply Red", title: "Greatest Hits" )
|
37
|
+
# puts album.genre # => "Pop/Rock"
|
38
|
+
# @param [String] artist the album artist
|
39
|
+
# @param [String] title the album title
|
40
|
+
# @return [List of Album] if more than one album
|
41
|
+
# @return [Album] if only one album
|
42
|
+
def albums( album_artist: nil, title: nil )
|
43
|
+
albums = []
|
44
|
+
if album_artist.nil? && title.nil?
|
45
|
+
albums = @albums
|
46
|
+
elsif !album_artist.nil? && title.nil?
|
47
|
+
albums = @albums.select{ |album| album.album_artist == album_artist }
|
48
|
+
elsif album_artist.nil? && !title.nil?
|
49
|
+
albums = @albums.select{ |album| album.title == title }
|
50
|
+
elsif !album_artist.nil? and !title.nil?
|
51
|
+
albums = @albums.select{ |album| album.album_artist == album_artist && album.title == title}
|
52
|
+
albums = albums[0]
|
53
|
+
end
|
54
|
+
return albums
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return a list of the playlists.
|
58
|
+
# @return [List of Playlist] the playlists
|
59
|
+
def playlists
|
60
|
+
return @playlists
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get a playlist of the specified name. Raises error if more than one.
|
64
|
+
# @param playlist_name [String] The name of the playlist to get
|
65
|
+
# @return [Playlist] the named playlist
|
66
|
+
def playlist( playlist_name )
|
67
|
+
play_list = @playlists.select { |playlist| playlist.name == playlist_name }
|
68
|
+
if play_list.count == 1
|
69
|
+
return play_list[0]
|
70
|
+
else
|
71
|
+
raise "ERROR: #{play_list.count} playlists with name #{playlist_name}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def create_playlist( playlist_name: "Playlist", track_list: nil )
|
76
|
+
playlist_obj = app("iTunes").make(
|
77
|
+
:new => :playlist,
|
78
|
+
:with_properties => {:name => playlist_name}
|
79
|
+
)
|
80
|
+
playlist = Playlist.new( playlist_obj )
|
81
|
+
playlist.tracks = track_list unless track_list.nil?
|
82
|
+
@playlists << playlist
|
83
|
+
return playlist
|
84
|
+
end
|
85
|
+
|
86
|
+
def destroy_playlist( playlist_name )
|
87
|
+
app("iTunes").sources["Library"].user_playlists[playlist_name].delete
|
88
|
+
# TODO remove from @playlists
|
89
|
+
end
|
90
|
+
|
91
|
+
def create_playlist_folder( folder_name: "untitled folder", playlists: nil )
|
92
|
+
playlist_obj = app("iTunes").make(
|
93
|
+
:new => :folder_playlist,
|
94
|
+
:with_properties => {:name => folder_name}
|
95
|
+
)
|
96
|
+
# TODO add playlists
|
97
|
+
# TODO make and add to @playlist_folders
|
98
|
+
end
|
99
|
+
|
100
|
+
def destroy_playlist_folder( folder_name: nil )
|
101
|
+
app("iTunes").sources["Library"].folder_playlists[folder_name].delete
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
# Get the tracks
|
107
|
+
# @param [String] playlist the name of a playlist to get tracks from
|
108
|
+
# @return [List of Track] the tracks
|
109
|
+
def get_tracks( playlist = nil)
|
110
|
+
|
111
|
+
track_list = []
|
112
|
+
|
113
|
+
if playlist.nil?
|
114
|
+
tracks = app("iTunes").playlists[1].tracks[its.video_kind.eq(:none)].get
|
115
|
+
else
|
116
|
+
tracks = app("iTunes").playlists[playlist].tracks[its.video_kind.eq(:none)].get
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
tracks.each do |track_obj|
|
121
|
+
# puts ">>> #{track_obj.class_.get}"
|
122
|
+
track_list << Track.new(track_obj)
|
123
|
+
end
|
124
|
+
return track_list
|
125
|
+
end
|
126
|
+
|
127
|
+
# Get the albums
|
128
|
+
# Three possibilities:
|
129
|
+
# 1. one album, one artist e.g. "Spiceworld, Spice Girls"
|
130
|
+
# 2. one album, multiple artists, not compilation e.g. "Greatest Hits"
|
131
|
+
# 3. one album, multiple artists, compilation e.g. "The Firm"
|
132
|
+
# @param [List of Track] the tracks
|
133
|
+
# @return [List of Album] the albums
|
134
|
+
def get_albums( tracks )
|
135
|
+
|
136
|
+
album_list = []
|
137
|
+
|
138
|
+
# build hash of tracks with album name as key
|
139
|
+
album_hash = Hash.new { |h,k| h[k] = [] }
|
140
|
+
tracks.each do |track|
|
141
|
+
album_hash[track.album] << track
|
142
|
+
end
|
143
|
+
|
144
|
+
# split the album tracks by track artist
|
145
|
+
album_hash.each do |album_name, album_tracks|
|
146
|
+
artist_hash = Hash.new { |h,k| h[k] = [] }
|
147
|
+
album_tracks.each do |album_track|
|
148
|
+
artist_hash[album_track.artist] << album_track
|
149
|
+
end
|
150
|
+
|
151
|
+
if album_tracks[0].compilation?
|
152
|
+
album_list << Album.new( tracks: album_tracks)
|
153
|
+
else
|
154
|
+
artist_hash.each do |artist, tracks|
|
155
|
+
album_list << Album.new( tracks: tracks)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
return album_list
|
162
|
+
end
|
163
|
+
|
164
|
+
def fetch_playlists( kind: :none )
|
165
|
+
playlist_list = []
|
166
|
+
reject_list = ["Music Videos", "Home Videos", "Books", "PDFs", "Audiobooks"]
|
167
|
+
playlists = app("iTunes").user_playlists[its.special_kind.eq(:none)].get
|
168
|
+
playlists.each do |playlist_obj|
|
169
|
+
playlist_list << Playlist.new( playlist_obj ) unless reject_list.include? (playlist_obj.name.get)
|
170
|
+
end
|
171
|
+
return playlist_list
|
172
|
+
end
|
173
|
+
|
174
|
+
def make_key( artist, album )
|
175
|
+
return "#{artist}__#{album}"
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
end
|
data/lib/rjl/metadata.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'allmusic' # https://github.com/richardjlyon/allmusic
|
2
|
+
require 'daybreak' # http://propublica.github.io/daybreak/
|
3
|
+
|
4
|
+
module RJL
|
5
|
+
# Gets metadata for an album from allmusic.com, caching the results. See
|
6
|
+
# [rjl-music](https://github.com/richardjlyon/allmusic) for details. Computes
|
7
|
+
# a simplified genre by selecting the highest frequency genre and style in the
|
8
|
+
# library and combining them in a string.
|
9
|
+
class Metadata
|
10
|
+
|
11
|
+
attr_reader :genres
|
12
|
+
attr_reader :styles
|
13
|
+
attr_reader :genre
|
14
|
+
|
15
|
+
# Create a new RJL::Metadata from the supplied Itunes object. This opens or
|
16
|
+
# creates a cache for storing metadata from allmusic to speed things up, and
|
17
|
+
# closes it on exit.
|
18
|
+
# @param [Itunes] itunes the itunes client to generate metadata for
|
19
|
+
def initialize( itunes )
|
20
|
+
@db = Daybreak::DB.new 'cache.db', :default => {}
|
21
|
+
@itunes = itunes
|
22
|
+
at_exit { @db.close }
|
23
|
+
end
|
24
|
+
|
25
|
+
# allmusic.com genres for album.
|
26
|
+
# @return [List of String] allmusic.com genres for album
|
27
|
+
def genres( album )
|
28
|
+
return get_metadata( album )[:genres]
|
29
|
+
end
|
30
|
+
|
31
|
+
# allmusic.com styles for album.
|
32
|
+
# @return [List of String] allmusic.com styles for album
|
33
|
+
def styles( album)
|
34
|
+
return get_metadata( album )[:styles]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Compute a genre for album. Reduce genres and styles and construct a
|
38
|
+
# string from them.
|
39
|
+
# @param [Album] album
|
40
|
+
# @return [String] computed genre for album
|
41
|
+
def genre( album )
|
42
|
+
metadata = compute_metadata( album )
|
43
|
+
@genre = build_genre_string( metadata )
|
44
|
+
return @genre
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Compute the frequencies of each genre and style in the iTunes database
|
50
|
+
def compute_frequencies
|
51
|
+
genre_freq = Hash.new(0)
|
52
|
+
style_freq = Hash.new(0)
|
53
|
+
|
54
|
+
@db.each do |album_key, metadata|
|
55
|
+
unless album_key == ""
|
56
|
+
metadata[:genres].each{ |key| genre_freq[key] += 1}
|
57
|
+
metadata[:styles].each{ |key| style_freq[key] += 1}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
return {genre_freq: genre_freq, style_freq: style_freq }
|
62
|
+
end
|
63
|
+
|
64
|
+
# Get genres and styles for the album from allmusic (or the cache, if it exists)
|
65
|
+
# @param [Album] the album
|
66
|
+
# @return [Hash] {genres: [List of String], styles: [List of String]}
|
67
|
+
def get_metadata( album )
|
68
|
+
genres = []
|
69
|
+
styles = []
|
70
|
+
album_key = make_key( album )
|
71
|
+
unless album_key.nil?
|
72
|
+
if @db[album_key].any?
|
73
|
+
genres = @db[album_key][:genres]
|
74
|
+
styles = @db[album_key][:styles]
|
75
|
+
else
|
76
|
+
allmusic = Allmusic.new album.album_artist, album.title
|
77
|
+
genres = allmusic.genres
|
78
|
+
styles = allmusic.styles
|
79
|
+
metadata = { :genres => genres, :styles => styles }
|
80
|
+
@db[album_key] = metadata
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
return {genres: genres, styles: styles}
|
85
|
+
end
|
86
|
+
|
87
|
+
# Reduce multiple genres and styles to a single genre and style.
|
88
|
+
# In this approach, select the genre and style with the highest frequency
|
89
|
+
# in the library.
|
90
|
+
def compute_metadata ( album )
|
91
|
+
genre_sort_hash = Hash.new
|
92
|
+
style_sort_hash = Hash.new
|
93
|
+
|
94
|
+
frequencies = compute_frequencies
|
95
|
+
self.genres( album ).each do |album_genre|
|
96
|
+
genre_sort_hash[album_genre] = frequencies[:genre_freq][album_genre]
|
97
|
+
end
|
98
|
+
genre_sort_list = genre_sort_hash.sort_by{ | genre, count | count }.reverse
|
99
|
+
genre, count = genre_sort_list[0]
|
100
|
+
|
101
|
+
self.styles( album ).each do |album_style|
|
102
|
+
style_sort_hash[album_style] = frequencies[:style_freq][album_style]
|
103
|
+
end
|
104
|
+
style_sort_list = style_sort_hash.sort_by{ | style, count | count }.reverse
|
105
|
+
style, count = style_sort_list[0]
|
106
|
+
|
107
|
+
return {genre: genre, style: style}
|
108
|
+
end
|
109
|
+
|
110
|
+
# Build the genre string e.g. "Jazz", "Guitar Jazz" => "Jazz [Guitar Jazz]"
|
111
|
+
def build_genre_string( metada )
|
112
|
+
genre = metada[:genre]
|
113
|
+
style = metada[:style]
|
114
|
+
computed_genre = case
|
115
|
+
when genre.nil? && style.nil?
|
116
|
+
""
|
117
|
+
when !genre.nil? && style.nil?
|
118
|
+
genre
|
119
|
+
when genre.nil? && !style.nil?
|
120
|
+
style
|
121
|
+
else
|
122
|
+
"#{genre} [#{style}]"
|
123
|
+
end
|
124
|
+
|
125
|
+
return computed_genre
|
126
|
+
end
|
127
|
+
|
128
|
+
# @example
|
129
|
+
# key = make_key( album ) # => "ABBA__Gold"
|
130
|
+
# @return [String] an album key
|
131
|
+
def make_key( album )
|
132
|
+
return "#{album.album_artist}__#{album.title}" unless album.nil?
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
data/lib/rjl/playlist.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# = Playlist
|
4
|
+
#
|
5
|
+
# Copyright 2016 Richard Lyon
|
6
|
+
# Distributed under the MIT license
|
7
|
+
#
|
8
|
+
module RJL
|
9
|
+
class Playlist
|
10
|
+
attr_reader :tracks
|
11
|
+
def initialize( playlist_obj )
|
12
|
+
@playlist_obj = playlist_obj
|
13
|
+
@folder = playlist_obj.special_kind.get
|
14
|
+
@tracks = get_tracks( playlist_obj )
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_tracks( playlist_obj )
|
18
|
+
tracks = []
|
19
|
+
track_obj_list = playlist_obj.tracks.get
|
20
|
+
track_obj_list.each do |track_obj|
|
21
|
+
tracks << RJL::Track.new( track_obj )
|
22
|
+
end
|
23
|
+
return tracks
|
24
|
+
end
|
25
|
+
|
26
|
+
def name
|
27
|
+
return @playlist_obj.name.get
|
28
|
+
end
|
29
|
+
def name=(str)
|
30
|
+
@playlist_obj.name.set(str)
|
31
|
+
end
|
32
|
+
|
33
|
+
def tracks
|
34
|
+
return @tracks
|
35
|
+
end
|
36
|
+
def tracks=(track_list)
|
37
|
+
track_list.each do |track|
|
38
|
+
app("iTunes").library_playlists[1].tracks[its.database_ID.eq(track.database_id)].duplicate(:to => app.playlists[self.name])
|
39
|
+
end
|
40
|
+
@tracks = track_list
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/rjl/track.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
module RJL
|
2
|
+
class Track
|
3
|
+
|
4
|
+
def initialize track_obj
|
5
|
+
@track_obj = track_obj
|
6
|
+
end
|
7
|
+
|
8
|
+
def database_id
|
9
|
+
return @track_obj.database_ID.get
|
10
|
+
end
|
11
|
+
|
12
|
+
def name
|
13
|
+
return @track_obj.name.get
|
14
|
+
end
|
15
|
+
def name=(str)
|
16
|
+
@track_obj.name.set(str)
|
17
|
+
end
|
18
|
+
|
19
|
+
def artist
|
20
|
+
return @track_obj.artist.get
|
21
|
+
end
|
22
|
+
def artist=(str)
|
23
|
+
@track_obj.artist.set(str)
|
24
|
+
end
|
25
|
+
|
26
|
+
def sort_artist
|
27
|
+
return @track_obj.sort_artist.get
|
28
|
+
end
|
29
|
+
def sort_artist=(str)
|
30
|
+
@track_obj.sort_artist.set(str)
|
31
|
+
end
|
32
|
+
|
33
|
+
def album
|
34
|
+
return @track_obj.album.get
|
35
|
+
end
|
36
|
+
def album=(str)
|
37
|
+
@track_obj.album.set(str)
|
38
|
+
end
|
39
|
+
|
40
|
+
def sort_album
|
41
|
+
return @track_obj.sort_album.get
|
42
|
+
end
|
43
|
+
def sort_album=(str)
|
44
|
+
@track_obj.sort_album.set(str)
|
45
|
+
end
|
46
|
+
|
47
|
+
def album_artist
|
48
|
+
return @track_obj.album_artist.get
|
49
|
+
end
|
50
|
+
def album_artist=(str)
|
51
|
+
@track_obj.album_artist.set(str)
|
52
|
+
end
|
53
|
+
|
54
|
+
def sort_album_artist
|
55
|
+
return @track_obj.sort_album_artist.get
|
56
|
+
end
|
57
|
+
def sort_album_artist=(str)
|
58
|
+
@track_obj.sort_album_artist.set(str)
|
59
|
+
end
|
60
|
+
|
61
|
+
def composer
|
62
|
+
return @track_obj.composer.get
|
63
|
+
end
|
64
|
+
def composer=(str)
|
65
|
+
@track_obj.composer.set(str)
|
66
|
+
end
|
67
|
+
|
68
|
+
def grouping
|
69
|
+
return @track_obj.grouping.get
|
70
|
+
end
|
71
|
+
def grouping=(str)
|
72
|
+
@track_obj.grouping.set(str)
|
73
|
+
end
|
74
|
+
|
75
|
+
def genre
|
76
|
+
return @track_obj.genre.get
|
77
|
+
end
|
78
|
+
def genre=(str)
|
79
|
+
unless self.class == :shared_track
|
80
|
+
@track_obj.genre.set(str)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def comment
|
85
|
+
return @track_obj.comment.get
|
86
|
+
end
|
87
|
+
def comment=(str)
|
88
|
+
@track_obj.comment.set(str)
|
89
|
+
end
|
90
|
+
|
91
|
+
def disc_number
|
92
|
+
return @track_obj.disc_number.get
|
93
|
+
end
|
94
|
+
def disc_number=(int)
|
95
|
+
@track_obj.disc_number.set(int)
|
96
|
+
end
|
97
|
+
|
98
|
+
def compilation?
|
99
|
+
return @track_obj.compilation.get
|
100
|
+
end
|
101
|
+
|
102
|
+
def class
|
103
|
+
return @track_obj.class_.get
|
104
|
+
end
|
105
|
+
|
106
|
+
# Tags use square bracket markup in the 'groupings' field of iTunes
|
107
|
+
# @return [List of String] tags
|
108
|
+
def tags
|
109
|
+
tags = []
|
110
|
+
begin
|
111
|
+
tags = self.grouping.gsub("][", ",")[1..-2].split(',')
|
112
|
+
rescue
|
113
|
+
end
|
114
|
+
return tags
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_s
|
118
|
+
puts "#{self.artist} - #{self.name}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
data/lib/rjl/utils.rb
ADDED
data/lib/rjl/version.rb
ADDED
data/lib/rjl.rb
ADDED
data/rjl-itunes.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require File.expand_path('../lib/rjl/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.author = 'Richard Lyon'
|
6
|
+
spec.email = 'richard@richardlyon.net'
|
7
|
+
spec.summary = %q{A ruby client for Apple's iTunes}
|
8
|
+
spec.description = %q{Manage album track genre in the library using data from allmusic.com}
|
9
|
+
spec.homepage = 'https://github.com/richardjlyon/rjl-itunes'
|
10
|
+
|
11
|
+
spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
spec.files = `git ls-files`.split("\n")
|
13
|
+
spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
spec.require_path = 'lib'
|
15
|
+
|
16
|
+
spec.name = 'rjl-itunes'
|
17
|
+
spec.version = RJL::VERSION
|
18
|
+
spec.license = 'MIT'
|
19
|
+
|
20
|
+
spec.add_development_dependency 'rake', '~> 10.4', '>= 10.4.2'
|
21
|
+
spec.add_runtime_dependency 'rjl-allmusic', '~> 0.5', '>= 0.5'
|
22
|
+
spec.add_runtime_dependency 'rb-scpt', '~> 1.0', '>= 1.0.1'
|
23
|
+
spec.add_runtime_dependency 'ruby-progressbar', '~> 1.7', '>= 1.7.5'
|
24
|
+
spec.add_runtime_dependency 'daybreak', '~> 0.3', '>= 0.3.0'
|
25
|
+
|
26
|
+
end
|
data/spec/album_spec.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RJL::Album do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
@itunes = RJL::Itunes.new
|
7
|
+
TEST = "TEST " + Time.now.strftime("%d/%m/%Y %H:%M")
|
8
|
+
end
|
9
|
+
|
10
|
+
describe ".albums" do
|
11
|
+
|
12
|
+
context "when no album or artist are specified " do
|
13
|
+
it "returns all the albums" do
|
14
|
+
expect(@itunes.albums.count).to eq(24)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context "when an album artist is specified" do
|
19
|
+
it "returns all albums by that artist" do
|
20
|
+
album_artist = "Altan"
|
21
|
+
albums = @itunes.albums(album_artist: album_artist)
|
22
|
+
expect(albums.count).to eq(2)
|
23
|
+
albums.each do |album|
|
24
|
+
expect(album.album_artist).to eq(album_artist)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when an album title is specified" do
|
30
|
+
it "returns all albums with that title" do
|
31
|
+
album_title = "Greatest Hits"
|
32
|
+
albums = @itunes.albums(title: album_title)
|
33
|
+
expect(albums.count).to eq(2)
|
34
|
+
albums.each do |album|
|
35
|
+
expect(album.title).to eq(album_title)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when an album title and artist is specified" do
|
41
|
+
it "returns that album " do
|
42
|
+
album_artist = "Simply Red"
|
43
|
+
album_title = "Greatest Hits"
|
44
|
+
album = @itunes.albums(album_artist: album_artist, title: album_title )
|
45
|
+
expect(album.class).to eq RJL::Album
|
46
|
+
expect(album.title).to eq(album_title)
|
47
|
+
expect(album.album_artist).to eq(album_artist)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when a non-existent title and artist is specified" do
|
52
|
+
it "returns nil" do
|
53
|
+
album_artist = "sdfsadasd"
|
54
|
+
album_title = "asasasdfasdf"
|
55
|
+
album = @itunes.albums(album_artist: album_artist, title: album_title )
|
56
|
+
expect(album).to be_nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when track artists in an album are all the same" do
|
61
|
+
it "returns as album artist the track artists" do
|
62
|
+
album_artist = "Simply Red"
|
63
|
+
album_title = "Greatest Hits"
|
64
|
+
album = @itunes.albums(album_artist: album_artist, title: album_title )
|
65
|
+
expect(album.album_artist).to eq(album_artist)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when track artists in an album are all not the same" do
|
70
|
+
it "returns as album artist 'Various Artists'" do
|
71
|
+
album_title = "The Firm"
|
72
|
+
album = @itunes.albums(title: album_title )[0]
|
73
|
+
expect(album.album_artist).to eq('Various Artists')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "when I change the genre of an album" do
|
78
|
+
it "changes all the genres of the album's tracks" do
|
79
|
+
album = @itunes.albums(album_artist: "Simply Red", title: "Greatest Hits" )
|
80
|
+
old_genre = album.genre
|
81
|
+
expect(old_genre).to eq("Pop/Rock [Alternative/Indie Rock]")
|
82
|
+
album.genre = TEST
|
83
|
+
expect(album.tracks[0].genre).to eq(TEST)
|
84
|
+
album.genre = old_genre
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context "when an album is protected" do
|
89
|
+
it "the genre can't be changed" do
|
90
|
+
album = @itunes.albums(album_artist: "Benny Green", title: "These Are Soulful Days" )
|
91
|
+
old_genre = album.genre
|
92
|
+
expect(old_genre).to eq("Jazz [Guitar Jazz]")
|
93
|
+
album.genre = TEST
|
94
|
+
expect(album.tracks[0].genre).to eq(old_genre)
|
95
|
+
album.genre = old_genre
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RJL::Metadata do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
@itunes = RJL::Itunes.new
|
7
|
+
@metadata = RJL::Metadata.new( @itunes )
|
8
|
+
end
|
9
|
+
|
10
|
+
describe ".genres" do
|
11
|
+
context "when given an album" do
|
12
|
+
it "gets the album's genres from allmusic" do
|
13
|
+
album = @itunes.albums(album_artist: "Simply Red", title: "Greatest Hits" )
|
14
|
+
genres = @metadata.genres( album )
|
15
|
+
expected_genres = ["Pop/Rock", "R&B"]
|
16
|
+
expect(genres).to eq(expected_genres)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe ".genre" do
|
22
|
+
context "when there is a valid album" do
|
23
|
+
it "gets a computed genre" do
|
24
|
+
album = @itunes.albums(album_artist: "Simply Red", title: "Greatest Hits" )
|
25
|
+
genre = @metadata.genre( album )
|
26
|
+
expect(genre).to eq("Pop/Rock [Alternative/Indie Rock]")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
context "when there is not a valid album" do
|
30
|
+
it "returns an empty string" do
|
31
|
+
album = @itunes.albums(album_artist: "sdafadsf", title: "sadfsadf" )
|
32
|
+
genre = @metadata.genre( album )
|
33
|
+
expect(genre).to eq("")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/spec/playlist.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Playlist do
|
4
|
+
before :each do
|
5
|
+
@itunes = Itunes.new
|
6
|
+
@test_playlist_name = "TEST " + Time.now.strftime("%d/%m/%Y %H:%M")
|
7
|
+
end
|
8
|
+
|
9
|
+
describe ".create_playlist" do
|
10
|
+
|
11
|
+
context "when no tracks are given" do
|
12
|
+
it "creates an empty playlist" do
|
13
|
+
@itunes.create_playlist( playlist_name: @test_playlist_name )
|
14
|
+
playlist = @itunes.playlist( @test_playlist_name )
|
15
|
+
expect(playlist.name).to eq( @test_playlist_name )
|
16
|
+
@itunes.destroy_playlist( @test_playlist_name )
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when tracks are given" do
|
21
|
+
it "creates a playlist with the tracks" do
|
22
|
+
some_tracks = @itunes.albums[2].tracks
|
23
|
+
@itunes.create_playlist( playlist_name: @test_playlist_name, track_list: some_tracks )
|
24
|
+
playlist = @itunes.playlist( @test_playlist_name )
|
25
|
+
expect(playlist.tracks.count).to eq(some_tracks.count)
|
26
|
+
@itunes.destroy_playlist( @test_playlist_name )
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe ".create_playlist_folder" do
|
32
|
+
|
33
|
+
context "when no playlists are given" do
|
34
|
+
it "creates an empty playlist folder" do
|
35
|
+
@itunes.create_playlist_folder( folder_name: @test_playlist_name )
|
36
|
+
# TODO check it exists
|
37
|
+
@itunes.destroy_playlist_folder( folder_name: @test_playlist_name )
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "when playlists are given" do
|
42
|
+
it "creates a playlist folder with the playlists" do
|
43
|
+
playlist = @itunes.create_playlist( playlist_name: @test_playlist_name )
|
44
|
+
@itunes.create_playlist_folder( folder_name: @test_playlist_name, playlists: [playlist] )
|
45
|
+
# TODO check it exists
|
46
|
+
@itunes.destroy_playlist_folder( folder_name: @test_playlist_name )
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/track_spec.rb
ADDED
metadata
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rjl-itunes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Richard Lyon
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '10.4'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 10.4.2
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '10.4'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 10.4.2
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rjl-allmusic
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0.5'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0.5'
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0.5'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0.5'
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: rb-scpt
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '1.0'
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 1.0.1
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.0'
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 1.0.1
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: ruby-progressbar
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - "~>"
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '1.7'
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.7.5
|
83
|
+
type: :runtime
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.7'
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 1.7.5
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: daybreak
|
95
|
+
requirement: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - "~>"
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0.3'
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 0.3.0
|
103
|
+
type: :runtime
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0.3'
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: 0.3.0
|
113
|
+
description: Manage album track genre in the library using data from allmusic.com
|
114
|
+
email: richard@richardlyon.net
|
115
|
+
executables:
|
116
|
+
- console
|
117
|
+
- fix_genres.rb
|
118
|
+
- setup
|
119
|
+
extensions: []
|
120
|
+
extra_rdoc_files: []
|
121
|
+
files:
|
122
|
+
- ".gitignore"
|
123
|
+
- ".rspec"
|
124
|
+
- ".yardopts"
|
125
|
+
- Changelog.md
|
126
|
+
- Gemfile
|
127
|
+
- LICENSE.txt
|
128
|
+
- README.md
|
129
|
+
- Rakefile
|
130
|
+
- bin/console
|
131
|
+
- bin/fix_genres.rb
|
132
|
+
- bin/setup
|
133
|
+
- lib/rjl.rb
|
134
|
+
- lib/rjl/album.rb
|
135
|
+
- lib/rjl/itunes.rb
|
136
|
+
- lib/rjl/metadata.rb
|
137
|
+
- lib/rjl/playlist.rb
|
138
|
+
- lib/rjl/track.rb
|
139
|
+
- lib/rjl/utils.rb
|
140
|
+
- lib/rjl/version.rb
|
141
|
+
- rjl-itunes.gemspec
|
142
|
+
- spec/album_spec.rb
|
143
|
+
- spec/metadata_spec.rb
|
144
|
+
- spec/playlist.rb
|
145
|
+
- spec/spec_helper.rb
|
146
|
+
- spec/track_spec.rb
|
147
|
+
homepage: https://github.com/richardjlyon/rjl-itunes
|
148
|
+
licenses:
|
149
|
+
- MIT
|
150
|
+
metadata: {}
|
151
|
+
post_install_message:
|
152
|
+
rdoc_options: []
|
153
|
+
require_paths:
|
154
|
+
- lib
|
155
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
161
|
+
requirements:
|
162
|
+
- - ">="
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: '0'
|
165
|
+
requirements: []
|
166
|
+
rubyforge_project:
|
167
|
+
rubygems_version: 2.5.1
|
168
|
+
signing_key:
|
169
|
+
specification_version: 4
|
170
|
+
summary: A ruby client for Apple's iTunes
|
171
|
+
test_files:
|
172
|
+
- spec/album_spec.rb
|
173
|
+
- spec/metadata_spec.rb
|
174
|
+
- spec/playlist.rb
|
175
|
+
- spec/spec_helper.rb
|
176
|
+
- spec/track_spec.rb
|
177
|
+
has_rdoc:
|