diff --git a/app/Models/Album.php b/app/Models/Album.php index 90841cfa..b5fbddcf 100644 --- a/app/Models/Album.php +++ b/app/Models/Album.php @@ -123,12 +123,36 @@ class Album extends Model public function writeCoverFile($binaryData, $extension) { $extension = trim(strtolower($extension), '. '); - $fileName = uniqid('', true).".$extension"; - $coverPath = app()->publicPath().'/public/img/covers/'.$fileName; + $destPath = $this->generateRandomCoverPath($extension); + file_put_contents($destPath, $binaryData); - file_put_contents($coverPath, $binaryData); + $this->update(['cover' => basename($destPath)]); + } - $this->update(['cover' => $fileName]); + /** + * Copy a cover file from an existing image on the system. + * + * @param string $srcPath The original image's full path. + */ + public function copyCoverFile($srcPath) + { + $extension = pathinfo($srcPath, PATHINFO_EXTENSION); + $destPath = $this->generateRandomCoverPath($extension); + copy($srcPath, $destPath); + + $this->update(['cover' => basename($destPath)]); + } + + /** + * Generate a random path for the cover image. + * + * @param string $extension The extension of the cover (without dot) + * + * @return string + */ + private function generateRandomCoverPath($extension) + { + return app()->publicPath().'/public/img/covers/'.uniqid('', true).".$extension"; } public function setCoverAttribute($value) diff --git a/app/Models/File.php b/app/Models/File.php index 2c314d82..83394509 100644 --- a/app/Models/File.php +++ b/app/Models/File.php @@ -2,7 +2,9 @@ namespace App\Models; +use Cache; use Exception; +use Symfony\Component\Finder\Finder; use getID3; use getid3_lib; use Illuminate\Support\Facades\Log; @@ -99,7 +101,7 @@ class File 'comments.track_number', ]; - for ($i = 0; $i < count($trackIndices) && $track === 0; $i++) { + for ($i = 0; $i < count($trackIndices) && $track === 0; ++$i) { $track = array_get($info, $trackIndices[$i], [0])[0]; } @@ -227,11 +229,18 @@ class File $album = Album::get($artist, $info['album'], $isCompilation); } - if (!empty($info['cover']) && !$album->has_cover) { - try { - $album->generateCover($info['cover']); - } catch (Exception $e) { - Log::error($e); + if (!$album->has_cover) { + // If the album has no cover, we try to get the cover image from existing tag data + if (!empty($info['cover'])) { + try { + $album->generateCover($info['cover']); + } catch (Exception $e) { + Log::error($e); + } + } + // or, if there's a cover image under the same directory, use it. + elseif ($cover = $this->getCoverFileUnderSameDirectory()) { + $album->copyCoverFile($cover); } } @@ -303,6 +312,42 @@ class File return $this->path; } + /** + * Issue #380. + * Some albums have its own cover image under the same directory as cover|folder.jpg/png. + * We'll check if such a cover file is found, and use it if positive. + * + * @return string|false The cover file's full path, or false if none found + */ + private function getCoverFileUnderSameDirectory() + { + // As directory scanning can be expensive, we cache and reuse the result. + $cacheKey = md5($this->path.'_cover'); + + if (!is_null($cover = Cache::get($cacheKey))) { + return $cover; + } + + $matches = array_keys(iterator_to_array( + Finder::create() + ->depth(0) + ->ignoreUnreadableDirs() + ->files() + ->name('/(cov|fold)er\.(jpe?g|png)$/i') + ->in(dirname($this->path)) + )); + + $cover = $matches ? $matches[0] : false; + // Even if a file is found, make sure it's a real image. + if ($cover && exif_imagetype($cover) === false) { + $cover = false; + } + + Cache::put($cacheKey, $cover, 24 * 60); + + return $cover; + } + /** * Get a unique hash from a file path. * diff --git a/tests/MediaTest.php b/tests/MediaTest.php index 6370f448..2b1404a9 100644 --- a/tests/MediaTest.php +++ b/tests/MediaTest.php @@ -32,6 +32,11 @@ class MediaTest extends TestCase // Ogg files and audio files in subdirectories should be recognized $this->seeInDatabase('songs', ['path' => $this->mediaPath.'/subdir/back-in-black.ogg']); + // GitHub issue #380. folder.png should be copied and used as the cover for files + // under subdir/ + $song = Song::wherePath($this->mediaPath.'/subdir/back-in-black.ogg')->first(); + $this->assertNotNull($song->album->cover); + // File search shouldn't be case-sensitive. $this->seeInDatabase('songs', ['path' => $this->mediaPath.'/subdir/no-name.MP3']); @@ -58,6 +63,7 @@ class MediaTest extends TestCase $this->assertTrue($song->album->is_compilation); $this->assertEquals(Artist::VARIOUS_ID, $song->album->artist_id); + $currentCover = $album->cover; $song = Song::orderBy('id', 'desc')->first(); diff --git a/tests/songs/subdir/folder.png b/tests/songs/subdir/folder.png new file mode 100644 index 00000000..429406bf Binary files /dev/null and b/tests/songs/subdir/folder.png differ