jellyfin music :)

This commit is contained in:
Lars Behrends
2025-11-10 05:06:26 +01:00
parent 0530f00cbf
commit aed9d87c5c
10 changed files with 1994 additions and 94 deletions

235
app/Models/MusicAlbum.php Normal file
View File

@@ -0,0 +1,235 @@
<?php
namespace App\Models;
class MusicAlbum extends Model
{
protected string $table = 'music_albums';
protected array $fillable = [
'title',
'artist_name',
'release_date',
'genre',
'track_count',
'total_duration_seconds',
'cover_url',
'spotify_id',
'musicbrainz_id',
'is_favorite',
'metadata',
'artist_id',
'source_id'
];
protected array $casts = [
'release_date' => 'date',
'track_count' => 'int',
'total_duration_seconds' => 'int',
'is_favorite' => 'bool'
];
public function source()
{
$stmt = $this->pdo->prepare("SELECT * FROM sources WHERE id = :source_id");
$stmt->execute(['source_id' => $this->source_id]);
$sourceData = $stmt->fetch(\PDO::FETCH_ASSOC);
return $sourceData ? new Source($this->pdo, $sourceData) : null;
}
/**
* Get the artist for this album
*/
public function artist()
{
$stmt = $this->pdo->prepare("SELECT * FROM music_artists WHERE id = :artist_id");
$stmt->execute(['artist_id' => $this->artist_id]);
$artistData = $stmt->fetch(\PDO::FETCH_ASSOC);
return $artistData ? new MusicArtist($this->pdo, $artistData) : null;
}
/**
* Get all tracks for this album
*/
public function tracks()
{
$stmt = $this->pdo->prepare("
SELECT * FROM music_tracks
WHERE album_id = :album_id
ORDER BY track_number ASC, title ASC
");
$stmt->execute(['album_id' => $this->id]);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Toggle favorite status
*/
public function toggleFavorite(): bool
{
return $this->update($this->id, [
'is_favorite' => !$this->is_favorite
]);
}
/**
* Get total count of albums with optional filters
*/
public static function getTotalCount(\PDO $pdo, string $search = '', array $genres = [], array $artists = []): int
{
$where = [];
$params = [];
$sql = "SELECT COUNT(*) as count FROM music_albums ma JOIN sources s ON ma.source_id = s.id";
if (!empty($search)) {
$where[] = "(ma.title LIKE :search OR ma.artist_name LIKE :search)";
$params[':search'] = "%$search%";
}
if (!empty($genres)) {
$genreConditions = [];
foreach ($genres as $i => $genre) {
$param = ":genre_$i";
$genreConditions[] = "ma.genre LIKE $param";
$params[$param] = "%$genre%";
}
$where[] = "(" . implode(' OR ', $genreConditions) . ")";
}
if (!empty($artists)) {
$artistConditions = [];
foreach ($artists as $i => $artist) {
$param = ":artist_$i";
$artistConditions[] = "ma.artist_name LIKE $param";
$params[$param] = "%$artist%";
}
$where[] = "(" . implode(' OR ', $artistConditions) . ")";
}
if (!empty($where)) {
$sql .= " WHERE " . implode(' AND ', $where);
}
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
return (int)$stmt->fetchColumn();
}
/**
* Get paginated albums with optional filters
*/
public static function getAllWithPagination(
\PDO $pdo,
int $page = 1,
int $perPage = 20,
string $search = '',
array $genres = [],
array $artists = [],
string $sort = 'title_asc'
): array {
$offset = ($page - 1) * $perPage;
$where = [];
$params = [];
if (!empty($search)) {
$where[] = "(title LIKE :search OR artist_name LIKE :search)";
$params[':search'] = "%$search%";
}
if (!empty($genres)) {
$genreConditions = [];
foreach ($genres as $i => $genre) {
$param = ":genre$i";
$genreConditions[] = "genre LIKE $param";
$params[$param] = "%$genre%";
}
$where[] = "(" . implode(' OR ', $genreConditions) . ")";
}
if (!empty($artists)) {
$artistConditions = [];
foreach ($artists as $i => $artist) {
$param = ":artist$i";
$artistConditions[] = "artist_name LIKE $param";
$params[$param] = "%$artist%";
}
$where[] = "(" . implode(' OR ', $artistConditions) . ")";
}
// Determine sort order
$orderBy = 'title ASC';
switch ($sort) {
case 'title_desc':
$orderBy = 'title DESC';
break;
case 'artist_asc':
$orderBy = 'artist_name ASC';
break;
case 'artist_desc':
$orderBy = 'artist_name DESC';
break;
case 'release_asc':
$orderBy = 'release_date ASC';
break;
case 'release_desc':
$orderBy = 'release_date DESC';
break;
}
$sql = "SELECT ma.*, s.display_name as source_name FROM music_albums ma JOIN sources s ON ma.source_id = s.id";
if (!empty($where)) {
$sql .= " WHERE " . implode(' AND ', $where);
}
$sql .= " ORDER BY $orderBy LIMIT :limit OFFSET :offset";
$stmt = $pdo->prepare($sql);
// Bind parameters
foreach ($params as $key => $value) {
$stmt->bindValue($key, $value);
}
$stmt->bindValue(':limit', $perPage, \PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, \PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Get all unique genres from albums
*/
public static function getAvailableGenres(\PDO $pdo): array
{
$stmt = $pdo->query("SELECT DISTINCT genre FROM music_albums WHERE genre IS NOT NULL AND genre != '' ORDER BY genre");
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
}
/**
* Get all unique artists from albums
*/
public static function getAvailableArtists(\PDO $pdo): array
{
$stmt = $pdo->query("SELECT DISTINCT artist_name FROM music_albums WHERE artist_name IS NOT NULL AND artist_name != '' ORDER BY artist_name");
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
}
/**
* Get album statistics
*/
public static function getStats(\PDO $pdo): array
{
$stmt = $pdo->query("
SELECT
COUNT(*) as total_albums,
COUNT(CASE WHEN is_favorite = 1 THEN 1 END) as favorite_albums,
SUM(track_count) as total_tracks,
SUM(total_duration_seconds) as total_duration
FROM music_albums
");
return $stmt->fetch(\PDO::FETCH_ASSOC);
}
}

197
app/Models/MusicArtist.php Normal file
View File

@@ -0,0 +1,197 @@
<?php
namespace App\Models;
class MusicArtist extends Model
{
protected string $table = 'music_artists';
protected array $fillable = [
'name',
'biography',
'formed_date',
'genre',
'country',
'image_url',
'banner_url',
'spotify_id',
'musicbrainz_id',
'is_favorite',
'metadata',
'source_id'
];
protected array $casts = [
'formed_date' => 'date',
'is_favorite' => 'bool'
];
public function source()
{
$stmt = $this->pdo->prepare("SELECT * FROM sources WHERE id = :source_id");
$stmt->execute(['source_id' => $this->source_id]);
$sourceData = $stmt->fetch(\PDO::FETCH_ASSOC);
return $sourceData ? new Source($this->pdo, $sourceData) : null;
}
/**
* Get all albums by this artist
*/
public function albums()
{
$stmt = $this->pdo->prepare("
SELECT * FROM music_albums
WHERE artist_id = :artist_id
ORDER BY release_date DESC, title ASC
");
$stmt->execute(['artist_id' => $this->id]);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Get all tracks by this artist
*/
public function tracks()
{
$stmt = $this->pdo->prepare("
SELECT * FROM music_tracks
WHERE artist_id = :artist_id
ORDER BY album_name ASC, track_number ASC, title ASC
");
$stmt->execute(['artist_id' => $this->id]);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Toggle favorite status
*/
public function toggleFavorite(): bool
{
return $this->update($this->id, [
'is_favorite' => !$this->is_favorite
]);
}
/**
* Get total count of artists with optional filters
*/
public static function getTotalCount(\PDO $pdo, string $search = '', array $genres = []): int
{
$where = [];
$params = [];
$sql = "SELECT COUNT(*) as count FROM music_artists ma JOIN sources s ON ma.source_id = s.id";
if (!empty($search)) {
$where[] = "(ma.name LIKE :search OR ma.biography LIKE :search)";
$params[':search'] = "%$search%";
}
if (!empty($genres)) {
$genreConditions = [];
foreach ($genres as $i => $genre) {
$param = ":genre_$i";
$genreConditions[] = "ma.genre LIKE $param";
$params[$param] = "%$genre%";
}
$where[] = "(" . implode(' OR ', $genreConditions) . ")";
}
if (!empty($where)) {
$sql .= " WHERE " . implode(' AND ', $where);
}
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
return (int)$stmt->fetchColumn();
}
/**
* Get paginated artists with optional filters
*/
public static function getAllWithPagination(
\PDO $pdo,
int $page = 1,
int $perPage = 20,
string $search = '',
array $genres = [],
string $sort = 'name_asc'
): array {
$offset = ($page - 1) * $perPage;
$where = [];
$params = [];
if (!empty($search)) {
$where[] = "(name LIKE :search OR biography LIKE :search)";
$params[':search'] = "%$search%";
}
if (!empty($genres)) {
$genreConditions = [];
foreach ($genres as $i => $genre) {
$param = ":genre$i";
$genreConditions[] = "genre LIKE $param";
$params[$param] = "%$genre%";
}
$where[] = "(" . implode(' OR ', $genreConditions) . ")";
}
// Determine sort order
$orderBy = 'name ASC';
switch ($sort) {
case 'name_desc':
$orderBy = 'name DESC';
break;
case 'formed_asc':
$orderBy = 'formed_date ASC';
break;
case 'formed_desc':
$orderBy = 'formed_date DESC';
break;
}
$sql = "SELECT ma.*, s.display_name as source_name FROM music_artists ma JOIN sources s ON ma.source_id = s.id";
if (!empty($where)) {
$sql .= " WHERE " . implode(' AND ', $where);
}
$sql .= " ORDER BY $orderBy LIMIT :limit OFFSET :offset";
$stmt = $pdo->prepare($sql);
// Bind parameters
foreach ($params as $key => $value) {
$stmt->bindValue($key, $value);
}
$stmt->bindValue(':limit', $perPage, \PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, \PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Get all unique genres from artists
*/
public static function getAvailableGenres(\PDO $pdo): array
{
$stmt = $pdo->query("SELECT DISTINCT genre FROM music_artists WHERE genre IS NOT NULL AND genre != '' ORDER BY genre");
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
}
/**
* Get artist statistics
*/
public static function getStats(\PDO $pdo): array
{
$stmt = $pdo->query("
SELECT
COUNT(*) as total_artists,
COUNT(CASE WHEN is_favorite = 1 THEN 1 END) as favorite_artists,
COUNT(DISTINCT genre) as total_genres
FROM music_artists
");
return $stmt->fetch(\PDO::FETCH_ASSOC);
}
}

327
app/Models/MusicTrack.php Normal file
View File

@@ -0,0 +1,327 @@
<?php
namespace App\Models;
class MusicTrack extends Model
{
protected string $table = 'music_tracks';
protected array $fillable = [
'title',
'artist_name',
'album_name',
'track_number',
'duration_seconds',
'genre',
'release_date',
'play_count',
'is_favorite',
'metadata',
'artist_id',
'album_id',
'source_id',
'last_played_at'
];
protected array $casts = [
'track_number' => 'int',
'duration_seconds' => 'int',
'play_count' => 'int',
'is_favorite' => 'bool',
'release_date' => 'date',
'last_played_at' => 'datetime'
];
public function source()
{
$stmt = $this->pdo->prepare("SELECT * FROM sources WHERE id = :source_id");
$stmt->execute(['source_id' => $this->source_id]);
$sourceData = $stmt->fetch(\PDO::FETCH_ASSOC);
return $sourceData ? new Source($this->pdo, $sourceData) : null;
}
/**
* Get the artist for this track
*/
public function artist()
{
$stmt = $this->pdo->prepare("SELECT * FROM music_artists WHERE id = :artist_id");
$stmt->execute(['artist_id' => $this->artist_id]);
$artistData = $stmt->fetch(\PDO::FETCH_ASSOC);
return $artistData ? new MusicArtist($this->pdo, $artistData) : null;
}
/**
* Get the album for this track
*/
public function album()
{
if (!$this->album_id) {
return null;
}
$stmt = $this->pdo->prepare("SELECT * FROM music_albums WHERE id = :album_id");
$stmt->execute(['album_id' => $this->album_id]);
$albumData = $stmt->fetch(\PDO::FETCH_ASSOC);
return $albumData ? new MusicAlbum($this->pdo, $albumData) : null;
}
/**
* Toggle favorite status
*/
public function toggleFavorite(): bool
{
return $this->update($this->id, [
'is_favorite' => !$this->is_favorite
]);
}
/**
* Increment play count and update last played time
*/
public function markAsPlayed(): bool
{
return $this->update($this->id, [
'play_count' => $this->play_count + 1,
'last_played_at' => date('Y-m-d H:i:s')
]);
}
/**
* Get total count of tracks with optional filters
*/
public static function getTotalCount(\PDO $pdo, string $search = '', array $genres = [], array $artists = [], array $albums = []): int
{
$where = [];
$params = [];
$sql = "SELECT COUNT(*) as count FROM music_tracks mt JOIN sources s ON mt.source_id = s.id";
if (!empty($search)) {
$where[] = "(mt.title LIKE :search OR mt.artist_name LIKE :search OR mt.album_name LIKE :search)";
$params[':search'] = "%$search%";
}
if (!empty($genres)) {
$genreConditions = [];
foreach ($genres as $i => $genre) {
$param = ":genre_$i";
$genreConditions[] = "mt.genre LIKE $param";
$params[$param] = "%$genre%";
}
$where[] = "(" . implode(' OR ', $genreConditions) . ")";
}
if (!empty($artists)) {
$artistConditions = [];
foreach ($artists as $i => $artist) {
$param = ":artist_$i";
$artistConditions[] = "mt.artist_name LIKE $param";
$params[$param] = "%$artist%";
}
$where[] = "(" . implode(' OR ', $artistConditions) . ")";
}
if (!empty($albums)) {
$albumConditions = [];
foreach ($albums as $i => $album) {
$param = ":album_$i";
$albumConditions[] = "mt.album_name LIKE $param";
$params[$param] = "%$album%";
}
$where[] = "(" . implode(' OR ', $albumConditions) . ")";
}
if (!empty($where)) {
$sql .= " WHERE " . implode(' AND ', $where);
}
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
return (int)$stmt->fetchColumn();
}
/**
* Get paginated tracks with optional filters
*/
public static function getAllWithPagination(
\PDO $pdo,
int $page = 1,
int $perPage = 20,
string $search = '',
array $genres = [],
array $artists = [],
array $albums = [],
string $sort = 'title_asc'
): array {
$offset = ($page - 1) * $perPage;
$where = [];
$params = [];
if (!empty($search)) {
$where[] = "(title LIKE :search OR artist_name LIKE :search OR album_name LIKE :search)";
$params[':search'] = "%$search%";
}
if (!empty($genres)) {
$genreConditions = [];
foreach ($genres as $i => $genre) {
$param = ":genre$i";
$genreConditions[] = "genre LIKE $param";
$params[$param] = "%$genre%";
}
$where[] = "(" . implode(' OR ', $genreConditions) . ")";
}
if (!empty($artists)) {
$artistConditions = [];
foreach ($artists as $i => $artist) {
$param = ":artist$i";
$artistConditions[] = "artist_name LIKE $param";
$params[$param] = "%$artist%";
}
$where[] = "(" . implode(' OR ', $artistConditions) . ")";
}
if (!empty($albums)) {
$albumConditions = [];
foreach ($albums as $i => $album) {
$param = ":album$i";
$albumConditions[] = "album_name LIKE $param";
$params[$param] = "%$album%";
}
$where[] = "(" . implode(' OR ', $albumConditions) . ")";
}
// Determine sort order
$orderBy = 'title ASC';
switch ($sort) {
case 'title_desc':
$orderBy = 'title DESC';
break;
case 'artist_asc':
$orderBy = 'artist_name ASC';
break;
case 'artist_desc':
$orderBy = 'artist_name DESC';
break;
case 'album_asc':
$orderBy = 'album_name ASC';
break;
case 'album_desc':
$orderBy = 'album_name DESC';
break;
case 'duration_asc':
$orderBy = 'duration_seconds ASC';
break;
case 'duration_desc':
$orderBy = 'duration_seconds DESC';
break;
case 'play_count_desc':
$orderBy = 'play_count DESC';
break;
case 'last_played_desc':
$orderBy = 'last_played_at DESC NULLS LAST';
break;
}
$sql = "SELECT mt.*, s.display_name as source_name FROM music_tracks mt JOIN sources s ON mt.source_id = s.id";
if (!empty($where)) {
$sql .= " WHERE " . implode(' AND ', $where);
}
$sql .= " ORDER BY $orderBy LIMIT :limit OFFSET :offset";
$stmt = $pdo->prepare($sql);
// Bind parameters
foreach ($params as $key => $value) {
$stmt->bindValue($key, $value);
}
$stmt->bindValue(':limit', $perPage, \PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, \PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Get all unique genres from tracks
*/
public static function getAvailableGenres(\PDO $pdo): array
{
$stmt = $pdo->query("SELECT DISTINCT genre FROM music_tracks WHERE genre IS NOT NULL AND genre != '' ORDER BY genre");
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
}
/**
* Get all unique artists from tracks
*/
public static function getAvailableArtists(\PDO $pdo): array
{
$stmt = $pdo->query("SELECT DISTINCT artist_name FROM music_tracks WHERE artist_name IS NOT NULL AND artist_name != '' ORDER BY artist_name");
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
}
/**
* Get all unique albums from tracks
*/
public static function getAvailableAlbums(\PDO $pdo): array
{
$stmt = $pdo->query("SELECT DISTINCT album_name FROM music_tracks WHERE album_name IS NOT NULL AND album_name != '' ORDER BY album_name");
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
}
/**
* Get track statistics
*/
public static function getStats(\PDO $pdo): array
{
$stmt = $pdo->query("
SELECT
COUNT(*) as total_tracks,
COUNT(CASE WHEN is_favorite = 1 THEN 1 END) as favorite_tracks,
SUM(play_count) as total_plays,
SUM(duration_seconds) as total_duration,
AVG(duration_seconds) as avg_duration
FROM music_tracks
");
return $stmt->fetch(\PDO::FETCH_ASSOC);
}
/**
* Get recently played tracks
*/
public static function getRecentlyPlayed(\PDO $pdo, int $limit = 10): array
{
$stmt = $pdo->prepare("
SELECT mt.*, s.display_name as source_name
FROM music_tracks mt
JOIN sources s ON mt.source_id = s.id
WHERE mt.last_played_at IS NOT NULL
ORDER BY mt.last_played_at DESC
LIMIT :limit
");
$stmt->execute(['limit' => $limit]);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Get most played tracks
*/
public static function getMostPlayed(\PDO $pdo, int $limit = 10): array
{
$stmt = $pdo->prepare("
SELECT mt.*, s.display_name as source_name
FROM music_tracks mt
JOIN sources s ON mt.source_id = s.id
ORDER BY mt.play_count DESC, mt.title ASC
LIMIT :limit
");
$stmt->execute(['limit' => $limit]);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
}