apple_music_library 0.3.0 → 0.6.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 +4 -4
- data/CHANGELOG.md +28 -0
- data/Gemfile.lock +1 -1
- data/README.md +14 -0
- data/apple_music_library.gemspec +2 -2
- data/lib/apple_music_library/artist.rb +12 -0
- data/lib/apple_music_library/library.rb +16 -84
- data/lib/apple_music_library/playlist.rb +27 -20
- data/lib/apple_music_library/playlist_folder.rb +55 -0
- data/lib/apple_music_library/track.rb +13 -0
- data/lib/apple_music_library/version.rb +1 -1
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 63c39fb0820781890a73912185de50cd207af719b59d25a022626374f1ab8eac
|
4
|
+
data.tar.gz: 99d363ef519977c4b2786684e539289a58cf76503e397c34c7060f42a02c52aa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8f45294602867c1de2a10ad3dbe1d88ad02508dc6060712c0cdb436e032c93ea5eb35f69eb0a9e9f1c5dae98089666d8928919f140b668d1c8980b11a7ec517
|
7
|
+
data.tar.gz: d0724a89d0d22c4be3a65550253284e176102e3c2c2f085ace60418516ba333ed7ebbdc25fda382ee18303d47bf29eeef256b533b231c733813f473c3c28a3a5
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## [Unreleased]
|
4
|
+
- Ability to print out playlist folder hierarchy
|
5
|
+
|
6
|
+
## 0.6.0 - 2022-03-11
|
7
|
+
### Added
|
8
|
+
- Ability to sort list top artists by play count
|
9
|
+
|
10
|
+
## 0.5.0 - 2022-03-11
|
11
|
+
### Added
|
12
|
+
- Basic functionality for track ratings via the #rating, #star_rating, and #loved
|
13
|
+
|
14
|
+
## 0.4.0 - 2022-03-11
|
15
|
+
### Added
|
16
|
+
- Basic functionality for Playlist Folders
|
17
|
+
### Fixed
|
18
|
+
- 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.
|
19
|
+
|
20
|
+
## 0.3.0 - 2022-03-10
|
21
|
+
|
22
|
+
### Fixed
|
23
|
+
- Fixed handling of albums, genres, and playlists as accessed via methods on artists/tracks.
|
24
|
+
|
25
|
+
## 0.2.0 - 2022-03-10
|
26
|
+
### Added
|
27
|
+
- Basic functionality to parse a library plist into Track, Album, Artist, Genre, and Playlist objects.
|
28
|
+
|
data/Gemfile.lock
CHANGED
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,14 @@ end
|
|
38
44
|
artist = library.artist('XTC')
|
39
45
|
puts artist.tracks.count
|
40
46
|
|
47
|
+
# Get favorite tracks by this artist
|
48
|
+
favorite_tracks = artist.tracks.select{|t| t.loved? || t.star_rating > 3}
|
49
|
+
|
50
|
+
# List out the most played artists
|
51
|
+
library.artists(:most_played).each do |artist|
|
52
|
+
puts "#{artist.play_count} :: #{artist.name}"
|
53
|
+
end
|
54
|
+
|
41
55
|
# Display track counts per genre
|
42
56
|
library.genres.each do |genre|
|
43
57
|
puts "#{genre.tracks.count} #{genre.name}"
|
data/apple_music_library.gemspec
CHANGED
@@ -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
|
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"
|
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
|
19
19
|
spec.metadata["homepage_uri"] = spec.homepage
|
20
20
|
spec.metadata["source_code_uri"] = "https://github.com/pugetive/apple_music_library"
|
21
|
-
|
21
|
+
spec.metadata["changelog_uri"] = "https://github.com/pugetive/apple_music_library/blob/main/CHANGELOG.md"
|
22
22
|
|
23
23
|
# Specify which files should be added to the gem when it is released.
|
24
24
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -15,6 +15,14 @@ module AppleMusicLibrary
|
|
15
15
|
@@artists.values
|
16
16
|
end
|
17
17
|
|
18
|
+
def self.most_played(limit = 20)
|
19
|
+
if limit.nil?
|
20
|
+
self.all.sort_by(&:play_count).reverse
|
21
|
+
else
|
22
|
+
self.all.sort_by(&:play_count).reverse.take(limit)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
18
26
|
def self.find_or_create(name)
|
19
27
|
if @@artists[name]
|
20
28
|
return @@artists[name]
|
@@ -49,5 +57,9 @@ module AppleMusicLibrary
|
|
49
57
|
@tracks
|
50
58
|
end
|
51
59
|
|
60
|
+
def play_count
|
61
|
+
tracks.map(&:play_count).sum
|
62
|
+
end
|
63
|
+
|
52
64
|
end
|
53
65
|
end
|
@@ -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'
|
@@ -43,8 +44,8 @@ module AppleMusicLibrary
|
|
43
44
|
@albums ||= Album.all
|
44
45
|
end
|
45
46
|
|
46
|
-
def artists
|
47
|
-
|
47
|
+
def artists(filter = nil, limit = nil)
|
48
|
+
filter.present? ? Artist.send(filter, limit) : Artist.all
|
48
49
|
end
|
49
50
|
|
50
51
|
def artist(artist_name)
|
@@ -67,8 +68,16 @@ module AppleMusicLibrary
|
|
67
68
|
Playlist.find_by_name(playlist_name)
|
68
69
|
end
|
69
70
|
|
70
|
-
def
|
71
|
-
@
|
71
|
+
def playlist_folders
|
72
|
+
@playlist_folders ||= PlaylistFolder.all
|
73
|
+
end
|
74
|
+
|
75
|
+
def playlist_folder(playlist_folder_name)
|
76
|
+
PlaylistFolder.find_by_name(playlist_folder_name)
|
77
|
+
end
|
78
|
+
|
79
|
+
def tracks(filter = nil)
|
80
|
+
filter.present? ? Track.send(filter) : Track.all
|
72
81
|
end
|
73
82
|
|
74
83
|
def track(track_id)
|
@@ -108,89 +117,12 @@ module AppleMusicLibrary
|
|
108
117
|
|
109
118
|
def extract_playlists_from_plist
|
110
119
|
plist['Playlists'].each do |playlist_info|
|
111
|
-
|
120
|
+
# The distinction between playlists and playlist folders
|
121
|
+
# is made inside the Playlist instantiation
|
122
|
+
Playlist.new(playlist_info)
|
112
123
|
end
|
113
124
|
end
|
114
125
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
# def track(track_id)
|
120
|
-
# track = @tracks_hash[track_id.to_i]
|
121
|
-
# if track.nil?
|
122
|
-
# message("Cound not find track with ID [#{track_id}]")
|
123
|
-
# message("Searched tracks: [#{@tracks_hash.keys.sort.join(', ')}]")
|
124
|
-
# end
|
125
|
-
# track
|
126
|
-
# end
|
127
|
-
|
128
|
-
# private
|
129
|
-
|
130
|
-
# def extract_plist_info
|
131
|
-
# non_music_formats = {}
|
132
|
-
# plist['Tracks'].each do |track|
|
133
|
-
# if MUSIC_FORMATS.include?(track[1]['Kind'])
|
134
|
-
# track = Track.new(track[1])
|
135
|
-
# # next if track.artist.name == 'NPR'
|
136
|
-
# @tracks_hash[track.id] = track
|
137
|
-
# # @locations_hash[track.location] = track
|
138
|
-
|
139
|
-
# artist = Artist.find_or_create(track.artist_name)
|
140
|
-
# album = add_track_to_album(track, track.album)
|
141
|
-
# artist.add_album(album)
|
142
|
-
|
143
|
-
# if track.genre_name.present?
|
144
|
-
# genre = add_track_to_genre(track)
|
145
|
-
# end
|
146
|
-
# else
|
147
|
-
# non_music_formats[track[1]['Kind']] = true
|
148
|
-
# end
|
149
|
-
# end
|
150
|
-
|
151
|
-
# plist['Playlists'].each do |playlist_info|
|
152
|
-
# playlist = Playlist.new(playlist_info, self)
|
153
|
-
# next if playlist.folder?
|
154
|
-
# @playlists_hash[playlist.id] = playlist
|
155
|
-
# end
|
156
|
-
|
157
|
-
# if non_music_formats.any?
|
158
|
-
# message("Skipped the following non-music formats:\n#{non_music_formats.keys.join(', ')}")
|
159
|
-
# end
|
160
|
-
# end
|
161
|
-
|
162
|
-
|
163
|
-
# def add_artist(artist)
|
164
|
-
# Artist.find_or_create(artist_name)
|
165
|
-
# end
|
166
|
-
|
167
|
-
# def add_track_to_album(track, album)
|
168
|
-
# unless album = @albums_hash[album.id]
|
169
|
-
# @albums_hash[album.id] = album
|
170
|
-
# end
|
171
|
-
# album.add_track(track)
|
172
|
-
# album
|
173
|
-
# end
|
174
|
-
|
175
|
-
# def find_or_create_album(album)
|
176
|
-
# unless @albums_hash[album.id]
|
177
|
-
# @albums_hash[album.id] = album
|
178
|
-
# end
|
179
|
-
# album.artist.add_album(album)
|
180
|
-
# end
|
181
|
-
|
182
|
-
|
183
|
-
# def add_track_to_genre(track)
|
184
|
-
# if @genres_hash[track.genre_name]
|
185
|
-
# return @genres_hash[track.genre_name]
|
186
|
-
# end
|
187
|
-
# # message("Creating new artist #{artist.name}")
|
188
|
-
# genre = AppleMusicLibrary::Genre.new(track.genre_name)
|
189
|
-
# @genres_hash[track.genre_name] = genre
|
190
|
-
# genre.add_track(track)
|
191
|
-
# genre
|
192
|
-
# end
|
193
|
-
|
194
126
|
end
|
195
127
|
|
196
128
|
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
|
@@ -25,18 +11,20 @@ module AppleMusicLibrary
|
|
25
11
|
'Description',
|
26
12
|
'Playlist ID',
|
27
13
|
'Playlist Persistent ID',
|
14
|
+
'Parent Persistent ID',
|
28
15
|
'All Items',
|
29
16
|
'Smart Info',
|
30
17
|
'Smart Criteria',
|
31
18
|
'Playlist Items',
|
32
19
|
'Folder']
|
33
20
|
|
34
|
-
def initialize(info
|
21
|
+
def initialize(info)
|
35
22
|
@info = info
|
36
23
|
|
37
|
-
|
24
|
+
if folder?
|
25
|
+
return PlaylistFolder.new(info)
|
26
|
+
end
|
38
27
|
|
39
|
-
@library = library
|
40
28
|
@tracks = []
|
41
29
|
|
42
30
|
load_tracks
|
@@ -49,11 +37,15 @@ module AppleMusicLibrary
|
|
49
37
|
end
|
50
38
|
|
51
39
|
def self.find_by_name(playlist_name)
|
52
|
-
@@playlists.values.select{|p| p.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
|
53
45
|
end
|
54
46
|
|
55
47
|
def id
|
56
|
-
|
48
|
+
playlist_persistent_id
|
57
49
|
end
|
58
50
|
|
59
51
|
ATTRIBUTES.each do |attribute|
|
@@ -72,7 +64,7 @@ module AppleMusicLibrary
|
|
72
64
|
if playlist_items and playlist_items.any?
|
73
65
|
playlist_items.each do |playlist_item|
|
74
66
|
track_id = playlist_item['Track ID']
|
75
|
-
track =
|
67
|
+
track = Track.find(track_id)
|
76
68
|
@tracks << track
|
77
69
|
end
|
78
70
|
end
|
@@ -81,3 +73,18 @@ module AppleMusicLibrary
|
|
81
73
|
|
82
74
|
end
|
83
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
|
@@ -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',
|
@@ -55,6 +56,10 @@ module AppleMusicLibrary
|
|
55
56
|
@@tracks.values
|
56
57
|
end
|
57
58
|
|
59
|
+
def self.loved
|
60
|
+
@loved ||= self.all.select{|t| t.loved?}
|
61
|
+
end
|
62
|
+
|
58
63
|
def id
|
59
64
|
track_id
|
60
65
|
end
|
@@ -71,6 +76,14 @@ module AppleMusicLibrary
|
|
71
76
|
@info['Genre']
|
72
77
|
end
|
73
78
|
|
79
|
+
def star_rating
|
80
|
+
rating / 20
|
81
|
+
end
|
82
|
+
|
83
|
+
def loved?
|
84
|
+
loved
|
85
|
+
end
|
86
|
+
|
74
87
|
ATTRIBUTES.each do |track_attribute|
|
75
88
|
define_method(track_attribute.to_s.snakecase) do
|
76
89
|
@info[track_attribute]
|
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.
|
4
|
+
version: 0.6.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-
|
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
|
@@ -138,6 +140,7 @@ licenses:
|
|
138
140
|
metadata:
|
139
141
|
homepage_uri: https://github.com/pugetive/apple_music_library
|
140
142
|
source_code_uri: https://github.com/pugetive/apple_music_library
|
143
|
+
changelog_uri: https://github.com/pugetive/apple_music_library/blob/main/CHANGELOG.md
|
141
144
|
post_install_message:
|
142
145
|
rdoc_options: []
|
143
146
|
require_paths:
|
@@ -156,6 +159,6 @@ requirements: []
|
|
156
159
|
rubygems_version: 3.3.3
|
157
160
|
signing_key:
|
158
161
|
specification_version: 4
|
159
|
-
summary: Provides a
|
160
|
-
|
162
|
+
summary: Provides a Ruby interface to the information stored in an Apple Music (formerly
|
163
|
+
iTunes) Library xml file.
|
161
164
|
test_files: []
|