Skip to content

Commit 42504da

Browse files
committed
Add support for playlist import.
Add simple playlist parser. Unfortunately this needs to be synchronous, thus must rely on the scanner keeping a copy in the cache.
1 parent 2445eaa commit 42504da

File tree

8 files changed

+178
-29
lines changed

8 files changed

+178
-29
lines changed

API/Common.pm

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,4 +209,20 @@ sub getUrl {
209209
return 'qobuz://' . $id . '.' . $ext;
210210
}
211211

212+
sub getPlaylistImage {
213+
my ($class, $playlist) = @_;
214+
215+
my $image;
216+
# pick the last image, as this is what is shown top most in the Qobuz Desktop client
217+
foreach ('image_rectangle', 'images300', 'images_300', 'images150', 'images_150', 'images') {
218+
if ($playlist->{$_} && ref $playlist->{$_} eq 'ARRAY') {
219+
$image = $playlist->{$_}->[-1];
220+
last;
221+
}
222+
}
223+
$image =~ s/([a-z\d]{13}_)[\d]+(\.jpg)/${1}600$2/;
224+
225+
return $image;
226+
}
227+
212228
1;

API/Sync.pm

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,52 @@ sub getAlbum {
132132
return $album;
133133
}
134134

135+
sub myPlaylists {
136+
my ($class, $limit) = @_;
137+
138+
my $playlists = $class->_get('playlist/getUserPlaylists', {
139+
username => Plugins::Qobuz::API::Common->username,
140+
limit => QOBUZ_DEFAULT_LIMIT,
141+
_ttl => QOBUZ_USER_DATA_EXPIRY,
142+
_use_token => 1,
143+
});
144+
145+
return ($playlists && ref $playlists && $playlists->{playlists} && ref $playlists->{playlists})
146+
? $playlists->{playlists}->{items}
147+
: [];
148+
}
149+
150+
sub getPlaylistTrackIDs {
151+
my ($class, $playlistId) = @_;
152+
153+
my $offset = 0;
154+
my @playlistTrackIds;
155+
156+
do {
157+
my $response = $class->_get('playlist/get', {
158+
playlist_id => $playlistId,
159+
extra => 'tracks',
160+
limit => QOBUZ_DEFAULT_LIMIT,
161+
offset => $offset,
162+
_ttl => QOBUZ_USER_DATA_EXPIRY,
163+
_use_token => 1,
164+
});
165+
166+
$offset = 0;
167+
168+
if ($response && ref $response && $response->{tracks} && ref $response->{tracks} && $response->{tracks}->{items} && ref $response->{tracks}->{items}) {
169+
my $tracks = $response->{tracks}->{items};
170+
push @playlistTrackIds, map { $_->{id} } @{_precacheTracks($tracks)};
171+
172+
if (scalar $tracks && $response->{tracks}->{total} > $response->{tracks}->{offset} + QOBUZ_DEFAULT_LIMIT) {
173+
$offset = $response->{tracks}->{offset} + QOBUZ_DEFAULT_LIMIT;
174+
}
175+
}
176+
} while $offset && $offset < QOBUZ_USERDATA_LIMIT;
177+
178+
return \@playlistTrackIds;
179+
}
180+
135181
sub _get {
136182
my ( $class, $url, $params ) = @_;
137183

Importer.pm

Lines changed: 73 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package Plugins::Qobuz::Importer;
22

33
use strict;
44

5-
use Date::Parse qw(str2time);
5+
use List::Util qw(max);
66

77
use Slim::Music::Import;
88
use Slim::Utils::Log;
@@ -72,7 +72,7 @@ sub startScan {
7272
my ($albums, $libraryMeta) = Plugins::Qobuz::API::Sync->myAlbums();
7373
$progress->total(scalar @$albums + 2);
7474

75-
$cache->set('latest_album_update', _libraryMetaId($libraryMeta), 86400);
75+
$cache->set('latest_album_update', _libraryMetaId($libraryMeta), time() + 360 * 86400);
7676

7777
my @albums;
7878

@@ -105,6 +105,60 @@ sub startScan {
105105
main::SCANNER && Slim::Schema->forceCommit;
106106
}
107107

108+
my $progress = Slim::Utils::Progress->new({
109+
'type' => 'importer',
110+
'name' => 'plugin_qobuz_playlists',
111+
'total' => 1,
112+
'every' => 1,
113+
});
114+
115+
$progress->update(string('PLUGIN_QOBUZ_PROGRESS_READ_PLAYLISTS'));
116+
117+
main::INFOLOG && $log->is_info && $log->info("Reading playlists...");
118+
my $playlists = Plugins::Qobuz::API::Sync->myPlaylists();
119+
120+
$progress->total(scalar @$playlists);
121+
122+
$progress->update(string('PLUGIN_QOBUZ_PROGRESS_READ_TRACKS'));
123+
my %tracks;
124+
my $c = my $latestPlaylistUpdate = 0;
125+
126+
main::INFOLOG && $log->is_info && $log->info("Getting playlist tracks...");
127+
128+
# we need to get the tracks first
129+
foreach my $playlist (@{$playlists || []}) {
130+
next unless $playlist->{id} && $playlist->{duration};
131+
132+
$latestPlaylistUpdate = max($latestPlaylistUpdate, $playlist->{updated_at});
133+
134+
$progress->update($playlist->{name});
135+
main::SCANNER && Slim::Schema->forceCommit;
136+
137+
my $playlistObj = Slim::Schema->updateOrCreate({
138+
url => 'qobuz://' . $playlist->{id} . '.qbz',
139+
playlist => 1,
140+
integrateRemote => 1,
141+
attributes => {
142+
TITLE => $playlist->{name},
143+
COVER => Plugins::Qobuz::API::Common->getPlaylistImage($playlist),
144+
AUDIO => 1,
145+
EXTID => $playlist->{id},
146+
CONTENT_TYPE => 'ssp'
147+
},
148+
});
149+
150+
my @trackIDs = map { Plugins::Qobuz::API::Common->getUrl($_) } @{Plugins::Qobuz::API::Sync->getPlaylistTrackIDs($playlist->{id})};
151+
$cache->set('playlist_tracks' . $playlist->{id}, \@trackIDs, time() + 86400 * 360);
152+
$playlistObj->setTracks(\@trackIDs) if $playlistObj && scalar @trackIDs;
153+
}
154+
155+
$cache->set('playlist_last_update', $latestPlaylistUpdate, time() + 86400 * 360);
156+
157+
main::INFOLOG && $log->is_info && $log->info("Done, finally!");
158+
159+
$progress->final();
160+
161+
Slim::Music::Import->endImporter($class);
108162
};
109163

110164

@@ -124,27 +178,26 @@ sub needsUpdate {
124178
# don't run any further test in the queue if we already have a result
125179
return $acb->($result) if $result;
126180

127-
# my $snapshotIds = $cache->get('spotty_snapshot_ids' . $accountId);
128-
129-
# my $api = Plugins::Spotty::Plugin->getAPIHandler($client);
130-
# $api->playlists(sub {
131-
# my ($playlists) = @_;
181+
my $previousPlaylistUpdate = $cache->get('playlist_last_update');
132182

183+
Plugins::Qobuz::API->getUserPlaylists(sub {
184+
my ($result) = @_;
133185
my $needUpdate;
134-
# for my $playlist (@$playlists) {
135-
# my $snapshotId = $snapshotIds->{$playlist->{id}};
136-
# # we need an update if
137-
# # - we haven't a snapshot ID for this playlist, OR
138-
# # - the snapshot ID doesn't match, OR
139-
# # - the playlist is Spotify generated and older than a day
140-
# if ( !$snapshotId || ($snapshotId =~ /^\d{10}$/ ? $snapshotId < $timestamp : $snapshotId ne $playlist->{snapshot_id}) ) {
141-
# $needUpdate = 1;
142-
# last;
143-
# }
144-
# }
186+
187+
if ($result && ref $result && $result->{playlists} && ref $result->{playlists} && $result->{playlists}->{items} && ref $result->{playlists}->{items}) {
188+
my $playlists = $result->{playlists}->{items};
189+
my $latestPlaylistUpdate = 0;
190+
191+
foreach (@$playlists) {
192+
if ($_->{updated_at} > $previousPlaylistUpdate) {
193+
$needUpdate = 1;
194+
last;
195+
}
196+
}
197+
}
145198

146199
$acb->($needUpdate);
147-
# });
200+
});
148201
}, sub {
149202
my ($result, $acb) = @_;
150203

@@ -209,7 +262,7 @@ sub _storeTracks {
209262
AUDIO => 1,
210263
EXTID => $track->{id},
211264
# COMPILATION => $track->{album}->{album_type} eq 'compilation',
212-
TIMESTAMP => str2time($album->{favorited_at} || 0),
265+
TIMESTAMP => $album->{favorited_at} || $album->{purchased_at},
213266
CONTENT_TYPE => $ct,
214267
SAMPLERATE => $track->{maximum_sampling_rate} * 1000,
215268
SAMPLESIZE => $track->{maximum_bit_depth},

PlaylistParser.pm

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package Plugins::Qobuz::PlaylistParser;
2+
3+
use strict;
4+
5+
use Plugins::Qobuz::API::Common;
6+
7+
my $cache = Plugins::Qobuz::API::Common->getCache();
8+
9+
sub read {
10+
my ($class, $fh, $base, $url) = @_;
11+
12+
my ($id) = $url =~ m|qobuz://(.*)\.qbz|;
13+
my $tracks = [];
14+
if ($id) {
15+
$tracks = $cache->get("playlist_tracks${id}") || [];
16+
}
17+
18+
return @$tracks;
19+
}
20+
21+
1;

Plugin.pm

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ sub initPlugin {
6464
qobuz => 'Plugins::Qobuz::ProtocolHandler'
6565
);
6666

67+
Slim::Formats::Playlists->registerParser('qbz', 'Plugins::Qobuz::PlaylistParser');
68+
6769
Slim::Player::ProtocolHandlers->registerIconHandler(
6870
qr|\.qobuz\.com/|,
6971
sub { $class->_pluginDataFor('icon') }
@@ -978,15 +980,7 @@ sub _artistItem {
978980
sub _playlistItem {
979981
my ($playlist, $showOwner, $isWeb) = @_;
980982

981-
# pick the last image, as this is what is shown top most in the Qobuz Desktop client
982-
my $image;
983-
foreach ('image_rectangle', 'images_300', 'images_150', 'images') {
984-
if ($playlist->{$_} && ref $playlist->{$_} eq 'ARRAY') {
985-
$image = $playlist->{$_}->[-1];
986-
last;
987-
}
988-
}
989-
$image =~ s/([a-z\d]{13}_)[\d]+(\.jpg)/${1}600$2/;
983+
my $image = Plugins::Qobuz::API::Common->getPlaylistImage($playlist);
990984

991985
my $owner = $showOwner ? $playlist->{owner}->{name} : undef;
992986

ProtocolHandler.pm

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,10 @@ sub getMetadataFor {
203203
$meta->{artist} .= ', ' . $meta->{composer};
204204
}
205205

206+
if ($meta->{cover} && ref $meta->{cover}) {
207+
$meta->{cover} = $meta->{cover}->{large} || $meta->{cover}->{small} || $meta->{cover}->{thumbnail};
208+
}
209+
206210
return $meta;
207211
}
208212

custom-types.conf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#########################################################################
2+
#ID Suffix Mime Content-Type Server File Type#
3+
#########################################################################
4+
qbz qbz audio/x-sb-qobuz playlist

strings.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,14 @@ PLUGIN_QOBUZ_PROGRESS_READ_ALBUMS
194194
DE Lese Alben...
195195
EN Fetching Albums...
196196

197+
PLUGIN_QOBUZ_PROGRESS_READ_TRACKS
198+
DE Lese Titel-Informationen...
199+
EN Fetching Title Information...
200+
201+
PLUGIN_QOBUZ_PLAYLISTS_PROGRESS
202+
DE Qobuz Wiedergabelisten
203+
EN Qobuz Playlists
204+
205+
PLUGIN_QOBUZ_PROGRESS_READ_PLAYLISTS
206+
DE Lese Wiedergabelisten...
207+
EN Fetching Playlists...

0 commit comments

Comments
 (0)