diff --git a/app/src/main/java/org/oxycblt/auxio/image/extractor/Components.kt b/app/src/main/java/org/oxycblt/auxio/image/extractor/Components.kt index 0bf1de4a2..f38d00695 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/extractor/Components.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/extractor/Components.kt @@ -27,7 +27,7 @@ import javax.inject.Inject class CoverKeyer @Inject constructor() : Keyer> { override fun key(data: Collection, options: Options) = - "${data.map { it.uniqueness }.hashCode()}" + "${data.map { it.key }.hashCode()}" } class CoverFetcher diff --git a/app/src/main/java/org/oxycblt/auxio/image/extractor/Cover.kt b/app/src/main/java/org/oxycblt/auxio/image/extractor/Cover.kt index bb0b6197f..7e64252f9 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/extractor/Cover.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/extractor/Cover.kt @@ -20,23 +20,28 @@ package org.oxycblt.auxio.image.extractor import android.net.Uri import org.oxycblt.auxio.list.sort.Sort -import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Song -/** - * Bundle of [Uri] information used in [CoverExtractor] to ensure consistent [Uri] use when loading - * images. - * - * @param mediaStoreUri The album cover [Uri] obtained from MediaStore. - * @param song The [Uri] of the first song (by track) of the album, which can also be used to obtain - * an album cover. - * @author Alexander Capehart (OxygenCobalt) - */ -data class Cover(val uniqueness: Uniqueness?, val mediaStoreUri: Uri, val songUri: Uri) { - sealed interface Uniqueness { - data class PerceptualHash(val perceptualHash: String) : Uniqueness +sealed interface Cover { + val key: String + val mediaStoreCoverUri: Uri - data class UID(val uid: Music.UID) : Uniqueness + /** + * The song has an embedded cover art we support, so we can operate with it on a per-song + * basis. + */ + data class Embedded(val songCoverUri: Uri, val songUri: Uri, val perceptualHash: String) : Cover { + override val mediaStoreCoverUri = songCoverUri + override val key = perceptualHash + } + + /** + * We couldn't find any embedded cover art ourselves, but the android system might have some + * through a cover.jpg file or something similar. + */ + data class External(val albumCoverUri: Uri) : Cover { + override val mediaStoreCoverUri = albumCoverUri + override val key = albumCoverUri.toString() } companion object { @@ -45,7 +50,7 @@ data class Cover(val uniqueness: Uniqueness?, val mediaStoreUri: Uri, val songUr fun order(songs: Collection) = FALLBACK_SORT.songs(songs) .map { it.cover } - .groupBy { it.uniqueness } + .groupBy { it.key } .entries .sortedByDescending { it.value.size } .map { it.value.first() } diff --git a/app/src/main/java/org/oxycblt/auxio/image/extractor/CoverExtractor.kt b/app/src/main/java/org/oxycblt/auxio/image/extractor/CoverExtractor.kt index 556b11445..3a6a337ea 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/extractor/CoverExtractor.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/extractor/CoverExtractor.kt @@ -140,21 +140,28 @@ constructor( private suspend fun openCoverInputStream(cover: Cover) = try { - when (imageSettings.coverMode) { - CoverMode.OFF -> null - CoverMode.MEDIA_STORE -> extractMediaStoreCover(cover) - CoverMode.QUALITY -> extractQualityCover(cover) + when (cover) { + is Cover.Embedded -> + when (imageSettings.coverMode) { + CoverMode.OFF -> null + CoverMode.MEDIA_STORE -> extractMediaStoreCover(cover) + CoverMode.QUALITY -> extractQualityCover(cover) + } + + is Cover.External -> { + extractMediaStoreCover(cover) + } } } catch (e: Exception) { logE("Unable to extract album cover due to an error: $e") null } - private suspend fun extractQualityCover(cover: Cover) = + private suspend fun extractQualityCover(cover: Cover.Embedded) = extractAospMetadataCover(cover) ?: extractExoplayerCover(cover) ?: extractMediaStoreCover(cover) - private fun extractAospMetadataCover(cover: Cover): InputStream? = + private fun extractAospMetadataCover(cover: Cover.Embedded): InputStream? = MediaMetadataRetriever().run { // This call is time-consuming but it also doesn't seem to hold up the main thread, // so it's probably fine not to wrap it.rmt @@ -166,7 +173,7 @@ constructor( embeddedPicture?.let { ByteArrayInputStream(it) }.also { release() } } - private suspend fun extractExoplayerCover(cover: Cover): InputStream? { + private suspend fun extractExoplayerCover(cover: Cover.Embedded): InputStream? { val tracks = MetadataRetriever.retrieveMetadata(mediaSourceFactory, MediaItem.fromUri(cover.songUri)) .asDeferred() @@ -186,7 +193,7 @@ constructor( private suspend fun extractMediaStoreCover(cover: Cover) = // Eliminate any chance that this blocking call might mess up the loading process - withContext(Dispatchers.IO) { context.contentResolver.openInputStream(cover.mediaStoreUri) } + withContext(Dispatchers.IO) { context.contentResolver.openInputStream(cover.mediaStoreCoverUri) } /** Derived from phonograph: https://github.com/kabouzeid/Phonograph */ private suspend fun createMosaic(streams: List, size: Size): FetchResult { diff --git a/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt b/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt index 96ef5fa3c..dcfda228f 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package org.oxycblt.auxio.music.device import org.oxycblt.auxio.R @@ -29,8 +29,9 @@ import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.fs.MimeType +import org.oxycblt.auxio.music.fs.toAlbumCoverUri import org.oxycblt.auxio.music.fs.toAudioUri -import org.oxycblt.auxio.music.fs.toCoverUri +import org.oxycblt.auxio.music.fs.toSongCoverUri import org.oxycblt.auxio.music.info.Date import org.oxycblt.auxio.music.info.Disc import org.oxycblt.auxio.music.info.Name @@ -76,7 +77,8 @@ class SongImpl( override val name = nameFactory.parse( requireNotNull(rawSong.name) { "Invalid raw ${rawSong.path}: No title" }, - rawSong.sortName) + rawSong.sortName + ) override val track = rawSong.track override val disc = rawSong.disc?.let { Disc(it, rawSong.subtitle) } @@ -114,11 +116,20 @@ class SongImpl( get() = _genres override val cover = - Cover( - rawSong.coverPerceptualHash?.let { Cover.Uniqueness.PerceptualHash(it) } - ?: Cover.Uniqueness.UID(uid), - requireNotNull(rawSong.mediaStoreId).toCoverUri(), - uri) + rawSong.coverPerceptualHash?.let { + // We were able to confirm that the song had a parsable cover and can be used on + // a per-song basis. Otherwise, just fall back to a per-album cover instead, as + // it implies either a cover.jpg pattern is used (likely) or ExoPlayer does not + // support the cover metadata of a given spec (unlikely). + Cover.Embedded( + requireNotNull(rawSong.mediaStoreId) { "Invalid raw ${rawSong.path}: No id" }.toSongCoverUri(), + uid, + it + ) + } + ?: Cover.External( + requireNotNull(rawSong.albumMediaStoreId).toAlbumCoverUri() + ) /** * The [RawAlbum] instances collated by the [Song]. This can be used to group [Song]s into an diff --git a/app/src/main/java/org/oxycblt/auxio/music/fs/StorageUtil.kt b/app/src/main/java/org/oxycblt/auxio/music/fs/StorageUtil.kt index da61d613b..6cdc7df67 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/fs/StorageUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/fs/StorageUtil.kt @@ -102,13 +102,15 @@ fun Long.toAudioUri() = * @return An external storage image [Uri]. May not exist. * @see ContentUris.withAppendedId */ -fun Long.toCoverUri(): Uri = +fun Long.toSongCoverUri(): Uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.buildUpon().run { - appendPath(this@toCoverUri.toString()) + appendPath(this@toSongCoverUri.toString()) appendPath("albumart") build() } +fun Long.toAlbumCoverUri(): Uri = ContentUris.withAppendedId(externalCoversUri, this) + // --- STORAGEMANAGER UTILITIES --- // Largely derived from Material Files: https://github.com/zhanghai/MaterialFiles