Move Metadata management back to main library; use FFProbe exclusively for metadata reading; harden error handling and resilience for misprocessed files.
This commit is contained in:
parent
6f1402c65f
commit
24ecda99ec
|
@ -29,7 +29,6 @@
|
|||
"azuracast/doctrine-batch-utilities": "dev-main",
|
||||
"azuracast/doctrine-entity-normalizer": "dev-main",
|
||||
"azuracast/flysystem-v2-extensions": "dev-main",
|
||||
"azuracast/metadata-manager": "dev-main",
|
||||
"azuracast/nowplaying": "dev-main",
|
||||
"azuracast/slim-callable-eventdispatcher": "dev-main",
|
||||
"bacon/bacon-qr-code": "^2.0",
|
||||
|
@ -48,6 +47,7 @@
|
|||
"guzzlehttp/oauth-subscriber": "^0.6.0",
|
||||
"http-interop/http-factory-guzzle": "^1.0",
|
||||
"intervention/image": "^2.6",
|
||||
"james-heinrich/getid3": "v2.0.0-beta4",
|
||||
"league/csv": "^9.6",
|
||||
"league/flysystem-aws-s3-v3": "^3.0",
|
||||
"league/flysystem-sftp-v3": "^3.0",
|
||||
|
@ -65,6 +65,7 @@
|
|||
"pagerfanta/doctrine-orm-adapter": "^3",
|
||||
"php-di/php-di": "^6.0",
|
||||
"php-di/slim-bridge": "^3.0",
|
||||
"php-ffmpeg/php-ffmpeg": "^1.0",
|
||||
"phpmyadmin/motranslator": "^5.3",
|
||||
"phpseclib/phpseclib": "^3.0",
|
||||
"psr/http-factory": ">1",
|
||||
|
@ -95,6 +96,7 @@
|
|||
"symfony/yaml": "^6",
|
||||
"theiconic/php-ga-measurement-protocol": "^2.9",
|
||||
"vlucas/phpdotenv": "^5.3",
|
||||
"voku/portable-utf8": "^6.0",
|
||||
"wikimedia/composer-merge-plugin": "dev-master",
|
||||
"zircote/swagger-php": "^4.3.0"
|
||||
},
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "ee39dba284c5c3f58875350e539c3826",
|
||||
"content-hash": "da5cc239fd0238e0d8fc481e437dcfa0",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aws/aws-crt-php",
|
||||
|
@ -341,77 +341,6 @@
|
|||
],
|
||||
"time": "2022-05-08T19:35:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "azuracast/metadata-manager",
|
||||
"version": "dev-main",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/AzuraCast/metadata-manager.git",
|
||||
"reference": "b03d7f37f9d1e862a47ecc1f7bdc78a996ff9712"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/AzuraCast/metadata-manager/zipball/b03d7f37f9d1e862a47ecc1f7bdc78a996ff9712",
|
||||
"reference": "b03d7f37f9d1e862a47ecc1f7bdc78a996ff9712",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"james-heinrich/getid3": "v2.0.0-beta4",
|
||||
"php": ">=7.4",
|
||||
"php-ffmpeg/php-ffmpeg": "^1.0",
|
||||
"symfony/console": ">5.0",
|
||||
"voku/portable-utf8": "^6"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-parallel-lint/php-console-highlighter": "^1",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.3",
|
||||
"phpstan/phpstan": "^1",
|
||||
"roave/security-advisories": "dev-latest"
|
||||
},
|
||||
"default-branch": true,
|
||||
"bin": [
|
||||
"bin/metadata-manager"
|
||||
],
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Azura\\MetadataManager\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Buster 'Silver Eagle' Neece",
|
||||
"email": "buster@busterneece.com",
|
||||
"homepage": "https://dashdev.net/",
|
||||
"role": "Lead Developer"
|
||||
}
|
||||
],
|
||||
"description": "A command-line wrapper around the PHP GetId3 library.",
|
||||
"homepage": "https://github.com/AzuraCast/metadata-manager",
|
||||
"support": {
|
||||
"source": "https://github.com/AzuraCast/metadata-manager/tree/main"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/AzuraCast",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://opencollective.com/azuracast",
|
||||
"type": "open_collective"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/AzuraCast",
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2022-04-28T11:59:24+00:00"
|
||||
},
|
||||
{
|
||||
"name": "azuracast/nowplaying",
|
||||
"version": "dev-main",
|
||||
|
@ -14400,7 +14329,6 @@
|
|||
"azuracast/doctrine-batch-utilities": 20,
|
||||
"azuracast/doctrine-entity-normalizer": 20,
|
||||
"azuracast/flysystem-v2-extensions": 20,
|
||||
"azuracast/metadata-manager": 20,
|
||||
"azuracast/nowplaying": 20,
|
||||
"azuracast/slim-callable-eventdispatcher": 20,
|
||||
"lstrojny/fxmlrpc": 20,
|
||||
|
|
|
@ -182,9 +182,17 @@ return function (CallableEventDispatcherInterface $dispatcher) {
|
|||
-10
|
||||
);
|
||||
|
||||
$dispatcher->addCallableListener(
|
||||
Event\Media\ReadMetadata::class,
|
||||
App\Media\Metadata\Reader::class
|
||||
);
|
||||
$dispatcher->addCallableListener(
|
||||
Event\Media\WriteMetadata::class,
|
||||
App\Media\Metadata\Writer::class
|
||||
);
|
||||
|
||||
$dispatcher->addServiceSubscriber(
|
||||
[
|
||||
App\Media\MetadataManager::class,
|
||||
App\Console\ErrorHandler::class,
|
||||
App\Radio\AutoDJ\Queue::class,
|
||||
App\Radio\AutoDJ\Annotations::class,
|
||||
|
|
|
@ -205,7 +205,10 @@ class StationMediaRepository extends Repository
|
|||
$mediaMtime = time();
|
||||
} else {
|
||||
if (!$fs->fileExists($path)) {
|
||||
throw new CannotProcessMediaException(sprintf('Media path "%s" not found.', $path));
|
||||
throw CannotProcessMediaException::forPath(
|
||||
$path,
|
||||
sprintf('Media path "%s" not found.', $path)
|
||||
);
|
||||
}
|
||||
|
||||
$mediaMtime = $fs->lastModified($path);
|
||||
|
|
|
@ -8,10 +8,10 @@ use App\Entity\Interfaces\IdentifiableEntityInterface;
|
|||
use App\Entity\Interfaces\PathAwareInterface;
|
||||
use App\Entity\Interfaces\ProcessableMediaInterface;
|
||||
use App\Entity\Interfaces\SongInterface;
|
||||
use App\Media\Metadata;
|
||||
use App\Media\MetadataInterface;
|
||||
use App\OpenApi;
|
||||
use App\Utilities\Time;
|
||||
use Azura\MetadataManager\Metadata;
|
||||
use Azura\MetadataManager\MetadataInterface;
|
||||
use Azura\Normalizer\Attributes\DeepNormalize;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
|
|
|
@ -4,8 +4,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Event\Media;
|
||||
|
||||
use Azura\MetadataManager\Metadata;
|
||||
use Azura\MetadataManager\MetadataInterface;
|
||||
use App\Media\Metadata;
|
||||
use App\Media\MetadataInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class ReadMetadata extends Event
|
||||
|
|
|
@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Event\Media;
|
||||
|
||||
use Azura\MetadataManager\MetadataInterface;
|
||||
use App\Media\MetadataInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class WriteMetadata extends Event
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Media;
|
||||
|
||||
final class Metadata implements MetadataInterface
|
||||
{
|
||||
/** @var array<string, mixed> */
|
||||
private array $tags = [];
|
||||
|
||||
private float $duration = 0.0;
|
||||
|
||||
private ?string $artwork = null;
|
||||
|
||||
private string $mimeType = '';
|
||||
|
||||
public function getTags(): array
|
||||
{
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
public function setTags(array $tags): void
|
||||
{
|
||||
$this->tags = $tags;
|
||||
}
|
||||
|
||||
public function addTag(string $key, $value): void
|
||||
{
|
||||
$this->tags[$key] = $value;
|
||||
}
|
||||
|
||||
public function getDuration(): float
|
||||
{
|
||||
return $this->duration;
|
||||
}
|
||||
|
||||
public function setDuration(float $duration): void
|
||||
{
|
||||
$this->duration = $duration;
|
||||
}
|
||||
|
||||
public function getArtwork(): ?string
|
||||
{
|
||||
return $this->artwork;
|
||||
}
|
||||
|
||||
public function setArtwork(?string $artwork): void
|
||||
{
|
||||
$this->artwork = $artwork;
|
||||
}
|
||||
|
||||
public function getMimeType(): string
|
||||
{
|
||||
return $this->mimeType;
|
||||
}
|
||||
|
||||
public function setMimeType(string $mimeType): void
|
||||
{
|
||||
$this->mimeType = $mimeType;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Media\Metadata;
|
||||
|
||||
use App\Event\Media\ReadMetadata;
|
||||
use App\Media\Metadata;
|
||||
use App\Media\MimeType;
|
||||
use App\Utilities\Arrays;
|
||||
use App\Utilities\File;
|
||||
use App\Utilities\Time;
|
||||
use FFMpeg\FFMpeg;
|
||||
use FFMpeg\FFProbe;
|
||||
use FFMpeg\FFProbe\DataMapping\Stream;
|
||||
use voku\helper\UTF8;
|
||||
|
||||
class Reader
|
||||
{
|
||||
public function __invoke(ReadMetadata $event): void
|
||||
{
|
||||
$path = $event->getPath();
|
||||
|
||||
$ffprobe = FFProbe::create();
|
||||
$format = $ffprobe->format($path);
|
||||
|
||||
$metadata = new Metadata();
|
||||
|
||||
if (is_numeric($format->get('duration'))) {
|
||||
$metadata->setDuration(
|
||||
Time::displayTimeToSeconds($format->get('duration')) ?? 0.0
|
||||
);
|
||||
}
|
||||
|
||||
$toProcess = [
|
||||
$format->get('comments'),
|
||||
$format->get('tags'),
|
||||
];
|
||||
|
||||
$metaTags = $this->aggregateMetaTags($toProcess);
|
||||
|
||||
$metadata->setTags($metaTags);
|
||||
$metadata->setMimeType(MimeType::getMimeTypeFromFile($path));
|
||||
|
||||
try {
|
||||
// Pull album art directly from relevant streams.
|
||||
$ffmpeg = FFMpeg::create();
|
||||
|
||||
/** @var Stream[] $videoStreams */
|
||||
$videoStreams = $ffprobe->streams($path)->videos()->all();
|
||||
foreach ($videoStreams as $videoStream) {
|
||||
$codecName = $videoStream->get('codec_name');
|
||||
if ($codecName !== 'mjpeg') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$artOutput = File::generateTempPath('artwork.jpg');
|
||||
@unlink($artOutput); // Ffmpeg won't overwrite the empty file.
|
||||
|
||||
$ffmpeg->getFFMpegDriver()->command([
|
||||
'-i',
|
||||
$path,
|
||||
'-an',
|
||||
'-vcodec',
|
||||
'copy',
|
||||
$artOutput,
|
||||
]);
|
||||
|
||||
$metadata->setArtwork(file_get_contents($artOutput) ?: null);
|
||||
@unlink($artOutput);
|
||||
break;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$metadata->setArtwork(null);
|
||||
}
|
||||
|
||||
$event->setMetadata($metadata);
|
||||
}
|
||||
|
||||
protected function aggregateMetaTags(array $toProcess): array
|
||||
{
|
||||
$metaTags = [];
|
||||
|
||||
foreach ($toProcess as $tagSet) {
|
||||
if (empty($tagSet)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($tagSet as $tagName => $tagContents) {
|
||||
if (!empty($tagContents) && !isset($metaTags[$tagName])) {
|
||||
$tagValue = $tagContents;
|
||||
if (is_array($tagValue)) {
|
||||
// Skip pictures
|
||||
if (isset($tagValue['data'])) {
|
||||
continue;
|
||||
}
|
||||
$flatValue = Arrays::flattenArray($tagValue);
|
||||
$tagValue = implode(', ', $flatValue);
|
||||
}
|
||||
|
||||
$metaTags[(string)$tagName] = $this->cleanUpString((string)$tagValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $metaTags;
|
||||
}
|
||||
|
||||
protected function cleanUpString(?string $original): string
|
||||
{
|
||||
$original ??= '';
|
||||
|
||||
$string = UTF8::encode('UTF-8', $original);
|
||||
$string = UTF8::fix_simple_utf8($string);
|
||||
return UTF8::clean(
|
||||
$string,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Media\Metadata;
|
||||
|
||||
use App\Event\Media\WriteMetadata;
|
||||
use JamesHeinrich\GetID3\GetID3;
|
||||
use JamesHeinrich\GetID3\WriteTags;
|
||||
|
||||
class Writer
|
||||
{
|
||||
public function __invoke(WriteMetadata $event): void
|
||||
{
|
||||
$path = $event->getPath();
|
||||
|
||||
$metadata = $event->getMetadata();
|
||||
if (null === $metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
$getID3 = new GetID3();
|
||||
$getID3->setOption(['encoding' => 'UTF8']);
|
||||
|
||||
$tagwriter = new WriteTags();
|
||||
$tagwriter->filename = $path;
|
||||
|
||||
$pathExt = strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
||||
|
||||
$tagFormats = null;
|
||||
switch ($pathExt) {
|
||||
case 'mp3':
|
||||
case 'mp2':
|
||||
case 'mp1':
|
||||
case 'riff':
|
||||
$tagFormats = ['id3v1', 'id3v2.3'];
|
||||
break;
|
||||
|
||||
case 'mpc':
|
||||
$tagFormats = ['ape'];
|
||||
break;
|
||||
|
||||
case 'flac':
|
||||
$tagFormats = ['metaflac'];
|
||||
break;
|
||||
|
||||
case 'real':
|
||||
$tagFormats = ['real'];
|
||||
break;
|
||||
|
||||
case 'ogg':
|
||||
$tagFormats = ['vorbiscomment'];
|
||||
break;
|
||||
}
|
||||
|
||||
if (null === $tagFormats) {
|
||||
throw new \RuntimeException('Cannot write tag formats based on file type.');
|
||||
}
|
||||
|
||||
$tagwriter->tagformats = $tagFormats;
|
||||
$tagwriter->overwrite_tags = true;
|
||||
$tagwriter->tag_encoding = 'UTF8';
|
||||
$tagwriter->remove_other_tags = true;
|
||||
|
||||
$writeTags = $metadata->getTags();
|
||||
|
||||
if ($metadata->getArtwork()) {
|
||||
$artContents = $metadata->getArtwork();
|
||||
if (false !== $artContents) {
|
||||
$writeTags['attached_picture'] = [
|
||||
'encodingid' => 0, // ISO-8859-1; 3=UTF8 but only allowed in ID3v2.4
|
||||
'description' => 'cover art',
|
||||
'data' => $artContents,
|
||||
'picturetypeid' => 0x03,
|
||||
'mime' => 'image/jpeg',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// All ID3 tags have to be written as ['key' => ['value']] (i.e. with "value" at position 0).
|
||||
$tagData = [];
|
||||
foreach ($writeTags as $tagKey => $tagValue) {
|
||||
$tagData[$tagKey] = [$tagValue];
|
||||
}
|
||||
|
||||
$tagwriter->tag_data = $tagData;
|
||||
$tagwriter->WriteTags();
|
||||
|
||||
if (!empty($tagwriter->errors) || !empty($tagwriter->warnings)) {
|
||||
$messages = array_merge($tagwriter->errors, $tagwriter->warnings);
|
||||
|
||||
throw new \RuntimeException(
|
||||
sprintf(
|
||||
'Cannot process media file %s: %s',
|
||||
$path,
|
||||
implode(', ', $messages)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Media;
|
||||
|
||||
interface MetadataInterface
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getTags(): array;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $tags
|
||||
*/
|
||||
public function setTags(array $tags): void;
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function addTag(string $key, $value): void;
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getDuration(): float;
|
||||
|
||||
/**
|
||||
* @param float $duration
|
||||
*/
|
||||
public function setDuration(float $duration): void;
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getArtwork(): ?string;
|
||||
|
||||
public function setArtwork(?string $artwork): void;
|
||||
|
||||
public function getMimeType(): string;
|
||||
|
||||
public function setMimeType(string $mimeType): void;
|
||||
}
|
|
@ -4,42 +4,20 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Media;
|
||||
|
||||
use App\Environment;
|
||||
use App\Event\Media\ReadMetadata;
|
||||
use App\Event\Media\WriteMetadata;
|
||||
use App\Exception\CannotProcessMediaException;
|
||||
use App\Utilities\File;
|
||||
use App\Utilities\Json;
|
||||
use Azura\MetadataManager\Metadata;
|
||||
use Azura\MetadataManager\MetadataInterface;
|
||||
use GuzzleHttp\Client;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class MetadataManager implements EventSubscriberInterface
|
||||
class MetadataManager
|
||||
{
|
||||
public function __construct(
|
||||
protected EventDispatcherInterface $eventDispatcher,
|
||||
protected Client $httpClient,
|
||||
protected Environment $environment
|
||||
protected LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
ReadMetadata::class => [
|
||||
['readFromId3', 0],
|
||||
],
|
||||
WriteMetadata::class => [
|
||||
['writeToId3', 0],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function read(string $filePath): MetadataInterface
|
||||
{
|
||||
if (!MimeType::isFileProcessable($filePath)) {
|
||||
|
@ -50,113 +28,45 @@ class MetadataManager implements EventSubscriberInterface
|
|||
);
|
||||
}
|
||||
|
||||
$event = new ReadMetadata($filePath);
|
||||
$this->eventDispatcher->dispatch($event);
|
||||
|
||||
return $event->getMetadata();
|
||||
}
|
||||
|
||||
public function readFromId3(ReadMetadata $event): void
|
||||
{
|
||||
$sourceFilePath = $event->getPath();
|
||||
|
||||
$jsonOutput = File::generateTempPath('metadata.json');
|
||||
$artOutput = File::generateTempPath('metadata.jpg');
|
||||
|
||||
try {
|
||||
$phpBinaryPath = (new PhpExecutableFinder())->find();
|
||||
if (false === $phpBinaryPath) {
|
||||
throw new RuntimeException('Could not find PHP executable path.');
|
||||
}
|
||||
$event = new ReadMetadata($filePath);
|
||||
$this->eventDispatcher->dispatch($event);
|
||||
|
||||
$scriptPath = $this->environment->getBaseDirectory() . '/vendor/bin/metadata-manager';
|
||||
|
||||
$process = new Process(
|
||||
return $event->getMetadata();
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error(
|
||||
sprintf(
|
||||
'Cannot read metadata for file "%s": %s',
|
||||
$filePath,
|
||||
$e->getMessage()
|
||||
),
|
||||
[
|
||||
$phpBinaryPath,
|
||||
$scriptPath,
|
||||
'read',
|
||||
$sourceFilePath,
|
||||
$jsonOutput,
|
||||
$artOutput,
|
||||
'path' => $filePath,
|
||||
'exception' => $e,
|
||||
]
|
||||
);
|
||||
|
||||
$process->mustRun();
|
||||
|
||||
$metadataJson = Json::loadFromFile($jsonOutput);
|
||||
$metadata = Metadata::fromJson($metadataJson);
|
||||
|
||||
if (is_file($artOutput)) {
|
||||
$artwork = file_get_contents($artOutput) ?: null;
|
||||
$metadata->setArtwork($artwork);
|
||||
}
|
||||
|
||||
$event->setMetadata($metadata);
|
||||
} finally {
|
||||
@unlink($jsonOutput);
|
||||
@unlink($artOutput);
|
||||
return new Metadata();
|
||||
}
|
||||
}
|
||||
|
||||
public function write(MetadataInterface $metadata, string $filePath): void
|
||||
{
|
||||
$event = new WriteMetadata($metadata, $filePath);
|
||||
$this->eventDispatcher->dispatch($event);
|
||||
}
|
||||
|
||||
public function writeToId3(WriteMetadata $event): void
|
||||
{
|
||||
$destFilePath = $event->getPath();
|
||||
|
||||
$metadata = $event->getMetadata();
|
||||
if (null === $metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
$jsonInput = File::generateTempPath('metadata.json');
|
||||
$artInput = File::generateTempPath('metadata.jpg');
|
||||
|
||||
try {
|
||||
// Write input files for the metadata process.
|
||||
file_put_contents(
|
||||
$jsonInput,
|
||||
json_encode($metadata, JSON_THROW_ON_ERROR)
|
||||
$event = new WriteMetadata($metadata, $filePath);
|
||||
$this->eventDispatcher->dispatch($event);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error(
|
||||
sprintf(
|
||||
'Cannot write metadata for file "%s": %s',
|
||||
$filePath,
|
||||
$e->getMessage()
|
||||
),
|
||||
[
|
||||
'path' => $filePath,
|
||||
'exception' => $e,
|
||||
]
|
||||
);
|
||||
|
||||
$artwork = $metadata->getArtwork();
|
||||
if (null !== $artwork) {
|
||||
file_put_contents(
|
||||
$artInput,
|
||||
$artwork
|
||||
);
|
||||
}
|
||||
|
||||
// Run remote process.
|
||||
$phpBinaryPath = (new PhpExecutableFinder())->find();
|
||||
if (false === $phpBinaryPath) {
|
||||
throw new RuntimeException('Could not find PHP executable path.');
|
||||
}
|
||||
|
||||
$scriptPath = $this->environment->getBaseDirectory() . '/vendor/bin/metadata-manager';
|
||||
|
||||
$processCommand = [
|
||||
$phpBinaryPath,
|
||||
$scriptPath,
|
||||
'write',
|
||||
$destFilePath,
|
||||
$jsonInput,
|
||||
];
|
||||
|
||||
if (null !== $artwork) {
|
||||
$processCommand[] = $artInput;
|
||||
}
|
||||
|
||||
$process = new Process($processCommand);
|
||||
$process->run();
|
||||
} finally {
|
||||
@unlink($jsonInput);
|
||||
@unlink($artInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue