Passed
Push — master ( 516f42...c66c88 )
by Pauli
03:14
created

Track::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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