|
1
|
|
|
<?php declare(strict_types=1); |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* ownCloud - Music app |
|
5
|
|
|
* |
|
6
|
|
|
* This file is licensed under the Affero General Public License version 3 or |
|
7
|
|
|
* later. See the COPYING file. |
|
8
|
|
|
* |
|
9
|
|
|
* @author Morris Jobke <[email protected]> |
|
10
|
|
|
* @author Pauli Järvinen <[email protected]> |
|
11
|
|
|
* @copyright Morris Jobke 2013, 2014 |
|
12
|
|
|
* @copyright Pauli Järvinen 2016 - 2025 |
|
13
|
|
|
*/ |
|
14
|
|
|
|
|
15
|
|
|
namespace OCA\Music\Db; |
|
16
|
|
|
|
|
17
|
|
|
use OCA\Music\Utility\StringUtil; |
|
18
|
|
|
use OCA\Music\Utility\Util; |
|
19
|
|
|
use OCP\IL10N; |
|
20
|
|
|
use OCP\IURLGenerator; |
|
21
|
|
|
|
|
22
|
|
|
/** |
|
23
|
|
|
* @method string getTitle() |
|
24
|
|
|
* @method void setTitle(string $title) |
|
25
|
|
|
* @method ?int getNumber() |
|
26
|
|
|
* @method void setNumber(?int $number) |
|
27
|
|
|
* @method ?int getDisk() |
|
28
|
|
|
* @method void setDisk(?int $disk) |
|
29
|
|
|
* @method ?int getYear() |
|
30
|
|
|
* @method void setYear(?int $year) |
|
31
|
|
|
* @method int getArtistId() |
|
32
|
|
|
* @method void setArtistId(int $artistId) |
|
33
|
|
|
* @method int getAlbumId() |
|
34
|
|
|
* @method void setAlbumId(int $albumId) |
|
35
|
|
|
* @method ?int getLength() |
|
36
|
|
|
* @method void setLength(?int $length) |
|
37
|
|
|
* @method int getFileId() |
|
38
|
|
|
* @method void setFileId(int $fileId) |
|
39
|
|
|
* @method ?int getBitrate() |
|
40
|
|
|
* @method void setBitrate(?int $bitrate) |
|
41
|
|
|
* @method string getMimetype() |
|
42
|
|
|
* @method void setMimetype(string $mimetype) |
|
43
|
|
|
* @method ?string getMbid() |
|
44
|
|
|
* @method void setMbid(?string $mbid) |
|
45
|
|
|
* @method ?string getStarred() |
|
46
|
|
|
* @method void setStarred(?string $timestamp) |
|
47
|
|
|
* @method int getRating() |
|
48
|
|
|
* @method void setRating(int $rating) |
|
49
|
|
|
* @method ?int getGenreId() |
|
50
|
|
|
* @method void setGenreId(?int $genreId) |
|
51
|
|
|
* @method int getPlayCount() |
|
52
|
|
|
* @method void setPlayCount(int $count) |
|
53
|
|
|
* @method ?string getLastPlayed() |
|
54
|
|
|
* @method void setLastPlayed(?string $timestamp) |
|
55
|
|
|
* @method int getDirty() |
|
56
|
|
|
* @method void setDirty(int $dirty) |
|
57
|
|
|
* |
|
58
|
|
|
* @method string getFilename() |
|
59
|
|
|
* @method int getSize() |
|
60
|
|
|
* @method int getFileModTime() |
|
61
|
|
|
* @method ?string getAlbumName() |
|
62
|
|
|
* @method ?string getArtistName() |
|
63
|
|
|
* @method ?string getGenreName() |
|
64
|
|
|
* @method int getFolderId() |
|
65
|
|
|
*/ |
|
66
|
|
|
class Track extends Entity { |
|
67
|
|
|
public string $title = ''; |
|
68
|
|
|
public ?int $number = null; |
|
69
|
|
|
public ?int $disk = null; |
|
70
|
|
|
public ?int $year = null; |
|
71
|
|
|
public ?int $artistId = null; |
|
72
|
|
|
public ?int $albumId = null; |
|
73
|
|
|
public ?int $length = null; |
|
74
|
|
|
public int $fileId = 0; |
|
75
|
|
|
public ?int $bitrate = null; |
|
76
|
|
|
public string $mimetype = ''; |
|
77
|
|
|
public ?string $mbid = null; |
|
78
|
|
|
public ?string $starred = null; |
|
79
|
|
|
public int $rating = 0; |
|
80
|
|
|
public ?int $genreId = null; |
|
81
|
|
|
public int $playCount = 0; |
|
82
|
|
|
public ?string $lastPlayed = null; |
|
83
|
|
|
public int $dirty = 0; |
|
84
|
|
|
|
|
85
|
|
|
// not from the music_tracks table but still part of the standard content of this entity: |
|
86
|
|
|
public string $filename = ''; |
|
87
|
|
|
public int $size = 0; |
|
88
|
|
|
public int $fileModTime = 0; |
|
89
|
|
|
public ?string $albumName = null; |
|
90
|
|
|
public ?string $artistName = null; |
|
91
|
|
|
public ?string $genreName = null; |
|
92
|
|
|
public int $folderId = 0; |
|
93
|
|
|
|
|
94
|
|
|
// the rest of the variables are injected separately when needed |
|
95
|
|
|
private ?Album $album = null; |
|
96
|
|
|
private ?int $numberOnPlaylist = null; |
|
97
|
|
|
private ?string $folderPath = null; |
|
98
|
|
|
private ?string $lyrics = null; |
|
99
|
|
|
|
|
100
|
|
|
public function __construct() { |
|
101
|
|
|
$this->addType('number', 'int'); |
|
102
|
|
|
$this->addType('disk', 'int'); |
|
103
|
|
|
$this->addType('year', 'int'); |
|
104
|
|
|
$this->addType('artistId', 'int'); |
|
105
|
|
|
$this->addType('albumId', 'int'); |
|
106
|
|
|
$this->addType('length', 'int'); |
|
107
|
|
|
$this->addType('bitrate', 'int'); |
|
108
|
|
|
$this->addType('fileId', 'int'); |
|
109
|
|
|
$this->addType('genreId', 'int'); |
|
110
|
|
|
$this->addType('playCount', 'int'); |
|
111
|
|
|
$this->addType('rating', 'int'); |
|
112
|
|
|
$this->addType('dirty', 'int'); |
|
113
|
|
|
$this->addType('size', 'int'); |
|
114
|
|
|
$this->addType('fileModTime', 'int'); |
|
115
|
|
|
$this->addType('folderId', 'int'); |
|
116
|
|
|
} |
|
117
|
|
|
|
|
118
|
|
|
public function getAlbum() : ?Album { |
|
119
|
|
|
return $this->album; |
|
120
|
|
|
} |
|
121
|
|
|
|
|
122
|
|
|
public function setAlbum(?Album $album) : void { |
|
123
|
|
|
$this->album = $album; |
|
124
|
|
|
} |
|
125
|
|
|
|
|
126
|
|
|
public function getNumberOnPlaylist() : ?int { |
|
127
|
|
|
return $this->numberOnPlaylist; |
|
128
|
|
|
} |
|
129
|
|
|
|
|
130
|
|
|
public function setNumberOnPlaylist(int $number) : void { |
|
131
|
|
|
$this->numberOnPlaylist = $number; |
|
132
|
|
|
} |
|
133
|
|
|
|
|
134
|
|
|
public function setFolderPath(string $path) : void { |
|
135
|
|
|
$this->folderPath = $path; |
|
136
|
|
|
} |
|
137
|
|
|
|
|
138
|
|
|
public function setLyrics(?string $lyrics) : void { |
|
139
|
|
|
$this->lyrics = $lyrics; |
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
|
|
public function getPath() : ?string { |
|
143
|
|
|
return ($this->folderPath ?? '') . '/' . $this->filename; |
|
144
|
|
|
} |
|
145
|
|
|
|
|
146
|
|
|
public function getUri(IURLGenerator $urlGenerator) : string { |
|
147
|
|
|
return $urlGenerator->linkToRoute( |
|
148
|
|
|
'music.shivaApi.track', |
|
149
|
|
|
['id' => $this->id] |
|
150
|
|
|
); |
|
151
|
|
|
} |
|
152
|
|
|
|
|
153
|
|
|
public function getArtistWithUri(IURLGenerator $urlGenerator) : array { |
|
154
|
|
|
return [ |
|
155
|
|
|
'id' => $this->artistId, |
|
156
|
|
|
'uri' => $urlGenerator->linkToRoute( |
|
157
|
|
|
'music.shivaApi.artist', |
|
158
|
|
|
['id' => $this->artistId] |
|
159
|
|
|
) |
|
160
|
|
|
]; |
|
161
|
|
|
} |
|
162
|
|
|
|
|
163
|
|
|
public function getAlbumWithUri(IURLGenerator $urlGenerator) : array { |
|
164
|
|
|
return [ |
|
165
|
|
|
'id' => $this->albumId, |
|
166
|
|
|
'uri' => $urlGenerator->linkToRoute( |
|
167
|
|
|
'music.shivaApi.album', |
|
168
|
|
|
['id' => $this->albumId] |
|
169
|
|
|
) |
|
170
|
|
|
]; |
|
171
|
|
|
} |
|
172
|
|
|
|
|
173
|
|
|
public function getArtistNameString(IL10N $l10n) : string { |
|
174
|
|
|
return $this->getArtistName() ?: Artist::unknownNameString($l10n); |
|
175
|
|
|
} |
|
176
|
|
|
|
|
177
|
|
|
public function getAlbumNameString(IL10N $l10n) : string { |
|
178
|
|
|
return $this->getAlbumName() ?: Album::unknownNameString($l10n); |
|
179
|
|
|
} |
|
180
|
|
|
|
|
181
|
|
|
public function getGenreNameString(IL10N $l10n) : string { |
|
182
|
|
|
return $this->getGenreName() ?: Genre::unknownNameString($l10n); |
|
183
|
|
|
} |
|
184
|
|
|
|
|
185
|
|
|
public function toCollection() : array { |
|
186
|
|
|
return [ |
|
187
|
|
|
'title' => $this->getTitle(), |
|
188
|
|
|
'number' => $this->getNumber(), |
|
189
|
|
|
'disk' => $this->getDisk(), |
|
190
|
|
|
'artistId' => $this->getArtistId(), |
|
191
|
|
|
'length' => $this->getLength(), |
|
192
|
|
|
'files' => [$this->getMimetype() => $this->getFileId()], |
|
193
|
|
|
'id' => $this->getId(), |
|
194
|
|
|
]; |
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
public function toShivaApi(IURLGenerator $urlGenerator) : array { |
|
198
|
|
|
return [ |
|
199
|
|
|
'title' => $this->getTitle(), |
|
200
|
|
|
'ordinal' => $this->getAdjustedTrackNumber(), |
|
201
|
|
|
'artist' => $this->getArtistWithUri($urlGenerator), |
|
202
|
|
|
'album' => $this->getAlbumWithUri($urlGenerator), |
|
203
|
|
|
'length' => $this->getLength(), |
|
204
|
|
|
'files' => [$this->getMimetype() => $urlGenerator->linkToRoute( |
|
205
|
|
|
'music.musicApi.download', |
|
206
|
|
|
['fileId' => $this->getFileId()] |
|
207
|
|
|
)], |
|
208
|
|
|
'bitrate' => $this->getBitrate(), |
|
209
|
|
|
'id' => $this->getId(), |
|
210
|
|
|
'slug' => $this->slugify('title'), |
|
|
|
|
|
|
211
|
|
|
'uri' => $this->getUri($urlGenerator) |
|
212
|
|
|
]; |
|
213
|
|
|
} |
|
214
|
|
|
|
|
215
|
|
|
public function toAmpacheApi( |
|
216
|
|
|
IL10N $l10n, |
|
217
|
|
|
callable $createPlayUrl, |
|
218
|
|
|
callable $createImageUrl, |
|
219
|
|
|
callable $renderAlbumOrArtistRef, |
|
220
|
|
|
string $genreKey, |
|
221
|
|
|
bool $includeArtists) : array { |
|
222
|
|
|
$album = $this->getAlbum(); |
|
223
|
|
|
|
|
224
|
|
|
$result = [ |
|
225
|
|
|
'id' => (string)$this->getId(), |
|
226
|
|
|
'title' => $this->getTitle() ?: '', |
|
227
|
|
|
'name' => $this->getTitle() ?: '', |
|
228
|
|
|
'artist' => $renderAlbumOrArtistRef($this->getArtistId() ?: 0, $this->getArtistNameString($l10n)), |
|
229
|
|
|
'albumartist' => $renderAlbumOrArtistRef($album->getAlbumArtistId() ?: 0, $album->getAlbumArtistNameString($l10n)), |
|
230
|
|
|
'album' => $renderAlbumOrArtistRef($album->getId() ?: 0, $album->getNameString($l10n)), |
|
231
|
|
|
'url' => $createPlayUrl($this), |
|
232
|
|
|
'time' => $this->getLength(), |
|
233
|
|
|
'year' => $this->getYear(), |
|
234
|
|
|
'track' => $this->getAdjustedTrackNumber(), // TODO: maybe there should be a user setting to select plain or adjusted number |
|
235
|
|
|
'playlisttrack' => $this->getAdjustedTrackNumber(), |
|
236
|
|
|
'disk' => $this->getDisk(), |
|
237
|
|
|
'filename' => $this->getFilename(), |
|
238
|
|
|
'format' => $this->getFileExtension(), |
|
239
|
|
|
'stream_format' => $this->getFileExtension(), |
|
240
|
|
|
'bitrate' => $this->getBitrate(), |
|
241
|
|
|
'stream_bitrate' => $this->getBitrate(), |
|
242
|
|
|
'mime' => $this->getMimetype(), |
|
243
|
|
|
'stream_mime' => $this->getMimetype(), |
|
244
|
|
|
'size' => $this->getSize(), |
|
245
|
|
|
'art' => $createImageUrl($this), |
|
246
|
|
|
'rating' => $this->getRating(), |
|
247
|
|
|
'preciserating' => $this->getRating(), |
|
248
|
|
|
'playcount' => $this->getPlayCount(), |
|
249
|
|
|
'flag' => !empty($this->getStarred()), |
|
250
|
|
|
'language' => null, |
|
251
|
|
|
'lyrics' => $this->lyrics, |
|
252
|
|
|
'mode' => null, // cbr/vbr |
|
253
|
|
|
'rate' => null, // sample rate [Hz] |
|
254
|
|
|
'replaygain_album_gain' => null, |
|
255
|
|
|
'replaygain_album_peak' => null, |
|
256
|
|
|
'replaygain_track_gain' => null, |
|
257
|
|
|
'replaygain_track_peak' => null, |
|
258
|
|
|
'r128_album_gain' => null, |
|
259
|
|
|
'r128_track_gain' => null, |
|
260
|
|
|
]; |
|
261
|
|
|
|
|
262
|
|
|
$result['has_art'] = !empty($result['art']); |
|
263
|
|
|
|
|
264
|
|
|
$genreId = $this->getGenreId(); |
|
265
|
|
|
if ($genreId !== null) { |
|
266
|
|
|
$result[$genreKey] = [[ |
|
267
|
|
|
'id' => (string)$genreId, |
|
268
|
|
|
'text' => $this->getGenreNameString($l10n), |
|
269
|
|
|
'count' => 1 |
|
270
|
|
|
]]; |
|
271
|
|
|
} |
|
272
|
|
|
|
|
273
|
|
|
if ($includeArtists) { |
|
274
|
|
|
// Add another property `artists`. Apparently, it exists to support multiple artists per song |
|
275
|
|
|
// but we don't have such possibility and this is always just a 1-item array. |
|
276
|
|
|
$result['artists'] = [$result['artist']]; |
|
277
|
|
|
} |
|
278
|
|
|
|
|
279
|
|
|
return $result; |
|
280
|
|
|
} |
|
281
|
|
|
|
|
282
|
|
|
/** |
|
283
|
|
|
* The same API format is used both on "old" and "new" API methods. The "new" API adds some |
|
284
|
|
|
* new fields for the songs, but providing some extra fields shouldn't be a problem for the |
|
285
|
|
|
* older clients. The $track entity must have the Album reference injected prior to calling this. |
|
286
|
|
|
* |
|
287
|
|
|
* @param string[] $ignoredArticles |
|
288
|
|
|
*/ |
|
289
|
|
|
public function toSubsonicApi(IL10N $l10n, array $ignoredArticles) : array { |
|
290
|
|
|
$albumId = $this->getAlbumId(); |
|
291
|
|
|
$album = $this->getAlbum(); |
|
292
|
|
|
$hasCoverArt = ($album !== null && !empty($album->getCoverFileId())); |
|
293
|
|
|
|
|
294
|
|
|
return [ |
|
295
|
|
|
'id' => 'track-' . $this->getId(), |
|
296
|
|
|
'parent' => 'album-' . $albumId, |
|
297
|
|
|
'discNumber' => $this->getDisk(), |
|
298
|
|
|
'title' => $this->getTitle(), |
|
299
|
|
|
'artist' => $this->getArtistNameString($l10n), |
|
300
|
|
|
'isDir' => false, |
|
301
|
|
|
'album' => $this->getAlbumNameString($l10n), |
|
302
|
|
|
'year' => $this->getYear(), |
|
303
|
|
|
'size' => $this->getSize(), |
|
304
|
|
|
'contentType' => $this->getMimetype(), |
|
305
|
|
|
'suffix' => $this->getFileExtension(), |
|
306
|
|
|
'duration' => $this->getLength() ?? 0, |
|
307
|
|
|
'bitRate' => empty($this->getBitrate()) ? null : (int)\round($this->getBitrate()/1000), // convert bps to kbps |
|
308
|
|
|
'path' => $this->getPath(), |
|
309
|
|
|
'isVideo' => false, |
|
310
|
|
|
'albumId' => 'album-' . $albumId, |
|
311
|
|
|
'artistId' => 'artist-' . $this->getArtistId(), |
|
312
|
|
|
'type' => 'music', |
|
313
|
|
|
'mediaType' => 'song', // OpenSubsonic |
|
314
|
|
|
'created' => Util::formatZuluDateTime($this->getCreated()), |
|
315
|
|
|
'track' => $this->getAdjustedTrackNumber(false), // DSub would get confused of playlist numbering, https://github.com/owncloud/music/issues/994 |
|
316
|
|
|
'starred' => Util::formatZuluDateTime($this->getStarred()), |
|
317
|
|
|
'userRating' => $this->getRating() ?: null, |
|
318
|
|
|
'averageRating' => $this->getRating() ?: null, |
|
319
|
|
|
'genre' => empty($this->getGenreId()) ? null : $this->getGenreNameString($l10n), |
|
320
|
|
|
'coverArt' => !$hasCoverArt ? null : 'album-' . $albumId, |
|
321
|
|
|
'playCount' => $this->getPlayCount(), |
|
322
|
|
|
'played' => Util::formatZuluDateTime($this->getLastPlayed()) ?? '', // OpenSubsonic |
|
323
|
|
|
'sortName' => StringUtil::splitPrefixAndBasename($this->getTitle(), $ignoredArticles)['basename'], // OpenSubsonic |
|
324
|
|
|
]; |
|
325
|
|
|
} |
|
326
|
|
|
|
|
327
|
|
|
public function getAdjustedTrackNumber(bool $enablePlaylistNumbering=true) : ?int { |
|
328
|
|
|
// Unless disabled, the number on playlist overrides the track number if it is set. |
|
329
|
|
|
if ($enablePlaylistNumbering && $this->numberOnPlaylist !== null) { |
|
330
|
|
|
$trackNumber = $this->numberOnPlaylist; |
|
331
|
|
|
} else { |
|
332
|
|
|
// On single-disk albums, the track number is given as-is. |
|
333
|
|
|
// On multi-disk albums, the disk-number is applied to the track number. |
|
334
|
|
|
// In case we have no Album reference, the best we can do is to apply the |
|
335
|
|
|
// disk number if it is greater than 1. For disk 1, we don't know if this |
|
336
|
|
|
// is a multi-disk album or not. |
|
337
|
|
|
$numberOfDisks = ($this->album) ? $this->album->getNumberOfDisks() : null; |
|
338
|
|
|
$trackNumber = $this->getNumber(); |
|
339
|
|
|
|
|
340
|
|
|
if ($this->disk > 1 || $numberOfDisks > 1) { |
|
341
|
|
|
$trackNumber = $trackNumber ?: 0; |
|
342
|
|
|
$trackNumber += (100 * $this->disk); |
|
343
|
|
|
} |
|
344
|
|
|
} |
|
345
|
|
|
|
|
346
|
|
|
return $trackNumber; |
|
347
|
|
|
} |
|
348
|
|
|
|
|
349
|
|
|
public function getFileExtension() : string { |
|
350
|
|
|
$parts = Util::explode('.', $this->getFilename()); |
|
351
|
|
|
return empty($parts) ? '' : \end($parts); |
|
352
|
|
|
} |
|
353
|
|
|
|
|
354
|
|
|
/** |
|
355
|
|
|
* Get an instance which has all the mandatory fields set to valid but empty values |
|
356
|
|
|
*/ |
|
357
|
|
|
public static function emptyInstance() : Track { |
|
358
|
|
|
$track = new self(); |
|
359
|
|
|
|
|
360
|
|
|
$track->id = -1; |
|
361
|
|
|
$track->title = ''; |
|
362
|
|
|
$track->artistId = -1; |
|
363
|
|
|
$track->albumId = -1; |
|
364
|
|
|
$track->fileId = -1; |
|
365
|
|
|
$track->mimetype = ''; |
|
366
|
|
|
$track->playCount = 0; |
|
367
|
|
|
$track->dirty = 0; |
|
368
|
|
|
|
|
369
|
|
|
$track->filename = ''; |
|
370
|
|
|
$track->size = 0; |
|
371
|
|
|
$track->fileModTime = 0; |
|
372
|
|
|
$track->folderId = -1; |
|
373
|
|
|
|
|
374
|
|
|
return $track; |
|
375
|
|
|
} |
|
376
|
|
|
} |
|
377
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.