Issues (40)

lib/Db/PodcastEpisode.php (1 issue)

Severity
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 Pauli Järvinen <[email protected]>
10
 * @copyright Pauli Järvinen 2021 - 2025
11
 */
12
13
namespace OCA\Music\Db;
14
15
use OCA\Music\Utility\Util;
16
use OCP\IURLGenerator;
17
18
/**
19
 * @method int getChannelId()
20
 * @method void setChannelId(int $id)
21
 * @method ?string getStreamUrl()
22
 * @method void setStreamUrl(?string $url)
23
 * @method ?string getMimetype()
24
 * @method void setMimetype(?string $mime)
25
 * @method ?int getSize()
26
 * @method void setSize(?int $size)
27
 * @method ?int getDuration()
28
 * @method void setDuration(?int $duration)
29
 * @method string getGuid()
30
 * @method void setGuid(string $guid)
31
 * @method string getGuidHash()
32
 * @method void setGuidHash(string $guidHash)
33
 * @method ?string getTitle()
34
 * @method void setTitle(?string $title)
35
 * @method ?int getEpisode()
36
 * @method void setEpisode(?int $episode)
37
 * @method ?int getSeason()
38
 * @method void setSeason(?int $season)
39
 * @method ?string getLinkUrl()
40
 * @method void setLinkUrl(?string $url)
41
 * @method ?string getPublished()
42
 * @method void setPublished(?string $timestamp)
43
 * @method ?string getKeywords()
44
 * @method void setKeywords(?string $keywords)
45
 * @method ?string getCopyright()
46
 * @method void setCopyright(?string $copyright)
47
 * @method ?string getAuthor()
48
 * @method void setAuthor(?string $author)
49
 * @method ?string getDescription()
50
 * @method void setDescription(?string $description)
51
 * @method ?string getStarred()
52
 * @method void setStarred(?string $timestamp)
53
 * @method int getRating()
54
 * @method void setRating(int $rating)
55
 */
56
class PodcastEpisode extends Entity {
57
	public int $channelId = 0;
58
	public ?string $streamUrl = null;
59
	public ?string $mimetype = null;
60
	public ?int $size = null;
61
	public ?int $duration = null;
62
	public string $guid = '';
63
	public string $guidHash = '';
64
	public ?string $title = null;
65
	public ?int $episode = null;
66
	public ?int $season = null;
67
	public ?string $linkUrl = null;
68
	public ?string $published = null;
69
	public ?string $keywords = null;
70
	public ?string $copyright = null;
71
	public ?string $author = null;
72
	public ?string $description = null;
73
	public ?string $starred = null;
74
	public int $rating = 0;
75
76
	public function __construct() {
77
		$this->addType('channelId', 'int');
78
		$this->addType('size', 'int');
79
		$this->addType('duration', 'int');
80
		$this->addType('episode', 'int');
81
		$this->addType('season', 'int');
82
		$this->addType('rating', 'int');
83
	}
84
85
	public function toApi(IURLGenerator $urlGenerator) : array {
86
		return [
87
			'id' => $this->getId(),
88
			'title' => $this->getTitle(),
89
			'ordinal' => $this->getEpisodeWithSeason(),
90
			'stream_url' => $urlGenerator->linkToRoute('music.podcastApi.episodeStream', ['id' => $this->id]),
91
			'mimetype' => $this->getMimetype()
92
		];
93
	}
94
95
	public function detailsToApi() : array {
96
		return [
97
			'id' => $this->getId(),
98
			'title' => $this->getTitle(),
99
			'episode' => $this->getEpisode(),
100
			'season' => $this->getSeason(),
101
			'description' => $this->getDescription(),
102
			'channel_id' => $this->getChannelId(),
103
			'link_url' => $this->getLinkUrl(),
104
			'stream_url' => $this->getStreamUrl(),
105
			'mimetype' => $this->getMimetype(),
106
			'author' => $this->getAuthor(),
107
			'copyright' => $this->getCopyright(),
108
			'duration' => $this->getDuration(),
109
			'size' => $this->getSize(),
110
			'bit_rate' => $this->getBitrate(),
111
			'guid' => $this->getGuid(),
112
			'keywords' => $this->getKeywords(),
113
			'published' => $this->getPublished(),
114
		];
115
	}
116
117
	public function toAmpacheApi(callable $createImageUrl, ?callable $createStreamUrl) : array {
118
		$imageUrl = $createImageUrl($this);
119
		return [
120
			'id' => (string)$this->getId(),
121
			'name' => $this->getTitle(),
122
			'title' => $this->getTitle(),
123
			'description' => $this->getDescription(),
124
			'author' => $this->getAuthor(),
125
			'author_full' => $this->getAuthor(),
126
			'website' => $this->getLinkUrl(),
127
			'pubdate' => Util::formatDateTimeUtcOffset($this->getPublished()),
128
			'state' => 'Completed',
129
			'filelength' => Util::formatTime($this->getDuration()),
130
			'filesize' => Util::formatFileSize($this->getSize(), 2) . 'B',
131
			'bitrate' => $this->getBitrate(),
132
			'stream_bitrate' => $this->getBitrate(),
133
			'time' => $this->getDuration(),
134
			'size' => $this->getSize(),
135
			'mime' => $this->getMimetype(),
136
			'url' => $createStreamUrl ? $createStreamUrl($this) : $this->getStreamUrl(),
137
			'art' => $imageUrl,
138
			'has_art' => !empty($imageUrl),
139
			'flag' => !empty($this->getStarred()),
140
			'rating' => $this->getRating(),
141
			'preciserating' => $this->getRating(),
142
		];
143
	}
144
145
	public function toSubsonicApi() : array {
146
		return [
147
			'id' => 'podcast_episode-' . $this->getId(),
148
			'streamId' => 'podcast_episode-' . $this->getId(),
149
			'channelId' => 'podcast_channel-' . $this->getChannelId(),
150
			'title' => $this->getTitle(),
151
			'artist' => $this->getAuthor(),
152
			'track' => $this->getEpisode(),
153
			'description' => $this->getDescription(),
154
			'publishDate' => Util::formatZuluDateTime($this->getPublished()),
155
			'status' => 'completed',
156
			'parent' => 'podcast_channel-' . $this->getChannelId(),
157
			'isDir' => false,
158
			'year' => $this->getYear(),
159
			'genre' => 'Podcast',
160
			'coverArt' => 'podcast_channel-' . $this->getChannelId(),
161
			'size' => $this->getSize(),
162
			'contentType' => $this->getMimetype(),
163
			'suffix' => $this->getSuffix(),
164
			'duration' => $this->getDuration(),
165
			'bitRate' => empty($this->getBitrate()) ? 0 : (int)\round($this->getBitrate()/1000), // convert bps to kbps
166
			'type' => 'podcast',
167
			'created' => Util::formatZuluDateTime($this->getCreated()),
168
			'starred' => Util::formatZuluDateTime($this->getStarred()),
169
			'userRating' => $this->getRating() ?: null,
170
			'averageRating' => $this->getRating() ?: null,
171
		];
172
	}
173
174
	public function getEpisodeWithSeason() : ?string {
175
		$result = (string)$this->getEpisode();
176
		// the season is considered only if there actually is an episode
177
		$season = $this->getSeason();
178
		if ($season !== null) {
179
			$result = "$season-$result";
180
		}
181
		return $result;
182
	}
183
184
	public function getYear() : ?int {
185
		$matches = null;
186
		if (\is_string($this->published) && \preg_match('/^(\d\d\d\d)-\d\d-\d\d.*/', $this->published, $matches) === 1) {
187
			return (int)$matches[1];
188
		} else {
189
			return null;
190
		}
191
	}
192
193
	/** @return ?float bits per second (bps) */
194
	public function getBitrate() : ?float {
195
		if (empty($this->size) || empty($this->duration)) {
196
			return null;
197
		} else {
198
			return $this->size / $this->duration * 8;
199
		}
200
	}
201
202
	public function getSuffix() : ?string {
203
		return self::mimeToSuffix($this->mimetype) ?? self::extractSuffixFromUrl($this->streamUrl);
204
	}
205
206
	private static function mimeToSuffix(?string $mime) : ?string {
207
		// a relevant subset from https://stackoverflow.com/a/53662733/4348850 wit a few additions
208
		$mime_map = [
209
			'audio/x-acc'					=> 'aac',
210
			'audio/ac3'						=> 'ac3',
211
			'audio/x-aiff'					=> 'aif',
212
			'audio/aiff'					=> 'aif',
213
			'audio/x-au'					=> 'au',
214
			'audio/x-flac'					=> 'flac',
215
			'audio/x-m4a'					=> 'm4a',
216
			'audio/mp4'						=> 'm4a',
217
			'audio/midi'					=> 'mid',
218
			'audio/mpeg'					=> 'mp3',
219
			'audio/mpg'						=> 'mp3',
220
			'audio/mpeg3'					=> 'mp3',
221
			'audio/mp3'						=> 'mp3',
222
			'audio/ogg'						=> 'ogg',
223
			'application/ogg'				=> 'ogg',
224
			'audio/x-realaudio'				=> 'ra',
225
			'audio/x-pn-realaudio'			=> 'ram',
226
			'audio/x-wav'					=> 'wav',
227
			'audio/wave'					=> 'wav',
228
			'audio/wav'						=> 'wav',
229
			'audio/x-ms-wma'				=> 'wma',
230
			'audio/m4b'						=> 'm4b',
231
			'application/vnd.apple.mpegurl'	=> 'm3u',
232
			'audio/mpegurl'					=> 'm3u',
233
		];
234
235
		return $mime_map[$mime] ?? null;
236
	}
237
238
	private static function extractSuffixFromUrl(?string $url) : ?string {
239
		if ($url === null) {
240
			return null;
241
		} else {
242
			$path = \parse_url($url, PHP_URL_PATH);
243
			if (\is_string($path)) {
0 ignored issues
show
The condition is_string($path) is always true.
Loading history...
244
				$ext = (string)\pathinfo($path, PATHINFO_EXTENSION);
245
				return !empty($ext) ? $ext : null;
246
			} else {
247
				return null;
248
			}
249
		}
250
	}
251
}
252