Skip to content

Commit 6072341

Browse files
committed
New core functionality and design of Album views are now locked.
1 parent 6c0d462 commit 6072341

File tree

8 files changed

+221
-104
lines changed

8 files changed

+221
-104
lines changed

classes/Auth.php

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,32 +23,32 @@ public static function musickit_config_status(): array
2323
// config status check
2424
public static function configStatus(array $opts): array
2525
{
26-
$missing = [];
27-
foreach (['teamId','keyId','privateKey'] as $k) {
28-
if (empty($opts[$k])) $missing[] = $k;
29-
}
26+
$missing = [];
27+
foreach (['teamId','keyId','privateKey'] as $k) {
28+
if (empty($opts[$k])) $missing[] = $k;
29+
}
3030

31-
$errors = [];
32-
// validate formats if present (don't "warn" on empty)
33-
if (!empty($opts['teamId']) && !\preg_match('/^[A-Z0-9]{10}$/', $opts['teamId'])) {
34-
$errors[] = 'teamId must be 10 uppercase letters/numbers';
35-
}
36-
if (!empty($opts['keyId']) && !\preg_match('/^[A-Z0-9]{10}$/', $opts['keyId'])) {
37-
$errors[] = 'keyId must be 10 uppercase letters/numbers';
38-
}
39-
if (!empty($opts['privateKey']) && \strpos($opts['privateKey'], 'BEGIN PRIVATE KEY') === false) {
40-
$errors[] = 'privateKey must be a valid PEM string';
41-
}
31+
$errors = [];
32+
// validate formats if present (don't "warn" on empty)
33+
if (!empty($opts['teamId']) && !\preg_match('/^[A-Z0-9]{10}$/', $opts['teamId'])) {
34+
$errors[] = 'teamId must be 10 uppercase letters/numbers';
35+
}
36+
if (!empty($opts['keyId']) && !\preg_match('/^[A-Z0-9]{10}$/', $opts['keyId'])) {
37+
$errors[] = 'keyId must be 10 uppercase letters/numbers';
38+
}
39+
if (!empty($opts['privateKey']) && \strpos($opts['privateKey'], 'BEGIN PRIVATE KEY') === false) {
40+
$errors[] = 'privateKey must be a valid PEM string';
41+
}
4242

43-
$ok = empty($missing) && empty($errors);
44-
$status = $ok ? 'ok' : (!empty($missing) ? 'unconfigured' : 'invalid');
43+
$ok = empty($missing) && empty($errors);
44+
$status = $ok ? 'ok' : (!empty($missing) ? 'unconfigured' : 'invalid');
4545

46-
return [
47-
'ok' => $ok,
48-
'status' => $status, // ok | unconfigured | invalid
49-
'missing' => $missing, // e.g. teamId | keyId
50-
'errors' => $errors, // value format issues only
51-
];
46+
return [
47+
'ok' => $ok,
48+
'status' => $status, // ok | unconfigured | invalid
49+
'missing' => $missing, // e.g. teamId | keyId
50+
'errors' => $errors, // value format issues only
51+
];
5252
}
5353

5454
// configuration status convenience wrapper

classes/MusicKit.php

Lines changed: 36 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
namespace Scottboms\MusicKit;
55

66
use Scottboms\MusicKit\Auth;
7+
use Scottboms\MusicKit\Utils;
78
use Kirby\Http\Response;
89
use Kirby\Http\Remote;
910

@@ -203,7 +204,7 @@ public static function songDetails(string $songId, string $language = 'en-US'):
203204
}
204205

205206
$duration = isset($a['durationInMillis'])
206-
? self::ms_to_mmss((int)$a['durationInMillis'])
207+
? Utils::format_mmss((int)$a['durationInMillis'])
207208
: null;
208209

209210
// releaseYear from releaseDate
@@ -230,7 +231,7 @@ public static function songDetails(string $songId, string $language = 'en-US'):
230231
'artistName' => $a['artistName'] ?? '',
231232
'albumName' => $a['albumName'] ?? '',
232233
'composerName' => $a['composerName'] ?? '',
233-
'genreNames' => $a['genreNames'] ?? [],
234+
'genreName' => Utils::firstGenre($a['genreNames'] ?? null),
234235
'releaseDate' => $releaseDate,
235236
'releaseYear' => $releaseYear,
236237
'url' => $url,
@@ -241,22 +242,6 @@ public static function songDetails(string $songId, string $language = 'en-US'):
241242
], 200);
242243
}
243244

244-
// helper: milliseconds to mm:ss
245-
public static function ms_to_mmss(?int $ms, bool $forceHours = false): ?string
246-
{
247-
if ($ms === null) return null;
248-
$total = (int) round($ms / 1000);
249-
$h = intdiv($total, 3600);
250-
$rem = $total % 3600;
251-
$m = intdiv($total, 60);
252-
$s = $total % 60;
253-
254-
if ($forceHours || $h > 0) {
255-
return sprintf('%d:%02d:%02d', $h, $m, $s); // H:MM:SS
256-
}
257-
return sprintf('%d:%02d', $m, $s);
258-
}
259-
260245
// get individual album details
261246
public static function albumDetails(string $albumId, string $language = 'en-US'): Response
262247
{
@@ -333,6 +318,18 @@ public static function albumDetails(string $albumId, string $language = 'en-US')
333318

334319
// albums tracks data
335320
$tracksRaw = $body['data'][0]['relationships']['tracks']['data'] ?? [];
321+
322+
// compute album-level "isDigitalMaster" based on any track
323+
$albumIsDigitalMaster = false;
324+
foreach ($tracksRaw as $t) {
325+
$ta = $t['attributes'] ?? [];
326+
if (!empty($ta['isAppleDigitalMaster'])) {
327+
$albumIsDigitalMaster = true;
328+
break;
329+
}
330+
}
331+
332+
// build normalized tracks list
336333
$tracks = array_map(function ($t) {
337334
$a = $t['attributes'] ?? [];
338335
$ms = $a['durationInMillis'] ?? null;
@@ -341,32 +338,33 @@ public static function albumDetails(string $albumId, string $language = 'en-US')
341338
'number' => $a['trackNumber'] ?? null,
342339
'name' => $a['name'] ?? '',
343340
'durationMs' => $ms,
344-
'duration' => self::ms_to_mmss($ms),
341+
'duration' => Utils::format_mmss($ms),
345342
];
346343
}, $tracksRaw);
347344

348345
// total duration
349346
$totalDurationMs = array_sum(array_map(fn($t) => $t['durationMs'] ?? 0, $tracks));
350-
$totalDuration = self::ms_to_mmss($totalDurationMs);
347+
$totalDuration = Utils::format_human($totalDurationMs);
351348

352349
return Response::json([
353-
'id' => $id,
354-
'name' => $a['name'] ?? '',
355-
'artistName' => $a['artistName'] ?? '',
356-
'genreNames' => $a['genreNames'] ?? [],
357-
'isDigitalMaster' => (bool)($a['isDigitalMaster'] ?? true),
358-
'isMasteredForItunes' => (bool)($a['isMasteredForItunes'] ?? false),
359-
'contentRating' => $a['contentRating'] ?? '',
360-
'releaseDate' => $releaseDate,
361-
'releaseYear' => $releaseYear,
362-
'url' => $url,
363-
'image' => $img,
364-
'recordLabel' => $a['recordLabel'],
365-
'copyright' => $a['copyright'],
366-
'trackCount' => $a['trackCount'],
367-
'totalDuration' => $totalDuration,
368-
'tracks' => $tracks,
369-
//'raw' => $body, // optional: full response payload
350+
'id' => $id,
351+
'name' => $a['name'] ?? '',
352+
'artistName' => $a['artistName'] ?? '',
353+
'genreName' => Utils::firstGenre($a['genreNames'] ?? null),
354+
'isMasteredForItunes' => (bool)($a['isMasteredForItunes'] ?? false),
355+
'isAppleDigitalMaster' => $albumIsDigitalMaster,
356+
'contentRating' => $a['contentRating'] ?? '',
357+
'releaseDate' => $releaseDate,
358+
'releaseDateFormatted' => Utils::humanDate($releaseDate),
359+
'releaseYear' => $releaseYear,
360+
'url' => $url,
361+
'image' => $img,
362+
'recordLabel' => $a['recordLabel'],
363+
'copyright' => $a['copyright'],
364+
'trackCount' => $a['trackCount'],
365+
'totalDuration' => $totalDuration,
366+
'tracks' => $tracks,
367+
'raw' => $body, // optional: full response payload
370368
], 200);
371369
}
372370

@@ -375,7 +373,7 @@ public static function albumDetails(string $albumId, string $language = 'en-US')
375373
* fetches recently played for the shared token, cache it,
376374
* and normalize to a render-friendly array
377375
*
378-
* @return array{items: list<array{id?:string,name:string,artist:string,url:string|null,image:string|null}>, error:?string}
376+
* @return Array {items: list<array{id?:string,name:string,artist:string,url:string|null,image:string|null}>, error:?string}
379377
*/
380378
public static function recentForFrontend(int $limit = 12, string $language = 'en-US', int $cacheTtl = 120): array
381379
{

classes/Utils.php

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Scottboms\MusicKit;
5+
6+
class Utils
7+
{
8+
/**
9+
* helper: get only first genre from array
10+
* @return String
11+
*/
12+
public static function firstGenre($genres): ?string
13+
{
14+
return (is_array($genres) && isset($genres[0]) && is_string($genres[0]))
15+
? $genres[0]
16+
: null;
17+
}
18+
19+
20+
/**
21+
* helper: human date format
22+
* @return String
23+
*/
24+
public static function humanDate(?string $iso): ?string
25+
{
26+
if (!$iso) return null;
27+
28+
// full date: yyyy-mm-dd
29+
if (\preg_match('/^\d{4}-\d{2}-\d{2}$/', $iso)) {
30+
$dt = \DateTime::createFromFormat('Y-m-d', $iso);
31+
return $dt ? $dt->format('F j, Y') : $iso;
32+
}
33+
34+
// year-month: yyyy-mm
35+
if (\preg_match('/^\d{4}-\d{2}$/', $iso)) {
36+
$dt = \DateTime::createFromFormat('Y-m', $iso);
37+
return $dt ? $dt->format('F Y') : $iso;
38+
}
39+
40+
// year-only: yyyy
41+
if (\preg_match('/^\d{4}$/', $iso)) {
42+
return $iso;
43+
}
44+
45+
// fallback: return as-is if format is unexpected
46+
return $iso;
47+
}
48+
49+
50+
/**
51+
* helper: milliseconds to mm:ss
52+
* @return String
53+
*/
54+
public static function format_mmss(?int $ms): ?string
55+
{
56+
if ($ms === null) return null;
57+
$totalSeconds = (int) floor($ms / 1000); // song timings typically floor
58+
$m = intdiv($totalSeconds, 60);
59+
$s = $totalSeconds % 60;
60+
return sprintf('%d:%02d', $m, $s);
61+
}
62+
63+
64+
/**
65+
* helper: milliseconds -> human text with rounding (hours/minutes/seconds)
66+
* @return String
67+
*/
68+
public static function format_human(?int $ms): ?string
69+
{
70+
if ($ms === null) return null;
71+
72+
// start by rounding to the nearest second
73+
$totalSeconds = (int) round($ms / 1000);
74+
$h = intdiv($totalSeconds, 3600);
75+
$rem = $totalSeconds % 3600;
76+
$m = intdiv($rem, 60);
77+
$s = $rem % 60;
78+
79+
// round seconds up into minutes at >= 30s
80+
if ($s >= 30) {
81+
$m++;
82+
$s = 0;
83+
}
84+
85+
// carry minutes into hours
86+
if ($m >= 60) {
87+
$h += intdiv($m, 60);
88+
$m = $m % 60;
89+
}
90+
91+
$parts = [];
92+
93+
if ($h > 0) {
94+
$parts[] = $h . ' hour' . ($h > 1 ? 's' : '');
95+
}
96+
97+
if ($m > 0) {
98+
$parts[] = $m . ' minute' . ($m > 1 ? 's' : '');
99+
}
100+
101+
// simplified rule: only show seconds if minutes are zero after rounding
102+
// this yields examples like 1 hour, 23 seconds or just 23 seconds
103+
if ($m === 0 && $s > 0) {
104+
$parts[] = $s . ' second' . ($s > 1 ? 's' : '');
105+
}
106+
107+
if (empty($parts)) {
108+
// happens for < 0.5s after rounding; choose the friendliest fallback
109+
return '0 minutes';
110+
}
111+
112+
return implode(', ', $parts);
113+
}
114+
115+
}

index.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)