From 9dd28cc8b91cdbe5c9f1ed3ab78ddd09116d81bf Mon Sep 17 00:00:00 2001 From: Buster Neece Date: Sat, 19 Nov 2022 22:16:03 -0600 Subject: [PATCH] Add back redundancy in metadata processing. --- config/events.php | 7 +- .../{Reader.php => Reader/FfprobeReader.php} | 65 +++++----- src/Media/Metadata/Reader/PhpReader.php | 120 ++++++++++++++++++ src/Media/Metadata/Writer.php | 18 +-- src/Utilities/Strings.php | 17 +++ 5 files changed, 178 insertions(+), 49 deletions(-) rename src/Media/Metadata/{Reader.php => Reader/FfprobeReader.php} (81%) create mode 100644 src/Media/Metadata/Reader/PhpReader.php diff --git a/config/events.php b/config/events.php index b45dc39eb..aaa4f50c4 100644 --- a/config/events.php +++ b/config/events.php @@ -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, diff --git a/src/Media/Metadata/Reader.php b/src/Media/Metadata/Reader/FfprobeReader.php similarity index 81% rename from src/Media/Metadata/Reader.php rename to src/Media/Metadata/Reader/FfprobeReader.php index 82d2b0303..9c2c2cff1 100644 --- a/src/Media/Metadata/Reader.php +++ b/src/Media/Metadata/Reader/FfprobeReader.php @@ -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 - ); - } } diff --git a/src/Media/Metadata/Reader/PhpReader.php b/src/Media/Metadata/Reader/PhpReader.php new file mode 100644 index 000000000..24add8a82 --- /dev/null +++ b/src/Media/Metadata/Reader/PhpReader.php @@ -0,0 +1,120 @@ +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; + } +} diff --git a/src/Media/Metadata/Writer.php b/src/Media/Metadata/Writer.php index 934445f21..d291ae9c9 100644 --- a/src/Media/Metadata/Writer.php +++ b/src/Media/Metadata/Writer.php @@ -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)); } } } diff --git a/src/Utilities/Strings.php b/src/Utilities/Strings.php index 8549e3ad6..fe345c8a3 100644 --- a/src/Utilities/Strings.php +++ b/src/Utilities/Strings.php @@ -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 + ); + } }