music: reorganize metadata/tag/model structure

This commit is contained in:
Alexander Capehart 2024-12-02 14:22:38 -07:00
parent 59652b2f9b
commit 7582c8c9cf
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
56 changed files with 287 additions and 383 deletions

View file

@ -32,8 +32,8 @@ import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.musikr.model.Disc
import org.oxycblt.auxio.musikr.model.ReleaseType
import org.oxycblt.auxio.musikr.tag.Disc
import org.oxycblt.auxio.musikr.tag.ReleaseType
import timber.log.Timber as L
interface DetailGenerator {

View file

@ -34,7 +34,7 @@ import org.oxycblt.auxio.detail.list.SongPropertyAdapter
import org.oxycblt.auxio.list.adapter.UpdateInstructions
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.musikr.model.Name
import org.oxycblt.auxio.musikr.tag.Name
import org.oxycblt.auxio.musikr.metadata.AudioProperties
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.formatDurationMs

View file

@ -37,8 +37,8 @@ import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.adapter.SimpleDiffCallback
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.musikr.model.Disc
import org.oxycblt.auxio.musikr.model.resolveNumber
import org.oxycblt.auxio.musikr.tag.Disc
import org.oxycblt.auxio.musikr.tag.resolveNumber
import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getAttrColorCompat

View file

@ -29,10 +29,10 @@ import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import org.oxycblt.auxio.musikr.cover.Cover
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.musikr.model.Date
import org.oxycblt.auxio.musikr.model.Disc
import org.oxycblt.auxio.musikr.model.Name
import org.oxycblt.auxio.musikr.model.ReleaseType
import org.oxycblt.auxio.musikr.tag.Date
import org.oxycblt.auxio.musikr.tag.Disc
import org.oxycblt.auxio.musikr.tag.Name
import org.oxycblt.auxio.musikr.tag.ReleaseType
import org.oxycblt.auxio.musikr.fs.MimeType
import org.oxycblt.auxio.musikr.fs.Path
import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment

View file

@ -25,12 +25,12 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
import org.oxycblt.auxio.music.MusicRepository.IndexingWorker
import org.oxycblt.auxio.musikr.model.Name
import org.oxycblt.auxio.musikr.interpret.Separators
import org.oxycblt.auxio.musikr.tag.Name
import org.oxycblt.auxio.musikr.tag.interpret.Separators
import org.oxycblt.auxio.musikr.Indexer
import org.oxycblt.auxio.musikr.IndexingProgress
import org.oxycblt.auxio.musikr.interpret.Interpretation
import org.oxycblt.auxio.musikr.model.MutableLibrary
import org.oxycblt.auxio.musikr.tag.Interpretation
import org.oxycblt.auxio.musikr.model.impl.MutableLibrary
import timber.log.Timber as L
/**

View file

@ -29,7 +29,7 @@ import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogSeparatorsBinding
import org.oxycblt.auxio.music.MusicSettings
import org.oxycblt.auxio.musikr.interpret.Separators
import org.oxycblt.auxio.musikr.tag.interpret.Separators
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
import timber.log.Timber as L

View file

@ -35,7 +35,7 @@ import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.musikr.model.resolveNumber
import org.oxycblt.auxio.musikr.tag.resolveNumber
import org.oxycblt.auxio.search.SearchEngine
class MusicBrowser

View file

@ -28,9 +28,9 @@ import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import org.oxycblt.auxio.musikr.explore.Explorer
import org.oxycblt.auxio.musikr.interpret.Interpretation
import org.oxycblt.auxio.musikr.interpret.Modeler
import org.oxycblt.auxio.musikr.model.MutableLibrary
import org.oxycblt.auxio.musikr.tag.Interpretation
import org.oxycblt.auxio.musikr.model.Modeler
import org.oxycblt.auxio.musikr.model.impl.MutableLibrary
interface Indexer {
suspend fun run(

View file

@ -24,15 +24,10 @@ import org.oxycblt.auxio.music.Song
sealed interface Cover {
val key: String
class Single(song: Song) : Cover {
override val key = "${song.uid}@${song.lastModified}"
val uid = song.uid
val uri = song.uri
val lastModified = song.lastModified
}
data class Single(override val key: String) : Cover
class Multi(val all: List<Single>) : Cover {
override val key = "multi@${all.map { it.key }.hashCode()}"
override val key = "multi@${all.hashCode()}"
}
companion object {
@ -40,15 +35,16 @@ sealed interface Cover {
fun nil() = Multi(listOf())
fun single(song: Song) = Single(song)
fun single(key: String) = Single(key)
fun multi(songs: Collection<Song>) = order(songs).run { Multi(this) }
private fun order(songs: Collection<Song>) =
FALLBACK_SORT.songs(songs)
.groupBy { it.album }
.map { it.cover }
.groupBy { it.key }
.entries
.sortedByDescending { it.value.size }
.map { it.value.first().cover }
.map { it.value.first() }
}
}

View file

@ -1,23 +0,0 @@
/*
* Copyright (c) 2024 Auxio Project
* Interpretation.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.musikr.interpret
import org.oxycblt.auxio.musikr.model.Name
data class Interpretation(val nameFactory: Name.Known.Factory, val separators: Separators)

View file

@ -1,11 +1,12 @@
package org.oxycblt.auxio.musikr.metadata
import android.media.MediaMetadataRetriever
import androidx.media3.common.Format
import androidx.media3.common.Metadata
import org.oxycblt.auxio.musikr.fs.DeviceFile
data class AudioMetadata(
val file: DeviceFile,
val exoPlayerMetadata: Metadata,
val exoPlayerFormat: Format,
val mediaMetadataRetriever: MediaMetadataRetriever
)

View file

@ -1,8 +1,53 @@
package org.oxycblt.auxio.musikr.metadata
import android.content.Context
import android.media.MediaMetadataRetriever
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.MetadataRetriever
import androidx.media3.exoplayer.source.MediaSource
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.withContext
import org.oxycblt.auxio.musikr.fs.DeviceFile
import javax.inject.Inject
interface MetadataExtractor {
fun extract(files: Flow<DeviceFile>): Flow<AudioMetadata>
}
class MetadataExtractorImpl @Inject constructor(
@ApplicationContext private val context: Context,
private val mediaSourceFactory: MediaSource.Factory
) : MetadataExtractor {
override fun extract(files: Flow<DeviceFile>) = files.mapNotNull {
val exoPlayerMetadataFuture = MetadataRetriever.retrieveMetadata(
mediaSourceFactory,
MediaItem.fromUri(it.uri)
)
val mediaMetadataRetriever = MediaMetadataRetriever().apply {
withContext(Dispatchers.IO) {
setDataSource(context, it.uri)
}
}
val trackGroupArray = exoPlayerMetadataFuture.await()
if (trackGroupArray.isEmpty) {
return@mapNotNull null
}
val trackGroup = trackGroupArray.get(0)
if (trackGroup.length == 0) {
return@mapNotNull null
}
val format = trackGroup.getFormat(0)
AudioMetadata(
it,
format,
mediaMetadataRetriever
)
}
}

View file

@ -28,4 +28,7 @@ import dagger.hilt.components.SingletonComponent
interface MetadataModule {
@Binds
fun audioPropertiesFactory(interpreter: AudioPropertiesFactoryImpl): AudioProperties.Factory
@Binds
fun metadataExtractor(extractor: MetadataExtractorImpl): MetadataExtractor
}

View file

@ -1,25 +1,5 @@
/*
* Copyright (c) 2023 Auxio Project
* TagExtractor.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.musikr.tag.extractor
package org.oxycblt.auxio.musikr.metadata
import android.content.Context
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Handler
import android.os.HandlerThread
@ -38,101 +18,9 @@ import androidx.media3.exoplayer.upstream.Allocator
import androidx.media3.exoplayer.upstream.DefaultAllocator
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import dagger.hilt.android.qualifiers.ApplicationContext
import timber.log.Timber
import java.util.concurrent.Future
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.guava.asDeferred
import org.oxycblt.auxio.musikr.tag.AudioFile
import org.oxycblt.auxio.musikr.fs.DeviceFile
import timber.log.Timber as L
interface TagExtractor {
fun extract(deviceFiles: Flow<DeviceFile>): Flow<AudioFile>
}
class TagExtractorImpl
@Inject
constructor(
@ApplicationContext private val context: Context,
private val mediaSourceFactory: MediaSource.Factory,
) : TagExtractor {
override fun extract(deviceFiles: Flow<DeviceFile>) = flow {
val retriever = ChunkedMetadataRetriever(mediaSourceFactory)
deviceFiles.collect { deviceFile ->
// val exoPlayerMetadataFuture =
// MetadataRetriever.retrieveMetadata(
// mediaSourceFactory, MediaItem.fromUri(deviceFile.uri))
val exoPlayerMetadataFuture = retriever.retrieve(deviceFile.uri)
val mediaMetadataRetriever = MediaMetadataRetriever()
mediaMetadataRetriever.setDataSource(context, deviceFile.uri)
val exoPlayerMetadata = exoPlayerMetadataFuture.asDeferred().await()
val result = extractTags(deviceFile, exoPlayerMetadata, mediaMetadataRetriever)
mediaMetadataRetriever.close()
emit(result)
}
retriever.release()
}
private fun extractTags(
input: DeviceFile,
output: TrackGroupArray,
retriever: MediaMetadataRetriever
): AudioFile {
if (output.isEmpty) return defaultAudioFile(input, retriever)
val track = output.get(0)
if (track.length == 0) return defaultAudioFile(input, retriever)
val format = track.getFormat(0)
val metadata = format.metadata ?: return defaultAudioFile(input, retriever)
val textTags = TextTags(metadata)
return AudioFile(
deviceFile = input,
durationMs =
need(
retriever
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
?.toLong(),
"duration"),
replayGainTrackAdjustment = textTags.replayGainTrackAdjustment(),
replayGainAlbumAdjustment = textTags.replayGainAlbumAdjustment(),
musicBrainzId = textTags.musicBrainzId(),
name = need(textTags.name() ?: input.path.name, "name"),
sortName = textTags.sortName(),
track = textTags.track(),
disc = textTags.disc(),
subtitle = textTags.subtitle(),
date = textTags.date(),
albumMusicBrainzId = textTags.albumMusicBrainzId(),
albumName = textTags.albumName(),
albumSortName = textTags.albumSortName(),
releaseTypes = textTags.releaseTypes() ?: listOf(),
artistMusicBrainzIds = textTags.artistMusicBrainzIds() ?: listOf(),
artistNames = textTags.artistNames() ?: listOf(),
artistSortNames = textTags.artistSortNames() ?: listOf(),
albumArtistMusicBrainzIds = textTags.albumArtistMusicBrainzIds() ?: listOf(),
albumArtistNames = textTags.albumArtistNames() ?: listOf(),
albumArtistSortNames = textTags.albumArtistSortNames() ?: listOf(),
genreNames = textTags.genreNames() ?: listOf())
}
private fun defaultAudioFile(
deviceFile: DeviceFile,
metadataRetriever: MediaMetadataRetriever
) =
AudioFile(
deviceFile,
name = need(deviceFile.path.name, "name"),
durationMs =
need(
metadataRetriever
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
?.toLong(),
"duration"),
)
private fun <T> need(a: T, called: String) =
requireNotNull(a) { "Invalid tag, missing $called" }
}
private const val MESSAGE_PREPARE = 0
private const val MESSAGE_CONTINUE_LOADING = 1
@ -140,19 +28,24 @@ private const val MESSAGE_CHECK_FAILURE = 2
private const val MESSAGE_RELEASE = 3
private const val CHECK_INTERVAL_MS = 100
/**
* Patched version of Media3's MetadataRetriever that extracts metadata from several tracks at once
* on one thread. This is generally more efficient than stacking several threads at once.
*
* @author Media3 Team, Alexander Capehart (OxygenCobalt)
*/
private class ChunkedMetadataRetriever(private val mediaSourceFactory: MediaSource.Factory) :
Handler.Callback {
// TODO: Rewrite and re-integrate
interface MetadataRetrieverExt {
fun retrieveMetadata(mediaItem: MediaItem): Future<TrackGroupArray>
fun retrieve()
interface Factory {
fun create(): MetadataRetrieverExt
}
}
class ReusableMetadataRetrieverImpl @Inject constructor(private val mediaSourceFactory: MediaSource.Factory) :
MetadataRetrieverExt, Handler.Callback {
private val mediaSourceThread = HandlerThread("Auxio:ChunkedMetadataRetriever:${hashCode()}")
private val mediaSourceHandler: HandlerWrapper
private var job: MetadataJob? = null
private data class JobParams(val uri: Uri, val future: SettableFuture<TrackGroupArray>)
private data class JobParams(val mediaItem: MediaItem, val future: SettableFuture<TrackGroupArray>)
private class JobData(
val params: JobParams,
@ -167,15 +60,15 @@ private class ChunkedMetadataRetriever(private val mediaSourceFactory: MediaSour
mediaSourceHandler = Clock.DEFAULT.createHandler(mediaSourceThread.looper, this)
}
fun retrieve(uri: Uri): ListenableFuture<TrackGroupArray> {
override fun retrieveMetadata(mediaItem: MediaItem): Future<TrackGroupArray> {
val job = job
check(job == null || job.data.params.future.isDone) { "Already working on something: $job" }
val future = SettableFuture.create<TrackGroupArray>()
mediaSourceHandler.obtainMessage(MESSAGE_PREPARE, JobParams(uri, future)).sendToTarget()
mediaSourceHandler.obtainMessage(MESSAGE_PREPARE, JobParams(mediaItem, future)).sendToTarget()
return future
}
fun release() {
override fun retrieve() {
mediaSourceHandler.removeCallbacksAndMessages(null)
mediaSourceThread.quit()
}
@ -186,7 +79,7 @@ private class ChunkedMetadataRetriever(private val mediaSourceFactory: MediaSour
val params = msg.obj as JobParams
val mediaSource =
mediaSourceFactory.createMediaSource(MediaItem.fromUri(params.uri))
mediaSourceFactory.createMediaSource(params.mediaItem)
val data = JobData(params, mediaSource, null)
val mediaSourceCaller = MediaSourceCaller(data)
mediaSource.prepareSource(
@ -217,8 +110,8 @@ private class ChunkedMetadataRetriever(private val mediaSourceFactory: MediaSour
mediaPeriod.maybeThrowPrepareError()
}
} catch (e: Exception) {
L.e("Failed to extract MediaSource")
L.e(e.stackTraceToString())
Timber.e("Failed to extract MediaSource")
Timber.e(e.stackTraceToString())
mediaPeriod?.let(mediaSource::releasePeriod)
mediaSource.releaseSource(mediaSourceCaller)
job.data.params.future.setException(e)

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2024 Auxio Project
* PrepareModule.kt is part of Auxio.
* Copyright (c) 2023 Auxio Project
* ModelModule.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
@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.interpret.prepare
package org.oxycblt.auxio.musikr.model
import dagger.Binds
import dagger.Module
@ -25,6 +25,6 @@ import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
interface PrepareModule {
@Binds fun prepare(factory: PreparerImpl): Preparer
interface InterpretModule {
@Binds fun interpreter(interpreter: ModelerImpl): Modeler
}

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2024 Auxio Project
* Interpreter.kt is part of Auxio.
* Modeler.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
@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.interpret
package org.oxycblt.auxio.musikr.model
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
@ -30,19 +30,20 @@ import kotlinx.coroutines.flow.toList
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.musikr.tag.AudioFile
import org.oxycblt.auxio.musikr.playlist.PlaylistFile
import org.oxycblt.auxio.musikr.interpret.link.AlbumLinker
import org.oxycblt.auxio.musikr.interpret.link.ArtistLinker
import org.oxycblt.auxio.musikr.interpret.link.GenreLinker
import org.oxycblt.auxio.musikr.interpret.link.Linked
import org.oxycblt.auxio.musikr.interpret.link.LinkedSong
import org.oxycblt.auxio.musikr.model.AlbumImpl
import org.oxycblt.auxio.musikr.model.ArtistImpl
import org.oxycblt.auxio.musikr.model.GenreImpl
import org.oxycblt.auxio.musikr.model.LibraryImpl
import org.oxycblt.auxio.musikr.model.MutableLibrary
import org.oxycblt.auxio.musikr.model.SongImpl
import org.oxycblt.auxio.musikr.interpret.prepare.PreSong
import org.oxycblt.auxio.musikr.interpret.prepare.Preparer
import org.oxycblt.auxio.musikr.model.graph.AlbumLinker
import org.oxycblt.auxio.musikr.model.graph.ArtistLinker
import org.oxycblt.auxio.musikr.model.graph.GenreLinker
import org.oxycblt.auxio.musikr.model.graph.Linked
import org.oxycblt.auxio.musikr.model.graph.LinkedSong
import org.oxycblt.auxio.musikr.model.impl.AlbumImpl
import org.oxycblt.auxio.musikr.model.impl.ArtistImpl
import org.oxycblt.auxio.musikr.model.impl.GenreImpl
import org.oxycblt.auxio.musikr.model.impl.LibraryImpl
import org.oxycblt.auxio.musikr.model.impl.MutableLibrary
import org.oxycblt.auxio.musikr.model.impl.SongImpl
import org.oxycblt.auxio.musikr.tag.Interpretation
import org.oxycblt.auxio.musikr.tag.interpret.PreSong
import org.oxycblt.auxio.musikr.tag.interpret.TagInterpreter
import timber.log.Timber as L
interface Modeler {
@ -53,14 +54,14 @@ interface Modeler {
): MutableLibrary
}
class ModelerImpl @Inject constructor(private val preparer: Preparer) : Modeler {
class ModelerImpl @Inject constructor(private val tagInterpreter: TagInterpreter) : Modeler {
override suspend fun model(
audioFiles: Flow<AudioFile>,
playlistFiles: Flow<PlaylistFile>,
interpretation: Interpretation
): MutableLibrary {
val preSongs =
preparer
tagInterpreter
.interpret(audioFiles, interpretation)
.flowOn(Dispatchers.Main)
.buffer(Channel.UNLIMITED)

View file

@ -16,13 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.interpret.link
package org.oxycblt.auxio.musikr.model.graph
import java.util.UUID
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import org.oxycblt.auxio.musikr.model.AlbumImpl
import org.oxycblt.auxio.musikr.model.SongImpl
import org.oxycblt.auxio.musikr.model.impl.AlbumImpl
import org.oxycblt.auxio.musikr.model.impl.SongImpl
class AlbumLinker {
private val tree = mutableMapOf<String?, MutableMap<UUID?, AlbumLink>>()

View file

@ -16,17 +16,17 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.interpret.link
package org.oxycblt.auxio.musikr.model.graph
import java.util.UUID
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.musikr.model.AlbumImpl
import org.oxycblt.auxio.musikr.model.ArtistImpl
import org.oxycblt.auxio.musikr.model.SongImpl
import org.oxycblt.auxio.musikr.interpret.prepare.PreAlbum
import org.oxycblt.auxio.musikr.interpret.prepare.PreArtist
import org.oxycblt.auxio.musikr.model.impl.AlbumImpl
import org.oxycblt.auxio.musikr.model.impl.ArtistImpl
import org.oxycblt.auxio.musikr.model.impl.SongImpl
import org.oxycblt.auxio.musikr.tag.interpret.PreAlbum
import org.oxycblt.auxio.musikr.tag.interpret.PreArtist
class ArtistLinker {
private val tree = mutableMapOf<String?, MutableMap<UUID?, ArtistLink>>()

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.interpret.link
package org.oxycblt.auxio.musikr.model.graph
class Contribution<T> {
private val map = mutableMapOf<T, Int>()

View file

@ -16,14 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.interpret.link
package org.oxycblt.auxio.musikr.model.graph
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import org.oxycblt.auxio.musikr.model.GenreImpl
import org.oxycblt.auxio.musikr.model.SongImpl
import org.oxycblt.auxio.musikr.interpret.prepare.PreGenre
import org.oxycblt.auxio.musikr.interpret.prepare.PreSong
import org.oxycblt.auxio.musikr.model.impl.GenreImpl
import org.oxycblt.auxio.musikr.model.impl.SongImpl
import org.oxycblt.auxio.musikr.tag.interpret.PreGenre
import org.oxycblt.auxio.musikr.tag.interpret.PreSong
class GenreLinker {
private val tree = mutableMapOf<String?, GenreLink>()

View file

@ -16,16 +16,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.interpret.link
package org.oxycblt.auxio.musikr.model.graph
import org.oxycblt.auxio.musikr.model.AlbumImpl
import org.oxycblt.auxio.musikr.model.ArtistImpl
import org.oxycblt.auxio.musikr.model.GenreImpl
import org.oxycblt.auxio.musikr.model.PlaylistImpl
import org.oxycblt.auxio.musikr.model.SongImpl
import org.oxycblt.auxio.musikr.interpret.prepare.PreAlbum
import org.oxycblt.auxio.musikr.interpret.prepare.PrePlaylist
import org.oxycblt.auxio.musikr.interpret.prepare.PreSong
import org.oxycblt.auxio.musikr.model.impl.AlbumImpl
import org.oxycblt.auxio.musikr.model.impl.ArtistImpl
import org.oxycblt.auxio.musikr.model.impl.GenreImpl
import org.oxycblt.auxio.musikr.model.impl.PlaylistImpl
import org.oxycblt.auxio.musikr.model.impl.SongImpl
import org.oxycblt.auxio.musikr.tag.interpret.PreAlbum
import org.oxycblt.auxio.musikr.tag.interpret.PrePlaylist
import org.oxycblt.auxio.musikr.tag.interpret.PreSong
interface LinkedSong {
val preSong: PreSong

View file

@ -16,12 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.interpret.link
package org.oxycblt.auxio.musikr.model.graph
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import org.oxycblt.auxio.musikr.playlist.PlaylistFile
import org.oxycblt.auxio.musikr.model.PlaylistImpl
import org.oxycblt.auxio.musikr.model.impl.PlaylistImpl
class PlaylistLinker {
fun register(

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.model
package org.oxycblt.auxio.musikr.model.impl
import kotlin.math.min
import org.oxycblt.auxio.musikr.cover.Cover
@ -27,10 +27,11 @@ import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.musikr.interpret.link.LinkedAlbum
import org.oxycblt.auxio.musikr.interpret.link.LinkedSong
import org.oxycblt.auxio.musikr.interpret.prepare.PreArtist
import org.oxycblt.auxio.musikr.interpret.prepare.PreGenre
import org.oxycblt.auxio.musikr.model.graph.LinkedAlbum
import org.oxycblt.auxio.musikr.model.graph.LinkedSong
import org.oxycblt.auxio.musikr.tag.Date
import org.oxycblt.auxio.musikr.tag.interpret.PreArtist
import org.oxycblt.auxio.musikr.tag.interpret.PreGenre
import org.oxycblt.auxio.util.update
/**

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.model
package org.oxycblt.auxio.musikr.model.impl
import org.oxycblt.auxio.music.Library
import org.oxycblt.auxio.music.Music

View file

@ -16,11 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.model
package org.oxycblt.auxio.musikr.model.impl
import org.oxycblt.auxio.musikr.cover.Cover
import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.musikr.interpret.link.LinkedPlaylist
import org.oxycblt.auxio.musikr.model.graph.LinkedPlaylist
import org.oxycblt.auxio.musikr.tag.Name
class PlaylistImpl(linkedPlaylist: LinkedPlaylist) : Playlist {
private val prePlaylist = linkedPlaylist.prePlaylist

View file

@ -28,7 +28,7 @@ import java.io.OutputStream
import javax.inject.Inject
import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.musikr.tag.extractor.correctWhitespace
import org.oxycblt.auxio.musikr.tag.util.correctWhitespace
import org.oxycblt.auxio.musikr.fs.Components
import org.oxycblt.auxio.musikr.fs.Path
import org.oxycblt.auxio.musikr.fs.Volume

View file

@ -1,6 +1,5 @@
package org.oxycblt.auxio.musikr.tag
import org.oxycblt.auxio.musikr.model.Date
import org.oxycblt.auxio.musikr.fs.DeviceFile
data class AudioFile(

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.model
package org.oxycblt.auxio.musikr.tag
import android.content.Context
import java.text.ParseException

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.model
package org.oxycblt.auxio.musikr.tag
import android.content.Context
import org.oxycblt.auxio.R

View file

@ -0,0 +1,5 @@
package org.oxycblt.auxio.musikr.tag
import org.oxycblt.auxio.musikr.tag.interpret.Separators
data class Interpretation(val nameFactory: Name.Known.Factory, val separators: Separators)

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.model
package org.oxycblt.auxio.musikr.tag
import android.content.Context
import androidx.annotation.StringRes

View file

@ -16,10 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.model
package org.oxycblt.auxio.musikr.tag
import org.oxycblt.auxio.R
import org.oxycblt.auxio.musikr.model.ReleaseType.Album
import org.oxycblt.auxio.musikr.tag.ReleaseType.Album
/**
* The type of release an [Album] is considered. This includes EPs, Singles, Compilations, etc.

View file

@ -49,5 +49,5 @@ class TagCacheImpl @Inject constructor(private val tagDao: TagDao) : TagCache {
}
override fun write(rawSongs: Flow<AudioFile>) =
rawSongs.onEach { file -> tagDao.updateTags(Tags.fromAudioFile(file)) }
rawSongs.onEach { file -> tagDao.updateTags(CachedTags.fromAudioFile(file)) }
}

View file

@ -28,28 +28,28 @@ import androidx.room.Query
import androidx.room.RoomDatabase
import androidx.room.TypeConverter
import androidx.room.TypeConverters
import org.oxycblt.auxio.musikr.model.Date
import org.oxycblt.auxio.musikr.tag.Date
import org.oxycblt.auxio.musikr.tag.AudioFile
import org.oxycblt.auxio.musikr.fs.DeviceFile
import org.oxycblt.auxio.musikr.tag.extractor.correctWhitespace
import org.oxycblt.auxio.musikr.tag.extractor.splitEscaped
import org.oxycblt.auxio.musikr.tag.util.correctWhitespace
import org.oxycblt.auxio.musikr.tag.util.splitEscaped
@Database(entities = [Tags::class], version = 50, exportSchema = false)
@Database(entities = [CachedTags::class], version = 50, exportSchema = false)
abstract class TagDatabase : RoomDatabase() {
abstract fun cachedSongsDao(): TagDao
}
@Dao
interface TagDao {
@Query("SELECT * FROM Tags WHERE uri = :uri AND dateModified = :dateModified")
suspend fun selectTags(uri: String, dateModified: Long): Tags?
@Query("SELECT * FROM CachedTags WHERE uri = :uri AND dateModified = :dateModified")
suspend fun selectTags(uri: String, dateModified: Long): CachedTags?
@Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun updateTags(tags: Tags)
@Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun updateTags(cachedTags: CachedTags)
}
@Entity
@TypeConverters(Tags.Converters::class)
data class Tags(
@TypeConverters(CachedTags.Converters::class)
data class CachedTags(
/**
* The Uri of the [AudioFile]'s audio file, obtained from SAF. This should ideally be a black
* box only used for comparison.
@ -140,7 +140,7 @@ data class Tags(
companion object {
fun fromAudioFile(audioFile: AudioFile) =
Tags(
CachedTags(
uri = audioFile.deviceFile.uri.toString(),
dateModified = audioFile.deviceFile.lastModified,
musicBrainzId = audioFile.musicBrainzId,

View file

@ -1,30 +0,0 @@
/*
* Copyright (c) 2024 Auxio Project
* ExtractorModule.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.musikr.tag.extractor
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
interface MetadataModule {
@Binds fun tagExtractor(impl: TagExtractorImpl): TagExtractor
}

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2023 Auxio Project
* InterpretModule.kt is part of Auxio.
* Copyright (c) 2024 Auxio Project
* ModelModule.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
@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.interpret
package org.oxycblt.auxio.musikr.tag.interpret
import dagger.Binds
import dagger.Module
@ -26,5 +26,5 @@ import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
interface InterpretModule {
@Binds fun interpreter(interpreter: ModelerImpl): Modeler
@Binds fun tagInterpreter(factory: TagInterpreterImpl): TagInterpreter
}

View file

@ -16,16 +16,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.interpret.prepare
package org.oxycblt.auxio.musikr.tag.interpret
import android.net.Uri
import java.util.UUID
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.musikr.model.Date
import org.oxycblt.auxio.musikr.model.Disc
import org.oxycblt.auxio.musikr.model.Name
import org.oxycblt.auxio.musikr.model.ReleaseType
import org.oxycblt.auxio.musikr.tag.Date
import org.oxycblt.auxio.musikr.tag.Disc
import org.oxycblt.auxio.musikr.tag.Name
import org.oxycblt.auxio.musikr.tag.ReleaseType
import org.oxycblt.auxio.musikr.playlist.PlaylistHandle
import org.oxycblt.auxio.musikr.fs.MimeType
import org.oxycblt.auxio.musikr.fs.Path

View file

@ -16,10 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.interpret
package org.oxycblt.auxio.musikr.tag.interpret
import org.oxycblt.auxio.musikr.tag.extractor.correctWhitespace
import org.oxycblt.auxio.musikr.tag.extractor.splitEscaped
import org.oxycblt.auxio.musikr.tag.util.correctWhitespace
import org.oxycblt.auxio.musikr.tag.util.splitEscaped
/**
* Defines the user-specified parsing of multi-value tags. This should be used to parse any tags

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2024 Auxio Project
* Preparer.kt is part of Auxio.
* TagInterpreter.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
@ -16,26 +16,27 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.interpret.prepare
package org.oxycblt.auxio.musikr.tag.interpret
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import org.oxycblt.auxio.R
import org.oxycblt.auxio.musikr.model.Disc
import org.oxycblt.auxio.musikr.model.Name
import org.oxycblt.auxio.musikr.model.ReleaseType
import org.oxycblt.auxio.musikr.tag.Disc
import org.oxycblt.auxio.musikr.tag.Name
import org.oxycblt.auxio.musikr.tag.ReleaseType
import org.oxycblt.auxio.musikr.tag.AudioFile
import org.oxycblt.auxio.musikr.fs.MimeType
import org.oxycblt.auxio.musikr.interpret.Interpretation
import org.oxycblt.auxio.musikr.tag.Interpretation
import org.oxycblt.auxio.musikr.tag.util.parseId3GenreNames
import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment
import org.oxycblt.auxio.util.toUuidOrNull
interface Preparer {
interface TagInterpreter {
fun interpret(audioFiles: Flow<AudioFile>, interpretation: Interpretation): Flow<PreSong>
}
class PreparerImpl @Inject constructor() : Preparer {
class TagInterpreterImpl @Inject constructor() : TagInterpreter {
override fun interpret(audioFiles: Flow<AudioFile>, interpretation: Interpretation) =
audioFiles.map { audioFile ->
val individualPreArtists =

View file

@ -0,0 +1,8 @@
package org.oxycblt.auxio.musikr.tag.parse
import org.oxycblt.auxio.musikr.fs.DeviceFile
data class ParsedTags(
val deviceFile: DeviceFile,
)

View file

@ -16,10 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.tag.extractor
package org.oxycblt.auxio.musikr.tag.parse
import androidx.core.text.isDigitsOnly
import org.oxycblt.auxio.musikr.model.Date
import org.oxycblt.auxio.musikr.tag.Date
import org.oxycblt.auxio.musikr.tag.util.parseId3v2PositionField
import org.oxycblt.auxio.musikr.tag.util.parseVorbisPositionField
import org.oxycblt.auxio.util.nonZeroOrNull
// Song
@ -234,9 +236,6 @@ private fun List<String>.parseR128Adjustment() =
private fun List<String>.parseReplayGainAdjustment() =
first().replace(REPLAYGAIN_ADJUSTMENT_FILTER_REGEX, "").toFloatOrNull()?.nonZeroOrNull()
val COMPILATION_ALBUM_ARTISTS = listOf("Various Artists")
val COMPILATION_RELEASE_TYPES = listOf("compilation")
/**
* Matches non-float information from ReplayGain adjustments. Derived from vanilla music:
* https://github.com/vanilla-music/vanilla

View file

@ -0,0 +1,4 @@
package org.oxycblt.auxio.musikr.tag.parse
interface TagParser {
}

View file

@ -16,12 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.tag.extractor
package org.oxycblt.auxio.musikr.tag.parse
import androidx.media3.common.Metadata
import androidx.media3.extractor.metadata.id3.InternalFrame
import androidx.media3.extractor.metadata.id3.TextInformationFrame
import androidx.media3.extractor.metadata.vorbis.VorbisComment
import org.oxycblt.auxio.musikr.tag.util.correctWhitespace
/**
* Processing wrapper for [Metadata] that allows organized access to text-based audio tags.

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2024 Auxio Project
* ID3Genre.kt is part of Auxio.
* ID3.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
@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.interpret.prepare
package org.oxycblt.auxio.musikr.tag.util
/// --- ID3v2 PARSING ---

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2022 Auxio Project
* TagUtil.kt is part of Auxio.
* Transform.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
@ -16,17 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.musikr.tag.extractor
import org.oxycblt.auxio.util.positiveOrNull
package org.oxycblt.auxio.musikr.tag.util
/// --- GENERIC PARSING ---
// TODO: Remove the escaping checks, it's too expensive to do this for every single tag.
// TODO: I want to eventually be able to move a lot of this into TagWorker once I no longer have
// to deal with the cross-module dependencies of MediaStoreExtractor.
/**
* Split a [String] by the given selector, automatically handling escaped characters that satisfy
* the selector.
@ -86,49 +81,3 @@ fun String.correctWhitespace() = trim().ifBlank { null }
* @return A list of non-blank strings with trailing whitespace removed.
*/
fun List<String>.correctWhitespace() = mapNotNull { it.correctWhitespace() }
/**
* Parse an ID3v2-style position + total [String] field. These fields consist of a number and an
* (optional) total value delimited by a /.
*
* @return The position value extracted from the string field, or null if:
* - The position could not be parsed
* - The position was zeroed AND the total value was not present/zeroed
*
* @see transformPositionField
*/
fun String.parseId3v2PositionField() =
split('/', limit = 2).let {
transformPositionField(it[0].toIntOrNull(), it.getOrNull(1)?.toIntOrNull())
}
/**
* Parse a vorbis-style position + total field. These fields consist of two fields for the position
* and total numbers.
*
* @param pos The position value, or null if not present.
* @param total The total value, if not present.
* @return The position value extracted from the field, or null if:
* - The position could not be parsed
* - The position was zeroed AND the total value was not present/zeroed
*
* @see transformPositionField
*/
fun parseVorbisPositionField(pos: String?, total: String?) =
transformPositionField(pos?.toIntOrNull(), total?.toIntOrNull())
/**
* Transform a raw position + total field into a position a way that tolerates placeholder values.
*
* @param pos The position value, or null if not present.
* @param total The total value, if not present.
* @return The position value extracted from the field, or null if:
* - The position could not be parsed
* - The position was zeroed AND the total value was not present/zeroed
*/
fun transformPositionField(pos: Int?, total: Int?) =
if (pos != null && (pos > 0 || (total?.positiveOrNull() != null))) {
pos
} else {
null
}

View file

@ -0,0 +1,50 @@
package org.oxycblt.auxio.musikr.tag.util
import org.oxycblt.auxio.util.positiveOrNull
/**
* Parse an ID3v2-style position + total [String] field. These fields consist of a number and an
* (optional) total value delimited by a /.
*
* @return The position value extracted from the string field, or null if:
* - The position could not be parsed
* - The position was zeroed AND the total value was not present/zeroed
*
* @see transformPositionField
*/
fun String.parseId3v2PositionField() =
split('/', limit = 2).let {
transformPositionField(it[0].toIntOrNull(), it.getOrNull(1)?.toIntOrNull())
}
/**
* Parse a vorbis-style position + total field. These fields consist of two fields for the position
* and total numbers.
*
* @param pos The position value, or null if not present.
* @param total The total value, if not present.
* @return The position value extracted from the field, or null if:
* - The position could not be parsed
* - The position was zeroed AND the total value was not present/zeroed
*
* @see transformPositionField
*/
fun parseVorbisPositionField(pos: String?, total: String?) =
transformPositionField(pos?.toIntOrNull(), total?.toIntOrNull())
/**
* Transform a raw position + total field into a position a way that tolerates placeholder values.
*
* @param pos The position value, or null if not present.
* @param total The total value, if not present.
* @return The position value extracted from the field, or null if:
* - The position could not be parsed
* - The position was zeroed AND the total value was not present/zeroed
*/
fun transformPositionField(pos: Int?, total: Int?) =
if (pos != null && (pos > 0 || (total?.positiveOrNull() != null))) {
pos
} else {
null
}

View file

@ -38,7 +38,7 @@ import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.musikr.model.Name
import org.oxycblt.auxio.musikr.tag.Name
import org.oxycblt.auxio.music.service.MediaSessionUID
import org.oxycblt.auxio.music.service.MusicBrowser
import org.oxycblt.auxio.playback.state.PlaybackCommand

View file

@ -28,7 +28,7 @@ import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.musikr.model.Name
import org.oxycblt.auxio.musikr.tag.Name
import timber.log.Timber as L
/**

View file

@ -22,7 +22,7 @@ import java.security.MessageDigest
import java.util.UUID
import kotlin.reflect.KClass
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.musikr.model.Date
import org.oxycblt.auxio.musikr.tag.Date
/**
* Sanitizes a value that is unlikely to be null. On debug builds, this aliases to [requireNotNull],

View file

@ -21,7 +21,7 @@ package org.oxycblt.auxio.music.info
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.oxycblt.auxio.musikr.model.Date
import org.oxycblt.auxio.musikr.tag.Date
class DateTest {
@Test

View file

@ -20,7 +20,7 @@ package org.oxycblt.auxio.music.info
import org.junit.Assert.assertEquals
import org.junit.Test
import org.oxycblt.auxio.musikr.model.Disc
import org.oxycblt.auxio.musikr.tag.Disc
class DiscTest {
@Test

View file

@ -21,8 +21,8 @@ package org.oxycblt.auxio.music.info
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Test
import org.oxycblt.auxio.musikr.model.Name
import org.oxycblt.auxio.musikr.model.SortToken
import org.oxycblt.auxio.musikr.tag.Name
import org.oxycblt.auxio.musikr.tag.SortToken
class NameTest {
@Test

View file

@ -20,7 +20,7 @@ package org.oxycblt.auxio.music.info
import org.junit.Assert.assertEquals
import org.junit.Test
import org.oxycblt.auxio.musikr.model.ReleaseType
import org.oxycblt.auxio.musikr.tag.ReleaseType
class ReleaseTypeTest {
@Test

View file

@ -20,7 +20,7 @@ package org.oxycblt.auxio.music.metadata
import org.junit.Assert.assertEquals
import org.junit.Test
import org.oxycblt.auxio.musikr.interpret.Separators
import org.oxycblt.auxio.musikr.tag.interpret.Separators
class SeparatorsTest {
@Test

View file

@ -20,11 +20,11 @@ package org.oxycblt.auxio.music.metadata
import org.junit.Assert.assertEquals
import org.junit.Test
import org.oxycblt.auxio.musikr.tag.extractor.correctWhitespace
import org.oxycblt.auxio.musikr.tag.extractor.parseId3v2PositionField
import org.oxycblt.auxio.musikr.tag.extractor.parseVorbisPositionField
import org.oxycblt.auxio.musikr.tag.extractor.splitEscaped
import org.oxycblt.auxio.musikr.interpret.prepare.parseId3GenreNames
import org.oxycblt.auxio.musikr.tag.util.correctWhitespace
import org.oxycblt.auxio.musikr.tag.util.parseId3v2PositionField
import org.oxycblt.auxio.musikr.tag.util.parseVorbisPositionField
import org.oxycblt.auxio.musikr.tag.util.splitEscaped
import org.oxycblt.auxio.musikr.tag.util.parseId3GenreNames
class TagUtilTest {
@Test

View file

@ -27,9 +27,9 @@ import androidx.media3.extractor.metadata.vorbis.VorbisComment
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.oxycblt.auxio.musikr.tag.extractor.TextTags
import org.oxycblt.auxio.musikr.tag.parse.TextTags
class TextTagsTest {
class TextCachedTagsTest {
@Test
fun textTags_vorbis() {
val textTags = TextTags(VORBIS_METADATA)