rjl-itunes 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,17 @@
1
+ .DS_Store
2
+ *.out
3
+ *.db
4
+ *.log
5
+ /sandpit/
6
+
7
+ /.bundle/
8
+ /.yardoc
9
+ /Gemfile.lock
10
+ /_yardoc/
11
+ /coverage/
12
+ /doc/
13
+ /pkg/
14
+ /spec/reports/
15
+ /tmp/
16
+ rjl-itunes-0.1.gem
17
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ - LICENSE.txt
data/Changelog.md ADDED
@@ -0,0 +1,13 @@
1
+ ## Change log
2
+
3
+ Below is a complete listing of changes for each revision of Rjl_itunes
4
+
5
+ ### 0.1
6
+
7
+ #### CHANGES SUMMARY
8
+
9
+ *
10
+
11
+ #### NOTES
12
+
13
+ *
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in wanker.gemspec
4
+ gemspec
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
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
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
@@ -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
@@ -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
@@ -0,0 +1,8 @@
1
+ # return true if tracks have only one value for 'parameter_name'
2
+ def unique?( tracks, parameter_name )
3
+ values = []
4
+ tracks.each do |track|
5
+ values << track.send( parameter_name )
6
+ end
7
+ return values.uniq.length == 1
8
+ end
@@ -0,0 +1,4 @@
1
+ module RJL
2
+ # Version string
3
+ VERSION = '0.1.1'
4
+ end
data/lib/rjl.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'rjl/itunes'
2
+ require 'rjl/album'
3
+ require 'rjl/track'
4
+ require 'rjl/playlist'
5
+ require 'rjl/utils'
6
+ require 'rjl/metadata'
7
+ require 'rjl/version'
@@ -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
@@ -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
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'rjl'
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe RJL::Track do
4
+ before :each do
5
+ @track = RJL::Itunes.new( "TEST" ).albums[0].tracks[0]
6
+ end
7
+
8
+ describe ".name" do
9
+ it "returns the correct name" do
10
+ expect(@track.name).to eql("Virgo")
11
+ end
12
+ end
13
+
14
+ end
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: