diff --git a/app/AlbumDownloader.php b/app/AlbumDownloader.php index e1445ef6..7ade8e0a 100644 --- a/app/AlbumDownloader.php +++ b/app/AlbumDownloader.php @@ -21,6 +21,7 @@ namespace Poniverse\Ponyfm; use Poniverse\Ponyfm\Models\Album; +use Poniverse\Ponyfm\Models\Track; use ZipStream; class AlbumDownloader @@ -43,6 +44,11 @@ class AlbumDownloader public function download() { + // Check whether the format is lossless yet not all master files are lossless + $isLosslessFormatWithLossyTracks = in_array($this->_format, Track::$LosslessFormats) + && !$this->_album->hasLosslessTracksOnly() + && $this->_album->hasLosslessTracks(); + $zip = new ZipStream($this->_album->user->display_name.' - '.$this->_album->title.'.zip'); $zip->setComment( 'Album: '.$this->_album->title."\r\n". @@ -69,8 +75,15 @@ class AlbumDownloader continue; } - $zip->addLargeFile($track->getFileFor($this->_format), - $directory.$track->getDownloadFilenameFor($this->_format)); + if ($isLosslessFormatWithLossyTracks && $track->isMasterLossy()) { + $masterFormatName = $track->getMasterFormatName(); + $zip->addLargeFile($track->getFileFor($masterFormatName), + $directory . $track->getDownloadFilenameFor($masterFormatName)); + } else { + $zip->addLargeFile($track->getFileFor($this->_format), + $directory . $track->getDownloadFilenameFor($this->_format)); + } + $notes .= $track->track_number.'. '.$track->title."\r\n". $track->description."\r\n". diff --git a/app/Http/Controllers/AlbumsController.php b/app/Http/Controllers/AlbumsController.php index 02455ae9..c133728b 100644 --- a/app/Http/Controllers/AlbumsController.php +++ b/app/Http/Controllers/AlbumsController.php @@ -61,7 +61,7 @@ class AlbumsController extends Controller public function getDownload($id, $extension) { - $album = Album::with('tracks', 'user')->find($id); + $album = Album::with('tracks', 'tracks.trackFiles', 'user')->find($id); if (!$album) { App::abort(404); } @@ -81,6 +81,10 @@ class AlbumsController extends Controller App::abort(404); } + if (!$album->hasLosslessTracks() && in_array($formatName, Track::$LosslessFormats)) { + App::abort(404); + } + ResourceLogItem::logItem('album', $id, ResourceLogItem::DOWNLOAD, $format['index']); $downloader = new AlbumDownloader($album, $formatName); $downloader->download(); diff --git a/app/Http/Controllers/PlaylistsController.php b/app/Http/Controllers/PlaylistsController.php index 275f08ff..776a0d5e 100644 --- a/app/Http/Controllers/PlaylistsController.php +++ b/app/Http/Controllers/PlaylistsController.php @@ -62,7 +62,7 @@ class PlaylistsController extends Controller public function getDownload($id, $extension) { - $playlist = Playlist::with('tracks', 'user', 'tracks.album')->find($id); + $playlist = Playlist::with('tracks', 'tracks.trackFiles', 'user', 'tracks.album')->find($id); if (!$playlist || (!$playlist->is_public && !Auth::check()) || (!$playlist->is_public && ($playlist->user_id !== Auth::user()->id))) { App::abort(404); } @@ -82,6 +82,10 @@ class PlaylistsController extends Controller App::abort(404); } + if (!$playlist->hasLosslessTracks() && in_array($formatName, Track::$LosslessFormats)) { + App::abort(404); + } + ResourceLogItem::logItem('playlist', $id, ResourceLogItem::DOWNLOAD, $format['index']); $downloader = new PlaylistDownloader($playlist, $formatName); $downloader->download(); diff --git a/app/Models/Album.php b/app/Models/Album.php index 9b9a1238..9b90b900 100644 --- a/app/Models/Album.php +++ b/app/Models/Album.php @@ -144,15 +144,20 @@ class Album extends Model implements Searchable, Commentable, Favouritable $formats = []; foreach (Track::$Formats as $name => $format) { + if (in_array($name, Track::$LosslessFormats) && !$album->hasLosslessTracksOnly() && !$album->hasLosslessTracks()) { + continue; + } + $formats[] = [ 'name' => $name, 'extension' => $format['extension'], 'url' => $album->getDownloadUrl($name), 'size' => Helpers::formatBytes($album->getFilesize($name)), - 'isCacheable' => (in_array($name, Track::$CacheableFormats) ? true : false) + 'isCacheable' => (in_array($name, Track::$CacheableFormats) ? true : false), + 'isMixedLosslessness' => (in_array($name, Track::$LosslessFormats) && !$album->hasLosslessTracksOnly() && $album->hasLosslessTracks()) ]; } - + $comments = []; foreach ($album->comments as $comment) { $comments[] = Comment::mapPublic($comment); @@ -249,34 +254,6 @@ class Album extends Model implements Searchable, Commentable, Favouritable return action('AlbumsController@getDownload', ['id' => $this->id, 'extension' => Track::$Formats[$format]['extension']]); } - public function getFilesize($format) - { - $tracks = $this->tracks; - if (!count($tracks)) { - return 0; - } - - return Cache::remember($this->getCacheKey('filesize-'.$format), 1440, function() use ($tracks, $format) { - $size = 0; - - foreach ($tracks as $track) { - /** @var $track Track */ - - // Ensure that only downloadable tracks are added onto the file size - if ($track->is_downloadable == 1) { - try { - $size += $track->getFilesize($format); - - } catch (TrackFileNotFoundException $e) { - // do nothing - this track won't be included in the download - } - } - } - - return $size; - }); - } - public function getCoverUrl($type = Image::NORMAL) { if (!$this->hasCover()) { diff --git a/app/Models/Playlist.php b/app/Models/Playlist.php index 4154be7f..c700b12a 100644 --- a/app/Models/Playlist.php +++ b/app/Models/Playlist.php @@ -119,12 +119,17 @@ class Playlist extends Model implements Searchable, Commentable, Favouritable $formats = []; foreach (Track::$Formats as $name => $format) { + if (in_array($name, Track::$LosslessFormats) && !$playlist->hasLosslessTracksOnly() && !$playlist->hasLosslessTracks()) { + continue; + } + $formats[] = [ 'name' => $name, 'extension' => $format['extension'], 'url' => $playlist->getDownloadUrl($name), 'size' => Helpers::formatBytes($playlist->getFilesize($name)), - 'isCacheable' => (in_array($name, Track::$CacheableFormats) ? true : false) + 'isCacheable' => (in_array($name, Track::$CacheableFormats) ? true : false), + 'isMixedLosslessness' => (in_array($name, Track::$LosslessFormats) && !$playlist->hasLosslessTracksOnly() && $playlist->hasLosslessTracks()) ]; } @@ -268,33 +273,6 @@ class Playlist extends Model implements Searchable, Commentable, Favouritable return action('PlaylistsController@getDownload', ['id' => $this->id, 'format' => Track::$Formats[$format]['extension']]); } - public function getFilesize($format) - { - $tracks = $this->tracks; - if (!count($tracks)) { - return 0; - } - - return Cache::remember($this->getCacheKey('filesize-'.$format), 1440, function() use ($tracks, $format) { - $size = 0; - foreach ($tracks as $track) { - /** @var $track Track */ - - // Ensure that only downloadable tracks are added onto the file size - if ($track->is_downloadable == 1) { - try { - $size += $track->getFilesize($format); - - } catch (TrackFileNotFoundException $e) { - // do nothing - this track won't be included in the download - } - } - } - - return $size; - }); - } - public function getCoverUrl($type = Image::NORMAL) { if ($this->tracks->count() == 0) { diff --git a/app/Models/Track.php b/app/Models/Track.php index 858b1107..abb6b117 100644 --- a/app/Models/Track.php +++ b/app/Models/Track.php @@ -205,6 +205,11 @@ class Track extends Model implements Searchable, Commentable, Favouritable 'AAC' ]; + public static $LosslessFormats = [ + 'FLAC', + 'ALAC' + ]; + public static function summary() { return self::select('tracks.id', 'title', 'user_id', 'slug', 'is_vocal', 'is_explicit', 'created_at', @@ -628,6 +633,22 @@ class Track extends Model implements Searchable, Commentable, Favouritable return $this->published_at != null && $this->deleted_at == null; } + + protected function getMasterTrackFile() : TrackFile + { + return $this->trackFiles->where('is_master', true)->first(); + } + + public function getMasterFormatName() : string + { + return $this->getMasterTrackFile()->format; + } + + public function isMasterLossy() : bool + { + return $this->getMasterTrackFile()->isLossy(); + } + public function getCoverUrl($type = Image::NORMAL) { if (!$this->hasCover()) { diff --git a/app/Models/TrackFile.php b/app/Models/TrackFile.php index ad6a0d10..a983f2b3 100644 --- a/app/Models/TrackFile.php +++ b/app/Models/TrackFile.php @@ -185,4 +185,9 @@ class TrackFile extends Model return $this->filesize; } + + public function isLossy() : bool + { + return !in_array($this->format, Track::$LosslessFormats); + } } diff --git a/app/PlaylistDownloader.php b/app/PlaylistDownloader.php index 1f8d7622..37056e25 100644 --- a/app/PlaylistDownloader.php +++ b/app/PlaylistDownloader.php @@ -21,6 +21,7 @@ namespace Poniverse\Ponyfm; use Poniverse\Ponyfm\Models\Playlist; +use Poniverse\Ponyfm\Models\Track; use ZipStream; class PlaylistDownloader @@ -43,6 +44,11 @@ class PlaylistDownloader public function download() { + // Check whether the format is lossless yet not all master files are lossless + $isLosslessFormatWithLossyTracks = in_array($this->_format, Track::$LosslessFormats) + && !$this->_playlist->hasLosslessTracksOnly() + && $this->_playlist->hasLosslessTracks(); + $zip = new ZipStream($this->_playlist->user->display_name.' - '.$this->_playlist->title.'.zip'); $zip->setComment( 'Playlist: '.$this->_playlist->title."\r\n". @@ -69,8 +75,15 @@ class PlaylistDownloader continue; } - $trackTarget = $track->downloadDirectory.'/'.$track->getDownloadFilenameFor($this->_format); - $zip->addLargeFile($track->getFileFor($this->_format), $trackTarget); + if ($isLosslessFormatWithLossyTracks && $track->isMasterLossy()) { + $masterFormatName = $track->getMasterFormatName(); + $trackTarget = $track->downloadDirectory . '/' . $track->getDownloadFilenameFor($masterFormatName); + $zip->addLargeFile($track->getFileFor($masterFormatName), $trackTarget); + } else { + $trackTarget = $track->downloadDirectory . '/' . $track->getDownloadFilenameFor($this->_format); + $zip->addLargeFile($track->getFileFor($this->_format), $trackTarget); + } + $notes .= $index.'. '.$track->title."\r\n". $track->description."\r\n". diff --git a/app/Traits/TrackCollection.php b/app/Traits/TrackCollection.php index f9073b0b..f7a6fe9e 100644 --- a/app/Traits/TrackCollection.php +++ b/app/Traits/TrackCollection.php @@ -24,8 +24,11 @@ namespace Poniverse\Ponyfm\Traits; use File; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; +use Illuminate\Support\Facades\Cache; use Poniverse\Ponyfm\Jobs\EncodeTrackFile; +use Poniverse\Ponyfm\Models\Track; use Poniverse\Ponyfm\Models\TrackFile; @@ -123,4 +126,86 @@ trait TrackCollection } ])->where('format', $format)->get(); } + + /** + * Returns a boolean based on whether at least one (@link TrackFile) + * for this (@link TrackCollection)'s tracks has a lossless master file. + * + * @return bool + */ + public function hasLosslessTracks() : bool + { + $hasLosslessTracks = false; + foreach ($this->tracks as $track) { + if (!$track->isMasterLossy()) { + $hasLosslessTracks = true; + break; + } + } + return $hasLosslessTracks; + } + + /** + * Returns a boolean based on whether all (@link TrackFile)s + * for this (@link TrackCollection)'s tracks have lossless master files. + * + * @return bool + */ + public function hasLosslessTracksOnly() : bool + { + $hasLosslessTracksOnly = true; + foreach ($this->tracks as $track) { + if ($track->isMasterLossy()) { + $hasLosslessTracksOnly = false; + break; + } + } + return $hasLosslessTracksOnly; + } + + /** + * Gets the filesize in bytes for a (@link Album) or (@link Playlist) based on a format. + * + * @param $format + * @return int + */ + public function getFilesize($format) : int + { + $tracks = $this->tracks; + if (!count($tracks)) { + return 0; + } + + return Cache::remember($this->getCacheKey('filesize-'.$format), 1440, function() use ($tracks, $format) { + $size = 0; + + // Check whether the format is lossless yet not all master files are lossless + $isLosslessFormatWithLossyTracks = in_array($format, Track::$LosslessFormats) + && !$this->hasLosslessTracksOnly() + && $this->hasLosslessTracks(); + + foreach ($tracks as $track) { + /** @var $track Track */ + + // Ensure that only downloadable tracks are added onto the file size + if (!$track->is_downloadable) { + continue; + } + + try { + // Get the file size corresponding to the losslessness of the track master file and format specified + if ($isLosslessFormatWithLossyTracks && $track->isMasterLossy()) { + $size += $track->getFilesize($track->getMasterFormatName()); + } else { + $size += $track->getFilesize($format); + } + + } catch (TrackFileNotFoundException $e) { + // do nothing - this track won't be included in the download + } + } + + return $size; + }); + } } diff --git a/public/templates/albums/show.html b/public/templates/albums/show.html index b7f520ca..710368ed 100644 --- a/public/templates/albums/show.html +++ b/public/templates/albums/show.html @@ -6,11 +6,11 @@