musikr: cleanup api
This commit is contained in:
parent
14355a1005
commit
0d5abb6407
42 changed files with 189 additions and 183 deletions
|
@ -94,7 +94,7 @@ sealed interface Music {
|
|||
|
||||
override fun toString() = "${format.namespace}:${item.intCode.toString(16)}-$uuid"
|
||||
|
||||
enum class Item(val intCode: Int) {
|
||||
internal enum class Item(val intCode: Int) {
|
||||
// Item used to be MusicType back when the music module was
|
||||
// part of Auxio, so these old integer codes remain.
|
||||
// TODO: Introduce new UID format that removes these.
|
||||
|
@ -126,7 +126,7 @@ sealed interface Music {
|
|||
@TypeConverter fun toMusicUid(string: String?) = string?.let(Companion::fromString)
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal companion object {
|
||||
/**
|
||||
* Creates an Auxio-style [UID] of random composition. Used if there is no
|
||||
* non-subjective, unlikely-to-change metadata of the music.
|
||||
|
|
|
@ -55,7 +55,7 @@ sealed interface IndexingProgress {
|
|||
data object Indeterminate : IndexingProgress
|
||||
}
|
||||
|
||||
class MusikrImpl(
|
||||
private class MusikrImpl(
|
||||
private val exploreStep: ExploreStep,
|
||||
private val extractStep: ExtractStep,
|
||||
private val evaluateStep: EvaluateStep
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
package org.oxycblt.musikr.cache
|
||||
|
||||
import org.oxycblt.musikr.fs.query.DeviceFile
|
||||
import org.oxycblt.musikr.fs.DeviceFile
|
||||
import org.oxycblt.musikr.pipeline.RawSong
|
||||
|
||||
interface Cache {
|
||||
|
|
|
@ -31,7 +31,7 @@ import androidx.room.RoomDatabase
|
|||
import androidx.room.TypeConverter
|
||||
import androidx.room.TypeConverters
|
||||
import org.oxycblt.musikr.cover.Cover
|
||||
import org.oxycblt.musikr.fs.query.DeviceFile
|
||||
import org.oxycblt.musikr.fs.DeviceFile
|
||||
import org.oxycblt.musikr.metadata.Properties
|
||||
import org.oxycblt.musikr.pipeline.RawSong
|
||||
import org.oxycblt.musikr.tag.Date
|
||||
|
|
|
@ -20,7 +20,7 @@ package org.oxycblt.musikr.cover
|
|||
|
||||
import java.security.MessageDigest
|
||||
|
||||
interface CoverIdentifier {
|
||||
internal interface CoverIdentifier {
|
||||
suspend fun identify(data: ByteArray): String
|
||||
|
||||
companion object {
|
||||
|
|
11
musikr/src/main/java/org/oxycblt/musikr/fs/DeviceFile.kt
Normal file
11
musikr/src/main/java/org/oxycblt/musikr/fs/DeviceFile.kt
Normal file
|
@ -0,0 +1,11 @@
|
|||
package org.oxycblt.musikr.fs
|
||||
|
||||
import android.net.Uri
|
||||
|
||||
data class DeviceFile(
|
||||
val uri: Uri,
|
||||
val mimeType: String,
|
||||
val path: Path,
|
||||
val size: Long,
|
||||
val lastModified: Long
|
||||
)
|
|
@ -84,7 +84,7 @@ sealed interface Format {
|
|||
"audio/wave" to Wav,
|
||||
)
|
||||
|
||||
fun infer(containerMimeType: String, codecMimeType: String): Format {
|
||||
internal fun infer(containerMimeType: String, codecMimeType: String): Format {
|
||||
val codecFormat = CODEC_MAP[codecMimeType]
|
||||
if (codecFormat != null) {
|
||||
// Codec found, possibly wrap in container.
|
||||
|
|
|
@ -26,7 +26,7 @@ import org.oxycblt.musikr.fs.path.DocumentPathFactory
|
|||
import org.oxycblt.musikr.fs.query.contentResolverSafe
|
||||
import org.oxycblt.musikr.util.splitEscaped
|
||||
|
||||
class MusicLocation internal constructor(val uri: Uri, val path: Path) {
|
||||
class MusicLocation private constructor(internal val uri: Uri, internal val path: Path) {
|
||||
override fun equals(other: Any?) = other is MusicLocation && uri == other.uri
|
||||
|
||||
override fun hashCode() = 31 * uri.hashCode()
|
||||
|
|
|
@ -47,14 +47,14 @@ data class Path(
|
|||
* @param fileName The name of the file to append to the path.
|
||||
* @return The new [Path] instance.
|
||||
*/
|
||||
fun file(fileName: String) = Path(volume, components.child(fileName))
|
||||
internal fun file(fileName: String) = Path(volume, components.child(fileName))
|
||||
|
||||
/**
|
||||
* Resolves the [Path] in a human-readable format.
|
||||
*
|
||||
* @param context [Context] required to obtain human-readable strings.
|
||||
*/
|
||||
fun resolve(context: Context) = "${volume.resolveName(context)}/$components"
|
||||
internal fun resolve(context: Context) = "${volume.resolveName(context)}/$components"
|
||||
}
|
||||
|
||||
sealed interface Volume {
|
||||
|
@ -154,9 +154,7 @@ value class Components private constructor(val components: List<String>) {
|
|||
|
||||
fun containing(other: Components) = Components(other.components.drop(components.size))
|
||||
|
||||
companion object {
|
||||
fun nil() = Components(listOf())
|
||||
|
||||
internal companion object {
|
||||
/**
|
||||
* Parses a path string into a [Components] instance by the unix path separator (/).
|
||||
*
|
||||
|
|
|
@ -34,7 +34,7 @@ import org.oxycblt.musikr.fs.query.useQuery
|
|||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
interface DocumentPathFactory {
|
||||
internal interface DocumentPathFactory {
|
||||
/**
|
||||
* Unpacks a document URI into a [Path] instance, using [fromDocumentId].
|
||||
*
|
||||
|
|
|
@ -29,7 +29,7 @@ import org.oxycblt.musikr.fs.Path
|
|||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
sealed interface MediaStorePathInterpreter {
|
||||
internal sealed interface MediaStorePathInterpreter {
|
||||
/**
|
||||
* Extract a [Path] from the wrapped [Cursor]. This should be called after the cursor has been
|
||||
* moved to the row that should be interpreted.
|
||||
|
|
|
@ -46,7 +46,7 @@ private val svApi21GetPathMethod: Method by lazyReflectedMethod(StorageVolume::c
|
|||
*
|
||||
* @see StorageManager.getStorageVolumes
|
||||
*/
|
||||
val StorageManager.storageVolumesCompat: List<StorageVolume>
|
||||
internal val StorageManager.storageVolumesCompat: List<StorageVolume>
|
||||
get() = storageVolumes.toList()
|
||||
|
||||
/**
|
||||
|
@ -55,7 +55,7 @@ val StorageManager.storageVolumesCompat: List<StorageVolume>
|
|||
*
|
||||
* @see StorageVolume.getDirectory
|
||||
*/
|
||||
val StorageVolume.directoryCompat: String?
|
||||
internal val StorageVolume.directoryCompat: String?
|
||||
@SuppressLint("NewApi")
|
||||
get() =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
|
@ -76,7 +76,7 @@ val StorageVolume.directoryCompat: String?
|
|||
* @return A human-readable name for this volume.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
fun StorageVolume.getDescriptionCompat(context: Context): String = getDescription(context)
|
||||
internal fun StorageVolume.getDescriptionCompat(context: Context): String = getDescription(context)
|
||||
|
||||
/**
|
||||
* If this [StorageVolume] is considered the "Primary" volume where the Android System is kept. May
|
||||
|
@ -84,7 +84,7 @@ fun StorageVolume.getDescriptionCompat(context: Context): String = getDescriptio
|
|||
*
|
||||
* @see StorageVolume.isPrimary
|
||||
*/
|
||||
val StorageVolume.isPrimaryCompat: Boolean
|
||||
internal val StorageVolume.isPrimaryCompat: Boolean
|
||||
@SuppressLint("NewApi") get() = isPrimary
|
||||
|
||||
/**
|
||||
|
@ -93,14 +93,14 @@ val StorageVolume.isPrimaryCompat: Boolean
|
|||
*
|
||||
* @see StorageVolume.isEmulated
|
||||
*/
|
||||
val StorageVolume.isEmulatedCompat: Boolean
|
||||
internal val StorageVolume.isEmulatedCompat: Boolean
|
||||
@SuppressLint("NewApi") get() = isEmulated
|
||||
|
||||
/**
|
||||
* If this [StorageVolume] represents the "Internal Shared Storage" volume, also known as "primary"
|
||||
* to [MediaStore] and Document [Uri]s, obtained in a version compatible manner.
|
||||
*/
|
||||
val StorageVolume.isInternalCompat: Boolean
|
||||
internal val StorageVolume.isInternalCompat: Boolean
|
||||
// Must contain the android system AND be an emulated drive, as non-emulated system
|
||||
// volumes use their UUID instead of primary in MediaStore/Document URIs.
|
||||
get() = isPrimaryCompat && isEmulatedCompat
|
||||
|
@ -111,7 +111,7 @@ val StorageVolume.isInternalCompat: Boolean
|
|||
*
|
||||
* @see StorageVolume.getUuid
|
||||
*/
|
||||
val StorageVolume.uuidCompat: String?
|
||||
internal val StorageVolume.uuidCompat: String?
|
||||
@SuppressLint("NewApi") get() = uuid
|
||||
|
||||
/**
|
||||
|
@ -120,7 +120,7 @@ val StorageVolume.uuidCompat: String?
|
|||
*
|
||||
* @see StorageVolume.getState
|
||||
*/
|
||||
val StorageVolume.stateCompat: String
|
||||
internal val StorageVolume.stateCompat: String
|
||||
@SuppressLint("NewApi") get() = state
|
||||
|
||||
/**
|
||||
|
@ -129,7 +129,7 @@ val StorageVolume.stateCompat: String
|
|||
*
|
||||
* @see StorageVolume.getMediaStoreVolumeName
|
||||
*/
|
||||
val StorageVolume.mediaStoreVolumeNameCompat: String?
|
||||
internal val StorageVolume.mediaStoreVolumeNameCompat: String?
|
||||
get() =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
mediaStoreVolumeName
|
||||
|
|
|
@ -25,7 +25,7 @@ import org.oxycblt.musikr.fs.Components
|
|||
import org.oxycblt.musikr.fs.Volume
|
||||
|
||||
/** A wrapper around [StorageManager] that provides instances of the [Volume] interface. */
|
||||
interface VolumeManager {
|
||||
internal interface VolumeManager {
|
||||
/**
|
||||
* The internal storage volume of the device.
|
||||
*
|
||||
|
|
|
@ -29,10 +29,11 @@ import kotlinx.coroutines.flow.emitAll
|
|||
import kotlinx.coroutines.flow.flatMapMerge
|
||||
import kotlinx.coroutines.flow.flattenMerge
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import org.oxycblt.musikr.fs.DeviceFile
|
||||
import org.oxycblt.musikr.fs.MusicLocation
|
||||
import org.oxycblt.musikr.fs.Path
|
||||
|
||||
interface DeviceFiles {
|
||||
internal interface DeviceFiles {
|
||||
fun explore(locations: Flow<MusicLocation>): Flow<DeviceFile>
|
||||
|
||||
companion object {
|
||||
|
@ -40,14 +41,6 @@ interface DeviceFiles {
|
|||
}
|
||||
}
|
||||
|
||||
data class DeviceFile(
|
||||
val uri: Uri,
|
||||
val mimeType: String,
|
||||
val path: Path,
|
||||
val size: Long,
|
||||
val lastModified: Long
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private class DeviceFilesImpl(private val contentResolver: ContentResolver) : DeviceFiles {
|
||||
override fun explore(locations: Flow<MusicLocation>): Flow<DeviceFile> =
|
||||
|
@ -99,7 +92,8 @@ private class DeviceFilesImpl(private val contentResolver: ContentResolver) : De
|
|||
mimeType,
|
||||
newPath,
|
||||
size,
|
||||
lastModified))
|
||||
lastModified)
|
||||
)
|
||||
}
|
||||
}
|
||||
emitAll(recursive.asFlow().flattenMerge())
|
||||
|
|
|
@ -27,7 +27,7 @@ import android.net.Uri
|
|||
* Get a content resolver that will not mangle MediaStore queries on certain devices. See
|
||||
* https://github.com/OxygenCobalt/Auxio/issues/50 for more info.
|
||||
*/
|
||||
val Context.contentResolverSafe: ContentResolver
|
||||
internal val Context.contentResolverSafe: ContentResolver
|
||||
get() = applicationContext.contentResolver
|
||||
|
||||
/**
|
||||
|
@ -42,7 +42,7 @@ val Context.contentResolverSafe: ContentResolver
|
|||
* @throws IllegalStateException If the [ContentResolver] did not return the queried [Cursor].
|
||||
* @see ContentResolver.query
|
||||
*/
|
||||
fun ContentResolver.safeQuery(
|
||||
internal fun ContentResolver.safeQuery(
|
||||
uri: Uri,
|
||||
projection: Array<out String>,
|
||||
selector: String? = null,
|
||||
|
@ -63,7 +63,7 @@ fun ContentResolver.safeQuery(
|
|||
* @throws IllegalStateException If the [ContentResolver] did not return the queried [Cursor].
|
||||
* @see ContentResolver.query
|
||||
*/
|
||||
inline fun <reified R> ContentResolver.useQuery(
|
||||
internal inline fun <reified R> ContentResolver.useQuery(
|
||||
uri: Uri,
|
||||
projection: Array<out String>,
|
||||
selector: String? = null,
|
||||
|
|
|
@ -25,7 +25,7 @@ import org.oxycblt.musikr.tag.interpret.PreGenre
|
|||
import org.oxycblt.musikr.tag.interpret.PreSong
|
||||
import org.oxycblt.musikr.util.unlikelyToBeNull
|
||||
|
||||
data class MusicGraph(
|
||||
internal data class MusicGraph(
|
||||
val songVertex: List<SongVertex>,
|
||||
val albumVertex: List<AlbumVertex>,
|
||||
val artistVertex: List<ArtistVertex>,
|
||||
|
@ -274,7 +274,7 @@ private class MusicGraphBuilderImpl : MusicGraph.Builder {
|
|||
}
|
||||
}
|
||||
|
||||
class SongVertex(
|
||||
internal class SongVertex(
|
||||
val preSong: PreSong,
|
||||
var albumVertex: AlbumVertex,
|
||||
var artistVertices: MutableList<ArtistVertex>,
|
||||
|
@ -283,12 +283,12 @@ class SongVertex(
|
|||
var tag: Any? = null
|
||||
}
|
||||
|
||||
class AlbumVertex(val preAlbum: PreAlbum, var artistVertices: MutableList<ArtistVertex>) {
|
||||
internal class AlbumVertex(val preAlbum: PreAlbum, var artistVertices: MutableList<ArtistVertex>) {
|
||||
val songVertices = mutableSetOf<SongVertex>()
|
||||
var tag: Any? = null
|
||||
}
|
||||
|
||||
class ArtistVertex(
|
||||
internal class ArtistVertex(
|
||||
val preArtist: PreArtist,
|
||||
) {
|
||||
val songVertices = mutableSetOf<SongVertex>()
|
||||
|
@ -297,7 +297,7 @@ class ArtistVertex(
|
|||
var tag: Any? = null
|
||||
}
|
||||
|
||||
class GenreVertex(val preGenre: PreGenre) {
|
||||
internal class GenreVertex(val preGenre: PreGenre) {
|
||||
val songVertices = mutableSetOf<SongVertex>()
|
||||
val artistVertices = mutableSetOf<ArtistVertex>()
|
||||
var tag: Any? = null
|
||||
|
|
|
@ -22,7 +22,7 @@ import android.content.Context
|
|||
import java.io.FileInputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
class AndroidInputStream(context: Context, fileRef: FileRef) : NativeInputStream {
|
||||
internal class AndroidInputStream(context: Context, fileRef: FileRef) : NativeInputStream {
|
||||
private val fileName = fileRef.fileName
|
||||
private val fd =
|
||||
requireNotNull(context.contentResolver.openFileDescriptor(fileRef.uri, "r")) {
|
||||
|
|
43
musikr/src/main/java/org/oxycblt/musikr/metadata/Metadata.kt
Normal file
43
musikr/src/main/java/org/oxycblt/musikr/metadata/Metadata.kt
Normal file
|
@ -0,0 +1,43 @@
|
|||
package org.oxycblt.musikr.metadata
|
||||
|
||||
internal data class Metadata(
|
||||
val id3v2: Map<String, List<String>>,
|
||||
val xiph: Map<String, List<String>>,
|
||||
val mp4: Map<String, List<String>>,
|
||||
val cover: ByteArray?,
|
||||
val properties: Properties
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Metadata
|
||||
|
||||
if (id3v2 != other.id3v2) return false
|
||||
if (xiph != other.xiph) return false
|
||||
if (mp4 != other.mp4) return false
|
||||
if (cover != null) {
|
||||
if (other.cover == null) return false
|
||||
if (!cover.contentEquals(other.cover)) return false
|
||||
} else if (other.cover != null) return false
|
||||
if (properties != other.properties) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = id3v2.hashCode()
|
||||
result = 31 * result + xiph.hashCode()
|
||||
result = 31 * result + mp4.hashCode()
|
||||
result = 31 * result + (cover?.contentHashCode() ?: 0)
|
||||
result = 31 * result + properties.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
data class Properties(
|
||||
val mimeType: String,
|
||||
val durationMs: Long,
|
||||
val bitrate: Int,
|
||||
val sampleRate: Int,
|
||||
)
|
|
@ -21,10 +21,10 @@ package org.oxycblt.musikr.metadata
|
|||
import android.content.Context
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.oxycblt.musikr.fs.query.DeviceFile
|
||||
import org.oxycblt.musikr.fs.DeviceFile
|
||||
import org.oxycblt.musikr.util.unlikelyToBeNull
|
||||
|
||||
interface MetadataExtractor {
|
||||
internal interface MetadataExtractor {
|
||||
suspend fun extract(file: DeviceFile): Metadata?
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -23,7 +23,7 @@ package org.oxycblt.musikr.metadata
|
|||
*
|
||||
* The vast majority of IO shim between Taglib/KTaglib should occur here to minimize JNI calls.
|
||||
*/
|
||||
interface NativeInputStream {
|
||||
internal interface NativeInputStream {
|
||||
fun name(): String
|
||||
|
||||
fun readBlock(length: Long): ByteArray
|
||||
|
|
|
@ -21,7 +21,7 @@ package org.oxycblt.musikr.metadata
|
|||
import android.content.Context
|
||||
import android.net.Uri
|
||||
|
||||
object TagLibJNI {
|
||||
internal object TagLibJNI {
|
||||
init {
|
||||
System.loadLibrary("taglib_jni")
|
||||
}
|
||||
|
@ -41,46 +41,4 @@ object TagLibJNI {
|
|||
private external fun openNative(ioStream: AndroidInputStream): Metadata?
|
||||
}
|
||||
|
||||
data class FileRef(val fileName: String, val uri: Uri)
|
||||
|
||||
data class Metadata(
|
||||
val id3v2: Map<String, List<String>>,
|
||||
val xiph: Map<String, List<String>>,
|
||||
val mp4: Map<String, List<String>>,
|
||||
val cover: ByteArray?,
|
||||
val properties: Properties
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Metadata
|
||||
|
||||
if (id3v2 != other.id3v2) return false
|
||||
if (xiph != other.xiph) return false
|
||||
if (mp4 != other.mp4) return false
|
||||
if (cover != null) {
|
||||
if (other.cover == null) return false
|
||||
if (!cover.contentEquals(other.cover)) return false
|
||||
} else if (other.cover != null) return false
|
||||
if (properties != other.properties) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = id3v2.hashCode()
|
||||
result = 31 * result + xiph.hashCode()
|
||||
result = 31 * result + mp4.hashCode()
|
||||
result = 31 * result + (cover?.contentHashCode() ?: 0)
|
||||
result = 31 * result + properties.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
data class Properties(
|
||||
val mimeType: String,
|
||||
val durationMs: Long,
|
||||
val bitrate: Int,
|
||||
val sampleRate: Int,
|
||||
)
|
||||
internal data class FileRef(val fileName: String, val uri: Uri)
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.oxycblt.musikr.tag.Date
|
|||
import org.oxycblt.musikr.tag.interpret.PreAlbum
|
||||
import org.oxycblt.musikr.util.update
|
||||
|
||||
interface AlbumCore {
|
||||
internal interface AlbumCore {
|
||||
val preAlbum: PreAlbum
|
||||
val songs: List<Song>
|
||||
|
||||
|
@ -39,7 +39,7 @@ interface AlbumCore {
|
|||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class AlbumImpl(private val core: AlbumCore) : Album {
|
||||
internal class AlbumImpl(private val core: AlbumCore) : Album {
|
||||
private val preAlbum = core.preAlbum
|
||||
|
||||
override val uid =
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.oxycblt.musikr.cover.Cover
|
|||
import org.oxycblt.musikr.tag.interpret.PreArtist
|
||||
import org.oxycblt.musikr.util.update
|
||||
|
||||
interface ArtistCore {
|
||||
internal interface ArtistCore {
|
||||
val preArtist: PreArtist
|
||||
val songs: Set<Song>
|
||||
val albums: Set<Album>
|
||||
|
@ -40,7 +40,7 @@ interface ArtistCore {
|
|||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class ArtistImpl(private val core: ArtistCore) : Artist {
|
||||
internal class ArtistImpl(private val core: ArtistCore) : Artist {
|
||||
override val uid =
|
||||
// Attempt to use a MusicBrainz ID first before falling back to a hashed UID.
|
||||
core.preArtist.musicBrainzId?.let { Music.UID.musicBrainz(Music.UID.Item.ARTIST, it) }
|
||||
|
|
|
@ -26,7 +26,7 @@ import org.oxycblt.musikr.cover.Cover
|
|||
import org.oxycblt.musikr.tag.interpret.PreGenre
|
||||
import org.oxycblt.musikr.util.update
|
||||
|
||||
interface GenreCore {
|
||||
internal interface GenreCore {
|
||||
val preGenre: PreGenre
|
||||
val songs: Set<Song>
|
||||
val artists: Set<Artist>
|
||||
|
@ -37,7 +37,7 @@ interface GenreCore {
|
|||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class GenreImpl(private val core: GenreCore) : Genre {
|
||||
internal class GenreImpl(private val core: GenreCore) : Genre {
|
||||
override val uid = Music.UID.auxio(Music.UID.Item.GENRE) { update(core.preGenre.rawName) }
|
||||
override val name = core.preGenre.name
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ import org.oxycblt.musikr.graph.GenreVertex
|
|||
import org.oxycblt.musikr.graph.MusicGraph
|
||||
import org.oxycblt.musikr.graph.SongVertex
|
||||
|
||||
interface LibraryFactory {
|
||||
internal interface LibraryFactory {
|
||||
fun create(graph: MusicGraph): MutableLibrary
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -24,7 +24,7 @@ import org.oxycblt.musikr.Playlist
|
|||
import org.oxycblt.musikr.Song
|
||||
import org.oxycblt.musikr.fs.Path
|
||||
|
||||
class LibraryImpl(
|
||||
internal class LibraryImpl(
|
||||
override val songs: Collection<SongImpl>,
|
||||
override val albums: Collection<AlbumImpl>,
|
||||
override val artists: Collection<ArtistImpl>,
|
||||
|
|
|
@ -25,13 +25,13 @@ import org.oxycblt.musikr.playlist.PlaylistHandle
|
|||
import org.oxycblt.musikr.tag.Name
|
||||
import org.oxycblt.musikr.tag.interpret.PrePlaylist
|
||||
|
||||
interface PlaylistCore {
|
||||
internal interface PlaylistCore {
|
||||
val prePlaylist: PrePlaylist
|
||||
val handle: PlaylistHandle
|
||||
val songs: List<Song>
|
||||
}
|
||||
|
||||
class PlaylistImpl(private val core: PlaylistCore) : Playlist {
|
||||
internal class PlaylistImpl(private val core: PlaylistCore) : Playlist {
|
||||
override val uid = core.handle.uid
|
||||
override val name: Name.Known = core.prePlaylist.name
|
||||
override val durationMs = core.songs.sumOf { it.durationMs }
|
||||
|
|
|
@ -24,7 +24,7 @@ import org.oxycblt.musikr.Genre
|
|||
import org.oxycblt.musikr.Song
|
||||
import org.oxycblt.musikr.tag.interpret.PreSong
|
||||
|
||||
interface SongCore {
|
||||
internal interface SongCore {
|
||||
val preSong: PreSong
|
||||
|
||||
fun resolveAlbum(): Album
|
||||
|
@ -39,7 +39,7 @@ interface SongCore {
|
|||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class SongImpl(private val handle: SongCore) : Song {
|
||||
internal class SongImpl(private val handle: SongCore) : Song {
|
||||
private val preSong = handle.preSong
|
||||
|
||||
override val uid = preSong.computeUid()
|
||||
|
|
|
@ -31,7 +31,7 @@ import org.oxycblt.musikr.graph.MusicGraph
|
|||
import org.oxycblt.musikr.model.LibraryFactory
|
||||
import org.oxycblt.musikr.tag.interpret.TagInterpreter
|
||||
|
||||
interface EvaluateStep {
|
||||
internal interface EvaluateStep {
|
||||
suspend fun evaluate(
|
||||
interpretation: Interpretation,
|
||||
extractedMusic: Flow<ExtractedMusic>
|
||||
|
|
|
@ -25,11 +25,11 @@ import kotlinx.coroutines.flow.asFlow
|
|||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import org.oxycblt.musikr.fs.MusicLocation
|
||||
import org.oxycblt.musikr.fs.query.DeviceFile
|
||||
import org.oxycblt.musikr.fs.DeviceFile
|
||||
import org.oxycblt.musikr.fs.query.DeviceFiles
|
||||
import org.oxycblt.musikr.playlist.m3u.M3U
|
||||
|
||||
interface ExploreStep {
|
||||
internal interface ExploreStep {
|
||||
fun explore(locations: List<MusicLocation>): Flow<ExploreNode>
|
||||
|
||||
companion object {
|
||||
|
@ -51,6 +51,6 @@ private class ExploreStepImpl(private val deviceFiles: DeviceFiles) : ExploreSte
|
|||
.flowOn(Dispatchers.IO)
|
||||
}
|
||||
|
||||
sealed interface ExploreNode {
|
||||
internal sealed interface ExploreNode {
|
||||
data class Audio(val file: DeviceFile) : ExploreNode
|
||||
}
|
||||
|
|
|
@ -31,13 +31,13 @@ import kotlinx.coroutines.flow.merge
|
|||
import org.oxycblt.musikr.Storage
|
||||
import org.oxycblt.musikr.cache.CacheResult
|
||||
import org.oxycblt.musikr.cover.Cover
|
||||
import org.oxycblt.musikr.fs.query.DeviceFile
|
||||
import org.oxycblt.musikr.fs.DeviceFile
|
||||
import org.oxycblt.musikr.metadata.MetadataExtractor
|
||||
import org.oxycblt.musikr.metadata.Properties
|
||||
import org.oxycblt.musikr.tag.parse.ParsedTags
|
||||
import org.oxycblt.musikr.tag.parse.TagParser
|
||||
|
||||
interface ExtractStep {
|
||||
internal interface ExtractStep {
|
||||
fun extract(storage: Storage, nodes: Flow<ExploreNode>): Flow<ExtractedMusic>
|
||||
|
||||
companion object {
|
||||
|
@ -99,6 +99,6 @@ data class RawSong(
|
|||
val cover: Cover.Single?
|
||||
)
|
||||
|
||||
sealed interface ExtractedMusic {
|
||||
internal sealed interface ExtractedMusic {
|
||||
data class Song(val song: RawSong) : ExtractedMusic
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import kotlinx.coroutines.flow.map
|
|||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.withIndex
|
||||
|
||||
sealed interface Divert<L, R> {
|
||||
internal sealed interface Divert<L, R> {
|
||||
data class Left<L, R>(val value: L) : Divert<L, R>
|
||||
|
||||
data class Right<L, R>(val value: R) : Divert<L, R>
|
||||
|
@ -33,7 +33,7 @@ sealed interface Divert<L, R> {
|
|||
|
||||
class DivertedFlow<L, R>(val manager: Flow<Nothing>, val left: Flow<L>, val right: Flow<R>)
|
||||
|
||||
inline fun <T, L, R> Flow<T>.divert(
|
||||
internal inline fun <T, L, R> Flow<T>.divert(
|
||||
crossinline predicate: (T) -> Divert<L, R>
|
||||
): DivertedFlow<L, R> {
|
||||
val leftChannel = Channel<L>(Channel.UNLIMITED)
|
||||
|
@ -52,7 +52,7 @@ inline fun <T, L, R> Flow<T>.divert(
|
|||
return DivertedFlow(managedFlow, leftChannel.receiveAsFlow(), rightChannel.receiveAsFlow())
|
||||
}
|
||||
|
||||
class DistributedFlow<T>(val manager: Flow<Nothing>, val flows: Array<Flow<T>>)
|
||||
internal class DistributedFlow<T>(val manager: Flow<Nothing>, val flows: Array<Flow<T>>)
|
||||
|
||||
/**
|
||||
* Equally "distributes" the values of some flow across n new flows.
|
||||
|
@ -60,7 +60,7 @@ class DistributedFlow<T>(val manager: Flow<Nothing>, val flows: Array<Flow<T>>)
|
|||
* Note that this function requires the "manager" flow to be consumed alongside the split flows in
|
||||
* order to function. Without this, all of the newly split flows will simply block.
|
||||
*/
|
||||
fun <T> Flow<T>.distribute(n: Int): DistributedFlow<T> {
|
||||
internal fun <T> Flow<T>.distribute(n: Int): DistributedFlow<T> {
|
||||
val posChannels = Array(n) { Channel<T>(Channel.UNLIMITED) }
|
||||
val managerFlow =
|
||||
flow<Nothing> {
|
||||
|
|
|
@ -84,7 +84,7 @@ data class ImportedPlaylist(val name: String?, val paths: List<PossiblePaths>)
|
|||
|
||||
typealias PossiblePaths = List<Path>
|
||||
|
||||
class ExternalPlaylistManagerImpl(
|
||||
private class ExternalPlaylistManagerImpl(
|
||||
private val context: Context,
|
||||
private val documentPathFactory: DocumentPathFactory,
|
||||
private val m3u: M3U
|
||||
|
|
|
@ -28,7 +28,7 @@ package org.oxycblt.musikr.tag.format
|
|||
* @return A list of one or more genre names, or null if this multi-value list has no valid
|
||||
* formatting.
|
||||
*/
|
||||
fun List<String>.parseId3GenreNames() =
|
||||
internal fun List<String>.parseId3GenreNames() =
|
||||
if (size == 1) {
|
||||
first().parseId3MultiValueGenre()
|
||||
} else {
|
||||
|
|
|
@ -30,7 +30,7 @@ import org.oxycblt.musikr.util.positiveOrNull
|
|||
*
|
||||
* @see transformPositionField
|
||||
*/
|
||||
fun String.parseId3v2PositionField() =
|
||||
internal fun String.parseId3v2PositionField() =
|
||||
split('/', limit = 2).let {
|
||||
transformPositionField(it[0].toIntOrNull(), it.getOrNull(1)?.toIntOrNull())
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ fun String.parseId3v2PositionField() =
|
|||
*
|
||||
* @see transformPositionField
|
||||
*/
|
||||
fun parseXiphPositionField(pos: String?, total: String?) =
|
||||
internal fun parseXiphPositionField(pos: String?, total: String?) =
|
||||
transformPositionField(pos?.toIntOrNull(), total?.toIntOrNull())
|
||||
|
||||
/**
|
||||
|
@ -59,7 +59,7 @@ fun parseXiphPositionField(pos: String?, total: String?) =
|
|||
* - The position could not be parsed
|
||||
* - The position was zeroed AND the total value was not present/zeroed
|
||||
*/
|
||||
fun transformPositionField(pos: Int?, total: Int?) =
|
||||
internal fun transformPositionField(pos: Int?, total: Int?) =
|
||||
if (pos != null && (pos > 0 || (total?.positiveOrNull() != null))) {
|
||||
pos
|
||||
} else {
|
||||
|
|
|
@ -32,7 +32,7 @@ import org.oxycblt.musikr.tag.ReleaseType
|
|||
import org.oxycblt.musikr.tag.ReplayGainAdjustment
|
||||
import org.oxycblt.musikr.util.update
|
||||
|
||||
data class PreSong(
|
||||
internal data class PreSong(
|
||||
val musicBrainzId: UUID?,
|
||||
val name: Name.Known,
|
||||
val rawName: String,
|
||||
|
@ -70,7 +70,7 @@ data class PreSong(
|
|||
}
|
||||
}
|
||||
|
||||
data class PreAlbum(
|
||||
internal data class PreAlbum(
|
||||
val musicBrainzId: UUID?,
|
||||
val name: Name,
|
||||
val rawName: String?,
|
||||
|
@ -78,11 +78,11 @@ data class PreAlbum(
|
|||
val preArtists: List<PreArtist>
|
||||
)
|
||||
|
||||
data class PreArtist(val musicBrainzId: UUID?, val name: Name, val rawName: String?)
|
||||
internal data class PreArtist(val musicBrainzId: UUID?, val name: Name, val rawName: String?)
|
||||
|
||||
data class PreGenre(
|
||||
internal data class PreGenre(
|
||||
val name: Name,
|
||||
val rawName: String?,
|
||||
)
|
||||
|
||||
data class PrePlaylist(val name: Name.Known, val rawName: String?, val handle: PlaylistHandle)
|
||||
internal data class PrePlaylist(val name: Name.Known, val rawName: String?, val handle: PlaylistHandle)
|
||||
|
|
|
@ -30,7 +30,7 @@ import org.oxycblt.musikr.tag.parse.ParsedTags
|
|||
import org.oxycblt.musikr.tag.format.parseId3GenreNames
|
||||
import org.oxycblt.musikr.util.toUuidOrNull
|
||||
|
||||
interface TagInterpreter {
|
||||
internal interface TagInterpreter {
|
||||
fun interpret(song: RawSong, interpretation: Interpretation): PreSong
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -21,7 +21,9 @@ package org.oxycblt.musikr.tag.interpret
|
|||
import java.text.CollationKey
|
||||
|
||||
/** An individual part of a name string that can be compared intelligently. */
|
||||
data class Token(val collationKey: CollationKey, val type: Type) : Comparable<Token> {
|
||||
data class Token(internal val collationKey: CollationKey, internal val type: Type) : Comparable<Token> {
|
||||
val value: String get() = collationKey.sourceString
|
||||
|
||||
override fun compareTo(other: Token): Int {
|
||||
// Numeric tokens should always be lower than lexicographic tokens.
|
||||
val modeComp = type.compareTo(other.type)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Auxio Project
|
||||
* ExoPlayerTagFields.kt is part of Auxio.
|
||||
* TagFields.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
|
||||
|
@ -26,32 +26,32 @@ import org.oxycblt.musikr.tag.format.parseXiphPositionField
|
|||
import org.oxycblt.musikr.util.nonZeroOrNull
|
||||
|
||||
// Song
|
||||
fun Metadata.musicBrainzId() =
|
||||
internal fun Metadata.musicBrainzId() =
|
||||
(xiph["MUSICBRAINZ_RELEASETRACKID"]
|
||||
?: xiph["MUSICBRAINZ RELEASE TRACK ID"]
|
||||
?: id3v2["TXXX:MUSICBRAINZ RELEASE TRACK ID"]
|
||||
?: id3v2["TXXX:MUSICBRAINZ_RELEASETRACKID"])
|
||||
?.first()
|
||||
|
||||
fun Metadata.name() = (xiph["TITLE"] ?: id3v2["TIT2"])?.first()
|
||||
internal fun Metadata.name() = (xiph["TITLE"] ?: id3v2["TIT2"])?.first()
|
||||
|
||||
fun Metadata.sortName() = (xiph["TITLESORT"] ?: id3v2["TSOT"])?.first()
|
||||
internal fun Metadata.sortName() = (xiph["TITLESORT"] ?: id3v2["TSOT"])?.first()
|
||||
|
||||
// Track.
|
||||
fun Metadata.track() =
|
||||
internal fun Metadata.track() =
|
||||
(parseXiphPositionField(
|
||||
xiph["TRACKNUMBER"]?.first(),
|
||||
(xiph["TOTALTRACKS"] ?: xiph["TRACKTOTAL"] ?: xiph["TRACKC"])?.first())
|
||||
?: id3v2["TRCK"]?.run { first().parseId3v2PositionField() })
|
||||
|
||||
// Disc and it's subtitle name.
|
||||
fun Metadata.disc() =
|
||||
internal fun Metadata.disc() =
|
||||
(parseXiphPositionField(
|
||||
xiph["DISCNUMBER"]?.first(),
|
||||
(xiph["TOTALDISCS"] ?: xiph["DISCTOTAL"] ?: xiph["DISCC"])?.run { first() })
|
||||
?: id3v2["TPOS"]?.run { first().parseId3v2PositionField() })
|
||||
|
||||
fun Metadata.subtitle() = (xiph["DISCSUBTITLE"] ?: id3v2["TSST"])?.first()
|
||||
internal fun Metadata.subtitle() = (xiph["DISCSUBTITLE"] ?: id3v2["TSST"])?.first()
|
||||
|
||||
// Dates are somewhat complicated, as not only did their semantics change from a flat year
|
||||
// value in ID3v2.3 to a full ISO-8601 date in ID3v2.4, but there are also a variety of
|
||||
|
@ -65,7 +65,7 @@ fun Metadata.subtitle() = (xiph["DISCSUBTITLE"] ?: id3v2["TSST"])?.first()
|
|||
// TODO: Show original and normal dates side-by-side
|
||||
// TODO: Handle dates that are in "January" because the actual specific release date
|
||||
// isn't known?
|
||||
fun Metadata.date() =
|
||||
internal fun Metadata.date() =
|
||||
(xiph["ORIGINALDATE"]?.run { Date.from(first()) }
|
||||
?: xiph["DATE"]?.run { Date.from(first()) }
|
||||
?: xiph["YEAR"]?.run { Date.from(first()) }
|
||||
|
@ -83,18 +83,18 @@ fun Metadata.date() =
|
|||
?: parseId3v23Date())
|
||||
|
||||
// Album
|
||||
fun Metadata.albumMusicBrainzId() =
|
||||
internal fun Metadata.albumMusicBrainzId() =
|
||||
(xiph["MUSICBRAINZ_ALBUMID"]
|
||||
?: xiph["MUSICBRAINZ ALBUM ID"]
|
||||
?: id3v2["TXXX:MUSICBRAINZ ALBUM ID"]
|
||||
?: id3v2["TXXX:MUSICBRAINZ_ALBUMID"])
|
||||
?.first()
|
||||
|
||||
fun Metadata.albumName() = (xiph["ALBUM"] ?: id3v2["TALB"])?.first()
|
||||
internal fun Metadata.albumName() = (xiph["ALBUM"] ?: id3v2["TALB"])?.first()
|
||||
|
||||
fun Metadata.albumSortName() = (xiph["ALBUMSORT"] ?: id3v2["TSOA"])?.first()
|
||||
internal fun Metadata.albumSortName() = (xiph["ALBUMSORT"] ?: id3v2["TSOA"])?.first()
|
||||
|
||||
fun Metadata.releaseTypes() =
|
||||
internal fun Metadata.releaseTypes() =
|
||||
(xiph["RELEASETYPE"]
|
||||
?: xiph["MUSICBRAINZ ALBUM TYPE"]
|
||||
?: id3v2["TXXX:MUSICBRAINZ ALBUM TYPE"]
|
||||
|
@ -104,20 +104,20 @@ fun Metadata.releaseTypes() =
|
|||
id3v2["GRP1"])
|
||||
|
||||
// Artist
|
||||
fun Metadata.artistMusicBrainzIds() =
|
||||
internal fun Metadata.artistMusicBrainzIds() =
|
||||
(xiph["MUSICBRAINZ_ARTISTID"]
|
||||
?: xiph["MUSICBRAINZ ARTIST ID"]
|
||||
?: id3v2["TXXX:MUSICBRAINZ ARTIST ID"]
|
||||
?: id3v2["TXXX:MUSICBRAINZ_ARTISTID"])
|
||||
|
||||
fun Metadata.artistNames() =
|
||||
internal fun Metadata.artistNames() =
|
||||
(xiph["ARTISTS"]
|
||||
?: xiph["ARTIST"]
|
||||
?: id3v2["TXXX:ARTISTS"]
|
||||
?: id3v2["TPE1"]
|
||||
?: id3v2["TXXX:ARTIST"])
|
||||
|
||||
fun Metadata.artistSortNames() =
|
||||
internal fun Metadata.artistSortNames() =
|
||||
(xiph["ARTISTSSORT"]
|
||||
?: xiph["ARTISTS_SORT"]
|
||||
?: xiph["ARTISTS SORT"]
|
||||
|
@ -130,13 +130,13 @@ fun Metadata.artistSortNames() =
|
|||
?: id3v2["TXXX:ARTISTSORT"]
|
||||
?: id3v2["TXXX:ARTIST SORT"])
|
||||
|
||||
fun Metadata.albumArtistMusicBrainzIds() =
|
||||
internal fun Metadata.albumArtistMusicBrainzIds() =
|
||||
(xiph["MUSICBRAINZ_ALBUMARTISTID"]
|
||||
?: xiph["MUSICBRAINZ ALBUM ARTIST ID"]
|
||||
?: id3v2["TXXX:MUSICBRAINZ ALBUM ARTIST ID"]
|
||||
?: id3v2["TXXX:MUSICBRAINZ_ALBUMARTISTID"])
|
||||
|
||||
fun Metadata.albumArtistNames() =
|
||||
internal fun Metadata.albumArtistNames() =
|
||||
(xiph["ALBUMARTISTS"]
|
||||
?: xiph["ALBUM_ARTISTS"]
|
||||
?: xiph["ALBUM ARTISTS"]
|
||||
|
@ -149,7 +149,7 @@ fun Metadata.albumArtistNames() =
|
|||
?: id3v2["TXXX:ALBUMARTIST"]
|
||||
?: id3v2["TXXX:ALBUM ARTIST"])
|
||||
|
||||
fun Metadata.albumArtistSortNames() =
|
||||
internal fun Metadata.albumArtistSortNames() =
|
||||
(xiph["ALBUMARTISTSSORT"]
|
||||
?: xiph["ALBUMARTISTS_SORT"]
|
||||
?: xiph["ALBUMARTISTS SORT"]
|
||||
|
@ -164,10 +164,10 @@ fun Metadata.albumArtistSortNames() =
|
|||
?: id3v2["TXXX:ALBUM ARTIST SORT"])
|
||||
|
||||
// Genre
|
||||
fun Metadata.genreNames() = xiph["GENRE"] ?: id3v2["TCON"]
|
||||
internal fun Metadata.genreNames() = xiph["GENRE"] ?: id3v2["TCON"]
|
||||
|
||||
// Compilation Flag
|
||||
fun Metadata.isCompilation() =
|
||||
internal fun Metadata.isCompilation() =
|
||||
(xiph["COMPILATION"]
|
||||
?: xiph["ITUNESCOMPILATION"]
|
||||
?: id3v2["TCMP"] // This is a non-standard itunes extension
|
||||
|
@ -179,16 +179,36 @@ fun Metadata.isCompilation() =
|
|||
}
|
||||
|
||||
// ReplayGain information
|
||||
fun Metadata.replayGainTrackAdjustment() =
|
||||
internal fun Metadata.replayGainTrackAdjustment() =
|
||||
(xiph["R128_TRACK_GAIN"]?.parseR128Adjustment()
|
||||
?: xiph["REPLAYGAIN_TRACK_GAIN"]?.parseReplayGainAdjustment()
|
||||
?: id3v2["TXXX:REPLAYGAIN_TRACK_GAIN"]?.parseReplayGainAdjustment())
|
||||
|
||||
fun Metadata.replayGainAlbumAdjustment() =
|
||||
internal fun Metadata.replayGainAlbumAdjustment() =
|
||||
(xiph["R128_ALBUM_GAIN"]?.parseR128Adjustment()
|
||||
?: xiph["REPLAYGAIN_ALBUM_GAIN"]?.parseReplayGainAdjustment()
|
||||
?: id3v2["TXXX:REPLAYGAIN_ALBUM_GAIN"]?.parseReplayGainAdjustment())
|
||||
|
||||
private fun List<String>.parseR128Adjustment() =
|
||||
first().replace(REPLAYGAIN_ADJUSTMENT_FILTER_REGEX, "").toFloatOrNull()?.nonZeroOrNull()?.run {
|
||||
// Convert to fixed-point and adjust to LUFS 18 to match the ReplayGain scale
|
||||
this / 256f + 5
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a ReplayGain adjustment into a float value.
|
||||
*
|
||||
* @return A parsed adjustment float, or null if the adjustment had invalid formatting.
|
||||
*/
|
||||
private fun List<String>.parseReplayGainAdjustment() =
|
||||
first().replace(REPLAYGAIN_ADJUSTMENT_FILTER_REGEX, "").toFloatOrNull()?.nonZeroOrNull()
|
||||
|
||||
/**
|
||||
* Matches non-float information from ReplayGain adjustments. Derived from vanilla music:
|
||||
* https://github.com/vanilla-music/vanilla
|
||||
*/
|
||||
private val REPLAYGAIN_ADJUSTMENT_FILTER_REGEX by lazy { Regex("[^\\d.-]") }
|
||||
|
||||
private fun Metadata.parseId3v23Date(): Date? {
|
||||
// Assume that TDAT/TIME can refer to TYER or TORY depending on if TORY
|
||||
// is present.
|
||||
|
@ -222,23 +242,3 @@ private fun Metadata.parseId3v23Date(): Date? {
|
|||
return Date.from(year)
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<String>.parseR128Adjustment() =
|
||||
first().replace(REPLAYGAIN_ADJUSTMENT_FILTER_REGEX, "").toFloatOrNull()?.nonZeroOrNull()?.run {
|
||||
// Convert to fixed-point and adjust to LUFS 18 to match the ReplayGain scale
|
||||
this / 256f + 5
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a ReplayGain adjustment into a float value.
|
||||
*
|
||||
* @return A parsed adjustment float, or null if the adjustment had invalid formatting.
|
||||
*/
|
||||
private fun List<String>.parseReplayGainAdjustment() =
|
||||
first().replace(REPLAYGAIN_ADJUSTMENT_FILTER_REGEX, "").toFloatOrNull()?.nonZeroOrNull()
|
||||
|
||||
/**
|
||||
* Matches non-float information from ReplayGain adjustments. Derived from vanilla music:
|
||||
* https://github.com/vanilla-music/vanilla
|
||||
*/
|
||||
val REPLAYGAIN_ADJUSTMENT_FILTER_REGEX by lazy { Regex("[^\\d.-]") }
|
|
@ -18,10 +18,10 @@
|
|||
|
||||
package org.oxycblt.musikr.tag.parse
|
||||
|
||||
import org.oxycblt.musikr.fs.query.DeviceFile
|
||||
import org.oxycblt.musikr.fs.DeviceFile
|
||||
import org.oxycblt.musikr.metadata.Metadata
|
||||
|
||||
interface TagParser {
|
||||
internal interface TagParser {
|
||||
fun parse(file: DeviceFile, metadata: Metadata): ParsedTags
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -29,7 +29,7 @@ import org.oxycblt.musikr.tag.Date
|
|||
* otherwise, it aliases to the unchecked dereference operator (!!). This can be used as a minor
|
||||
* optimization in certain cases.
|
||||
*/
|
||||
fun <T> unlikelyToBeNull(value: T?) =
|
||||
internal fun <T> unlikelyToBeNull(value: T?) =
|
||||
if (BuildConfig.DEBUG) {
|
||||
requireNotNull(value)
|
||||
} else {
|
||||
|
@ -41,14 +41,14 @@ fun <T> unlikelyToBeNull(value: T?) =
|
|||
*
|
||||
* @return The given number if it's non-zero, null otherwise.
|
||||
*/
|
||||
fun Int.positiveOrNull() = if (this > 0) this else null
|
||||
internal 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
|
||||
internal fun Float.nonZeroOrNull() = if (this != 0f) this else null
|
||||
|
||||
/**
|
||||
* Aliases a check to ensure a given value is in a specified range.
|
||||
|
@ -56,7 +56,7 @@ fun Float.nonZeroOrNull() = if (this != 0f) this else null
|
|||
* @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
|
||||
internal fun Int.inRangeOrNull(range: IntRange) = if (range.contains(this)) this else null
|
||||
|
||||
/**
|
||||
* Convert a [String] to a [UUID].
|
||||
|
@ -64,7 +64,7 @@ fun Int.inRangeOrNull(range: IntRange) = if (range.contains(this)) this else nul
|
|||
* @return A [UUID] converted from the [String] value, or null if the value was not valid.
|
||||
* @see UUID.fromString
|
||||
*/
|
||||
fun String.toUuidOrNull(): UUID? =
|
||||
internal fun String.toUuidOrNull(): UUID? =
|
||||
try {
|
||||
UUID.fromString(this)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
|
@ -76,7 +76,7 @@ fun String.toUuidOrNull(): UUID? =
|
|||
*
|
||||
* @param string The [String] to hash. If null, it will not be hashed.
|
||||
*/
|
||||
fun MessageDigest.update(string: String?) {
|
||||
internal fun MessageDigest.update(string: String?) {
|
||||
if (string != null) {
|
||||
update(string.lowercase().toByteArray())
|
||||
} else {
|
||||
|
@ -89,7 +89,7 @@ fun MessageDigest.update(string: String?) {
|
|||
*
|
||||
* @param date The [Date] to hash. If null, nothing will be done.
|
||||
*/
|
||||
fun MessageDigest.update(date: Date?) {
|
||||
internal fun MessageDigest.update(date: Date?) {
|
||||
if (date != null) {
|
||||
update(date.toString().toByteArray())
|
||||
} else {
|
||||
|
@ -102,7 +102,7 @@ fun MessageDigest.update(date: Date?) {
|
|||
*
|
||||
* @param strings The [String]s to hash. If a [String] is null, it will not be hashed.
|
||||
*/
|
||||
fun MessageDigest.update(strings: List<String?>) {
|
||||
internal fun MessageDigest.update(strings: List<String?>) {
|
||||
strings.forEach(::update)
|
||||
}
|
||||
|
||||
|
@ -111,7 +111,7 @@ fun MessageDigest.update(strings: List<String?>) {
|
|||
*
|
||||
* @param n The [Int] to write. If null, nothing will be done.
|
||||
*/
|
||||
fun MessageDigest.update(n: Int?) {
|
||||
internal 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 {
|
||||
|
@ -126,7 +126,7 @@ fun MessageDigest.update(n: Int?) {
|
|||
* @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 {
|
||||
internal fun lazyReflectedMethod(clazz: KClass<*>, method: String, vararg params: KClass<*>) = lazy {
|
||||
clazz.java.getDeclaredMethod(method, *params.map { it.java }.toTypedArray()).also {
|
||||
it.isAccessible = true
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ package org.oxycblt.musikr.util
|
|||
* @param selector A block that determines if the string should be split at a given character.
|
||||
* @return One or more [String]s split by the selector.
|
||||
*/
|
||||
inline fun String.splitEscaped(selector: (Char) -> Boolean): List<String> {
|
||||
internal inline fun String.splitEscaped(selector: (Char) -> Boolean): List<String> {
|
||||
val split = mutableListOf<String>()
|
||||
var currentString = ""
|
||||
var i = 0
|
||||
|
@ -53,11 +53,11 @@ inline fun String.splitEscaped(selector: (Char) -> Boolean): List<String> {
|
|||
* @return A string with trailing whitespace remove,d or null if the [String] was all whitespace or
|
||||
* empty.
|
||||
*/
|
||||
fun String.correctWhitespace() = trim().ifBlank { null }
|
||||
internal fun String.correctWhitespace() = trim().ifBlank { null }
|
||||
|
||||
/**
|
||||
* Fix trailing whitespace or blank contents within a list of [String]s.
|
||||
*
|
||||
* @return A list of non-blank strings with trailing whitespace removed.
|
||||
*/
|
||||
fun List<String>.correctWhitespace() = mapNotNull { it.correctWhitespace() }
|
||||
internal fun List<String>.correctWhitespace() = mapNotNull { it.correctWhitespace() }
|
||||
|
|
Loading…
Reference in a new issue