mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
jellyfin music :)
This commit is contained in:
@@ -472,7 +472,7 @@ class AdminController extends AdminBaseController
|
||||
|
||||
// Validate sync type based on source type
|
||||
if ($source['name'] === 'jellyfin') {
|
||||
$validSyncTypes = ['full', 'incremental', 'all', 'movies', 'tvshows', 'cleanup'];
|
||||
$validSyncTypes = ['full', 'incremental', 'all', 'movies', 'tvshows', 'music', 'cleanup'];
|
||||
if (!in_array($syncType, $validSyncTypes)) {
|
||||
return $this->json($response, [
|
||||
'success' => false,
|
||||
|
||||
@@ -27,21 +27,43 @@ class MusicController extends Controller
|
||||
// Get search parameters
|
||||
$search = trim($queryParams['search'] ?? '');
|
||||
|
||||
// Get filter parameters
|
||||
$genres = $queryParams['genres'] ?? [];
|
||||
if (!is_array($genres)) {
|
||||
$genres = [$genres];
|
||||
}
|
||||
$genres = array_filter($genres);
|
||||
|
||||
$artists = $queryParams['artists'] ?? [];
|
||||
if (!is_array($artists)) {
|
||||
$artists = [$artists];
|
||||
}
|
||||
$artists = array_filter($artists);
|
||||
|
||||
// Get view mode
|
||||
$viewMode = $queryParams['view'] ?? 'grid'; // grid, list, covers
|
||||
|
||||
// For now, return empty arrays since Music isn't implemented yet
|
||||
$music = [];
|
||||
$totalCount = 0;
|
||||
// Get sort parameter
|
||||
$sort = $queryParams['sort'] ?? 'title_asc';
|
||||
|
||||
// Get albums with pagination, filters, and sorting
|
||||
$albums = \App\Models\MusicAlbum::getAllWithPagination($this->pdo, $page, $perPage, $search, $genres, $artists, $sort);
|
||||
|
||||
// Get total count for pagination
|
||||
$totalCount = \App\Models\MusicAlbum::getTotalCount($this->pdo, $search, $genres, $artists);
|
||||
|
||||
// Get available filter options
|
||||
$availableGenres = \App\Models\MusicAlbum::getAvailableGenres($this->pdo);
|
||||
$availableArtists = \App\Models\MusicAlbum::getAvailableArtists($this->pdo);
|
||||
|
||||
// Calculate pagination info
|
||||
$totalPages = 0;
|
||||
$hasNextPage = false;
|
||||
$hasPrevPage = false;
|
||||
$totalPages = ceil($totalCount / $perPage);
|
||||
$hasNextPage = $page < $totalPages;
|
||||
$hasPrevPage = $page > 1;
|
||||
|
||||
return $this->view->render($response, 'music/index.twig', [
|
||||
'title' => 'Music',
|
||||
'music' => $music,
|
||||
'albums' => $albums,
|
||||
'pagination' => [
|
||||
'current_page' => $page,
|
||||
'per_page' => $perPage,
|
||||
@@ -54,19 +76,79 @@ class MusicController extends Controller
|
||||
],
|
||||
'search' => $search,
|
||||
'view_mode' => $viewMode,
|
||||
'view_modes' => ['grid', 'list', 'covers']
|
||||
'view_modes' => ['grid', 'list', 'covers'],
|
||||
'filters' => [
|
||||
'genres' => $genres,
|
||||
'artists' => $artists
|
||||
],
|
||||
'available_filters' => [
|
||||
'genres' => $availableGenres,
|
||||
'artists' => $availableArtists
|
||||
],
|
||||
'sort' => $sort,
|
||||
'sort_options' => [
|
||||
'title_asc' => 'Title (A-Z)',
|
||||
'title_desc' => 'Title (Z-A)',
|
||||
'artist_asc' => 'Artist (A-Z)',
|
||||
'artist_desc' => 'Artist (Z-A)',
|
||||
'release_desc' => 'Release Date (Newest First)',
|
||||
'release_asc' => 'Release Date (Oldest First)',
|
||||
'added_desc' => 'Recently Added',
|
||||
'added_asc' => 'Oldest Added'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(Request $request, Response $response, $args)
|
||||
{
|
||||
$musicId = (int) $args['id'];
|
||||
$albumId = (int) $args['id'];
|
||||
|
||||
// Get album details
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT ma.*, s.display_name as source_name
|
||||
FROM music_albums ma
|
||||
JOIN sources s ON ma.source_id = s.id
|
||||
WHERE ma.id = :id
|
||||
");
|
||||
$stmt->execute(['id' => $albumId]);
|
||||
$album = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$album) {
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
// Decode metadata for display
|
||||
$metadata = json_decode($album['metadata'], true);
|
||||
|
||||
// Get tracks for this album
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT mt.*
|
||||
FROM music_tracks mt
|
||||
WHERE mt.album_id = :album_id
|
||||
ORDER BY mt.track_number ASC, mt.title ASC
|
||||
");
|
||||
$stmt->execute(['album_id' => $albumId]);
|
||||
$tracks = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// Get artist information
|
||||
$artist = null;
|
||||
if ($album['artist_id']) {
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT ma.*, s.display_name as source_name
|
||||
FROM music_artists ma
|
||||
JOIN sources s ON ma.source_id = s.id
|
||||
WHERE ma.id = :artist_id
|
||||
");
|
||||
$stmt->execute(['artist_id' => $album['artist_id']]);
|
||||
$artist = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
// For now, return a placeholder since Music isn't implemented yet
|
||||
return $this->view->render($response, 'music/show.twig', [
|
||||
'title' => 'Music Details',
|
||||
'music' => ['id' => $musicId, 'title' => 'Coming Soon'],
|
||||
'message' => 'Music details page is not yet implemented.'
|
||||
'title' => $album['title'],
|
||||
'album' => $album,
|
||||
'tracks' => $tracks,
|
||||
'artist' => $artist,
|
||||
'metadata' => $metadata
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
235
app/Models/MusicAlbum.php
Normal file
235
app/Models/MusicAlbum.php
Normal 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
197
app/Models/MusicArtist.php
Normal 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
327
app/Models/MusicTrack.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,9 @@ namespace App\Services;
|
||||
use App\Models\Movie;
|
||||
use App\Models\TvShow;
|
||||
use App\Models\TvEpisode;
|
||||
use App\Models\MusicArtist;
|
||||
use App\Models\MusicAlbum;
|
||||
use App\Models\MusicTrack;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Exception;
|
||||
@@ -94,8 +97,19 @@ class JellyfinSyncService extends BaseSyncService
|
||||
$this->logProgress('Skipping TV shows sync (sync type: ' . $syncType . ')');
|
||||
}
|
||||
|
||||
// Sync music (artists, albums, tracks) - TODO: Implement when music models are created
|
||||
// $this->syncMusic();
|
||||
// Sync music (artists, albums, tracks) if requested
|
||||
if (in_array($syncType, ['all', 'music'])) {
|
||||
try {
|
||||
$this->syncMusic();
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress('Error syncing music: ' . $e->getMessage());
|
||||
if ($syncType === 'music') {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->logProgress('Skipping music sync (sync type: ' . $syncType . ')');
|
||||
}
|
||||
|
||||
$this->logProgress("Processed {$this->processedCount} items");
|
||||
}
|
||||
@@ -519,6 +533,299 @@ class JellyfinSyncService extends BaseSyncService
|
||||
$this->logProgress("--- Completed sync for episode: {$episodeName} ---");
|
||||
}
|
||||
|
||||
private function syncMusic(): void
|
||||
{
|
||||
try {
|
||||
$this->logProgress('=== Starting Music Sync ===');
|
||||
|
||||
// Sync artists first
|
||||
$this->logProgress('Fetching music artists from Jellyfin...');
|
||||
$artists = $this->getJellyfinItems('MusicArtist');
|
||||
$artistCount = count($artists);
|
||||
$this->logProgress("Found {$artistCount} artists in Jellyfin");
|
||||
|
||||
$processedArtists = 0;
|
||||
$successfulArtists = 0;
|
||||
$failedArtists = 0;
|
||||
|
||||
foreach ($artists as $artistData) {
|
||||
$processedArtists++;
|
||||
$artistName = $artistData['Name'] ?? 'Unknown Artist';
|
||||
$this->logProgress("Processing artist {$processedArtists}/{$artistCount}: {$artistName}");
|
||||
|
||||
try {
|
||||
$this->syncMusicArtist($artistData);
|
||||
$successfulArtists++;
|
||||
$this->logProgress("✓ Successfully synced artist: {$artistName}");
|
||||
} catch (Exception $e) {
|
||||
$failedArtists++;
|
||||
$this->logProgress("✗ Failed to sync artist {$artistName}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$this->logProgress("Artists sync summary: {$successfulArtists} successful, {$failedArtists} failed");
|
||||
|
||||
// Sync albums
|
||||
$this->logProgress('Fetching music albums from Jellyfin...');
|
||||
$albums = $this->getJellyfinItems('MusicAlbum');
|
||||
$albumCount = count($albums);
|
||||
$this->logProgress("Found {$albumCount} albums in Jellyfin");
|
||||
|
||||
$processedAlbums = 0;
|
||||
$successfulAlbums = 0;
|
||||
$failedAlbums = 0;
|
||||
|
||||
foreach ($albums as $albumData) {
|
||||
$processedAlbums++;
|
||||
$albumName = $albumData['Name'] ?? 'Unknown Album';
|
||||
$this->logProgress("Processing album {$processedAlbums}/{$albumCount}: {$albumName}");
|
||||
|
||||
try {
|
||||
$this->syncMusicAlbum($albumData);
|
||||
$successfulAlbums++;
|
||||
$this->logProgress("✓ Successfully synced album: {$albumName}");
|
||||
} catch (Exception $e) {
|
||||
$failedAlbums++;
|
||||
$this->logProgress("✗ Failed to sync album {$albumName}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$this->logProgress("Albums sync summary: {$successfulAlbums} successful, {$failedAlbums} failed");
|
||||
|
||||
// Sync tracks
|
||||
$this->logProgress('Fetching music tracks from Jellyfin...');
|
||||
$tracks = $this->getJellyfinItems('Audio');
|
||||
$trackCount = count($tracks);
|
||||
$this->logProgress("Found {$trackCount} tracks in Jellyfin");
|
||||
|
||||
$processedTracks = 0;
|
||||
$successfulTracks = 0;
|
||||
$failedTracks = 0;
|
||||
|
||||
foreach ($tracks as $trackData) {
|
||||
$processedTracks++;
|
||||
$trackName = $trackData['Name'] ?? 'Unknown Track';
|
||||
$this->logProgress("Processing track {$processedTracks}/{$trackCount}: {$trackName}");
|
||||
|
||||
try {
|
||||
$this->syncMusicTrack($trackData);
|
||||
$successfulTracks++;
|
||||
$this->logProgress("✓ Successfully synced track: {$trackName}");
|
||||
} catch (Exception $e) {
|
||||
$failedTracks++;
|
||||
$this->logProgress("✗ Failed to sync track {$trackName}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$this->logProgress("Tracks sync summary: {$successfulTracks} successful, {$failedTracks} failed");
|
||||
$this->logProgress("=== Music Sync Completed ===");
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->logProgress('CRITICAL ERROR in music sync: ' . $e->getMessage());
|
||||
$this->logProgress('Stack trace: ' . $e->getTraceAsString());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function syncMusicArtist(array $artistData): void
|
||||
{
|
||||
$artistModel = new MusicArtist($this->pdo);
|
||||
|
||||
// Check if artist already exists
|
||||
$existingArtist = $artistModel->findAll([
|
||||
'name' => $artistData['Name'],
|
||||
'source_id' => $this->source['id']
|
||||
]);
|
||||
|
||||
$artistDataForDb = [
|
||||
'name' => $artistData['Name'],
|
||||
'biography' => $artistData['Overview'] ?? null,
|
||||
'formed_date' => isset($artistData['PremiereDate']) ? date('Y-m-d', strtotime($artistData['PremiereDate'])) : null,
|
||||
'genre' => isset($artistData['Genres'][0]) ? $artistData['Genres'][0] : null,
|
||||
'country' => null, // Jellyfin doesn't provide country info
|
||||
'image_url' => $this->getImageUrl($artistData['Id'], 'Primary'),
|
||||
'banner_url' => $this->getImageUrl($artistData['Id'], 'Backdrop'),
|
||||
'spotify_id' => $artistData['ProviderIds']['MusicBrainzArtist'] ?? null,
|
||||
'musicbrainz_id' => $artistData['ProviderIds']['MusicBrainzArtist'] ?? null,
|
||||
'source_id' => $this->source['id'],
|
||||
'metadata' => json_encode([
|
||||
'jellyfin_id' => $artistData['Id']
|
||||
])
|
||||
];
|
||||
|
||||
if (empty($existingArtist)) {
|
||||
$artistModel->create($artistDataForDb);
|
||||
$this->newCount++;
|
||||
} else {
|
||||
$artistModel->update($existingArtist[0]['id'], $artistDataForDb);
|
||||
$this->updatedCount++;
|
||||
}
|
||||
|
||||
$this->processedCount++;
|
||||
}
|
||||
|
||||
private function syncMusicAlbum(array $albumData): void
|
||||
{
|
||||
$albumModel = new MusicAlbum($this->pdo);
|
||||
|
||||
// Get or create artist first
|
||||
$artistId = null;
|
||||
if (isset($albumData['AlbumArtists']) && !empty($albumData['AlbumArtists'])) {
|
||||
$artistName = $albumData['AlbumArtists'][0]['Name'];
|
||||
$artistId = $this->getOrCreateMusicArtist($artistName);
|
||||
}
|
||||
|
||||
// Check if album already exists
|
||||
$existingAlbum = $albumModel->findAll([
|
||||
'title' => $albumData['Name'],
|
||||
'artist_id' => $artistId,
|
||||
'source_id' => $this->source['id']
|
||||
]);
|
||||
|
||||
// Download cover image
|
||||
$coverPath = $this->downloadPosterImage($albumData['Id'], $albumData['Name']);
|
||||
if (!$coverPath) {
|
||||
$coverPath = $this->getImageUrl($albumData['Id'], 'Primary');
|
||||
}
|
||||
|
||||
$albumDataForDb = [
|
||||
'title' => $albumData['Name'],
|
||||
'artist_name' => isset($albumData['AlbumArtists'][0]['Name']) ? $albumData['AlbumArtists'][0]['Name'] : 'Unknown Artist',
|
||||
'release_date' => isset($albumData['PremiereDate']) ? date('Y-m-d', strtotime($albumData['PremiereDate'])) : null,
|
||||
'genre' => isset($albumData['Genres'][0]) ? $albumData['Genres'][0] : null,
|
||||
'track_count' => 0, // Will be updated when tracks are synced
|
||||
'total_duration_seconds' => 0, // Will be updated when tracks are synced
|
||||
'cover_url' => $coverPath,
|
||||
'spotify_id' => $albumData['ProviderIds']['MusicBrainzAlbum'] ?? null,
|
||||
'musicbrainz_id' => $albumData['ProviderIds']['MusicBrainzAlbum'] ?? null,
|
||||
'artist_id' => $artistId,
|
||||
'source_id' => $this->source['id'],
|
||||
'metadata' => json_encode([
|
||||
'jellyfin_id' => $albumData['Id']
|
||||
])
|
||||
];
|
||||
|
||||
if (empty($existingAlbum)) {
|
||||
$albumModel->create($albumDataForDb);
|
||||
$this->newCount++;
|
||||
} else {
|
||||
$albumModel->update($existingAlbum[0]['id'], $albumDataForDb);
|
||||
$this->updatedCount++;
|
||||
}
|
||||
|
||||
$this->processedCount++;
|
||||
}
|
||||
|
||||
private function syncMusicTrack(array $trackData): void
|
||||
{
|
||||
$trackModel = new MusicTrack($this->pdo);
|
||||
|
||||
// Get or create artist
|
||||
$artistId = null;
|
||||
if (isset($trackData['AlbumArtists']) && !empty($trackData['AlbumArtists'])) {
|
||||
$artistName = $trackData['AlbumArtists'][0]['Name'];
|
||||
$artistId = $this->getOrCreateMusicArtist($artistName);
|
||||
}
|
||||
|
||||
// Get or create album
|
||||
$albumId = null;
|
||||
if (isset($trackData['Album']) && !empty($trackData['Album'])) {
|
||||
$albumId = $this->getOrCreateMusicAlbum($trackData['Album'], $artistId);
|
||||
}
|
||||
|
||||
// Check if track already exists
|
||||
$existingTrack = $trackModel->findAll([
|
||||
'title' => $trackData['Name'],
|
||||
'artist_id' => $artistId,
|
||||
'album_id' => $albumId,
|
||||
'source_id' => $this->source['id']
|
||||
]);
|
||||
|
||||
$durationSeconds = isset($trackData['RunTimeTicks']) ? intval($trackData['RunTimeTicks'] / 10000000) : null;
|
||||
|
||||
$trackDataForDb = [
|
||||
'title' => $trackData['Name'],
|
||||
'artist_name' => isset($trackData['AlbumArtists'][0]['Name']) ? $trackData['AlbumArtists'][0]['Name'] : 'Unknown Artist',
|
||||
'album_name' => $trackData['Album'] ?? null,
|
||||
'track_number' => $trackData['IndexNumber'] ?? null,
|
||||
'duration_seconds' => $durationSeconds,
|
||||
'genre' => isset($trackData['Genres'][0]) ? $trackData['Genres'][0] : null,
|
||||
'release_date' => isset($trackData['PremiereDate']) ? date('Y-m-d', strtotime($trackData['PremiereDate'])) : null,
|
||||
'play_count' => 0,
|
||||
'artist_id' => $artistId,
|
||||
'album_id' => $albumId,
|
||||
'source_id' => $this->source['id'],
|
||||
'metadata' => json_encode([
|
||||
'jellyfin_id' => $trackData['Id']
|
||||
])
|
||||
];
|
||||
|
||||
if (empty($existingTrack)) {
|
||||
$trackModel->create($trackDataForDb);
|
||||
$this->newCount++;
|
||||
} else {
|
||||
$trackModel->update($existingTrack[0]['id'], $trackDataForDb);
|
||||
$this->updatedCount++;
|
||||
}
|
||||
|
||||
$this->processedCount++;
|
||||
}
|
||||
|
||||
private function getOrCreateMusicArtist(string $artistName): ?int
|
||||
{
|
||||
$artistModel = new MusicArtist($this->pdo);
|
||||
|
||||
// Check if artist exists
|
||||
$existingArtist = $artistModel->findAll([
|
||||
'name' => $artistName,
|
||||
'source_id' => $this->source['id']
|
||||
]);
|
||||
|
||||
if (!empty($existingArtist)) {
|
||||
return $existingArtist[0]['id'];
|
||||
}
|
||||
|
||||
// Create new artist
|
||||
$artistData = [
|
||||
'name' => $artistName,
|
||||
'source_id' => $this->source['id'],
|
||||
'metadata' => json_encode([])
|
||||
];
|
||||
|
||||
$artistId = $artistModel->create($artistData);
|
||||
return $artistId;
|
||||
}
|
||||
|
||||
private function getOrCreateMusicAlbum(string $albumName, ?int $artistId): ?int
|
||||
{
|
||||
$albumModel = new MusicAlbum($this->pdo);
|
||||
|
||||
// Check if album exists
|
||||
$existingAlbum = $albumModel->findAll([
|
||||
'title' => $albumName,
|
||||
'artist_id' => $artistId,
|
||||
'source_id' => $this->source['id']
|
||||
]);
|
||||
|
||||
if (!empty($existingAlbum)) {
|
||||
return $existingAlbum[0]['id'];
|
||||
}
|
||||
|
||||
// Create new album
|
||||
$albumData = [
|
||||
'title' => $albumName,
|
||||
'artist_name' => 'Unknown Artist', // Will be updated when artist is found
|
||||
'track_count' => 0,
|
||||
'total_duration_seconds' => 0,
|
||||
'artist_id' => $artistId,
|
||||
'source_id' => $this->source['id'],
|
||||
'metadata' => json_encode([])
|
||||
];
|
||||
|
||||
$albumId = $albumModel->create($albumData);
|
||||
return $albumId;
|
||||
}
|
||||
|
||||
private function getShowEpisodes(string $jellyfinShowId): array
|
||||
{
|
||||
$this->logProgress("--- Fetching episodes for show ID: {$jellyfinShowId} ---");
|
||||
|
||||
Reference in New Issue
Block a user