Use standalone MetadataManager library.
This commit is contained in:
parent
424c28b027
commit
32296cbda6
29
bin/metadata
29
bin/metadata
|
@ -1,29 +0,0 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT);
|
||||
ini_set('display_errors', '1');
|
||||
|
||||
$autoloader = require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
$environment = App\AppFactory::buildEnvironment(
|
||||
[
|
||||
App\Environment::BASE_DIR => dirname(__DIR__),
|
||||
]
|
||||
);
|
||||
|
||||
$console = new Silly\Application('AzuraCast Metadata Processor', App\Version::FALLBACK_VERSION);
|
||||
|
||||
$console->command(
|
||||
'read path json-output [--art-output=]',
|
||||
new App\MediaProcessor\Command\ReadCommand
|
||||
);
|
||||
|
||||
$console->command(
|
||||
'write path json-input [--art-input=]',
|
||||
new App\MediaProcessor\Command\WriteCommand
|
||||
);
|
||||
|
||||
$console->run();
|
|
@ -19,6 +19,7 @@
|
|||
"ext-xmlwriter": "*",
|
||||
"azuracast/azuraforms": "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",
|
||||
|
@ -35,7 +36,6 @@
|
|||
"guzzlehttp/oauth-subscriber": "^0.6.0",
|
||||
"http-interop/http-factory-guzzle": "^1.0",
|
||||
"intervention/image": "^2.6",
|
||||
"james-heinrich/getid3": "dev-master#0bc9aca",
|
||||
"laminas/laminas-config": "^3.3",
|
||||
"league/csv": "^9.6",
|
||||
"league/flysystem-aws-s3-v3": "^2.0",
|
||||
|
@ -81,7 +81,6 @@
|
|||
"symfony/yaml": "^5.3",
|
||||
"theiconic/php-ga-measurement-protocol": "^2.9",
|
||||
"vlucas/phpdotenv": "^5.3",
|
||||
"voku/portable-utf8": "^5.4",
|
||||
"wikimedia/composer-merge-plugin": "dev-master",
|
||||
"zircote/swagger-php": "^3"
|
||||
},
|
||||
|
|
|
@ -4,20 +4,20 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "20b7638fe0d73d395369811b1b582368",
|
||||
"content-hash": "47b9920342b23a86135e11644f663a6c",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.191.1",
|
||||
"version": "3.191.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "221eb3790a2f5cc73067ec1c13d4c788a0296351"
|
||||
"reference": "d659144bf8618c891fd01f0f375f1f0b4db21b05"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/221eb3790a2f5cc73067ec1c13d4c788a0296351",
|
||||
"reference": "221eb3790a2f5cc73067ec1c13d4c788a0296351",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d659144bf8618c891fd01f0f375f1f0b4db21b05",
|
||||
"reference": "d659144bf8618c891fd01f0f375f1f0b4db21b05",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -92,9 +92,9 @@
|
|||
"support": {
|
||||
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
|
||||
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.191.1"
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.191.2"
|
||||
},
|
||||
"time": "2021-08-20T18:43:14+00:00"
|
||||
"time": "2021-08-23T18:17:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "azuracast/azuraforms",
|
||||
|
@ -247,6 +247,76 @@
|
|||
],
|
||||
"time": "2021-06-21T02:10:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "azuracast/metadata-manager",
|
||||
"version": "dev-main",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/AzuraCast/metadata-manager.git",
|
||||
"reference": "36638b7dfe52e3d2a502e9269b02b4d885820ddd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/AzuraCast/metadata-manager/zipball/36638b7dfe52e3d2a502e9269b02b4d885820ddd",
|
||||
"reference": "36638b7dfe52e3d2a502e9269b02b4d885820ddd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"james-heinrich/getid3": "dev-master#0bc9aca",
|
||||
"php": ">=7.4",
|
||||
"symfony/console": ">5.0",
|
||||
"voku/portable-utf8": "^5.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-parallel-lint/php-console-highlighter": "^0.5.0",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.3",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"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": "2021-08-24T03:19:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "azuracast/nowplaying",
|
||||
"version": "dev-main",
|
||||
|
@ -1882,16 +1952,16 @@
|
|||
},
|
||||
{
|
||||
"name": "doctrine/orm",
|
||||
"version": "2.9.4",
|
||||
"version": "2.9.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/orm.git",
|
||||
"reference": "b19a13f4edfaa5806109cd899f5912a7df1547b5"
|
||||
"reference": "77cc86ed880e3f1f6c9c5819e131a8aaeeeee0da"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/orm/zipball/b19a13f4edfaa5806109cd899f5912a7df1547b5",
|
||||
"reference": "b19a13f4edfaa5806109cd899f5912a7df1547b5",
|
||||
"url": "https://api.github.com/repos/doctrine/orm/zipball/77cc86ed880e3f1f6c9c5819e131a8aaeeeee0da",
|
||||
"reference": "77cc86ed880e3f1f6c9c5819e131a8aaeeeee0da",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1970,9 +2040,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/orm/issues",
|
||||
"source": "https://github.com/doctrine/orm/tree/2.9.4"
|
||||
"source": "https://github.com/doctrine/orm/tree/2.9.5"
|
||||
},
|
||||
"time": "2021-08-11T20:53:03+00:00"
|
||||
"time": "2021-08-23T10:20:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/persistence",
|
||||
|
@ -2935,12 +3005,12 @@
|
|||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/JamesHeinrich/getID3.git",
|
||||
"reference": "0bc9aca"
|
||||
"reference": "cb831b64d21b2b2361e7011853d0fc26e323e11c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/JamesHeinrich/getID3/zipball/0bc9aca",
|
||||
"reference": "0bc9aca",
|
||||
"url": "https://api.github.com/repos/JamesHeinrich/getID3/zipball/cb831b64d21b2b2361e7011853d0fc26e323e11c",
|
||||
"reference": "cb831b64d21b2b2361e7011853d0fc26e323e11c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -11822,12 +11892,12 @@
|
|||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Roave/SecurityAdvisories.git",
|
||||
"reference": "3c3cc12a9f163e589a12b9ea756c5a2dae9c59dd"
|
||||
"reference": "cd0a994884c7323cdc591f02b6027e00c1d88e74"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/3c3cc12a9f163e589a12b9ea756c5a2dae9c59dd",
|
||||
"reference": "3c3cc12a9f163e589a12b9ea756c5a2dae9c59dd",
|
||||
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/cd0a994884c7323cdc591f02b6027e00c1d88e74",
|
||||
"reference": "cd0a994884c7323cdc591f02b6027e00c1d88e74",
|
||||
"shasum": ""
|
||||
},
|
||||
"conflict": {
|
||||
|
@ -12005,7 +12075,7 @@
|
|||
"phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3",
|
||||
"phpwhois/phpwhois": "<=4.2.5",
|
||||
"phpxmlrpc/extras": "<0.6.1",
|
||||
"pimcore/pimcore": "<10.0.7",
|
||||
"pimcore/pimcore": "<10.1.1",
|
||||
"pocketmine/pocketmine-mp": "<3.15.4",
|
||||
"pressbooks/pressbooks": "<5.18",
|
||||
"prestashop/autoupgrade": ">=4,<4.10.1",
|
||||
|
@ -12028,8 +12098,8 @@
|
|||
"scheb/two-factor-bundle": ">=0,<3.26|>=4,<4.11",
|
||||
"sensiolabs/connect": "<4.2.3",
|
||||
"serluck/phpwhois": "<=4.2.6",
|
||||
"shopware/core": "<=6.4.1",
|
||||
"shopware/platform": "<=6.4.1",
|
||||
"shopware/core": "<=6.4.3",
|
||||
"shopware/platform": "<=6.4.3",
|
||||
"shopware/production": "<=6.3.5.2",
|
||||
"shopware/shopware": "<=5.6.9",
|
||||
"silverstripe/admin": ">=1.0.3,<1.0.4|>=1.1,<1.1.1",
|
||||
|
@ -12197,7 +12267,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-08-19T16:07:21+00:00"
|
||||
"time": "2021-08-23T20:03:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
|
@ -13760,9 +13830,9 @@
|
|||
"stability-flags": {
|
||||
"azuracast/azuraforms": 20,
|
||||
"azuracast/flysystem-v2-extensions": 20,
|
||||
"azuracast/metadata-manager": 20,
|
||||
"azuracast/nowplaying": 20,
|
||||
"azuracast/slim-callable-eventdispatcher": 20,
|
||||
"james-heinrich/getid3": 20,
|
||||
"lstrojny/fxmlrpc": 20,
|
||||
"rlanvin/php-ip": 20,
|
||||
"supervisorphp/supervisor": 20,
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
class Metadata implements \JsonSerializable
|
||||
{
|
||||
/** @var array<string, mixed> */
|
||||
protected array $tags = [];
|
||||
|
||||
protected float $duration = 0.0;
|
||||
|
||||
protected ?string $artwork = null;
|
||||
|
||||
protected string $mimeType = '';
|
||||
|
||||
public function getTags(): array
|
||||
{
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
public function setTags(array $tags): void
|
||||
{
|
||||
$this->tags = $tags;
|
||||
}
|
||||
|
||||
public function addTag(string $key, mixed $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;
|
||||
}
|
||||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
// Artwork is not included in this JSON feed.
|
||||
return [
|
||||
'tags' => $this->tags,
|
||||
'duration' => $this->duration,
|
||||
'mimeType' => $this->mimeType,
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromJson(array $data): self
|
||||
{
|
||||
$metadata = new self();
|
||||
|
||||
if (isset($data['tags'])) {
|
||||
$metadata->setTags((array)$data['tags']);
|
||||
}
|
||||
if (isset($data['duration'])) {
|
||||
$metadata->setDuration((float)$data['duration']);
|
||||
}
|
||||
if (isset($data['mimeType'])) {
|
||||
$metadata->setMimeType((string)$data['mimeType']);
|
||||
}
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
}
|
|
@ -10,6 +10,8 @@ use App\Entity\Interfaces\ProcessableMediaInterface;
|
|||
use App\Entity\Interfaces\SongInterface;
|
||||
use App\Normalizer\Attributes\DeepNormalize;
|
||||
use App\Utilities\Time;
|
||||
use Azura\MetadataManager\Metadata;
|
||||
use Azura\MetadataManager\MetadataInterface;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
@ -460,7 +462,7 @@ class StationMedia implements SongInterface, ProcessableMediaInterface, PathAwar
|
|||
return $this->playlists;
|
||||
}
|
||||
|
||||
public function fromMetadata(Metadata $metadata): void
|
||||
public function fromMetadata(MetadataInterface $metadata): void
|
||||
{
|
||||
$this->setLength((int)$metadata->getDuration());
|
||||
|
||||
|
@ -488,7 +490,7 @@ class StationMedia implements SongInterface, ProcessableMediaInterface, PathAwar
|
|||
$this->updateSongId();
|
||||
}
|
||||
|
||||
public function toMetadata(): Metadata
|
||||
public function toMetadata(): MetadataInterface
|
||||
{
|
||||
$metadata = new Metadata();
|
||||
$metadata->setDuration($this->getLength() ?? 0.0);
|
||||
|
|
|
@ -4,12 +4,13 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Event\Media;
|
||||
|
||||
use App\Entity;
|
||||
use Azura\MetadataManager\Metadata;
|
||||
use Azura\MetadataManager\MetadataInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class ReadMetadata extends Event
|
||||
{
|
||||
protected ?Entity\Metadata $metadata = null;
|
||||
protected ?MetadataInterface $metadata = null;
|
||||
|
||||
public function __construct(
|
||||
protected string $path
|
||||
|
@ -21,13 +22,13 @@ class ReadMetadata extends Event
|
|||
return $this->path;
|
||||
}
|
||||
|
||||
public function setMetadata(Entity\Metadata $metadata): void
|
||||
public function setMetadata(MetadataInterface $metadata): void
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
}
|
||||
|
||||
public function getMetadata(): Entity\Metadata
|
||||
public function getMetadata(): MetadataInterface
|
||||
{
|
||||
return $this->metadata ?? new Entity\Metadata();
|
||||
return $this->metadata ?? new Metadata();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,18 +4,18 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Event\Media;
|
||||
|
||||
use App\Entity;
|
||||
use Azura\MetadataManager\MetadataInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class WriteMetadata extends Event
|
||||
{
|
||||
public function __construct(
|
||||
protected Entity\Metadata $metadata,
|
||||
protected MetadataInterface $metadata,
|
||||
protected string $path
|
||||
) {
|
||||
}
|
||||
|
||||
public function getMetadata(): ?Entity\Metadata
|
||||
public function getMetadata(): ?MetadataInterface
|
||||
{
|
||||
return $this->metadata;
|
||||
}
|
||||
|
|
|
@ -4,13 +4,14 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Media;
|
||||
|
||||
use App\Entity;
|
||||
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 Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
@ -38,7 +39,7 @@ class MetadataManager implements EventSubscriberInterface
|
|||
];
|
||||
}
|
||||
|
||||
public function read(string $filePath): Entity\Metadata
|
||||
public function read(string $filePath): MetadataInterface
|
||||
{
|
||||
if (!MimeType::isFileProcessable($filePath)) {
|
||||
$mimeType = MimeType::getMimeTypeFromFile($filePath);
|
||||
|
@ -67,7 +68,7 @@ class MetadataManager implements EventSubscriberInterface
|
|||
throw new \RuntimeException('Could not find PHP executable path.');
|
||||
}
|
||||
|
||||
$scriptPath = $this->environment->getBaseDirectory() . '/bin/metadata';
|
||||
$scriptPath = $this->environment->getBaseDirectory() . '/vendor/bin/metadata-manager';
|
||||
|
||||
$process = new Process(
|
||||
[
|
||||
|
@ -76,14 +77,14 @@ class MetadataManager implements EventSubscriberInterface
|
|||
'read',
|
||||
$sourceFilePath,
|
||||
$jsonOutput,
|
||||
'--art-output=' . $artOutput,
|
||||
$artOutput,
|
||||
]
|
||||
);
|
||||
|
||||
$process->mustRun();
|
||||
|
||||
$metadataJson = Json::loadFromFile($jsonOutput);
|
||||
$metadata = Entity\Metadata::fromJson($metadataJson);
|
||||
$metadata = Metadata::fromJson($metadataJson);
|
||||
|
||||
if (is_file($artOutput)) {
|
||||
$artwork = file_get_contents($artOutput) ?: null;
|
||||
|
@ -97,7 +98,7 @@ class MetadataManager implements EventSubscriberInterface
|
|||
}
|
||||
}
|
||||
|
||||
public function write(Entity\Metadata $metadata, string $filePath): void
|
||||
public function write(MetadataInterface $metadata, string $filePath): void
|
||||
{
|
||||
$event = new WriteMetadata($metadata, $filePath);
|
||||
$this->eventDispatcher->dispatch($event);
|
||||
|
@ -136,7 +137,7 @@ class MetadataManager implements EventSubscriberInterface
|
|||
throw new \RuntimeException('Could not find PHP executable path.');
|
||||
}
|
||||
|
||||
$scriptPath = $this->environment->getBaseDirectory() . '/bin/metadata';
|
||||
$scriptPath = $this->environment->getBaseDirectory() . '/vendor/bin/metadata-manager';
|
||||
|
||||
$processCommand = [
|
||||
$phpBinaryPath,
|
||||
|
@ -147,7 +148,7 @@ class MetadataManager implements EventSubscriberInterface
|
|||
];
|
||||
|
||||
if (null !== $artwork) {
|
||||
$processCommand[] = '--art-input=' . $artInput;
|
||||
$processCommand[] = $artInput;
|
||||
}
|
||||
|
||||
$process = new Process($processCommand);
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\MediaProcessor\Command;
|
||||
|
||||
use App\Entity;
|
||||
use App\Utilities\Arrays;
|
||||
use App\Utilities\Time;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use voku\helper\UTF8;
|
||||
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
|
||||
class ReadCommand
|
||||
{
|
||||
public function __invoke(
|
||||
SymfonyStyle $io,
|
||||
OutputInterface $output,
|
||||
string $path,
|
||||
string $jsonOutput,
|
||||
?string $artOutput
|
||||
): int {
|
||||
if (!is_file($path)) {
|
||||
$io->error(sprintf('File not readable: %s', $path));
|
||||
return 1;
|
||||
}
|
||||
|
||||
$id3 = new \getID3();
|
||||
|
||||
$id3->option_md5_data = true;
|
||||
$id3->option_md5_data_source = true;
|
||||
$id3->encoding = 'UTF-8';
|
||||
|
||||
$info = $id3->analyze($path);
|
||||
$id3->CopyTagsToComments($info);
|
||||
|
||||
if (!empty($info['error'])) {
|
||||
$io->error(
|
||||
sprintf(
|
||||
'Cannot process media at path %s: %s',
|
||||
pathinfo($path, PATHINFO_FILENAME),
|
||||
json_encode($info['error'], JSON_THROW_ON_ERROR)
|
||||
)
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
$metadata = new Entity\Metadata();
|
||||
|
||||
if (is_numeric($info['playtime_seconds'])) {
|
||||
$metadata->setDuration(
|
||||
Time::displayTimeToSeconds($info['playtime_seconds']) ?? 0.0
|
||||
);
|
||||
}
|
||||
|
||||
$metaTags = [];
|
||||
|
||||
$toProcess = [
|
||||
$info['comments'] ?? null,
|
||||
$info['tags'] ?? null,
|
||||
];
|
||||
|
||||
foreach ($toProcess as $tagSet) {
|
||||
if (empty($tagSet)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($tagSet as $tagName => $tagContents) {
|
||||
if (!empty($tagContents[0]) && !isset($metaTags[$tagName])) {
|
||||
$tagValue = $tagContents[0];
|
||||
if (is_array($tagValue)) {
|
||||
// Skip pictures
|
||||
if (isset($tagValue['data'])) {
|
||||
continue;
|
||||
}
|
||||
$flatValue = Arrays::flattenArray($tagValue);
|
||||
$tagValue = implode(', ', $flatValue);
|
||||
}
|
||||
|
||||
$metaTags[$tagName] = $this->cleanUpString((string)$tagValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$metadata->setTags($metaTags);
|
||||
$metadata->setMimeType($info['mime_type']);
|
||||
|
||||
file_put_contents(
|
||||
$jsonOutput,
|
||||
json_encode($metadata, JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
if (null !== $artOutput) {
|
||||
$artwork = null;
|
||||
if (!empty($info['attached_picture'][0])) {
|
||||
$artwork = $info['attached_picture'][0]['data'];
|
||||
} elseif (!empty($info['comments']['picture'][0])) {
|
||||
$artwork = $info['comments']['picture'][0]['data'];
|
||||
} elseif (!empty($info['id3v2']['APIC'][0]['data'])) {
|
||||
$artwork = $info['id3v2']['APIC'][0]['data'];
|
||||
} elseif (!empty($info['id3v2']['PIC'][0]['data'])) {
|
||||
$artwork = $info['id3v2']['PIC'][0]['data'];
|
||||
}
|
||||
|
||||
if (!empty($artwork)) {
|
||||
file_put_contents(
|
||||
$artOutput,
|
||||
$artwork
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\MediaProcessor\Command;
|
||||
|
||||
use App\Entity\Metadata;
|
||||
use App\Utilities\Json;
|
||||
use getID3;
|
||||
use getid3_writetags;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class WriteCommand
|
||||
{
|
||||
public function __invoke(
|
||||
SymfonyStyle $io,
|
||||
OutputInterface $output,
|
||||
string $path,
|
||||
string $jsonInput,
|
||||
?string $artInput
|
||||
): int {
|
||||
$getID3 = new getID3();
|
||||
$getID3->setOption(['encoding' => 'UTF8']);
|
||||
|
||||
$tagwriter = new getid3_writetags();
|
||||
$tagwriter->filename = $path;
|
||||
|
||||
$pathExt = strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
||||
|
||||
$tagFormats = match ($pathExt) {
|
||||
'mp3', 'mp2', 'mp1', 'riff' => ['id3v1', 'id3v2.3'],
|
||||
'mpc' => ['ape'],
|
||||
'flac' => ['metaflac'],
|
||||
'real' => ['real'],
|
||||
'ogg' => ['vorbiscomment'],
|
||||
default => null
|
||||
};
|
||||
|
||||
if (null === $tagFormats) {
|
||||
$io->error('Cannot write tag formats based on file type.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$tagwriter->tagformats = $tagFormats;
|
||||
$tagwriter->overwrite_tags = true;
|
||||
$tagwriter->tag_encoding = 'UTF8';
|
||||
$tagwriter->remove_other_tags = true;
|
||||
|
||||
$json = Json::loadFromFile($jsonInput);
|
||||
$writeTags = Metadata::fromJson($json)->getTags();
|
||||
|
||||
if ($artInput && is_file($artInput)) {
|
||||
$artContents = file_get_contents($artInput);
|
||||
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);
|
||||
|
||||
$io->error(
|
||||
sprintf(
|
||||
'Cannot process media file %s: %s',
|
||||
$path,
|
||||
implode(', ', $messages)
|
||||
)
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue