music: reorganize metadata/tag/model structure
This commit is contained in:
parent
59652b2f9b
commit
7582c8c9cf
56 changed files with 287 additions and 383 deletions
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -28,4 +28,7 @@ import dagger.hilt.components.SingletonComponent
|
|||
interface MetadataModule {
|
||||
@Binds
|
||||
fun audioPropertiesFactory(interpreter: AudioPropertiesFactoryImpl): AudioProperties.Factory
|
||||
|
||||
@Binds
|
||||
fun metadataExtractor(extractor: MetadataExtractorImpl): MetadataExtractor
|
||||
}
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
}
|
|
@ -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)
|
|
@ -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>>()
|
|
@ -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>>()
|
|
@ -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>()
|
|
@ -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>()
|
|
@ -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
|
|
@ -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(
|
|
@ -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
|
||||
|
||||
/**
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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.
|
|
@ -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)) }
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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 =
|
|
@ -0,0 +1,8 @@
|
|||
package org.oxycblt.auxio.musikr.tag.parse
|
||||
|
||||
import org.oxycblt.auxio.musikr.fs.DeviceFile
|
||||
|
||||
data class ParsedTags(
|
||||
val deviceFile: DeviceFile,
|
||||
|
||||
)
|
|
@ -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
|
|
@ -0,0 +1,4 @@
|
|||
package org.oxycblt.auxio.musikr.tag.parse
|
||||
|
||||
interface TagParser {
|
||||
}
|
|
@ -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.
|
|
@ -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 ---
|
||||
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
Loading…
Reference in a new issue