apple_music_library 0.2.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d1148dd5f546044638ace3b635c61799d36f427cdf380e19d146be4ab55a0128
4
- data.tar.gz: ceeafe5ace4e3ac2d76aa34bb2695592584799755b9318ce99b055bdfc971f83
3
+ metadata.gz: 28f3fe7d3df7d724f7e7290f9eb6c33e2dae08d22e60ed07dfeb87c9bda9c283
4
+ data.tar.gz: 302301b2f8dd1826f71d18474fa15dfa804babf481b46ab4db99694364e0342f
5
5
  SHA512:
6
- metadata.gz: 289f57dc10c8ec89d0ddf9506fbea8083ab8c6d07d0bfc1b060d481434e47e375e73bb8e6b5518570babd5add4f24bb3817a2bf6dce5108f173edbe43ed5b506
7
- data.tar.gz: 0276d14ab7d07c5b2aaf1c56c95494ddb81680a2d6811ce3c5a0254e3f93a18fbbf8c8d85223de7b6ea0ec110a00748cc9727d936c46c98c5fd87ab4f0d98aa6
6
+ metadata.gz: 5bcb31d7d2326fe4d6f614a6a29a6c69b4e47e349a235b47234afc97f10618630eb4ae995bfc2c402fe2629d080e6834067cc2b6606ba22132c1662af9d19c1e
7
+ data.tar.gz: d1785f58aab9a2a75d1c77ce165f37307141af7dc25833d12f669dbc0c201a9633384187c9d71b36e3a5e7a8c1f7a8696d8e14197b826f5538d5b49e3310cd82
data/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+ - Ability to print out playlist folder hierarchy
5
+
6
+ ## 0.5.0 - 2022-03-11
7
+ ### Added
8
+ - Basic functionality for track ratings via the #rating, #star_rating, and #loved
9
+
10
+ ## 0.4.0 - 2022-03-11
11
+ ### Added
12
+ - Basic functionality for Playlist Folders
13
+ ### Fixed
14
+ - The #find_by_name methods for Playlists and PlaylistFolders now return a single object when only a single exact match is found (the most common case) but continue to return an array of objects when there are multiple matches.
15
+
16
+ ## 0.3.0 - 2022-03-10
17
+
18
+ ### Fixed
19
+ - Fixed handling of albums, genres, and playlists as accessed via methods on artists/tracks.
20
+
21
+ ## 0.2.0 - 2022-03-10
22
+ ### Added
23
+ - Basic functionality to parse a library plist into Track, Album, Artist, Genre, and Playlist objects.
24
+
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- apple_music_library (0.2.0)
4
+ apple_music_library (0.5.0)
5
5
  facets (~> 3.1)
6
6
  plist (~> 3.6)
7
7
 
data/README.md CHANGED
@@ -28,6 +28,12 @@ library = AppleMusicLibrary.new('path/to/Library.xml')
28
28
  # Count albums
29
29
  puts library.albums.count
30
30
 
31
+ # Show playlist inside playlist folder
32
+ playlist_folder = library.playlist_folder('Folder for Testing')
33
+ playlist_folder.playlists.each do |playlist|
34
+ puts playlist.name
35
+ end
36
+
31
37
  # Show tracks in a specific playlist
32
38
  playlist = library.playlist('XTC Favorites')
33
39
  playlist.tracks.each do |track|
@@ -38,6 +44,9 @@ end
38
44
  artist = library.artist('XTC')
39
45
  puts artist.tracks.count
40
46
 
47
+ # Get favorite tracks by this artists
48
+ favorite_tracks = artist.tracks.select{|t| t.loved? || t.star_rating > 3}
49
+
41
50
  # Display track counts per genre
42
51
  library.genres.each do |genre|
43
52
  puts "#{genre.tracks.count} #{genre.name}"
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["Todd Gehman"]
9
9
  spec.email = ["github@pugetive.com"]
10
10
 
11
- spec.summary = "Provides a ruby OO interface to the information stored in an Apple Music (formerly iTunes) Library xml file."
11
+ spec.summary = "Provides a Ruby interface to the information stored in an Apple Music (formerly iTunes) Library xml file."
12
12
  # spec.description = "TODO: Write a longer description or delete this line."
13
13
  spec.homepage = "https://github.com/pugetive/apple_music_library"
14
14
  spec.license = "MIT"
@@ -5,12 +5,26 @@ module AppleMusicLibrary
5
5
 
6
6
  attr_reader :artist, :album_name, :tracks
7
7
 
8
+ @@albums = {}
9
+
8
10
  def initialize(artist, album_name)
9
11
  @artist = artist
10
12
  @album_name = album_name
11
13
  @tracks = []
12
14
  end
13
15
 
16
+ def self.all
17
+ @@albums.values
18
+ end
19
+
20
+ def self.find_or_create(artist, album_name)
21
+ key = "#{artist.name}-#{album_name}"
22
+ if @@albums[key]
23
+ return @@albums[key]
24
+ end
25
+ @@albums[key] = self.new(artist, album_name)
26
+ end
27
+
14
28
  def id
15
29
  "#{artist.name}-#{album_name}"
16
30
  end
@@ -20,7 +34,7 @@ module AppleMusicLibrary
20
34
  end
21
35
 
22
36
  def year
23
- @year ||= tracks.sort(&:year).last.year
37
+ @year ||= @tracks.sort_by(&:year).last.year
24
38
  end
25
39
 
26
40
  end
@@ -3,24 +3,50 @@ module AppleMusicLibrary
3
3
 
4
4
  attr_reader :name
5
5
 
6
+ @@artists = {}
7
+
6
8
  def initialize(name)
7
9
  @name = name
8
10
  @albums = []
11
+ @tracks = []
12
+ end
13
+
14
+ def self.all
15
+ @@artists.values
16
+ end
17
+
18
+ def self.find_or_create(name)
19
+ if @@artists[name]
20
+ return @@artists[name]
21
+ end
22
+ @@artists[name] = self.new(name)
23
+ end
24
+
25
+ def self.find_by_name(artist_name)
26
+ if @@artists[artist_name]
27
+ return @@artists[artist_name]
28
+ end
29
+ nil
9
30
  end
10
31
 
11
32
  def add_album(album)
12
- unless albums.include?(album)
33
+ unless @albums.include?(album)
13
34
  @albums << album
14
35
  end
15
36
  end
16
37
 
38
+ def add_track(track)
39
+ unless @tracks.include?(track)
40
+ @tracks << track
41
+ end
42
+ end
17
43
 
18
44
  def albums
19
- @albums.sort(&:year)
45
+ @albums.sort_by(&:year)
20
46
  end
21
47
 
22
48
  def tracks
23
- @tracks ||= @albums.map(&:tracks).flatten
49
+ @tracks
24
50
  end
25
51
 
26
52
  end
@@ -3,11 +3,28 @@ module AppleMusicLibrary
3
3
 
4
4
  attr_reader :name, :tracks
5
5
 
6
+ @@genres = {}
7
+
6
8
  def initialize(name)
7
9
  @name = name
8
10
  @tracks = []
9
11
  end
10
12
 
13
+ def self.all
14
+ @@genres.values
15
+ end
16
+
17
+ def self.find_or_create(name)
18
+ if @@genres[name]
19
+ return @@genres[name]
20
+ end
21
+ @@genres[name] = self.new(name)
22
+ end
23
+
24
+ def self.find_by_name(name)
25
+ @@genres[name]
26
+ end
27
+
11
28
  def add_track(track)
12
29
  unless tracks.include?(track)
13
30
  @tracks << track
@@ -2,6 +2,7 @@ require_relative 'album'
2
2
  require_relative 'artist'
3
3
  require_relative 'genre'
4
4
  require_relative 'playlist'
5
+ require_relative 'playlist_folder'
5
6
  require_relative 'track'
6
7
  require_relative "utils"
7
8
  require 'plist'
@@ -36,53 +37,51 @@ module AppleMusicLibrary
36
37
  raise Error, "Failed to parse library xml file [#{e.message}]"
37
38
  end
38
39
 
39
- @artists_hash = {}
40
- @tracks_hash = {}
41
- @playlists_hash = {}
42
- @albums_hash = {}
43
- @genres_hash = {}
44
-
45
- @locations_hash = {}
46
- @slugs_hash = {}
47
-
48
40
  extract_plist_info
49
41
  end
50
42
 
51
- def tracks
52
- @tracks_hash.values
43
+ def albums
44
+ @albums ||= Album.all
53
45
  end
54
46
 
55
47
  def artists
56
- @artists_hash.values
48
+ @artists ||= Artist.all
57
49
  end
58
50
 
59
51
  def artist(artist_name)
60
- artists.find{|a| a.name == artist_name}
52
+ Artist.find_by_name(artist_name)
61
53
  end
62
54
 
63
- def albums
64
- @albums_hash.values
55
+ def genres
56
+ @genres ||= Genre.all
57
+ end
58
+
59
+ def genre(genre_name)
60
+ Genre.find_by_name(genre_name)
65
61
  end
66
62
 
67
63
  def playlists
68
- @playlists_hash.values
64
+ @playlists ||= Playlist.all
69
65
  end
70
66
 
71
67
  def playlist(playlist_name)
72
- playlists.find{|p| p.name == playlist_name}
68
+ Playlist.find_by_name(playlist_name)
73
69
  end
74
70
 
71
+ def playlist_folders
72
+ @playlist_folders ||= PlaylistFolder.all
73
+ end
75
74
 
76
- def albums
77
- @albums_hash.values
75
+ def playlist_folder(playlist_folder_name)
76
+ PlaylistFolder.find_by_name(playlist_folder_name)
78
77
  end
79
78
 
80
- def genres
81
- @genres_hash.values.sort_by{|g| g.name}
79
+ def tracks(filter = nil)
80
+ filter.present? ? Track.send(filter) : Track.all
82
81
  end
83
82
 
84
- def genre(genre_name)
85
- genres.find{|g| g.name == genre_name}
83
+ def track(track_id)
84
+ Track.find(track_id)
86
85
  end
87
86
 
88
87
  def valid?
@@ -93,82 +92,35 @@ module AppleMusicLibrary
93
92
  tracks.any?
94
93
  end
95
94
 
96
- def track(track_id)
97
- track = @tracks_hash[track_id.to_i]
98
- if track.nil?
99
- message("Cound not find track with ID [#{track_id}]")
100
- message("Searched tracks: [#{@tracks_hash.keys.sort.join(', ')}]")
101
- end
102
- track
103
- end
104
-
105
95
  private
106
96
 
107
97
  def extract_plist_info
98
+ extract_tracks_from_plist
99
+ extract_playlists_from_plist
100
+ end
101
+
102
+ def extract_tracks_from_plist
108
103
  non_music_formats = {}
109
104
  plist['Tracks'].each do |track|
110
105
  if MUSIC_FORMATS.include?(track[1]['Kind'])
111
- track = Track.new(track[1])
112
- # next if track.artist.name == 'NPR'
113
- @tracks_hash[track.id] = track
114
- # @locations_hash[track.location] = track
115
-
116
- artist = add_artist(track.artist)
117
- album = add_track_to_album(track, track.album)
118
- artist.add_album(album)
119
-
120
- if track.genre_name.present?
121
- genre = add_track_to_genre(track)
122
- end
106
+ Track.new(track[1])
123
107
  else
124
108
  non_music_formats[track[1]['Kind']] = true
125
109
  end
126
110
  end
127
111
 
128
- plist['Playlists'].each do |playlist_info|
129
- playlist = Playlist.new(playlist_info, self)
130
- next if playlist.folder?
131
- @playlists_hash[playlist.id] = playlist
132
- end
133
-
134
112
  if non_music_formats.any?
135
113
  message("Skipped the following non-music formats:\n#{non_music_formats.keys.join(', ')}")
136
114
  end
137
- end
138
-
139
-
140
- def add_artist(artist)
141
- unless @artists_hash[artist.name]
142
- @artists_hash[artist.name] = artist
143
- end
144
- artist
145
- end
146
-
147
- def add_track_to_album(track, album)
148
- return if @albums_hash[album.id]
149
- # message("Creating new artist #{artist.name}")
150
- @albums_hash[album.id] = album
151
- album.add_track(track)
152
- album
153
- end
154
115
 
155
- def find_or_create_album(album)
156
- unless @albums_hash[album.id]
157
- @albums_hash[album.id] = album
158
- end
159
- album.artist.add_album(album)
160
116
  end
161
117
 
162
-
163
- def add_track_to_genre(track)
164
- if @genres_hash[track.genre_name]
165
- return @genres_hash[track.genre_name]
118
+ def extract_playlists_from_plist
119
+ plist['Playlists'].each do |playlist_info|
120
+ # The distinction between playlists and playlist folders
121
+ # is made inside the Playlist instantiation
122
+ Playlist.new(playlist_info)
166
123
  end
167
- # message("Creating new artist #{artist.name}")
168
- genre = AppleMusicLibrary::Genre.new(track.genre_name)
169
- @genres_hash[track.genre_name] = genre
170
- genre.add_track(track)
171
- genre
172
124
  end
173
125
 
174
126
  end
@@ -1,17 +1,3 @@
1
- # <key>Name</key><string>Non-XTC</string>
2
- # <key>Description</key><string></string>
3
- # <key>Playlist ID</key><integer>79359</integer>
4
- # <key>Playlist Persistent ID</key><string>59C95825DF1F0DCC</string>
5
- # <key>All Items</key><true/>
6
- # <key>Smart Info</key>
7
- # <data>
8
- # AQEAAwAAAAIAAAAZAAAAAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
9
- # AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
10
- # AAAAAA==
11
- # </data>
12
- # <key>Smart Criteria</key>
13
- # <data>
14
-
15
1
  require 'facets'
16
2
 
17
3
  module AppleMusicLibrary
@@ -19,26 +5,47 @@ module AppleMusicLibrary
19
5
 
20
6
  attr_reader :info, :tracks
21
7
 
8
+ @@playlists = {}
9
+
22
10
  ATTRIBUTES = ['Name',
23
11
  'Description',
24
12
  'Playlist ID',
25
13
  'Playlist Persistent ID',
14
+ 'Parent Persistent ID',
26
15
  'All Items',
27
16
  'Smart Info',
28
17
  'Smart Criteria',
29
18
  'Playlist Items',
30
19
  'Folder']
31
20
 
32
- def initialize(info, library)
21
+ def initialize(info)
33
22
  @info = info
34
- @library = library
23
+
24
+ if folder?
25
+ return PlaylistFolder.new(info)
26
+ end
27
+
35
28
  @tracks = []
36
29
 
37
30
  load_tracks
31
+
32
+ @@playlists[id] = self
33
+ end
34
+
35
+ def self.all
36
+ @@playlists.values
37
+ end
38
+
39
+ def self.find_by_name(playlist_name)
40
+ results = @@playlists.values.select{|p| p.name == playlist_name}
41
+ if results.size == 1
42
+ return results.first
43
+ end
44
+ results
38
45
  end
39
46
 
40
47
  def id
41
- playlist_id
48
+ playlist_persistent_id
42
49
  end
43
50
 
44
51
  ATTRIBUTES.each do |attribute|
@@ -57,7 +64,7 @@ module AppleMusicLibrary
57
64
  if playlist_items and playlist_items.any?
58
65
  playlist_items.each do |playlist_item|
59
66
  track_id = playlist_item['Track ID']
60
- track = @library.track(track_id)
67
+ track = Track.find(track_id)
61
68
  @tracks << track
62
69
  end
63
70
  end
@@ -66,3 +73,18 @@ module AppleMusicLibrary
66
73
 
67
74
  end
68
75
  end
76
+
77
+ # <key>Name</key><string>Non-XTC</string>
78
+ # <key>Description</key><string></string>
79
+ # <key>Playlist ID</key><integer>79359</integer>
80
+ # <key>Playlist Persistent ID</key><string>59C95825DF1F0DCC</string>
81
+ # <key>All Items</key><true/>
82
+ # <key>Smart Info</key>
83
+ # <data>
84
+ # AQEAAwAAAAIAAAAZAAAAAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
85
+ # AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
86
+ # AAAAAA==
87
+ # </data>
88
+ # <key>Smart Criteria</key>
89
+ # <data>
90
+
@@ -0,0 +1,55 @@
1
+ require 'facets'
2
+
3
+ module AppleMusicLibrary
4
+ class PlaylistFolder
5
+
6
+ attr_reader :info
7
+
8
+ @@playlist_folders = {}
9
+
10
+ def initialize(info)
11
+ @info = info
12
+
13
+ @@playlist_folders[id] = self
14
+ end
15
+
16
+ def self.all
17
+ @@playlist_folders.values
18
+ end
19
+
20
+ def self.find_by_name(playlist_folder_name)
21
+ results = @@playlist_folders.values.select{|pf| pf.name == playlist_folder_name}
22
+ if results.size == 1
23
+ return results.first
24
+ end
25
+ results
26
+ end
27
+
28
+ def id
29
+ playlist_persistent_id
30
+ end
31
+
32
+ def children
33
+ playlist_folders.concat(playlists)
34
+ end
35
+
36
+ def playlist_folders
37
+ @@playlist_folders.values.select{|pf| pf.parent_persistent_id == id}
38
+ end
39
+
40
+ def playlists
41
+ Playlist.all.select{|p| p.parent_persistent_id == id}
42
+ end
43
+
44
+ Playlist::ATTRIBUTES.each do |attribute|
45
+ define_method(attribute.to_s.snakecase) do
46
+ info[attribute]
47
+ end
48
+ end
49
+
50
+
51
+ private
52
+
53
+
54
+ end
55
+ end
@@ -2,13 +2,13 @@ require 'facets'
2
2
 
3
3
  module AppleMusicLibrary
4
4
  class Track
5
- attr_reader :info, :artist, :album
5
+ attr_reader :artist, :album, :genre
6
6
 
7
- ATTRIBUTES = ['Track ID',
8
- 'Name',
9
7
  # 'Artist', The #artist, #album, and #genre methods return objects rather than strings and have methods like #artist_name to retrieve the strings.
10
8
  # 'Album',
11
9
  # 'Genre',
10
+ ATTRIBUTES = ['Track ID',
11
+ 'Name',
12
12
  'Kind',
13
13
  'Size',
14
14
  'Total Time',
@@ -24,6 +24,7 @@ module AppleMusicLibrary
24
24
  'Rating',
25
25
  'Album Rating',
26
26
  'Album Rating Computed',
27
+ 'Loved',
27
28
  'Normalization',
28
29
  'Persistent ID',
29
30
  'Track Type',
@@ -31,10 +32,32 @@ module AppleMusicLibrary
31
32
  'File Folder Count',
32
33
  'Library Folder Count']
33
34
 
35
+ @@tracks = {}
36
+
34
37
  def initialize(info)
35
38
  @info = info
36
- @artist = Artist.new(artist_name)
37
- @album = Album.new(@artist, album_name)
39
+ @artist = Artist.find_or_create(artist_name)
40
+ @album = Album.find_or_create(@artist, album_name)
41
+ @genre = Genre.find_or_create(genre_name)
42
+
43
+ @artist.add_track(self)
44
+ @artist.add_album(@album)
45
+ @album.add_track(self)
46
+ @genre.add_track(self)
47
+ @@tracks[id] = self
48
+ end
49
+
50
+
51
+ def self.find(track_id)
52
+ @@tracks[track_id]
53
+ end
54
+
55
+ def self.all
56
+ @@tracks.values
57
+ end
58
+
59
+ def self.loved
60
+ @loved ||= self.all.select{|t| t.loved?}
38
61
  end
39
62
 
40
63
  def id
@@ -42,22 +65,29 @@ module AppleMusicLibrary
42
65
  end
43
66
 
44
67
  def artist_name
45
- info['Artist']
68
+ @info['Artist']
46
69
  end
47
70
 
48
71
  def album_name
49
- info['Album']
72
+ @info['Album']
50
73
  end
51
74
 
52
75
  def genre_name
53
- info['Genre']
76
+ @info['Genre']
54
77
  end
55
78
 
56
- ATTRIBUTES.each do |attribute|
57
- define_method(attribute.to_s.snakecase) do
58
- info[attribute]
59
- end
79
+ def star_rating
80
+ rating / 20
81
+ end
82
+
83
+ def loved?
84
+ loved
85
+ end
60
86
 
87
+ ATTRIBUTES.each do |track_attribute|
88
+ define_method(track_attribute.to_s.snakecase) do
89
+ @info[track_attribute]
90
+ end
61
91
  end
62
92
 
63
93
  # def slug
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AppleMusicLibrary
4
- VERSION = "0.2.0"
4
+ VERSION = "0.5.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apple_music_library
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Todd Gehman
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-10 00:00:00.000000000 Z
11
+ date: 2022-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: awesome_print
@@ -116,6 +116,7 @@ extensions: []
116
116
  extra_rdoc_files: []
117
117
  files:
118
118
  - ".rspec"
119
+ - CHANGELOG.md
119
120
  - Gemfile
120
121
  - Gemfile.lock
121
122
  - LICENSE.txt
@@ -128,6 +129,7 @@ files:
128
129
  - lib/apple_music_library/genre.rb
129
130
  - lib/apple_music_library/library.rb
130
131
  - lib/apple_music_library/playlist.rb
132
+ - lib/apple_music_library/playlist_folder.rb
131
133
  - lib/apple_music_library/track.rb
132
134
  - lib/apple_music_library/utils.rb
133
135
  - lib/apple_music_library/version.rb
@@ -156,6 +158,6 @@ requirements: []
156
158
  rubygems_version: 3.3.3
157
159
  signing_key:
158
160
  specification_version: 4
159
- summary: Provides a ruby OO interface to the information stored in an Apple Music
160
- (formerly iTunes) Library xml file.
161
+ summary: Provides a Ruby interface to the information stored in an Apple Music (formerly
162
+ iTunes) Library xml file.
161
163
  test_files: []