From 5a063e32c3fc2934bbadbb252e30bfe325b2d282 Mon Sep 17 00:00:00 2001 From: Peter Deltchev Date: Tue, 16 Feb 2016 01:12:59 -0800 Subject: [PATCH] Refactored tag parsing for uploaded files into its own command. --- app/Commands/ParseTrackTagsCommand.php | 432 +++++++++++++++++++++++++ app/Commands/UploadTrackCommand.php | 411 +---------------------- app/Library/AudioCache.php | 6 +- 3 files changed, 447 insertions(+), 402 deletions(-) create mode 100644 app/Commands/ParseTrackTagsCommand.php diff --git a/app/Commands/ParseTrackTagsCommand.php b/app/Commands/ParseTrackTagsCommand.php new file mode 100644 index 00000000..92b6ddca --- /dev/null +++ b/app/Commands/ParseTrackTagsCommand.php @@ -0,0 +1,432 @@ +. + */ + +namespace Poniverse\Ponyfm\Commands; + +use Carbon\Carbon; +use Config; +use getID3; +use Poniverse\Ponyfm\Models\Album; +use Poniverse\Ponyfm\Models\Genre; +use Poniverse\Ponyfm\Models\Image; +use Poniverse\Ponyfm\Models\Track; +use AudioCache; +use File; +use Illuminate\Support\Str; +use Poniverse\Ponyfm\Models\TrackType; +use Poniverse\Ponyfm\Models\User; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; + + +class ParseTrackTagsCommand extends CommandBase +{ + private $track; + private $fileToParse; + private $input; + + public function __construct(Track $track, \Symfony\Component\HttpFoundation\File\File $fileToParse, $inputTags = []) + { + $this->track = $track; + $this->fileToParse = $fileToParse; + $this->input = $inputTags; + } + + /** + * @throws \Exception + * @return CommandResponse + */ + public function execute() + { + $audio = AudioCache::get($this->fileToParse->getPathname()); + list($parsedTags, $rawTags) = $this->parseOriginalTags($this->fileToParse, $this->track->user, $audio->getAudioCodec()); + $this->track->original_tags = ['parsed_tags' => $parsedTags, 'raw_tags' => $rawTags]; + + + if ($this->input['cover'] !== null) { + $this->track->cover_id = Image::upload($this->input['cover'], $this->track->user_id)->id; + } else { + $this->track->cover_id = $parsedTags['cover_id']; + } + + $this->track->title = $this->input['title'] ?? $parsedTags['title'] ?? $this->track->title; + $this->track->track_type_id = $this->input['track_type_id'] ?? TrackType::UNCLASSIFIED_TRACK; + + $this->track->genre_id = isset($this->input['genre']) + ? $this->getGenreId($this->input['genre']) + : $parsedTags['genre_id']; + + $this->track->album_id = isset($this->input['album']) + ? $this->getAlbumId($this->track->user_id, $this->input['album']) + : $parsedTags['album_id']; + + if ($this->track->album_id === null) { + $this->track->track_number = null; + } else { + $this->track->track_number = $this->input['track_number'] ?? $parsedTags['track_number']; + } + + $this->track->released_at = isset($this->input['released_at']) + ? Carbon::createFromFormat(Carbon::ISO8601, $this->input['released_at']) + : $parsedTags['release_date']; + + $this->track->description = $this->input['description'] ?? $parsedTags['comments']; + $this->track->lyrics = $this->input['lyrics'] ?? $parsedTags['lyrics']; + + $this->track->is_vocal = $this->input['is_vocal'] ?? $parsedTags['is_vocal']; + $this->track->is_explicit = $this->input['is_explicit'] ?? false; + $this->track->is_downloadable = $this->input['is_downloadable'] ?? true; + $this->track->is_listed = $this->input['is_listed'] ?? true; + + $this->track->save(); + return CommandResponse::succeed(); + } + + /** + * Returns the ID of the given genre, creating it if necessary. + * + * @param string $genreName + * @return int + */ + protected function getGenreId(string $genreName) { + return Genre::firstOrCreate([ + 'name' => $genreName, + 'slug' => Str::slug($genreName) + ])->id; + } + + /** + * Returns the ID of the given album, creating it if necessary. + * The cover ID is only used if a new album is created - it will not be + * written to an existing album. + * + * @param int $artistId + * @param string|null $albumName + * @param null $coverId + * @return int|null + */ + protected function getAlbumId(int $artistId, $albumName, $coverId = null) { + if (null !== $albumName) { + $album = Album::firstOrNew([ + 'user_id' => $artistId, + 'title' => $albumName + ]); + + if (null === $album->id) { + $album->description = ''; + $album->track_count = 0; + $album->cover_id = $coverId; + $album->save(); + } + + return $album->id; + } else { + return null; + } + } + + /** + * Extracts a file's tags. + * + * @param \Symfony\Component\HttpFoundation\File\File $file + * @param User $artist + * @param string $audioCodec + * @return array the "processed" and raw tags extracted from the file + * @throws \Exception + */ + protected function parseOriginalTags(\Symfony\Component\HttpFoundation\File\File $file, User $artist, string $audioCodec) { + //========================================================================================================== + // Extract the original tags. + //========================================================================================================== + $getId3 = new getID3; + + // all tags read by getID3, including the cover art + $allTags = $getId3->analyze($file->getPathname()); + + // $rawTags => tags specific to a file format (ID3 or Atom), pre-normalization but with cover art removed + // $parsedTags => normalized tags used by Pony.fm + + if ($audioCodec === 'mp3') { + list($parsedTags, $rawTags) = $this->getId3Tags($allTags); + + } elseif (Str::startsWith($audioCodec, ['aac', 'alac'])) { + list($parsedTags, $rawTags) = $this->getAtomTags($allTags); + + } elseif (in_array($audioCodec, ['vorbis', 'flac'])) { + list($parsedTags, $rawTags) = $this->getVorbisTags($allTags); + + } elseif (Str::startsWith($audioCodec, ['pcm', 'adpcm'])) { + list($parsedTags, $rawTags) = $this->getAtomTags($allTags); + + } else { + // Assume the file is untagged if it's in an unknown format. + $parsedTags = [ + 'title' => null, + 'artist' => null, + 'band' => null, + 'genre' => null, + 'track_number' => null, + 'album' => null, + 'year' => null, + 'release_date' => null, + 'comments' => null, + 'lyrics' => null, + ]; + $rawTags = []; + } + + //========================================================================================================== + // Determine the release date. + //========================================================================================================== + if ($parsedTags['release_date'] === null && $parsedTags['year'] !== null) { + $parsedTags['release_date'] = Carbon::create($parsedTags['year'], 1, 1); + } + + //========================================================================================================== + // Does this track have vocals? + //========================================================================================================== + $parsedTags['is_vocal'] = $parsedTags['lyrics'] !== null; + + + //========================================================================================================== + // Determine the genre. + //========================================================================================================== + $genreName = $parsedTags['genre']; + + if ($genreName !== null) { + $parsedTags['genre_id'] = $this->getGenreId($genreName); + + } else { + $parsedTags['genre_id'] = null; + } + + //========================================================================================================== + // Extract the cover art, if any exists. + //========================================================================================================== + + $coverId = null; + if (array_key_exists('comments', $allTags) && array_key_exists('picture', $allTags['comments'])) { + $image = $allTags['comments']['picture'][0]; + + if ($image['image_mime'] === 'image/png') { + $extension = 'png'; + + } elseif ($image['image_mime'] === 'image/jpeg') { + $extension = 'jpg'; + + } else { + throw new BadRequestHttpException('Unknown cover format embedded in the track file!'); + } + + // write temporary image file + $tmpPath = Config::get('ponyfm.files_directory') . '/tmp'; + + $filename = $file->getFilename() . ".cover.${extension}"; + $imageFilePath = "${tmpPath}/${filename}"; + + File::put($imageFilePath, $image['data']); + $imageFile = new UploadedFile($imageFilePath, $filename, $image['image_mime']); + + $cover = Image::upload($imageFile, $artist); + $coverId = $cover->id; + + } else { + // no cover art was found - carry on + } + + $parsedTags['cover_id'] = $coverId; + + + //========================================================================================================== + // Is this part of an album? + //========================================================================================================== + $albumId = null; + $albumName = $parsedTags['album']; + + if ($albumName !== null) { + $albumId = $this->getAlbumId($artist->id, $albumName, $coverId); + } + + $parsedTags['album_id'] = $albumId; + + + return [$parsedTags, $rawTags]; + } + + + /** + * @param array $rawTags + * @return array + */ + protected function getId3Tags($rawTags) { + if (array_key_exists('tags', $rawTags) && array_key_exists('id3v2', $rawTags['tags'])) { + $tags = $rawTags['tags']['id3v2']; + } elseif (array_key_exists('tags', $rawTags) && array_key_exists('id3v1', $rawTags['tags'])) { + $tags = $rawTags['tags']['id3v1']; + } else { + $tags = []; + } + + + $comment = null; + + if (isset($tags['comment'])) { + // The "comment" tag comes in with a badly encoded string index + // so its array key has to be used implicitly. + $key = array_keys($tags['comment'])[0]; + + // The comment may have a null byte at the end. trim() removes it. + $comment = trim($tags['comment'][$key]); + + // Replace the malformed comment with the "fixed" one. + unset($tags['comment'][$key]); + $tags['comment'][0] = $comment; + } + + return [ + [ + 'title' => isset($tags['title']) ? $tags['title'][0] : null, + 'artist' => isset($tags['artist']) ? $tags['artist'][0] : null, + 'band' => isset($tags['band']) ? $tags['band'][0] : null, + 'genre' => isset($tags['genre']) ? $tags['genre'][0] : null, + 'track_number' => isset($tags['track_number']) ? $tags['track_number'][0] : null, + 'album' => isset($tags['album']) ? $tags['album'][0] : null, + 'year' => isset($tags['year']) ? (int) $tags['year'][0] : null, + 'release_date' => isset($tags['release_date']) ? $this->parseDateString($tags['release_date'][0]) : null, + 'comments' => $comment, + 'lyrics' => isset($tags['unsynchronised_lyric']) ? $tags['unsynchronised_lyric'][0] : null, + ], + $tags + ]; + } + + /** + * @param array $rawTags + * @return array + */ + protected function getAtomTags($rawTags) { + if (array_key_exists('tags', $rawTags) && array_key_exists('quicktime', $rawTags['tags'])) { + $tags = $rawTags['tags']['quicktime']; + } else { + $tags = []; + } + + $trackNumber = null; + if (isset($tags['track_number'])) { + $trackNumberComponents = explode('/', $tags['track_number'][0]); + $trackNumber = $trackNumberComponents[0]; + } + + return [ + [ + 'title' => isset($tags['title']) ? $tags['title'][0] : null, + 'artist' => isset($tags['artist']) ? $tags['artist'][0] : null, + 'band' => isset($tags['band']) ? $tags['band'][0] : null, + 'album_artist' => isset($tags['album_artist']) ? $tags['album_artist'][0] : null, + 'genre' => isset($tags['genre']) ? $tags['genre'][0] : null, + 'track_number' => $trackNumber, + 'album' => isset($tags['album']) ? $tags['album'][0] : null, + 'year' => isset($tags['year']) ? (int) $tags['year'][0] : null, + 'release_date' => isset($tags['release_date']) ? $this->parseDateString($tags['release_date'][0]) : null, + 'comments' => isset($tags['comments']) ? $tags['comments'][0] : null, + 'lyrics' => isset($tags['lyrics']) ? $tags['lyrics'][0] : null, + ], + $tags + ]; + } + + /** + * @param array $rawTags + * @return array + */ + protected function getVorbisTags($rawTags) { + if (array_key_exists('tags', $rawTags) && array_key_exists('vorbiscomment', $rawTags['tags'])) { + $tags = $rawTags['tags']['vorbiscomment']; + } else { + $tags = []; + } + + $trackNumber = null; + if (isset($tags['track_number'])) { + $trackNumberComponents = explode('/', $tags['track_number'][0]); + $trackNumber = $trackNumberComponents[0]; + } + + return [ + [ + 'title' => isset($tags['title']) ? $tags['title'][0] : null, + 'artist' => isset($tags['artist']) ? $tags['artist'][0] : null, + 'band' => isset($tags['band']) ? $tags['band'][0] : null, + 'album_artist' => isset($tags['album_artist']) ? $tags['album_artist'][0] : null, + 'genre' => isset($tags['genre']) ? $tags['genre'][0] : null, + 'track_number' => $trackNumber, + 'album' => isset($tags['album']) ? $tags['album'][0] : null, + 'year' => isset($tags['year']) ? (int) $tags['year'][0] : null, + 'release_date' => isset($tags['date']) ? $this->parseDateString($tags['date'][0]) : null, + 'comments' => isset($tags['comments']) ? $tags['comments'][0] : null, + 'lyrics' => isset($tags['lyrics']) ? $tags['lyrics'][0] : null, + ], + $tags + ]; + } + + /** + * Parses a potentially-partial date string into a proper date object. + * + * The tagging formats we deal with base their date format on ISO 8601, but + * the timestamp may be incomplete. + * + * @link https://code.google.com/p/mp4v2/wiki/iTunesMetadata + * @link https://wiki.xiph.org/VorbisComment#Date_and_time + * @link http://id3.org/id3v2.4.0-frames + * + * @param string $dateString + * @return null|Carbon + */ + protected function parseDateString(string $dateString) { + switch (Str::length($dateString)) { + // YYYY + case 4: + return Carbon::createFromFormat('Y', $dateString) + ->month(1) + ->day(1); + + // YYYY-MM + case 7: + return Carbon::createFromFormat('Y-m', $dateString) + ->day(1); + + // YYYY-MM-DD + case 10: + return Carbon::createFromFormat('Y-m-d', $dateString); + break; + + default: + // We might have an ISO-8601 string in our hooves. + // If not, give up. + try { + return Carbon::createFromFormat(Carbon::ISO8601, $dateString); + + } catch (\InvalidArgumentException $e) { + return null; + } + } + } +} diff --git a/app/Commands/UploadTrackCommand.php b/app/Commands/UploadTrackCommand.php index 4e42c86a..a18d792a 100644 --- a/app/Commands/UploadTrackCommand.php +++ b/app/Commands/UploadTrackCommand.php @@ -22,23 +22,10 @@ namespace Poniverse\Ponyfm\Commands; use Carbon\Carbon; use Config; -use getID3; use Illuminate\Foundation\Bus\DispatchesJobs; use Input; -use Log; -use Poniverse\Ponyfm\Exceptions\InvalidEncodeOptionsException; -use Poniverse\Ponyfm\Exceptions\UnknownTagFormatException; -use Poniverse\Ponyfm\Models\Album; -use Poniverse\Ponyfm\Models\Genre; -use Poniverse\Ponyfm\Models\Image; use Poniverse\Ponyfm\Models\Track; use AudioCache; -use File; -use Illuminate\Support\Str; -use Poniverse\Ponyfm\Models\TrackType; -use Poniverse\Ponyfm\Models\User; -use Symfony\Component\HttpFoundation\File\UploadedFile; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Validator; class UploadTrackCommand extends CommandBase @@ -81,22 +68,21 @@ class UploadTrackCommand extends CommandBase public function execute() { $user = \Auth::user(); - $trackFile = \Input::file('track', null); + $trackFile = Input::file('track', null); + $coverFile = Input::file('cover', null); if (null === $trackFile) { return CommandResponse::fail(['track' => ['You must upload an audio file!']]); } $audio = \AudioCache::get($trackFile->getPathname()); - list($parsedTags, $rawTags) = $this->parseOriginalTags($trackFile, $user, $audio->getAudioCodec()); - $track = new Track(); $track->user_id = $user->id; - $track->title = Input::get('title', $parsedTags['title']); + // The title set here is a placeholder; it'll be replaced by ParseTrackTagsCommand + // if the file contains a title tag. + $track->title = Input::get('title', pathinfo($trackFile->getClientOriginalName(), PATHINFO_FILENAME)); $track->duration = $audio->getDuration(); - - $track->save(); $track->ensureDirectoryExists(); @@ -107,6 +93,7 @@ class UploadTrackCommand extends CommandBase $input = Input::all(); $input['track'] = $trackFile; + $input['cover'] = $coverFile; $validator = \Validator::make($input, [ 'track' => @@ -138,392 +125,22 @@ class UploadTrackCommand extends CommandBase $track->delete(); return CommandResponse::fail($validator); } - - // Process optional track fields $autoPublish = (bool) ($input['auto_publish'] ?? $this->_autoPublishByDefault); - - if (Input::hasFile('cover')) { - $track->cover_id = Image::upload(Input::file('cover'), $track->user_id)->id; - } else { - $track->cover_id = $parsedTags['cover_id']; - } - - $track->title = $input['title'] ?? $parsedTags['title'] ?? $track->title; - $track->track_type_id = $input['track_type_id'] ?? TrackType::UNCLASSIFIED_TRACK; - - $track->genre_id = isset($input['genre']) - ? $this->getGenreId($input['genre']) - : $parsedTags['genre_id']; - - $track->album_id = isset($input['album']) - ? $this->getAlbumId($user->id, $input['album']) - : $parsedTags['album_id']; - - if ($track->album_id === null) { - $track->track_number = null; - } else { - $track->track_number = $input['track_number'] ?? $parsedTags['track_number']; - } - - $track->released_at = isset($input['released_at']) - ? Carbon::createFromFormat(Carbon::ISO8601, $input['released_at']) - : $parsedTags['release_date']; - - $track->description = $input['description'] ?? $parsedTags['comments']; - $track->lyrics = $input['lyrics'] ?? $parsedTags['lyrics']; - - $track->is_vocal = $input['is_vocal'] ?? $parsedTags['is_vocal']; - $track->is_explicit = $input['is_explicit'] ?? false; - $track->is_downloadable = $input['is_downloadable'] ?? true; - $track->is_listed = $input['is_listed'] ?? true; - $track->source = $this->_customTrackSource ?? 'direct_upload'; + $track->source = $this->_customTrackSource ?? 'direct_upload'; // If json_decode() isn't called here, Laravel will surround the JSON // string with quotes when storing it in the database, which breaks things. $track->metadata = json_decode(Input::get('metadata', null)); - $track->original_tags = ['parsed_tags' => $parsedTags, 'raw_tags' => $rawTags]; - $track->save(); + // Parse any tags in the uploaded files. + $parseTagsCommand = new ParseTrackTagsCommand($track, $trackFile, $input); + $result = $parseTagsCommand->execute(); + if ($result->didFail()) { + return $result; + } + $generateTrackFiles = new GenerateTrackFilesCommand($track, $trackFile, $autoPublish); return $generateTrackFiles->execute(); } - - /** - * Returns the ID of the given genre, creating it if necessary. - * - * @param string $genreName - * @return int - */ - protected function getGenreId(string $genreName) { - return Genre::firstOrCreate([ - 'name' => $genreName, - 'slug' => Str::slug($genreName) - ])->id; - } - - /** - * Returns the ID of the given album, creating it if necessary. - * The cover ID is only used if a new album is created - it will not be - * written to an existing album. - * - * @param int $artistId - * @param string|null $albumName - * @param null $coverId - * @return int|null - */ - protected function getAlbumId(int $artistId, $albumName, $coverId = null) { - if (null !== $albumName) { - $album = Album::firstOrNew([ - 'user_id' => $artistId, - 'title' => $albumName - ]); - - if (null === $album->id) { - $album->description = ''; - $album->track_count = 0; - $album->cover_id = $coverId; - $album->save(); - } - - return $album->id; - } else { - return null; - } - } - - /** - * Extracts a file's tags. - * - * @param UploadedFile $file - * @param User $artist - * @param string $audioCodec - * @return array the "processed" and raw tags extracted from the file - * @throws BadRequestHttpException - */ - protected function parseOriginalTags(UploadedFile $file, User $artist, string $audioCodec) { - //========================================================================================================== - // Extract the original tags. - //========================================================================================================== - $getId3 = new getID3; - - // all tags read by getID3, including the cover art - $allTags = $getId3->analyze($file->getPathname()); - - // $rawTags => tags specific to a file format (ID3 or Atom), pre-normalization but with cover art removed - // $parsedTags => normalized tags used by Pony.fm - - if ($audioCodec === 'mp3') { - list($parsedTags, $rawTags) = $this->getId3Tags($allTags); - - } elseif (Str::startsWith($audioCodec, ['aac', 'alac'])) { - list($parsedTags, $rawTags) = $this->getAtomTags($allTags); - - } elseif (in_array($audioCodec, ['vorbis', 'flac'])) { - list($parsedTags, $rawTags) = $this->getVorbisTags($allTags); - - } elseif (Str::startsWith($audioCodec, ['pcm', 'adpcm'])) { - list($parsedTags, $rawTags) = $this->getAtomTags($allTags); - - } else { - // Assume the file is untagged if it's in an unknown format. - $parsedTags = [ - 'title' => null, - 'artist' => null, - 'band' => null, - 'genre' => null, - 'track_number' => null, - 'album' => null, - 'year' => null, - 'release_date' => null, - 'comments' => null, - 'lyrics' => null, - ]; - $rawTags = []; - } - - - //========================================================================================================== - // Fill in the title tag if it's missing - //========================================================================================================== - $parsedTags['title'] = $parsedTags['title'] ?? pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME); - - - //========================================================================================================== - // Determine the release date. - //========================================================================================================== - if ($parsedTags['release_date'] === null && $parsedTags['year'] !== null) { - $parsedTags['release_date'] = Carbon::create($parsedTags['year'], 1, 1); - } - - //========================================================================================================== - // Does this track have vocals? - //========================================================================================================== - $parsedTags['is_vocal'] = $parsedTags['lyrics'] !== null; - - - //========================================================================================================== - // Determine the genre. - //========================================================================================================== - $genreName = $parsedTags['genre']; - - if ($genreName) { - $parsedTags['genre_id'] = $this->getGenreId($genreName); - - } else { - $parsedTags['genre_id'] = $this->getGenreId('Unknown'); - } - - //========================================================================================================== - // Extract the cover art, if any exists. - //========================================================================================================== - - $coverId = null; - if (array_key_exists('comments', $allTags) && array_key_exists('picture', $allTags['comments'])) { - $image = $allTags['comments']['picture'][0]; - - if ($image['image_mime'] === 'image/png') { - $extension = 'png'; - - } elseif ($image['image_mime'] === 'image/jpeg') { - $extension = 'jpg'; - - } else { - throw new BadRequestHttpException('Unknown cover format embedded in the track file!'); - } - - // write temporary image file - $tmpPath = Config::get('ponyfm.files_directory') . '/tmp'; - - $filename = $file->getFilename() . ".cover.${extension}"; - $imageFilePath = "${tmpPath}/${filename}"; - - File::put($imageFilePath, $image['data']); - $imageFile = new UploadedFile($imageFilePath, $filename, $image['image_mime']); - - $cover = Image::upload($imageFile, $artist); - $coverId = $cover->id; - - } else { - // no cover art was found - carry on - } - - $parsedTags['cover_id'] = $coverId; - - - //========================================================================================================== - // Is this part of an album? - //========================================================================================================== - $albumId = null; - $albumName = $parsedTags['album']; - - if ($albumName !== null) { - $albumId = $this->getAlbumId($artist->id, $albumName, $coverId); - } - - $parsedTags['album_id'] = $albumId; - - - return [$parsedTags, $rawTags]; - } - - - /** - * @param array $rawTags - * @return array - */ - protected function getId3Tags($rawTags) { - if (array_key_exists('tags', $rawTags) && array_key_exists('id3v2', $rawTags['tags'])) { - $tags = $rawTags['tags']['id3v2']; - } elseif (array_key_exists('tags', $rawTags) && array_key_exists('id3v1', $rawTags['tags'])) { - $tags = $rawTags['tags']['id3v1']; - } else { - $tags = []; - } - - - $comment = null; - - if (isset($tags['comment'])) { - // The "comment" tag comes in with a badly encoded string index - // so its array key has to be used implicitly. - $key = array_keys($tags['comment'])[0]; - - // The comment may have a null byte at the end. trim() removes it. - $comment = trim($tags['comment'][$key]); - - // Replace the malformed comment with the "fixed" one. - unset($tags['comment'][$key]); - $tags['comment'][0] = $comment; - } - - return [ - [ - 'title' => isset($tags['title']) ? $tags['title'][0] : null, - 'artist' => isset($tags['artist']) ? $tags['artist'][0] : null, - 'band' => isset($tags['band']) ? $tags['band'][0] : null, - 'genre' => isset($tags['genre']) ? $tags['genre'][0] : null, - 'track_number' => isset($tags['track_number']) ? $tags['track_number'][0] : null, - 'album' => isset($tags['album']) ? $tags['album'][0] : null, - 'year' => isset($tags['year']) ? (int) $tags['year'][0] : null, - 'release_date' => isset($tags['release_date']) ? $this->parseDateString($tags['release_date'][0]) : null, - 'comments' => $comment, - 'lyrics' => isset($tags['unsynchronised_lyric']) ? $tags['unsynchronised_lyric'][0] : null, - ], - $tags - ]; - } - - /** - * @param array $rawTags - * @return array - */ - protected function getAtomTags($rawTags) { - if (array_key_exists('tags', $rawTags) && array_key_exists('quicktime', $rawTags['tags'])) { - $tags = $rawTags['tags']['quicktime']; - } else { - $tags = []; - } - - $trackNumber = null; - if (isset($tags['track_number'])) { - $trackNumberComponents = explode('/', $tags['track_number'][0]); - $trackNumber = $trackNumberComponents[0]; - } - - return [ - [ - 'title' => isset($tags['title']) ? $tags['title'][0] : null, - 'artist' => isset($tags['artist']) ? $tags['artist'][0] : null, - 'band' => isset($tags['band']) ? $tags['band'][0] : null, - 'album_artist' => isset($tags['album_artist']) ? $tags['album_artist'][0] : null, - 'genre' => isset($tags['genre']) ? $tags['genre'][0] : null, - 'track_number' => $trackNumber, - 'album' => isset($tags['album']) ? $tags['album'][0] : null, - 'year' => isset($tags['year']) ? (int) $tags['year'][0] : null, - 'release_date' => isset($tags['release_date']) ? $this->parseDateString($tags['release_date'][0]) : null, - 'comments' => isset($tags['comments']) ? $tags['comments'][0] : null, - 'lyrics' => isset($tags['lyrics']) ? $tags['lyrics'][0] : null, - ], - $tags - ]; - } - - /** - * @param array $rawTags - * @return array - */ - protected function getVorbisTags($rawTags) { - if (array_key_exists('tags', $rawTags) && array_key_exists('vorbiscomment', $rawTags['tags'])) { - $tags = $rawTags['tags']['vorbiscomment']; - } else { - $tags = []; - } - - $trackNumber = null; - if (isset($tags['track_number'])) { - $trackNumberComponents = explode('/', $tags['track_number'][0]); - $trackNumber = $trackNumberComponents[0]; - } - - return [ - [ - 'title' => isset($tags['title']) ? $tags['title'][0] : null, - 'artist' => isset($tags['artist']) ? $tags['artist'][0] : null, - 'band' => isset($tags['band']) ? $tags['band'][0] : null, - 'album_artist' => isset($tags['album_artist']) ? $tags['album_artist'][0] : null, - 'genre' => isset($tags['genre']) ? $tags['genre'][0] : null, - 'track_number' => $trackNumber, - 'album' => isset($tags['album']) ? $tags['album'][0] : null, - 'year' => isset($tags['year']) ? (int) $tags['year'][0] : null, - 'release_date' => isset($tags['date']) ? $this->parseDateString($tags['date'][0]) : null, - 'comments' => isset($tags['comments']) ? $tags['comments'][0] : null, - 'lyrics' => isset($tags['lyrics']) ? $tags['lyrics'][0] : null, - ], - $tags - ]; - } - - /** - * Parses a potentially-partial date string into a proper date object. - * - * The tagging formats we deal with base their date format on ISO 8601, but - * the timestamp may be incomplete. - * - * @link https://code.google.com/p/mp4v2/wiki/iTunesMetadata - * @link https://wiki.xiph.org/VorbisComment#Date_and_time - * @link http://id3.org/id3v2.4.0-frames - * - * @param string $dateString - * @return null|Carbon - */ - protected function parseDateString(string $dateString) { - switch (Str::length($dateString)) { - // YYYY - case 4: - return Carbon::createFromFormat('Y', $dateString) - ->month(1) - ->day(1); - - // YYYY-MM - case 7: - return Carbon::createFromFormat('Y-m', $dateString) - ->day(1); - - // YYYY-MM-DD - case 10: - return Carbon::createFromFormat('Y-m-d', $dateString); - break; - - default: - // We might have an ISO-8601 string in our hooves. - // If not, give up. - try { - return Carbon::createFromFormat(Carbon::ISO8601, $dateString); - - } catch (\InvalidArgumentException $e) { - return null; - } - } - } } diff --git a/app/Library/AudioCache.php b/app/Library/AudioCache.php index 23474848..aa1440d3 100644 --- a/app/Library/AudioCache.php +++ b/app/Library/AudioCache.php @@ -22,11 +22,7 @@ class AudioCache { private static $_movieCache = array(); - /** - * @param $filename - * @return FFmpegMovie - */ - public static function get($filename) + public static function get(string $filename):FFmpegMovie { if (isset(self::$_movieCache[$filename])) { return self::$_movieCache[$filename];