apple_music_library 0.2.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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: []