drum 0.1.12
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 +7 -0
- data/.editorconfig +10 -0
- data/.github/workflows/deploy.yml +33 -0
- data/.github/workflows/documentation.yml +29 -0
- data/.github/workflows/test.yml +19 -0
- data/.gitignore +13 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +134 -0
- data/LICENSE +21 -0
- data/README.md +114 -0
- data/Rakefile +13 -0
- data/artwork/icon.svg +186 -0
- data/artwork/icon128.png +0 -0
- data/bin/console +14 -0
- data/bin/drum +5 -0
- data/bin/drum.bat +2 -0
- data/bin/setup +8 -0
- data/drum.gemspec +36 -0
- data/lib/drum/model/album.rb +114 -0
- data/lib/drum/model/artist.rb +72 -0
- data/lib/drum/model/playlist.rb +222 -0
- data/lib/drum/model/raw_ref.rb +29 -0
- data/lib/drum/model/ref.rb +19 -0
- data/lib/drum/model/track.rb +157 -0
- data/lib/drum/model/user.rb +72 -0
- data/lib/drum/service/applemusic.rb +619 -0
- data/lib/drum/service/file.rb +89 -0
- data/lib/drum/service/mock.rb +50 -0
- data/lib/drum/service/service.rb +43 -0
- data/lib/drum/service/spotify.rb +615 -0
- data/lib/drum/service/stdio.rb +51 -0
- data/lib/drum/utils/ext.rb +88 -0
- data/lib/drum/utils/log.rb +93 -0
- data/lib/drum/utils/persist.rb +50 -0
- data/lib/drum/version.rb +3 -0
- data/lib/drum.rb +250 -0
- data/userdoc/playlist-format.md +96 -0
- metadata +207 -0
@@ -0,0 +1,114 @@
|
|
1
|
+
module Drum
|
2
|
+
# A album, i.e. a composition of tracks by an artist.
|
3
|
+
#
|
4
|
+
# @!attribute id
|
5
|
+
# @return [String] The (internal) id of the album
|
6
|
+
# @!attribute name
|
7
|
+
# @return [String] The name of the album
|
8
|
+
# @!attribute artist_ids
|
9
|
+
# @return [Array<String>] The artist ids of the album
|
10
|
+
# @!attribute spotify
|
11
|
+
# @return [optional, AlbumSpotify] Spotify-specific metadata
|
12
|
+
# @!attribute applemusic
|
13
|
+
# @return [optional, AlbumAppleMusic] Apple Music-specific metadata
|
14
|
+
Album = Struct.new(
|
15
|
+
:id,
|
16
|
+
:name,
|
17
|
+
:artist_ids,
|
18
|
+
:spotify, :applemusic,
|
19
|
+
keyword_init: true
|
20
|
+
) do
|
21
|
+
def initialize(*)
|
22
|
+
super
|
23
|
+
self.artist_ids ||= []
|
24
|
+
end
|
25
|
+
|
26
|
+
# Parses an album from a nested Hash that uses string keys.
|
27
|
+
#
|
28
|
+
# @param [Hash<String, Object>] h The Hash to be parsed
|
29
|
+
# @return [Album] The parsed album
|
30
|
+
def self.deserialize(h)
|
31
|
+
Album.new(
|
32
|
+
id: h['id'],
|
33
|
+
name: h['name'],
|
34
|
+
artist_ids: h['artist_ids'],
|
35
|
+
spotify: h['spotify'].try { |s| AlbumSpotify.deserialize(s) },
|
36
|
+
applemusic: h['applemusic'].try { |s| AlbumAppleMusic.deserialize(s) }
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Serializes the album to a nested Hash that uses string keys.
|
41
|
+
#
|
42
|
+
# @return [Hash<String, Object>] The serialized representation
|
43
|
+
def serialize
|
44
|
+
{
|
45
|
+
'id' => self.id,
|
46
|
+
'name' => self.name,
|
47
|
+
'artist_ids' => self.artist_ids,
|
48
|
+
'spotify' => self.spotify&.serialize,
|
49
|
+
'applemusic' => self.applemusic&.serialize
|
50
|
+
}.compact
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Spotify-specific metadata about the album.
|
55
|
+
#
|
56
|
+
# @!attribute id
|
57
|
+
# @return [String] The id of the album on Spotify
|
58
|
+
# @!attribute image_url
|
59
|
+
# @return [String] The URL of the album cover art on Spotify
|
60
|
+
AlbumSpotify = Struct.new(
|
61
|
+
:id,
|
62
|
+
:image_url,
|
63
|
+
keyword_init: true
|
64
|
+
) do
|
65
|
+
# Parses spotify metadata from a Hash that uses string keys.
|
66
|
+
#
|
67
|
+
# @param [Hash<String, Object>] h The Hash to be parsed
|
68
|
+
# @return [AlbumSpotify] The parsed metadata
|
69
|
+
def self.deserialize(h)
|
70
|
+
AlbumSpotify.new(
|
71
|
+
id: h['id'],
|
72
|
+
image_url: h['image_url']
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Serializes the metadata to a Hash that uses string keys.
|
77
|
+
#
|
78
|
+
# @return [Hash<String, Object>] The serialized representation
|
79
|
+
def serialize
|
80
|
+
{
|
81
|
+
'id' => self.id,
|
82
|
+
'image_url' => self.image_url
|
83
|
+
}.compact
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Apple Music-specific metadata about the album.
|
88
|
+
#
|
89
|
+
# @!attribute image_url
|
90
|
+
# @return [optional, String] The cover image of the album
|
91
|
+
AlbumAppleMusic = Struct.new(
|
92
|
+
:image_url,
|
93
|
+
keyword_init: true
|
94
|
+
) do
|
95
|
+
# Parses Apple Music metadata from a Hash that uses string keys.
|
96
|
+
#
|
97
|
+
# @param [Hash<String, Object>] h The Hash to be parsed
|
98
|
+
# @return [AlbumAppleMusic] The parsed metadata
|
99
|
+
def self.deserialize(h)
|
100
|
+
AlbumAppleMusic.new(
|
101
|
+
image_url: h['image_url']
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Serializes the metadata to a Hash that uses string keys.
|
106
|
+
#
|
107
|
+
# @return [Hash<String, Object>] The serialized representation
|
108
|
+
def serialize
|
109
|
+
{
|
110
|
+
'image_url' => self.image_url
|
111
|
+
}.compact
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Drum
|
2
|
+
# An artist.
|
3
|
+
#
|
4
|
+
# @!attribute id
|
5
|
+
# @return [String] The (internal) id of the artist
|
6
|
+
# @!attribute name
|
7
|
+
# @return [optional, String] The displayed/formatted name of the artist
|
8
|
+
# @!attribute spotify
|
9
|
+
# @return [optional, ArtistSpotify] Spotify-specific metadata
|
10
|
+
Artist = Struct.new(
|
11
|
+
:id,
|
12
|
+
:name,
|
13
|
+
:spotify,
|
14
|
+
keyword_init: true
|
15
|
+
) do
|
16
|
+
# Parses an artist from a nested Hash that uses string keys.
|
17
|
+
#
|
18
|
+
# @param [Hash<String, Object>] h The Hash to be parsed
|
19
|
+
# @return [Artist] The parsed artist
|
20
|
+
def self.deserialize(h)
|
21
|
+
Artist.new(
|
22
|
+
id: h['id'],
|
23
|
+
name: h['name'],
|
24
|
+
spotify: h['spotify'].try { |s| ArtistSpotify.deserialize(s) }
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Serializes the artist to a nested Hash that uses string keys.
|
29
|
+
#
|
30
|
+
# @return [Hash<String, Object>] The serialized representation
|
31
|
+
def serialize
|
32
|
+
{
|
33
|
+
'id' => self.id,
|
34
|
+
'name' => self.name,
|
35
|
+
'spotify' => self.spotify&.serialize
|
36
|
+
}.compact
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Spotify-specific metadata about the artist.
|
41
|
+
#
|
42
|
+
# @!attribute id
|
43
|
+
# @return [String] The id of the artist on Spotify
|
44
|
+
# @!attribute image_url
|
45
|
+
# @return [optional, String] An image of the artist
|
46
|
+
ArtistSpotify = Struct.new(
|
47
|
+
:id,
|
48
|
+
:image_url,
|
49
|
+
keyword_init: true
|
50
|
+
) do
|
51
|
+
# Parses spotify metadata from a Hash that uses string keys.
|
52
|
+
#
|
53
|
+
# @param [Hash<String, Object>] h The Hash to be parsed
|
54
|
+
# @return [ArtistSpotify] The parsed metadata
|
55
|
+
def self.deserialize(h)
|
56
|
+
ArtistSpotify.new(
|
57
|
+
id: h['id'],
|
58
|
+
image_url: h['image_url']
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Serializes the metadata to a Hash that uses string keys.
|
63
|
+
#
|
64
|
+
# @return [Hash<String, Object>] The serialized representation
|
65
|
+
def serialize
|
66
|
+
{
|
67
|
+
'id' => self.id,
|
68
|
+
'image_url' => self.image_url
|
69
|
+
}.compact
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
require 'drum/utils/ext'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Drum
|
5
|
+
# TODO: Smart playlists!
|
6
|
+
# TODO: Add created_at/added_at or similar
|
7
|
+
# (the Apple Music API provides us with a 'dateAdded', perhaps Spotify has something similar?)
|
8
|
+
|
9
|
+
# A list of tracks with metadata.
|
10
|
+
#
|
11
|
+
# @!attribute id
|
12
|
+
# @return [String] An internal id for the playlist
|
13
|
+
# @!attribute name
|
14
|
+
# @return [String] The name of the playlist
|
15
|
+
# @!attribute description
|
16
|
+
# @return [optional, String] A description of the playlist
|
17
|
+
# @!attribute author_id
|
18
|
+
# @return [optional, String] The author id
|
19
|
+
# @!attribute path
|
20
|
+
# @return [optional, Array<String>] The path of parent 'folders' to this playlist.
|
21
|
+
# @!attribute users
|
22
|
+
# @return [optional, Hash<String, User>] A hash of ids to users used somewhere in the playlist
|
23
|
+
# @!attribute artists
|
24
|
+
# @return [optional, Hash<String, Artist>] A hash of ids to artists used somewhere in the playlist
|
25
|
+
# @!attribute albums
|
26
|
+
# @return [optional, Hash<String, Album>] A hash of ids to albums used somewhere in the playlist
|
27
|
+
# @!attribute tracks
|
28
|
+
# @return [optional, Array<Track>] The list of tracks of the playlist, order matters here
|
29
|
+
# @!attribute spotify
|
30
|
+
# @return [optional, PlaylistSpotify] Spotify-specific metadata
|
31
|
+
# @!attribute applemusic
|
32
|
+
# @return [optional, PlaylistAppleMusic] Apple Music-specific metadata
|
33
|
+
class Playlist < Struct.new(
|
34
|
+
:id, :name, :description,
|
35
|
+
:path,
|
36
|
+
:author_id,
|
37
|
+
:users, :artists, :albums, :tracks,
|
38
|
+
:spotify, :applemusic,
|
39
|
+
keyword_init: true
|
40
|
+
)
|
41
|
+
def initialize(*)
|
42
|
+
super
|
43
|
+
self.description ||= ''
|
44
|
+
self.path ||= []
|
45
|
+
self.users ||= {}
|
46
|
+
self.artists ||= {}
|
47
|
+
self.albums ||= {}
|
48
|
+
self.tracks ||= []
|
49
|
+
end
|
50
|
+
|
51
|
+
# TODO: Handle merging in the store_x methods?
|
52
|
+
|
53
|
+
# Stores a user if it does not exist already.
|
54
|
+
#
|
55
|
+
# @param [User] user The user to store.
|
56
|
+
def store_user(user)
|
57
|
+
unless self.users.key?(user.id)
|
58
|
+
self.users[user.id] = user
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Stores an artist if it does not exist already.
|
63
|
+
#
|
64
|
+
# @param [Artist] artist The artist to store.
|
65
|
+
def store_artist(artist)
|
66
|
+
unless self.artists.key?(artist.id)
|
67
|
+
self.artists[artist.id] = artist
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Stores an album if it does not exist already.
|
72
|
+
#
|
73
|
+
# @param [Album] album The album to store.
|
74
|
+
def store_album(album)
|
75
|
+
unless self.albums.key?(album.id)
|
76
|
+
self.albums[album.id] = album
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Stores a track.
|
81
|
+
#
|
82
|
+
# @param [Track] track The track to store.
|
83
|
+
def store_track(track)
|
84
|
+
self.tracks << track
|
85
|
+
end
|
86
|
+
|
87
|
+
# Describes a track in a way useful for song matching by search.
|
88
|
+
#
|
89
|
+
# @return [String] A short description of track name and artists
|
90
|
+
def track_search_phrase(track)
|
91
|
+
"#{track.name} #{track.artist_ids.filter_map { |i| self.artists[i]&.name }.join(' ')}"
|
92
|
+
end
|
93
|
+
|
94
|
+
# Parses a playlist from a nested Hash that uses string keys.
|
95
|
+
#
|
96
|
+
# @param [Hash<String, Object>] h The Hash to be parsed
|
97
|
+
# @return [Playlist] The parsed playlist
|
98
|
+
def self.deserialize(h)
|
99
|
+
Playlist.new(
|
100
|
+
id: h['id'],
|
101
|
+
name: h['name'],
|
102
|
+
description: h['description'],
|
103
|
+
author_id: h['author_id'],
|
104
|
+
path: h['path'],
|
105
|
+
users: h['users']&.map { |u| User.deserialize(u) }&.to_h_by_id,
|
106
|
+
artists: h['artists']&.map { |a| Artist.deserialize(a) }&.to_h_by_id,
|
107
|
+
albums: h['albums']&.map { |a| Album.deserialize(a) }&.to_h_by_id,
|
108
|
+
tracks: h['tracks']&.map { |t| Track.deserialize(t) },
|
109
|
+
spotify: h['spotify'].try { |s| PlaylistSpotify.deserialize(s) },
|
110
|
+
applemusic: h['applemusic'].try { |a| PlaylistAppleMusic.deserialize(a) }
|
111
|
+
)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Serializes the playlist to a nested Hash that uses string keys.
|
115
|
+
#
|
116
|
+
# @return [Hash<String, Object>] The serialized representation
|
117
|
+
def serialize
|
118
|
+
{
|
119
|
+
'id' => self.id,
|
120
|
+
'name' => self.name,
|
121
|
+
'description' => (self.description unless self.description.empty?),
|
122
|
+
'author_id' => self.author_id,
|
123
|
+
'path' => (self.path unless self.path.empty?),
|
124
|
+
'users' => (self.users.each_value.map { |u| u.serialize } unless self.users.empty?),
|
125
|
+
'artists' => (self.artists.each_value.map { |a| a.serialize } unless self.artists.empty?),
|
126
|
+
'albums' => (self.albums.each_value.map { |a| a.serialize } unless self.albums.empty?),
|
127
|
+
'tracks' => (self.tracks.map { |t| t.serialize } unless self.tracks.empty?),
|
128
|
+
'spotify' => self.spotify&.serialize,
|
129
|
+
'applemusic' => self.applemusic&.serialize
|
130
|
+
}.compact
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Spotify-specific metadata about the playlist.
|
135
|
+
#
|
136
|
+
# @!attribute id
|
137
|
+
# @return [String] The id of the playlist on Spotify
|
138
|
+
# @!attribute public
|
139
|
+
# @return [optional, Boolean] Whether the playlist is public on Spotify
|
140
|
+
# @!attribute collaborative
|
141
|
+
# @return [optional, Boolean] Whether the playlist is collaborative on Spotify
|
142
|
+
# @!attribute image_url
|
143
|
+
# @return [optional, String] The playlist cover URL
|
144
|
+
PlaylistSpotify = Struct.new(
|
145
|
+
:id,
|
146
|
+
:public, :collaborative,
|
147
|
+
:image_url,
|
148
|
+
keyword_init: true
|
149
|
+
) do
|
150
|
+
# Parses spotify metadata from a Hash that uses string keys.
|
151
|
+
#
|
152
|
+
# @param [Hash<String, Object>] h The Hash to be parsed
|
153
|
+
# @return [PlaylistSpotify] The parsed metadata
|
154
|
+
def self.deserialize(h)
|
155
|
+
PlaylistSpotify.new(
|
156
|
+
id: h['id'],
|
157
|
+
public: h['public'],
|
158
|
+
collaborative: h['collaborative'],
|
159
|
+
image_url: h['image_url']
|
160
|
+
)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Serializes the metadata to a Hash that uses string keys.
|
164
|
+
#
|
165
|
+
# @return [Hash<String, Object>] The serialized representation
|
166
|
+
def serialize
|
167
|
+
{
|
168
|
+
'id' => self.id,
|
169
|
+
'public' => self.public,
|
170
|
+
'collaborative' => self.collaborative,
|
171
|
+
'image_url' => self.image_url
|
172
|
+
}.compact
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# TODO: Add image URL to Apple Music metadata?
|
177
|
+
|
178
|
+
# Apple Music-specific metadata about the playlist.
|
179
|
+
#
|
180
|
+
# @!attribute library_id
|
181
|
+
# @return [optional, String] The library-internal id of the playlist
|
182
|
+
# @!attribute global_id
|
183
|
+
# @return [optional, String] The global id of the playlist (implies that it is available through the catalog API)
|
184
|
+
# @!attribute public
|
185
|
+
# @return [optional, Boolean] Whether the playlist is public
|
186
|
+
# @!attribute editable
|
187
|
+
# @return [optional, Boolean] Whether the playlist is editable
|
188
|
+
# @!attribute image_url
|
189
|
+
# @return [optional, String] The playlist cover image, if present
|
190
|
+
PlaylistAppleMusic = Struct.new(
|
191
|
+
:library_id, :global_id,
|
192
|
+
:public, :editable, :image_url,
|
193
|
+
keyword_init: true
|
194
|
+
) do
|
195
|
+
# Parses Apple Music metadata from a Hash that uses string keys.
|
196
|
+
#
|
197
|
+
# @param [Hash<String, Object>] h The Hash to be parsed
|
198
|
+
# @return [PlaylistAppleMusic] The parsed metadata
|
199
|
+
def self.deserialize(h)
|
200
|
+
PlaylistAppleMusic.new(
|
201
|
+
library_id: h['library_id'],
|
202
|
+
global_id: h['global_id'],
|
203
|
+
public: h['public'],
|
204
|
+
editable: h['editable'],
|
205
|
+
image_url: h['image_url']
|
206
|
+
)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Serializes the metadata to a Hash that uses string keys.
|
210
|
+
#
|
211
|
+
# @return [Hash<String, Object>] The serialized representation
|
212
|
+
def serialize
|
213
|
+
{
|
214
|
+
'library_id' => self.library_id,
|
215
|
+
'global_id' => self.global_id,
|
216
|
+
'public' => self.public,
|
217
|
+
'editable' => self.editable,
|
218
|
+
'image_url' => self.image_url
|
219
|
+
}.compact
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Drum
|
2
|
+
# A 'half-parsed' reference to a resource, either a token or
|
3
|
+
# something else. The specifics are left to the service-specific
|
4
|
+
# Ref-parser.
|
5
|
+
#
|
6
|
+
# @!attribute raw
|
7
|
+
# @return [String] The raw text (@-stripped, though, if it's a token)
|
8
|
+
# @!attribute is_token
|
9
|
+
# @return [Boolean] Whether the ref is a token (i.e. begins with @)
|
10
|
+
RawRef = Struct.new(
|
11
|
+
:text,
|
12
|
+
:is_token,
|
13
|
+
keyword_init: true
|
14
|
+
) do
|
15
|
+
TOKEN_PREFIX = '@'
|
16
|
+
|
17
|
+
# Parses a RawRef from the given string.
|
18
|
+
#
|
19
|
+
# @param [String] raw The raw string to be parsed
|
20
|
+
# @return [RawRef] The parsed RawRef
|
21
|
+
def self.parse(raw)
|
22
|
+
if raw.start_with?(TOKEN_PREFIX)
|
23
|
+
RawRef.new(text: raw.delete_prefix(TOKEN_PREFIX), is_token: true)
|
24
|
+
else
|
25
|
+
RawRef.new(text: raw, is_token: false)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Drum
|
2
|
+
# A parsed reference to a resource, usually one or multiple playlists.
|
3
|
+
# Can be a folder, a library or the like, located on the local machine
|
4
|
+
# or a remote service.
|
5
|
+
#
|
6
|
+
# See the README for examples.
|
7
|
+
#
|
8
|
+
# @!attribute service_name
|
9
|
+
# @return [String] The name of the service
|
10
|
+
# @!attribute resource_type
|
11
|
+
# @return [Symbol] The type of the resource, service-dependent
|
12
|
+
# @!attribute resource_location
|
13
|
+
# @return [Object] The path/id of the resource, service-dependent (usually a String or Symbol)
|
14
|
+
Ref = Struct.new(
|
15
|
+
:service_name,
|
16
|
+
:resource_type,
|
17
|
+
:resource_location
|
18
|
+
)
|
19
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
|
2
|
+
module Drum
|
3
|
+
# TODO: Add Spotify's audio features
|
4
|
+
|
5
|
+
# A track/song.
|
6
|
+
#
|
7
|
+
# @!attribute name
|
8
|
+
# @return [String] The name of the track
|
9
|
+
# @!attribute artist_ids
|
10
|
+
# @return [Array<String>] The (internal) artist ids
|
11
|
+
# @!attribute composer_ids
|
12
|
+
# @return [optional, Array<String>] The (internal) composer ids
|
13
|
+
# @!attribute genres
|
14
|
+
# @return [optional, Array<String>] The track's genre names
|
15
|
+
# @!attribute album_id
|
16
|
+
# @return [optional, String] The (internal) album id
|
17
|
+
# @!attribute duration_ms
|
18
|
+
# @return [optional, Float] The duration of the track in milliseconds
|
19
|
+
# @!attribute explicit
|
20
|
+
# @return [optional, Boolean] Whether the track is explicit
|
21
|
+
# @!attribute released_at
|
22
|
+
# @return [optional, DateTime] The date/time the this track was released
|
23
|
+
# @!attribute added_at
|
24
|
+
# @return [optional, DateTime] The date/time the this track was added to the playlist
|
25
|
+
# @!attribute added_by
|
26
|
+
# @return [optional, String] The user id of the user who added this track to the playlist
|
27
|
+
# @!attribute isrc
|
28
|
+
# @return [optional, String] The International Standard Recording Code of this track
|
29
|
+
# @!attribute spotify
|
30
|
+
# @return [optional, TrackSpotify] Spotify-specific metadata
|
31
|
+
# @!attribute applemusic
|
32
|
+
# @return [optional, TrackAppleMusic] Apple Music-specific metadata
|
33
|
+
Track = Struct.new(
|
34
|
+
:name,
|
35
|
+
:artist_ids, :composer_ids, :album_id,
|
36
|
+
:genres,
|
37
|
+
:duration_ms, :explicit,
|
38
|
+
:released_at, :added_at, :added_by,
|
39
|
+
:isrc, :spotify, :applemusic,
|
40
|
+
keyword_init: true
|
41
|
+
) do
|
42
|
+
def initialize(*)
|
43
|
+
super
|
44
|
+
self.artist_ids ||= []
|
45
|
+
self.composer_ids ||= []
|
46
|
+
self.genres ||= []
|
47
|
+
end
|
48
|
+
|
49
|
+
# Parses a track from a nested Hash that uses string keys.
|
50
|
+
#
|
51
|
+
# @param [Hash<String, Object>] h The Hash to be parsed
|
52
|
+
# @return [Track] The parsed track
|
53
|
+
def self.deserialize(h)
|
54
|
+
Track.new(
|
55
|
+
name: h['name'],
|
56
|
+
artist_ids: h['artist_ids'],
|
57
|
+
composer_ids: h['composer_ids'],
|
58
|
+
genres: h['genres'],
|
59
|
+
album_id: h['album_id'],
|
60
|
+
duration_ms: h['duration_ms'],
|
61
|
+
explicit: h['explicit'],
|
62
|
+
released_at: h['released_at'].try { |d| DateTime.parse(d) },
|
63
|
+
added_at: h['added_at'].try { |d| DateTime.parse(d) },
|
64
|
+
added_by: h['added_by'],
|
65
|
+
isrc: h['isrc'],
|
66
|
+
spotify: h['spotify'].try { |s| TrackSpotify.deserialize(s) },
|
67
|
+
applemusic: h['applemusic'].try { |s| TrackAppleMusic.deserialize(s) }
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Serializes the track to a nested Hash that uses string keys.
|
72
|
+
#
|
73
|
+
# @return [Hash<String, Object>] The serialized representation
|
74
|
+
def serialize
|
75
|
+
{
|
76
|
+
'name' => self.name,
|
77
|
+
'artist_ids' => self.artist_ids,
|
78
|
+
'composer_ids' => (self.composer_ids unless self.composer_ids.empty?),
|
79
|
+
'genres' => (self.genres unless self.genres.empty?),
|
80
|
+
'album_id' => self.album_id,
|
81
|
+
'duration_ms' => self.duration_ms,
|
82
|
+
'explicit' => self.explicit,
|
83
|
+
'released_at' => self.released_at&.iso8601,
|
84
|
+
'added_at' => self.added_at&.iso8601,
|
85
|
+
'added_by' => self.added_by,
|
86
|
+
'isrc' => self.isrc,
|
87
|
+
'spotify' => self.spotify&.serialize,
|
88
|
+
'applemusic' => self.applemusic&.serialize
|
89
|
+
}.compact
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Spotify-specific metadata about the track.
|
94
|
+
#
|
95
|
+
# @!attribute id
|
96
|
+
# @return [String] The id of the track on Spotify
|
97
|
+
TrackSpotify = Struct.new(
|
98
|
+
:id,
|
99
|
+
keyword_init: true
|
100
|
+
) do
|
101
|
+
# Parses spotify metadata from a Hash that uses string keys.
|
102
|
+
#
|
103
|
+
# @param [Hash<String, Object>] h The Hash to be parsed
|
104
|
+
# @return [TrackSpotify] The parsed metadata
|
105
|
+
def self.deserialize(h)
|
106
|
+
TrackSpotify.new(
|
107
|
+
id: h['id']
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Serializes the metadata to a Hash that uses string keys.
|
112
|
+
#
|
113
|
+
# @return [Hash<String, Object>] The serialized representation
|
114
|
+
def serialize
|
115
|
+
{
|
116
|
+
'id' => self.id
|
117
|
+
}.compact
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Apple Music-specific metadata about the track.
|
122
|
+
#
|
123
|
+
# @!attribute library_id
|
124
|
+
# @return [optional, String] The library-internal id of the track
|
125
|
+
# @!attribute catalog_id
|
126
|
+
# @return [optional, String] The global catalog id of the track
|
127
|
+
# @!attribute preview_url
|
128
|
+
# @return [optional, String] A short preview of the song audio
|
129
|
+
TrackAppleMusic = Struct.new(
|
130
|
+
:library_id, :catalog_id,
|
131
|
+
:preview_url,
|
132
|
+
keyword_init: true
|
133
|
+
) do
|
134
|
+
# Parses Apple Music metadata from a Hash that uses string keys.
|
135
|
+
#
|
136
|
+
# @param [Hash<String, Object>] h The Hash to be parsed
|
137
|
+
# @return [TrackAppleMusic] The parsed metadata
|
138
|
+
def self.deserialize(h)
|
139
|
+
TrackAppleMusic.new(
|
140
|
+
library_id: h['library_id'],
|
141
|
+
catalog_id: h['catalog_id'],
|
142
|
+
preview_url: h['preview_url']
|
143
|
+
)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Serializes the metadata to a Hash that uses string keys.
|
147
|
+
#
|
148
|
+
# @return [Hash<String, Object>] The serialized representation
|
149
|
+
def serialize
|
150
|
+
{
|
151
|
+
'library_id' => self.library_id,
|
152
|
+
'catalog_id' => self.catalog_id,
|
153
|
+
'preview_url' => self.preview_url
|
154
|
+
}.compact
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Drum
|
2
|
+
# A user.
|
3
|
+
#
|
4
|
+
# @!attribute id
|
5
|
+
# @return [String] The (internal) id of the user
|
6
|
+
# @!attribute display_name
|
7
|
+
# @return [optional, String] The general formatted name of the user
|
8
|
+
# @!attribute spotify
|
9
|
+
# @return [optional, UserSpotify] Spotify-specific metadata
|
10
|
+
User = Struct.new(
|
11
|
+
:id,
|
12
|
+
:display_name,
|
13
|
+
:spotify,
|
14
|
+
keyword_init: true
|
15
|
+
) do
|
16
|
+
# Parses a user from a nested Hash that uses string keys.
|
17
|
+
#
|
18
|
+
# @param [Hash<String, Object>] h The Hash to be parsed
|
19
|
+
# @return [User] The parsed user
|
20
|
+
def self.deserialize(h)
|
21
|
+
User.new(
|
22
|
+
id: h['id'],
|
23
|
+
display_name: h['display_name'],
|
24
|
+
spotify: h['spotify'].try { |s| UserSpotify.deserialize(s) }
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Serializes the user to a nested Hash that uses string keys.
|
29
|
+
#
|
30
|
+
# @return [Hash<String, Object>] The serialized representation
|
31
|
+
def serialize
|
32
|
+
{
|
33
|
+
'id' => self.id,
|
34
|
+
'display_name' => self.display_name,
|
35
|
+
'spotify' => self.spotify&.serialize
|
36
|
+
}.compact
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Spotify-specific metadata about the user.
|
41
|
+
#
|
42
|
+
# @!attribute id
|
43
|
+
# @return [String] The id of the artist on Spotify
|
44
|
+
# @!attribute image_url
|
45
|
+
# @return [optional, String] The profile image of the user
|
46
|
+
UserSpotify = Struct.new(
|
47
|
+
:id,
|
48
|
+
:image_url,
|
49
|
+
keyword_init: true
|
50
|
+
) do
|
51
|
+
# Parses Spotify metadata from a Hash that uses string keys.
|
52
|
+
#
|
53
|
+
# @param [Hash<String, Object>] h The Hash to be parsed
|
54
|
+
# @return [UserSpotify] The parsed user
|
55
|
+
def self.deserialize(h)
|
56
|
+
UserSpotify.new(
|
57
|
+
id: h['id'],
|
58
|
+
image_url: h['image_url']
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Serializes the metadata to a Hash that uses string keys.
|
63
|
+
#
|
64
|
+
# @return [Hash<String, Object>] The serialized representation
|
65
|
+
def serialize
|
66
|
+
{
|
67
|
+
'id' => self.id,
|
68
|
+
'image_url' => self.image_url
|
69
|
+
}.compact
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|