Add back redundancy in metadata processing.
This commit is contained in:
parent
10e1c4d6cc
commit
9dd28cc8b9
|
@ -187,7 +187,12 @@ return static function (CallableEventDispatcherInterface $dispatcher) {
|
|||
|
||||
$dispatcher->addCallableListener(
|
||||
Event\Media\ReadMetadata::class,
|
||||
App\Media\Metadata\Reader::class
|
||||
App\Media\Metadata\Reader\PhpReader::class,
|
||||
);
|
||||
$dispatcher->addCallableListener(
|
||||
Event\Media\ReadMetadata::class,
|
||||
App\Media\Metadata\Reader\FfprobeReader::class,
|
||||
priority: -10
|
||||
);
|
||||
$dispatcher->addCallableListener(
|
||||
Event\Media\WriteMetadata::class,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Media\Metadata;
|
||||
namespace App\Media\Metadata\Reader;
|
||||
|
||||
use App\Event\Media\ReadMetadata;
|
||||
use App\Media\Enums\MetadataTags;
|
||||
|
@ -10,22 +10,33 @@ use App\Media\Metadata;
|
|||
use App\Media\MimeType;
|
||||
use App\Utilities\Arrays;
|
||||
use App\Utilities\File;
|
||||
use App\Utilities\Strings;
|
||||
use App\Utilities\Time;
|
||||
use FFMpeg\FFMpeg;
|
||||
use FFMpeg\FFProbe;
|
||||
use FFMpeg\FFProbe\DataMapping\Stream;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Throwable;
|
||||
use voku\helper\UTF8;
|
||||
|
||||
final class Reader
|
||||
final class FfprobeReader
|
||||
{
|
||||
private readonly FFProbe $ffprobe;
|
||||
|
||||
private readonly FFMpeg $ffmpeg;
|
||||
|
||||
public function __construct(
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->ffprobe = FFProbe::create([], $logger);
|
||||
$this->ffmpeg = FFMpeg::create([], $logger, $this->ffprobe);
|
||||
}
|
||||
|
||||
public function __invoke(ReadMetadata $event): void
|
||||
{
|
||||
$path = $event->getPath();
|
||||
|
||||
$ffprobe = FFProbe::create();
|
||||
$format = $ffprobe->format($path);
|
||||
$streams = $ffprobe->streams($path);
|
||||
$format = $this->ffprobe->format($path);
|
||||
$streams = $this->ffprobe->streams($path);
|
||||
|
||||
$metadata = new Metadata();
|
||||
$metadata->setMimeType(MimeType::getMimeTypeFromFile($path));
|
||||
|
@ -35,15 +46,19 @@ final class Reader
|
|||
$metadata->setDuration($duration);
|
||||
}
|
||||
|
||||
$metadata->setTags($this->aggregateMetaTags(
|
||||
$format,
|
||||
$streams
|
||||
));
|
||||
$metadata->setTags(
|
||||
$this->aggregateMetaTags(
|
||||
$format,
|
||||
$streams
|
||||
)
|
||||
);
|
||||
|
||||
$metadata->setArtwork($this->getAlbumArt(
|
||||
$streams,
|
||||
$path
|
||||
));
|
||||
$metadata->setArtwork(
|
||||
$this->getAlbumArt(
|
||||
$streams,
|
||||
$path
|
||||
)
|
||||
);
|
||||
|
||||
$event->setMetadata($metadata);
|
||||
}
|
||||
|
@ -109,7 +124,7 @@ final class Reader
|
|||
$tagValue = implode(', ', $flatValue);
|
||||
}
|
||||
|
||||
$tagValue = $this->cleanUpString((string)$tagValue);
|
||||
$tagValue = Strings::stringToUtf8((string)$tagValue);
|
||||
|
||||
$tagName = $tagEnum->value;
|
||||
if (isset($metaTags[$tagName])) {
|
||||
|
@ -128,8 +143,6 @@ final class Reader
|
|||
string $path
|
||||
): ?string {
|
||||
// Pull album art directly from relevant streams.
|
||||
$ffmpeg = FFMpeg::create();
|
||||
|
||||
try {
|
||||
/** @var Stream $videoStream */
|
||||
foreach ($streams->videos() as $videoStream) {
|
||||
|
@ -141,7 +154,7 @@ final class Reader
|
|||
$artOutput = File::generateTempPath('artwork.jpg');
|
||||
@unlink($artOutput); // Ffmpeg won't overwrite the empty file.
|
||||
|
||||
$ffmpeg->getFFMpegDriver()->command([
|
||||
$this->ffmpeg->getFFMpegDriver()->command([
|
||||
'-i',
|
||||
$path,
|
||||
'-an',
|
||||
|
@ -159,20 +172,4 @@ final class Reader
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
private 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,120 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Media\Metadata\Reader;
|
||||
|
||||
use App\Event\Media\ReadMetadata;
|
||||
use App\Media\Metadata;
|
||||
use App\Utilities\Arrays;
|
||||
use App\Utilities\Strings;
|
||||
use App\Utilities\Time;
|
||||
use JamesHeinrich\GetID3\GetID3;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
|
||||
final class PhpReader
|
||||
{
|
||||
public function __construct(
|
||||
private readonly LoggerInterface $logger
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(ReadMetadata $event): void
|
||||
{
|
||||
$path = $event->getPath();
|
||||
|
||||
try {
|
||||
$getid3 = new GetID3();
|
||||
$getid3->option_md5_data = true;
|
||||
$getid3->option_md5_data_source = true;
|
||||
$getid3->encoding = 'UTF-8';
|
||||
|
||||
$info = $getid3->analyze($path);
|
||||
$getid3->CopyTagsToComments($info);
|
||||
|
||||
if (!empty($info['error'])) {
|
||||
throw new \RuntimeException(
|
||||
json_encode($info['error'], JSON_THROW_ON_ERROR)
|
||||
);
|
||||
}
|
||||
|
||||
$metadata = new Metadata();
|
||||
|
||||
if (is_numeric($info['playtime_seconds'])) {
|
||||
$metadata->setDuration(
|
||||
Time::displayTimeToSeconds($info['playtime_seconds']) ?? 0.0
|
||||
);
|
||||
}
|
||||
|
||||
$toProcess = [
|
||||
$info['comments'] ?? null,
|
||||
$info['tags'] ?? null,
|
||||
];
|
||||
|
||||
$metaTags = $this->aggregateMetaTags($toProcess);
|
||||
|
||||
$metadata->setTags($metaTags);
|
||||
$metadata->setMimeType($info['mime_type']);
|
||||
|
||||
$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)) {
|
||||
$metadata->setArtwork($artwork);
|
||||
}
|
||||
|
||||
$event->setMetadata($metadata);
|
||||
$event->stopPropagation();
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->info(
|
||||
sprintf(
|
||||
'getid3 failed for file %s: %s',
|
||||
$path,
|
||||
$e->getMessage()
|
||||
),
|
||||
[
|
||||
'exception' => $e,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function aggregateMetaTags(array $toProcess): array
|
||||
{
|
||||
$metaTags = [];
|
||||
|
||||
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[(string)$tagName] = Strings::stringToUtf8((string)$tagValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $metaTags;
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
|||
namespace App\Media\Metadata;
|
||||
|
||||
use App\Event\Media\WriteMetadata;
|
||||
use JamesHeinrich\GetID3\GetID3;
|
||||
use JamesHeinrich\GetID3\WriteTags;
|
||||
use RuntimeException;
|
||||
|
||||
|
@ -20,11 +19,11 @@ final class Writer
|
|||
return;
|
||||
}
|
||||
|
||||
$getID3 = new GetID3();
|
||||
$getID3->setOption(['encoding' => 'UTF8']);
|
||||
|
||||
$tagwriter = new WriteTags();
|
||||
$tagwriter->filename = $path;
|
||||
$tagwriter->overwrite_tags = true;
|
||||
$tagwriter->tag_encoding = 'UTF8';
|
||||
$tagwriter->remove_other_tags = true;
|
||||
|
||||
$pathExt = strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
||||
|
||||
|
@ -42,9 +41,6 @@ final class Writer
|
|||
}
|
||||
|
||||
$tagwriter->tagformats = $tagFormats;
|
||||
$tagwriter->overwrite_tags = true;
|
||||
$tagwriter->tag_encoding = 'UTF8';
|
||||
$tagwriter->remove_other_tags = true;
|
||||
|
||||
$writeTags = $metadata->getTags();
|
||||
|
||||
|
@ -73,13 +69,7 @@ final class Writer
|
|||
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)
|
||||
)
|
||||
);
|
||||
throw new RuntimeException(implode(', ', $messages));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace App\Utilities;
|
||||
|
||||
use RuntimeException;
|
||||
use voku\helper\UTF8;
|
||||
|
||||
final class Strings
|
||||
{
|
||||
|
@ -130,4 +131,20 @@ final class Strings
|
|||
$result = str_replace(' ', '_', $result);
|
||||
return mb_strtolower($result);
|
||||
}
|
||||
|
||||
public static function stringToUtf8(?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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue