Add back redundancy in metadata processing.

This commit is contained in:
Buster Neece 2022-11-19 22:16:03 -06:00
parent 10e1c4d6cc
commit 9dd28cc8b9
No known key found for this signature in database
GPG Key ID: F1D2E64A0005E80E
5 changed files with 178 additions and 49 deletions

View File

@ -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,

View File

@ -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
);
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}
}

View File

@ -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
);
}
}