AzuraCast/src/Media/MediaProcessor.php

215 lines
6.4 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Media;
use App\Doctrine\ReloadableEntityManagerInterface;
use App\Entity\Repository\StationMediaRepository;
use App\Entity\Repository\UnprocessableMediaRepository;
use App\Entity\StationMedia;
use App\Entity\StorageLocation;
use App\Exception\CannotProcessMediaException;
use App\Message\AddNewMediaMessage;
use App\Message\ProcessCoverArtMessage;
use App\Message\ReprocessMediaMessage;
use Symfony\Component\Filesystem\Filesystem;
final class MediaProcessor
{
public function __construct(
private readonly ReloadableEntityManagerInterface $em,
private readonly StationMediaRepository $mediaRepo,
private readonly UnprocessableMediaRepository $unprocessableMediaRepo
) {
}
public function __invoke(
ReprocessMediaMessage|AddNewMediaMessage|ProcessCoverArtMessage $message
): void {
$storageLocation = $this->em->find(StorageLocation::class, $message->storage_location_id);
if (!($storageLocation instanceof StorageLocation)) {
return;
}
if ($message instanceof ReprocessMediaMessage) {
$mediaRow = $this->em->find(StationMedia::class, $message->media_id);
if ($mediaRow instanceof StationMedia) {
$this->processMedia($storageLocation, $mediaRow, $message->force);
$this->em->flush();
}
} else {
$this->process($storageLocation, $message->path);
}
}
public function processAndUpload(
StorageLocation $storageLocation,
string $path,
string $localPath
): ?StationMedia {
$fs = $storageLocation->getFilesystem();
if (!(new Filesystem())->exists($localPath)) {
throw CannotProcessMediaException::forPath(
$path,
sprintf('Local file path "%s" not found.', $localPath)
);
}
try {
if (MimeType::isFileProcessable($localPath)) {
$record = $this->mediaRepo->findByPath($path, $storageLocation);
if (!($record instanceof StationMedia)) {
$record = new StationMedia($storageLocation, $path);
}
try {
$this->mediaRepo->loadFromFile($record, $localPath, $fs);
$record->setMtime(time());
$this->em->persist($record);
} catch (CannotProcessMediaException $e) {
$this->unprocessableMediaRepo->setForPath(
$storageLocation,
$path,
$e->getMessage()
);
throw $e;
}
$this->em->flush();
$this->unprocessableMediaRepo->clearForPath($storageLocation, $path);
return $record;
}
if (MimeType::isPathImage($localPath)) {
$this->processCoverArt(
$storageLocation,
$path,
file_get_contents($localPath) ?: ''
);
return null;
}
throw CannotProcessMediaException::forPath(
$path,
'File type cannot be processed.'
);
} finally {
$fs->uploadAndDeleteOriginal($localPath, $path);
}
}
public function process(
StorageLocation $storageLocation,
string $path,
bool $force = false
): ?StationMedia {
if (MimeType::isPathProcessable($path)) {
$record = $this->mediaRepo->findByPath($path, $storageLocation);
$created = false;
if (!($record instanceof StationMedia)) {
$record = new StationMedia($storageLocation, $path);
$created = true;
}
try {
$reprocessed = $this->processMedia($storageLocation, $record, $force);
} catch (CannotProcessMediaException $e) {
$this->unprocessableMediaRepo->setForPath(
$storageLocation,
$path,
$e->getMessage()
);
throw $e;
}
if ($created || $reprocessed) {
$this->em->flush();
$this->unprocessableMediaRepo->clearForPath($storageLocation, $path);
}
return $record;
}
if (MimeType::isPathImage($path)) {
$this->processCoverArt(
$storageLocation,
$path
);
return null;
}
throw CannotProcessMediaException::forPath(
$path,
'File type cannot be processed.'
);
}
public function processMedia(
StorageLocation $storageLocation,
StationMedia $media,
bool $force = false
): bool {
$fs = $storageLocation->getFilesystem();
$path = $media->getPath();
if (!$fs->fileExists($path)) {
throw CannotProcessMediaException::forPath(
$path,
sprintf('Media path "%s" not found.', $path)
);
}
$mediaMtime = $fs->lastModified($path);
// No need to update if all of these conditions are true.
if (!$force && !$media->needsReprocessing($mediaMtime)) {
return false;
}
$fs->withLocalFile(
$path,
function ($localPath) use ($media, $fs): void {
$this->mediaRepo->loadFromFile($media, $localPath, $fs);
}
);
$media->setMtime($mediaMtime);
$this->em->persist($media);
return true;
}
public function processCoverArt(
StorageLocation $storageLocation,
string $path,
?string $contents = null
): void {
$fs = $storageLocation->getFilesystem();
if (null === $contents) {
if (!$fs->fileExists($path)) {
throw CannotProcessMediaException::forPath(
$path,
sprintf('Cover art path "%s" not found.', $path)
);
}
$contents = $fs->read($path);
}
$folderHash = StationMedia::getFolderHashForPath($path);
$destPath = StationMedia::getFolderArtPath($folderHash);
$fs->write(
$destPath,
AlbumArt::resize($contents)
);
}
}