Set up sync task for podcast playlists.
This commit is contained in:
parent
09d186d506
commit
24c56345df
|
@ -114,6 +114,7 @@ return static function (CallableEventDispatcherInterface $dispatcher) {
|
|||
$e->addTasks([
|
||||
App\Sync\Task\CheckFolderPlaylistsTask::class,
|
||||
App\Sync\Task\CheckMediaTask::class,
|
||||
App\Sync\Task\CheckPodcastPlaylistsTask::class,
|
||||
App\Sync\Task\CheckRequestsTask::class,
|
||||
App\Sync\Task\CheckUpdatesTask::class,
|
||||
App\Sync\Task\CleanupHistoryTask::class,
|
||||
|
|
|
@ -29,6 +29,9 @@ class PodcastEpisode implements IdentifiableEntityInterface
|
|||
#[ORM\JoinColumn(name: 'playlist_media_id', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
|
||||
protected ?StationMedia $playlist_media = null;
|
||||
|
||||
#[ORM\Column(nullable: true, insertable: false, updatable: false)]
|
||||
protected ?int $playlist_media_id = null;
|
||||
|
||||
#[ORM\OneToOne(mappedBy: 'episode')]
|
||||
protected ?PodcastMedia $media = null;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace App\Entity\Repository;
|
||||
|
||||
use App\Doctrine\Repository;
|
||||
use App\Entity\Enums\PodcastSources;
|
||||
use App\Entity\Podcast;
|
||||
use App\Entity\PodcastEpisode;
|
||||
use App\Entity\PodcastMedia;
|
||||
|
@ -17,6 +18,7 @@ use App\Media\MetadataManager;
|
|||
use InvalidArgumentException;
|
||||
use League\Flysystem\UnableToDeleteFile;
|
||||
use League\Flysystem\UnableToRetrieveMetadata;
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* @extends Repository<PodcastEpisode>
|
||||
|
@ -124,6 +126,11 @@ final class PodcastEpisodeRepository extends Repository
|
|||
?ExtendedFilesystemInterface $fs = null
|
||||
): void {
|
||||
$podcast = $episode->getPodcast();
|
||||
|
||||
if ($podcast->getSource() !== PodcastSources::Manual) {
|
||||
throw new LogicException('Cannot upload media to this podcast type.');
|
||||
}
|
||||
|
||||
$storageLocation = $podcast->getStorageLocation();
|
||||
|
||||
$fs ??= $this->storageLocationRepo->getAdapter($storageLocation)
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Sync\Task;
|
||||
|
||||
use App\Entity\Enums\PodcastSources;
|
||||
use App\Entity\Podcast;
|
||||
use App\Entity\PodcastEpisode;
|
||||
use App\Entity\Repository\PodcastEpisodeRepository;
|
||||
use App\Entity\Station;
|
||||
use App\Entity\StationMedia;
|
||||
use App\Flysystem\StationFilesystems;
|
||||
|
||||
final class CheckPodcastPlaylistsTask extends AbstractTask
|
||||
{
|
||||
public function __construct(
|
||||
private readonly StationFilesystems $stationFilesystems,
|
||||
private readonly PodcastEpisodeRepository $podcastEpisodeRepo
|
||||
) {
|
||||
}
|
||||
|
||||
public static function getSchedulePattern(): string
|
||||
{
|
||||
return '*/10 * * * *';
|
||||
}
|
||||
|
||||
public function run(bool $force = false): void
|
||||
{
|
||||
foreach ($this->iterateStations() as $station) {
|
||||
$this->syncPodcastPlaylists($station);
|
||||
}
|
||||
}
|
||||
|
||||
public function syncPodcastPlaylists(Station $station): void
|
||||
{
|
||||
$this->logger->info(
|
||||
'Processing playlist-based podcasts for station...',
|
||||
[
|
||||
'station' => $station->getName(),
|
||||
]
|
||||
);
|
||||
|
||||
$fsMedia = $this->stationFilesystems->getMediaFilesystem($station);
|
||||
$fsPodcasts = $this->stationFilesystems->getPodcastsFilesystem($station);
|
||||
|
||||
$podcasts = $this->em->createQuery(
|
||||
<<<'DQL'
|
||||
SELECT p, sp
|
||||
FROM App\Entity\Podcast p
|
||||
JOIN p.playlist sp
|
||||
WHERE p.source = :source
|
||||
DQL
|
||||
)->setParameter('source', PodcastSources::Playlist->value)
|
||||
->execute();
|
||||
|
||||
$mediaInPlaylistQuery = $this->em->createQuery(
|
||||
<<<'DQL'
|
||||
SELECT spm.media_id
|
||||
FROM App\Entity\StationPlaylistMedia spm
|
||||
WHERE spm.playlist = :playlist
|
||||
DQL
|
||||
);
|
||||
|
||||
$mediaInPodcastQuery = $this->em->createQuery(
|
||||
<<<'DQL'
|
||||
SELECT pe.id, pe.playlist_media_id
|
||||
FROM App\Entity\PodcastEpisode pe
|
||||
WHERE pe.podcast = :podcast
|
||||
DQL
|
||||
);
|
||||
|
||||
$stats = [
|
||||
'added' => 0,
|
||||
'removed' => 0,
|
||||
'unchanged' => 0,
|
||||
];
|
||||
|
||||
/** @var Podcast $podcast */
|
||||
foreach ($podcasts as $podcast) {
|
||||
$playlist = $podcast->getPlaylist();
|
||||
|
||||
$mediaInPlaylist = array_column(
|
||||
$mediaInPlaylistQuery->setParameter('playlist', $playlist)->getArrayResult(),
|
||||
'media_id',
|
||||
'media_id'
|
||||
);
|
||||
|
||||
$mediaInPodcast = array_column(
|
||||
$mediaInPodcastQuery->setParameter('podcast', $podcast)->getArrayResult(),
|
||||
'id',
|
||||
'playlist_media_id'
|
||||
);
|
||||
|
||||
$mediaToAdd = [];
|
||||
foreach ($mediaInPlaylist as $mediaId) {
|
||||
if (isset($mediaInPodcast[$mediaId])) {
|
||||
$stats['unchanged']++;
|
||||
unset($mediaInPodcast[$mediaId]);
|
||||
} else {
|
||||
$mediaToAdd[] = $mediaId;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($mediaToAdd as $mediaId) {
|
||||
$media = $this->em->find(StationMedia::class, $mediaId);
|
||||
|
||||
if ($media instanceof StationMedia) {
|
||||
// Create new podcast episode.
|
||||
$podcastEpisode = new PodcastEpisode($podcast);
|
||||
$podcastEpisode->setPlaylistMedia($media);
|
||||
|
||||
$podcastEpisode->setDescription(
|
||||
implode("\n", array_filter([
|
||||
$media->getArtist(),
|
||||
$media->getAlbum(),
|
||||
$media->getLyrics(),
|
||||
]))
|
||||
);
|
||||
$podcastEpisode->setTitle($media->getTitle() ?? 'Untitled Episode');
|
||||
|
||||
if (!$podcast->playlistAutoPublish()) {
|
||||
// Set a date in the future to unpublish the episode.
|
||||
$podcastEpisode->setPublishAt(
|
||||
strtotime('+10 years')
|
||||
);
|
||||
} else {
|
||||
$podcastEpisode->setPublishAt($media->getMtime());
|
||||
}
|
||||
|
||||
$this->em->persist($podcastEpisode);
|
||||
$this->em->flush();
|
||||
|
||||
$artPath = StationMedia::getArtPath($media->getUniqueId());
|
||||
if ($fsMedia->fileExists($artPath)) {
|
||||
$art = $fsMedia->read($artPath);
|
||||
$this->podcastEpisodeRepo->writeEpisodeArt($podcastEpisode, $art);
|
||||
}
|
||||
|
||||
$stats['added']++;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove remaining media that doesn't match.
|
||||
foreach ($mediaInPodcast as $episodeId) {
|
||||
$episode = $this->em->find(PodcastEpisode::class, $episodeId);
|
||||
|
||||
if ($episode instanceof PodcastEpisode) {
|
||||
$this->podcastEpisodeRepo->delete($episode, $fsPodcasts);
|
||||
}
|
||||
|
||||
$stats['removed']++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->logger->debug(
|
||||
'Playlist-based podcasts for station processed.',
|
||||
[
|
||||
'station' => $station->getName(),
|
||||
'stats' => $stats,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue