musikr: remove trivial auxio dependence

There's still some thorny resource use left over, but this is a good
starting point to start breaking off musikr from auxio.
This commit is contained in:
Alexander Capehart 2024-12-13 20:08:58 -07:00
parent 3da9e6c5b3
commit de1c091517
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
39 changed files with 324 additions and 203 deletions

View file

@ -31,6 +31,7 @@ import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.menu.Menu
import org.oxycblt.auxio.music.PlaylistDecision
import org.oxycblt.auxio.music.PlaylistMessage
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.PlaybackDecision
import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.util.collect
@ -43,7 +44,6 @@ import org.oxycblt.musikr.Album
import org.oxycblt.musikr.Music
import org.oxycblt.musikr.MusicParent
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.resolveNames
import timber.log.Timber as L
/**

View file

@ -31,6 +31,7 @@ import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.menu.Menu
import org.oxycblt.auxio.music.PlaylistDecision
import org.oxycblt.auxio.music.PlaylistMessage
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.PlaybackDecision
import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
@ -43,7 +44,6 @@ import org.oxycblt.musikr.Artist
import org.oxycblt.musikr.Music
import org.oxycblt.musikr.MusicParent
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.resolveNames
import timber.log.Timber as L
/**

View file

@ -32,6 +32,7 @@ import org.oxycblt.auxio.databinding.DialogSongDetailBinding
import org.oxycblt.auxio.detail.list.SongProperty
import org.oxycblt.auxio.detail.list.SongPropertyAdapter
import org.oxycblt.auxio.list.adapter.UpdateInstructions
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.playback.replaygain.formatDb
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
@ -40,7 +41,6 @@ import org.oxycblt.auxio.util.concatLocalized
import org.oxycblt.musikr.Music
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.metadata.AudioProperties
import org.oxycblt.musikr.resolveNames
import org.oxycblt.musikr.tag.Name
import timber.log.Timber as L

View file

@ -40,12 +40,12 @@ import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
import org.oxycblt.auxio.list.recycler.MaterialDragCallback
import org.oxycblt.auxio.list.recycler.SongViewHolder
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getAttrColorCompat
import org.oxycblt.auxio.util.inflater
import org.oxycblt.musikr.Playlist
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.resolveNames
import timber.log.Timber as L
/**

View file

@ -23,7 +23,6 @@ import android.view.ViewGroup
import androidx.annotation.StringRes
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemSongPropertyBinding
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.adapter.FlexibleListAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
import org.oxycblt.auxio.list.recycler.DialogRecyclerView
@ -53,7 +52,7 @@ class SongPropertyAdapter :
* @param value The value of the property.
* @author Alexander Capehart (OxygenCobalt)
*/
data class SongProperty(@StringRes val name: Int, val value: String) : Item
data class SongProperty(@StringRes val name: Int, val value: String)
/**
* A [RecyclerView.ViewHolder] that displays a [SongProperty]. Use [from] to create an instance.

View file

@ -22,9 +22,9 @@ import androidx.annotation.StringRes
// TODO: Consider breaking this up into sealed classes for individual adapters
/** A marker for something that is a RecyclerView item. Has no functionality on it's own. */
interface Item
typealias Item = Any
interface Header : Item
interface Header
/**
* A "header" used for delimiting groups of data.
@ -44,7 +44,7 @@ interface PlainHeader : Header {
*/
data class BasicHeader(@StringRes override val titleRes: Int) : PlainHeader
interface Divider<T> : Item {
interface Divider<T> {
val anchor: T?
}

View file

@ -28,6 +28,7 @@ import org.oxycblt.auxio.databinding.DialogMenuBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.util.getPlural
@ -37,7 +38,6 @@ import org.oxycblt.musikr.Artist
import org.oxycblt.musikr.Genre
import org.oxycblt.musikr.Playlist
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.resolveNames
/**
* [MenuDialogFragment] implementation for a [Song].

View file

@ -32,6 +32,8 @@ import org.oxycblt.auxio.list.PlainDivider
import org.oxycblt.auxio.list.SelectableListListener
import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
import org.oxycblt.auxio.music.areNamesTheSame
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getPlural
import org.oxycblt.auxio.util.inflater
@ -40,8 +42,6 @@ import org.oxycblt.musikr.Artist
import org.oxycblt.musikr.Genre
import org.oxycblt.musikr.Playlist
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.areNamesTheSame
import org.oxycblt.musikr.resolveNames
/**
* A [RecyclerView.ViewHolder] that displays a [Song]. Use [from] to create an instance.

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2024 Auxio Project
* MusicUtil.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music
import android.content.Context
import kotlin.math.max
import org.oxycblt.auxio.util.concatLocalized
import org.oxycblt.musikr.Music
/**
* Run [Name.resolve] on each instance in the given list and concatenate them into a [String] in a
* localized manner.
*
* @param context [Context] required
* @return A concatenated string.
*/
fun <T : Music> List<T>.resolveNames(context: Context) =
concatLocalized(context) { it.name.resolve(context) }
/**
* Returns if [Music.name] matches for each item in a list. Useful for scenarios where the display
* information of an item must be compared without a context.
*
* @param other The list of items to compare to.
* @return True if they are the same (by [Music.name]), false otherwise.
*/
fun <T : Music> List<T>.areNamesTheSame(other: List<T>): Boolean {
for (i in 0 until max(size, other.size)) {
val a = getOrNull(i) ?: return false
val b = other.getOrNull(i) ?: return false
if (a.name != b.name) {
return false
}
}
return true
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2024 Auxio Project
* ReplayGainPreAmp.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music
/**
* The current ReplayGain pre-amp configuration.
*
* @param with The pre-amp (in dB) to use when ReplayGain tags are present.
* @param without The pre-amp (in dB) to use when ReplayGain tags are not present.
* @author Alexander Capehart (OxygenCobalt)
*/
data class ReplayGainPreAmp(val with: Float, val without: Float)

View file

@ -25,7 +25,6 @@ import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.oxycblt.auxio.R
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.sort.Sort
import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.PlaylistDecision
@ -354,4 +353,4 @@ sealed interface ChosenName {
* [Playlist].
* @author Alexander Capehart (OxygenCobalt)
*/
data class PlaylistChoice(val playlist: Playlist, val alreadyAdded: Boolean) : Item
data class PlaylistChoice(val playlist: Playlist, val alreadyAdded: Boolean)

View file

@ -27,6 +27,7 @@ import androidx.annotation.StringRes
import androidx.media.utils.MediaConstants
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.formatDurationDs
import org.oxycblt.auxio.util.getPlural
import org.oxycblt.musikr.Album
@ -36,7 +37,6 @@ import org.oxycblt.musikr.Music
import org.oxycblt.musikr.MusicParent
import org.oxycblt.musikr.Playlist
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.resolveNames
sealed interface MediaSessionUID {
data class Tab(val node: TabNode) : MediaSessionUID {

View file

@ -26,13 +26,13 @@ import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentPlaybackBarBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.getAttrColorCompat
import org.oxycblt.auxio.util.getColorCompat
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.resolveNames
import timber.log.Timber as L
/**

View file

@ -35,6 +35,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.playback.ui.StyledSeekBar
import org.oxycblt.auxio.playback.ui.SwipeCoverView
@ -44,7 +45,6 @@ import org.oxycblt.auxio.util.showToast
import org.oxycblt.auxio.util.systemBarInsetsCompat
import org.oxycblt.musikr.MusicParent
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.resolveNames
import timber.log.Timber as L
/**

View file

@ -24,8 +24,8 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.ReplayGainPreAmp
import org.oxycblt.auxio.playback.replaygain.ReplayGainMode
import org.oxycblt.auxio.playback.replaygain.ReplayGainPreAmp
import org.oxycblt.auxio.settings.Settings
import timber.log.Timber as L

View file

@ -32,11 +32,11 @@ import org.oxycblt.auxio.list.adapter.FlexibleListAdapter
import org.oxycblt.auxio.list.adapter.PlayingIndicatorAdapter
import org.oxycblt.auxio.list.recycler.MaterialDragCallback
import org.oxycblt.auxio.list.recycler.SongViewHolder
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getAttrColorCompat
import org.oxycblt.auxio.util.inflater
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.resolveNames
import timber.log.Timber as L
/**

View file

@ -26,6 +26,7 @@ import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogPreAmpBinding
import org.oxycblt.auxio.music.ReplayGainPreAmp
import org.oxycblt.auxio.playback.PlaybackSettings
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
import timber.log.Timber as L

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2022 Auxio Project
* ReplayGain.kt is part of Auxio.
* Copyright (c) 2024 Auxio Project
* ReplayGainMode.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,10 +18,7 @@
package org.oxycblt.auxio.playback.replaygain
import android.content.Context
import kotlin.math.abs
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
/**
* The current ReplayGain configuration.
@ -55,34 +52,3 @@ enum class ReplayGainMode {
}
}
}
/**
* Represents a ReplayGain adjustment to apply during song playback.
*
* @param track The track-specific adjustment that should be applied. Null if not available.
* @param album A more general album-specific adjustment that should be applied. Null if not
* available.
*/
data class ReplayGainAdjustment(val track: Float?, val album: Float?)
/**
* The current ReplayGain pre-amp configuration.
*
* @param with The pre-amp (in dB) to use when ReplayGain tags are present.
* @param without The pre-amp (in dB) to use when ReplayGain tags are not present.
* @author Alexander Capehart (OxygenCobalt)
*/
data class ReplayGainPreAmp(val with: Float, val without: Float)
/**
* Format a decibel value in a human-readable format.
*
* @param context The context to resolve resources from.
* @return A formatted decibel value. Will be prefixed by a + or - sign.
*/
fun Float.formatDb(context: Context) =
if (this >= 0) {
context.getString(R.string.fmt_db_pos, this)
} else {
context.getString(R.string.fmt_db_neg, abs(this))
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2024 Auxio Project
* ReplayGainUtil.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.playback.replaygain
import android.content.Context
import kotlin.math.abs
import org.oxycblt.auxio.R
/**
* Format a decibel value in a human-readable format.
*
* @param context The context to resolve resources from.
* @return A formatted decibel value. Will be prefixed by a + or - sign.
*/
fun Float.formatDb(context: Context) =
if (this >= 0) {
context.getString(R.string.fmt_db_pos, this)
} else {
context.getString(R.string.fmt_db_neg, abs(this))
}

View file

@ -36,6 +36,7 @@ import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
import org.oxycblt.auxio.image.BitmapProvider
import org.oxycblt.auxio.image.ImageSettings
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.music.service.MediaSessionUID
import org.oxycblt.auxio.music.service.toMediaDescription
import org.oxycblt.auxio.playback.ActionMode
@ -48,7 +49,6 @@ import org.oxycblt.auxio.util.newBroadcastPendingIntent
import org.oxycblt.auxio.util.newMainPendingIntent
import org.oxycblt.musikr.MusicParent
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.resolveNames
import timber.log.Timber as L
/**

View file

@ -18,11 +18,8 @@
package org.oxycblt.auxio.util
import java.security.MessageDigest
import java.util.UUID
import kotlin.reflect.KClass
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.musikr.tag.Date
/**
* Sanitizes a value that is unlikely to be null. On debug builds, this aliases to [requireNotNull],
@ -43,28 +40,6 @@ fun <T> unlikelyToBeNull(value: T?) =
*/
fun Int.positiveOrNull() = if (this > 0) this else null
/**
* Aliases a check to ensure that the given number is non-zero.
*
* @return The same number if it's non-zero, null otherwise.
*/
fun Long.positiveOrNull() = if (this > 0) this else null
/**
* Aliases a check to ensure that the given number is non-zero.
*
* @return The same number if it's non-zero, null otherwise.
*/
fun Float.nonZeroOrNull() = if (this != 0f) this else null
/**
* Aliases a check to ensure a given value is in a specified range.
*
* @param range The valid range of values for this number.
* @return The same number if it is in the range, null otherwise.
*/
fun Int.inRangeOrNull(range: IntRange) = if (range.contains(this)) this else null
/**
* Lazily set up a reflected field. Automatically handles visibility changes. Adapted from Material
* Files: https://github.com/zhanghai/MaterialFiles
@ -75,6 +50,7 @@ fun Int.inRangeOrNull(range: IntRange) = if (range.contains(this)) this else nul
fun lazyReflectedField(clazz: KClass<*>, field: String) = lazy {
clazz.java.getDeclaredField(field).also { it.isAccessible = true }
}
/**
* Lazily set up a reflected method. Automatically handles visibility changes. Adapted from Material
* Files: https://github.com/zhanghai/MaterialFiles
@ -87,64 +63,3 @@ fun lazyReflectedMethod(clazz: KClass<*>, method: String, vararg params: KClass<
it.isAccessible = true
}
}
/**
* Convert a [String] to a [UUID].
*
* @return A [UUID] converted from the [String] value, or null if the value was not valid.
* @see UUID.fromString
*/
fun String.toUuidOrNull(): UUID? =
try {
UUID.fromString(this)
} catch (e: IllegalArgumentException) {
null
}
/**
* Update a [MessageDigest] with a lowercase [String].
*
* @param string The [String] to hash. If null, it will not be hashed.
*/
fun MessageDigest.update(string: String?) {
if (string != null) {
update(string.lowercase().toByteArray())
} else {
update(0)
}
}
/**
* Update a [MessageDigest] with the string representation of a [Date].
*
* @param date The [Date] to hash. If null, nothing will be done.
*/
fun MessageDigest.update(date: Date?) {
if (date != null) {
update(date.toString().toByteArray())
} else {
update(0)
}
}
/**
* Update a [MessageDigest] with the lowercase versions of all of the input [String]s.
*
* @param strings The [String]s to hash. If a [String] is null, it will not be hashed.
*/
fun MessageDigest.update(strings: List<String?>) {
strings.forEach(::update)
}
/**
* Update a [MessageDigest] with the little-endian bytes of a [Int].
*
* @param n The [Int] to write. If null, nothing will be done.
*/
fun MessageDigest.update(n: Int?) {
if (n != null) {
update(byteArrayOf(n.toByte(), n.shr(8).toByte(), n.shr(16).toByte(), n.shr(24).toByte()))
} else {
update(0)
}
}

View file

@ -30,12 +30,12 @@ import android.view.View
import android.widget.RemoteViews
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.service.PlaybackActions
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.ui.UISettings
import org.oxycblt.auxio.ui.UISettingsImpl
import org.oxycblt.auxio.util.newBroadcastPendingIntent
import org.oxycblt.musikr.resolveNames
import timber.log.Timber as L
/**

View file

@ -18,20 +18,13 @@
package org.oxycblt.musikr
import android.content.Context
import android.net.Uri
import android.os.Parcelable
import androidx.room.TypeConverter
import java.security.MessageDigest
import java.util.UUID
import kotlin.math.max
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment
import org.oxycblt.auxio.util.concatLocalized
import org.oxycblt.auxio.util.toUuidOrNull
import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.fs.Format
import org.oxycblt.musikr.fs.Path
@ -39,6 +32,8 @@ import org.oxycblt.musikr.tag.Date
import org.oxycblt.musikr.tag.Disc
import org.oxycblt.musikr.tag.Name
import org.oxycblt.musikr.tag.ReleaseType
import org.oxycblt.musikr.tag.ReplayGainAdjustment
import org.oxycblt.musikr.util.toUuidOrNull
/**
* Abstract music data. This contains universal information about all concrete music
@ -46,7 +41,7 @@ import org.oxycblt.musikr.tag.ReleaseType
*
* @author Alexander Capehart (OxygenCobalt)
*/
sealed interface Music : Item {
sealed interface Music {
/**
* A unique identifier for this music item.
*
@ -189,7 +184,7 @@ sealed interface Music : Item {
* Creates a MusicBrainz-style [UID] with a [UUID] derived from the MusicBrainz ID
* extracted from a file.
*
* @param item The analogous [MusicType] of the item that created this [UID].
* @param item The [Item] that created this [UID].
* @param mbid The analogous MusicBrainz ID for this item that was extracted from a
* file.
* @return A new MusicBrainz-style [UID].
@ -372,32 +367,3 @@ interface Playlist : MusicParent {
/** Useful information to quickly obtain a (single) cover for a Genre. */
val cover: Cover
}
/**
* Run [Name.resolve] on each instance in the given list and concatenate them into a [String] in a
* localized manner.
*
* @param context [Context] required
* @return A concatenated string.
*/
fun <T : Music> List<T>.resolveNames(context: Context) =
concatLocalized(context) { it.name.resolve(context) }
/**
* Returns if [Music.name] matches for each item in a list. Useful for scenarios where the display
* information of an item must be compared without a context.
*
* @param other The list of items to compare to.
* @return True if they are the same (by [Music.name]), false otherwise.
*/
fun <T : Music> List<T>.areNamesTheSame(other: List<T>): Boolean {
for (i in 0 until max(size, other.size)) {
val a = getOrNull(i) ?: return false
val b = other.getOrNull(i) ?: return false
if (a.name != b.name) {
return false
}
}
return true
}

View file

@ -18,7 +18,6 @@
package org.oxycblt.musikr.cover
import org.oxycblt.auxio.list.sort.Sort
import org.oxycblt.musikr.Song
sealed interface Cover {
@ -31,8 +30,6 @@ sealed interface Cover {
}
companion object {
private val FALLBACK_SORT = Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING)
fun nil() = Multi(listOf())
fun single(key: String) = Single(key)
@ -40,10 +37,11 @@ sealed interface Cover {
fun multi(songs: Collection<Song>) = order(songs).run { Multi(this) }
private fun order(songs: Collection<Song>) =
FALLBACK_SORT.songs(songs)
songs
.mapNotNull { it.cover }
.groupBy { it.key }
.entries
.sortedByDescending { it.key }
.sortedByDescending { it.value.size }
.map { it.value.first() }
}

View file

@ -19,7 +19,7 @@
package org.oxycblt.musikr.fs
import android.webkit.MimeTypeMap
import org.oxycblt.auxio.util.unlikelyToBeNull
import org.oxycblt.musikr.util.unlikelyToBeNull
sealed interface Format {
val mimeType: String

View file

@ -18,12 +18,12 @@
package org.oxycblt.musikr.graph
import org.oxycblt.auxio.util.unlikelyToBeNull
import org.oxycblt.musikr.Music
import org.oxycblt.musikr.tag.interpret.PreAlbum
import org.oxycblt.musikr.tag.interpret.PreArtist
import org.oxycblt.musikr.tag.interpret.PreGenre
import org.oxycblt.musikr.tag.interpret.PreSong
import org.oxycblt.musikr.util.unlikelyToBeNull
import timber.log.Timber as L
data class MusicGraph(

View file

@ -21,11 +21,11 @@ package org.oxycblt.musikr.metadata
import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.oxycblt.auxio.util.unlikelyToBeNull
import org.oxycblt.ktaglib.FileRef
import org.oxycblt.ktaglib.KTagLib
import org.oxycblt.ktaglib.Metadata
import org.oxycblt.musikr.fs.query.DeviceFile
import org.oxycblt.musikr.util.unlikelyToBeNull
interface MetadataExtractor {
suspend fun extract(file: DeviceFile): Metadata?

View file

@ -18,7 +18,6 @@
package org.oxycblt.musikr.model
import org.oxycblt.auxio.util.update
import org.oxycblt.musikr.Album
import org.oxycblt.musikr.Artist
import org.oxycblt.musikr.Music
@ -26,6 +25,7 @@ import org.oxycblt.musikr.Song
import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.tag.Date
import org.oxycblt.musikr.tag.interpret.PreAlbum
import org.oxycblt.musikr.util.update
interface AlbumCore {
val preAlbum: PreAlbum

View file

@ -18,7 +18,6 @@
package org.oxycblt.musikr.model
import org.oxycblt.auxio.util.update
import org.oxycblt.musikr.Album
import org.oxycblt.musikr.Artist
import org.oxycblt.musikr.Genre
@ -26,6 +25,7 @@ import org.oxycblt.musikr.Music
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.tag.interpret.PreArtist
import org.oxycblt.musikr.util.update
interface ArtistCore {
val preArtist: PreArtist

View file

@ -18,13 +18,13 @@
package org.oxycblt.musikr.model
import org.oxycblt.auxio.util.update
import org.oxycblt.musikr.Artist
import org.oxycblt.musikr.Genre
import org.oxycblt.musikr.Music
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.tag.interpret.PreGenre
import org.oxycblt.musikr.util.update
interface GenreCore {
val preGenre: PreGenre

View file

@ -25,7 +25,7 @@ import java.io.BufferedWriter
import java.io.InputStream
import java.io.InputStreamReader
import java.io.OutputStream
import org.oxycblt.auxio.util.unlikelyToBeNull
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.musikr.Playlist
import org.oxycblt.musikr.fs.Components
import org.oxycblt.musikr.fs.Path
@ -34,8 +34,8 @@ import org.oxycblt.musikr.fs.path.VolumeManager
import org.oxycblt.musikr.playlist.ExportConfig
import org.oxycblt.musikr.playlist.ImportedPlaylist
import org.oxycblt.musikr.playlist.PossiblePaths
import org.oxycblt.musikr.resolveNames
import org.oxycblt.musikr.tag.util.correctWhitespace
import org.oxycblt.musikr.util.unlikelyToBeNull
import timber.log.Timber as L
/**

View file

@ -23,8 +23,8 @@ import java.text.ParseException
import java.text.SimpleDateFormat
import kotlin.math.max
import org.oxycblt.auxio.R
import org.oxycblt.auxio.util.inRangeOrNull
import org.oxycblt.auxio.util.positiveOrNull
import org.oxycblt.musikr.util.inRangeOrNull
import org.oxycblt.musikr.util.positiveOrNull
import timber.log.Timber as L
/**

View file

@ -20,7 +20,6 @@ package org.oxycblt.musikr.tag
import android.content.Context
import org.oxycblt.auxio.R
import org.oxycblt.auxio.list.Item
/**
* A disc identifier for a song.
@ -28,7 +27,7 @@ import org.oxycblt.auxio.list.Item
* @param number The disc number.
* @param name The name of the disc group, if any. Null if not present.
*/
class Disc(val number: Int, val name: String?) : Item, Comparable<Disc> {
class Disc(val number: Int, val name: String?) : Comparable<Disc> {
// We don't want to group discs by differing subtitles, so only compare by the number
override fun equals(other: Any?) = other is Disc && number == other.number

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2022 Auxio Project
* ReplayGainAdjustment.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.musikr.tag
/**
* Represents a ReplayGain adjustment to apply during song playback.
*
* @param track The track-specific adjustment that should be applied. Null if not available.
* @param album A more general album-specific adjustment that should be applied. Null if not
* available.
*/
data class ReplayGainAdjustment(val track: Float?, val album: Float?)

View file

@ -20,8 +20,6 @@ package org.oxycblt.musikr.tag.interpret
import android.net.Uri
import java.util.UUID
import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment
import org.oxycblt.auxio.util.update
import org.oxycblt.musikr.Music
import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.fs.Format
@ -31,6 +29,8 @@ import org.oxycblt.musikr.tag.Date
import org.oxycblt.musikr.tag.Disc
import org.oxycblt.musikr.tag.Name
import org.oxycblt.musikr.tag.ReleaseType
import org.oxycblt.musikr.tag.ReplayGainAdjustment
import org.oxycblt.musikr.util.update
data class PreSong(
val musicBrainzId: UUID?,

View file

@ -19,8 +19,6 @@
package org.oxycblt.musikr.tag.interpret
import org.oxycblt.auxio.R
import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment
import org.oxycblt.auxio.util.toUuidOrNull
import org.oxycblt.musikr.Interpretation
import org.oxycblt.musikr.fs.Format
import org.oxycblt.musikr.fs.query.DeviceFile
@ -28,8 +26,10 @@ import org.oxycblt.musikr.pipeline.RawSong
import org.oxycblt.musikr.tag.Disc
import org.oxycblt.musikr.tag.Name
import org.oxycblt.musikr.tag.ReleaseType
import org.oxycblt.musikr.tag.ReplayGainAdjustment
import org.oxycblt.musikr.tag.parse.ParsedTags
import org.oxycblt.musikr.tag.util.parseId3GenreNames
import org.oxycblt.musikr.util.toUuidOrNull
interface TagInterpreter {
fun interpret(song: RawSong, interpretation: Interpretation): PreSong

View file

@ -19,11 +19,11 @@
package org.oxycblt.musikr.tag.parse
import androidx.core.text.isDigitsOnly
import org.oxycblt.auxio.util.nonZeroOrNull
import org.oxycblt.ktaglib.Metadata
import org.oxycblt.musikr.tag.Date
import org.oxycblt.musikr.tag.util.parseId3v2PositionField
import org.oxycblt.musikr.tag.util.parseXiphPositionField
import org.oxycblt.musikr.util.nonZeroOrNull
// Song
fun Metadata.musicBrainzId() =

View file

@ -18,7 +18,7 @@
package org.oxycblt.musikr.tag.util
import org.oxycblt.auxio.util.positiveOrNull
import org.oxycblt.musikr.util.positiveOrNull
/**
* Parse an ID3v2-style position + total [String] field. These fields consist of a number and an

View file

@ -0,0 +1,133 @@
/*
* Copyright (c) 2021 Auxio Project
* LangUtil.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.musikr.util
import java.security.MessageDigest
import java.util.UUID
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.musikr.tag.Date
import kotlin.reflect.KClass
/**
* Sanitizes a value that is unlikely to be null. On debug builds, this aliases to [requireNotNull],
* otherwise, it aliases to the unchecked dereference operator (!!). This can be used as a minor
* optimization in certain cases.
*/
fun <T> unlikelyToBeNull(value: T?) =
if (BuildConfig.DEBUG) {
requireNotNull(value)
} else {
value!!
}
/**
* Aliases a check to ensure that the given number is non-zero.
*
* @return The given number if it's non-zero, null otherwise.
*/
fun Int.positiveOrNull() = if (this > 0) this else null
/**
* Aliases a check to ensure that the given number is non-zero.
*
* @return The same number if it's non-zero, null otherwise.
*/
fun Float.nonZeroOrNull() = if (this != 0f) this else null
/**
* Aliases a check to ensure a given value is in a specified range.
*
* @param range The valid range of values for this number.
* @return The same number if it is in the range, null otherwise.
*/
fun Int.inRangeOrNull(range: IntRange) = if (range.contains(this)) this else null
/**
* Convert a [String] to a [UUID].
*
* @return A [UUID] converted from the [String] value, or null if the value was not valid.
* @see UUID.fromString
*/
fun String.toUuidOrNull(): UUID? =
try {
UUID.fromString(this)
} catch (e: IllegalArgumentException) {
null
}
/**
* Update a [MessageDigest] with a lowercase [String].
*
* @param string The [String] to hash. If null, it will not be hashed.
*/
fun MessageDigest.update(string: String?) {
if (string != null) {
update(string.lowercase().toByteArray())
} else {
update(0)
}
}
/**
* Update a [MessageDigest] with the string representation of a [Date].
*
* @param date The [Date] to hash. If null, nothing will be done.
*/
fun MessageDigest.update(date: Date?) {
if (date != null) {
update(date.toString().toByteArray())
} else {
update(0)
}
}
/**
* Update a [MessageDigest] with the lowercase versions of all of the input [String]s.
*
* @param strings The [String]s to hash. If a [String] is null, it will not be hashed.
*/
fun MessageDigest.update(strings: List<String?>) {
strings.forEach(::update)
}
/**
* Update a [MessageDigest] with the little-endian bytes of a [Int].
*
* @param n The [Int] to write. If null, nothing will be done.
*/
fun MessageDigest.update(n: Int?) {
if (n != null) {
update(byteArrayOf(n.toByte(), n.shr(8).toByte(), n.shr(16).toByte(), n.shr(24).toByte()))
} else {
update(0)
}
}
/**
* Lazily set up a reflected method. Automatically handles visibility changes. Adapted from Material
* Files: https://github.com/zhanghai/MaterialFiles
*
* @param clazz The [KClass] to reflect into.
* @param method The name of the method to obtain.
*/
fun lazyReflectedMethod(clazz: KClass<*>, method: String, vararg params: KClass<*>) = lazy {
clazz.java.getDeclaredMethod(method, *params.map { it.java }.toTypedArray()).also {
it.isAccessible = true
}
}