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.MusicType
|
||||||
import org.oxycblt.auxio.music.Playlist
|
import org.oxycblt.auxio.music.Playlist
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.musikr.model.Disc
|
import org.oxycblt.auxio.musikr.tag.Disc
|
||||||
import org.oxycblt.auxio.musikr.model.ReleaseType
|
import org.oxycblt.auxio.musikr.tag.ReleaseType
|
||||||
import timber.log.Timber as L
|
import timber.log.Timber as L
|
||||||
|
|
||||||
interface DetailGenerator {
|
interface DetailGenerator {
|
||||||
|
|
|
@ -34,7 +34,7 @@ import org.oxycblt.auxio.detail.list.SongPropertyAdapter
|
||||||
import org.oxycblt.auxio.list.adapter.UpdateInstructions
|
import org.oxycblt.auxio.list.adapter.UpdateInstructions
|
||||||
import org.oxycblt.auxio.music.Music
|
import org.oxycblt.auxio.music.Music
|
||||||
import org.oxycblt.auxio.music.Song
|
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.musikr.metadata.AudioProperties
|
||||||
import org.oxycblt.auxio.music.resolveNames
|
import org.oxycblt.auxio.music.resolveNames
|
||||||
import org.oxycblt.auxio.playback.formatDurationMs
|
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.list.adapter.SimpleDiffCallback
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.musikr.model.Disc
|
import org.oxycblt.auxio.musikr.tag.Disc
|
||||||
import org.oxycblt.auxio.musikr.model.resolveNumber
|
import org.oxycblt.auxio.musikr.tag.resolveNumber
|
||||||
import org.oxycblt.auxio.playback.formatDurationMs
|
import org.oxycblt.auxio.playback.formatDurationMs
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.getAttrColorCompat
|
import org.oxycblt.auxio.util.getAttrColorCompat
|
||||||
|
|
|
@ -29,10 +29,10 @@ import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import org.oxycblt.auxio.musikr.cover.Cover
|
import org.oxycblt.auxio.musikr.cover.Cover
|
||||||
import org.oxycblt.auxio.list.Item
|
import org.oxycblt.auxio.list.Item
|
||||||
import org.oxycblt.auxio.musikr.model.Date
|
import org.oxycblt.auxio.musikr.tag.Date
|
||||||
import org.oxycblt.auxio.musikr.model.Disc
|
import org.oxycblt.auxio.musikr.tag.Disc
|
||||||
import org.oxycblt.auxio.musikr.model.Name
|
import org.oxycblt.auxio.musikr.tag.Name
|
||||||
import org.oxycblt.auxio.musikr.model.ReleaseType
|
import org.oxycblt.auxio.musikr.tag.ReleaseType
|
||||||
import org.oxycblt.auxio.musikr.fs.MimeType
|
import org.oxycblt.auxio.musikr.fs.MimeType
|
||||||
import org.oxycblt.auxio.musikr.fs.Path
|
import org.oxycblt.auxio.musikr.fs.Path
|
||||||
import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment
|
import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment
|
||||||
|
|
|
@ -25,12 +25,12 @@ import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.yield
|
import kotlinx.coroutines.yield
|
||||||
import org.oxycblt.auxio.music.MusicRepository.IndexingWorker
|
import org.oxycblt.auxio.music.MusicRepository.IndexingWorker
|
||||||
import org.oxycblt.auxio.musikr.model.Name
|
import org.oxycblt.auxio.musikr.tag.Name
|
||||||
import org.oxycblt.auxio.musikr.interpret.Separators
|
import org.oxycblt.auxio.musikr.tag.interpret.Separators
|
||||||
import org.oxycblt.auxio.musikr.Indexer
|
import org.oxycblt.auxio.musikr.Indexer
|
||||||
import org.oxycblt.auxio.musikr.IndexingProgress
|
import org.oxycblt.auxio.musikr.IndexingProgress
|
||||||
import org.oxycblt.auxio.musikr.interpret.Interpretation
|
import org.oxycblt.auxio.musikr.tag.Interpretation
|
||||||
import org.oxycblt.auxio.musikr.model.MutableLibrary
|
import org.oxycblt.auxio.musikr.model.impl.MutableLibrary
|
||||||
import timber.log.Timber as L
|
import timber.log.Timber as L
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -29,7 +29,7 @@ import org.oxycblt.auxio.BuildConfig
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.DialogSeparatorsBinding
|
import org.oxycblt.auxio.databinding.DialogSeparatorsBinding
|
||||||
import org.oxycblt.auxio.music.MusicSettings
|
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 org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
|
||||||
import timber.log.Timber as L
|
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.MusicType
|
||||||
import org.oxycblt.auxio.music.Playlist
|
import org.oxycblt.auxio.music.Playlist
|
||||||
import org.oxycblt.auxio.music.Song
|
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
|
import org.oxycblt.auxio.search.SearchEngine
|
||||||
|
|
||||||
class MusicBrowser
|
class MusicBrowser
|
||||||
|
|
|
@ -28,9 +28,9 @@ import kotlinx.coroutines.flow.buffer
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import org.oxycblt.auxio.musikr.explore.Explorer
|
import org.oxycblt.auxio.musikr.explore.Explorer
|
||||||
import org.oxycblt.auxio.musikr.interpret.Interpretation
|
import org.oxycblt.auxio.musikr.tag.Interpretation
|
||||||
import org.oxycblt.auxio.musikr.interpret.Modeler
|
import org.oxycblt.auxio.musikr.model.Modeler
|
||||||
import org.oxycblt.auxio.musikr.model.MutableLibrary
|
import org.oxycblt.auxio.musikr.model.impl.MutableLibrary
|
||||||
|
|
||||||
interface Indexer {
|
interface Indexer {
|
||||||
suspend fun run(
|
suspend fun run(
|
||||||
|
|
|
@ -24,15 +24,10 @@ import org.oxycblt.auxio.music.Song
|
||||||
sealed interface Cover {
|
sealed interface Cover {
|
||||||
val key: String
|
val key: String
|
||||||
|
|
||||||
class Single(song: Song) : Cover {
|
data class Single(override val key: String) : Cover
|
||||||
override val key = "${song.uid}@${song.lastModified}"
|
|
||||||
val uid = song.uid
|
|
||||||
val uri = song.uri
|
|
||||||
val lastModified = song.lastModified
|
|
||||||
}
|
|
||||||
|
|
||||||
class Multi(val all: List<Single>) : 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 {
|
companion object {
|
||||||
|
@ -40,15 +35,16 @@ sealed interface Cover {
|
||||||
|
|
||||||
fun nil() = Multi(listOf())
|
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) }
|
fun multi(songs: Collection<Song>) = order(songs).run { Multi(this) }
|
||||||
|
|
||||||
private fun order(songs: Collection<Song>) =
|
private fun order(songs: Collection<Song>) =
|
||||||
FALLBACK_SORT.songs(songs)
|
FALLBACK_SORT.songs(songs)
|
||||||
.groupBy { it.album }
|
.map { it.cover }
|
||||||
|
.groupBy { it.key }
|
||||||
.entries
|
.entries
|
||||||
.sortedByDescending { it.value.size }
|
.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
|
package org.oxycblt.auxio.musikr.metadata
|
||||||
|
|
||||||
import android.media.MediaMetadataRetriever
|
import android.media.MediaMetadataRetriever
|
||||||
|
import androidx.media3.common.Format
|
||||||
import androidx.media3.common.Metadata
|
import androidx.media3.common.Metadata
|
||||||
import org.oxycblt.auxio.musikr.fs.DeviceFile
|
import org.oxycblt.auxio.musikr.fs.DeviceFile
|
||||||
|
|
||||||
data class AudioMetadata(
|
data class AudioMetadata(
|
||||||
val file: DeviceFile,
|
val file: DeviceFile,
|
||||||
val exoPlayerMetadata: Metadata,
|
val exoPlayerFormat: Format,
|
||||||
val mediaMetadataRetriever: MediaMetadataRetriever
|
val mediaMetadataRetriever: MediaMetadataRetriever
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,8 +1,53 @@
|
||||||
package org.oxycblt.auxio.musikr.metadata
|
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.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 org.oxycblt.auxio.musikr.fs.DeviceFile
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
interface MetadataExtractor {
|
interface MetadataExtractor {
|
||||||
fun extract(files: Flow<DeviceFile>): Flow<AudioMetadata>
|
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 {
|
interface MetadataModule {
|
||||||
@Binds
|
@Binds
|
||||||
fun audioPropertiesFactory(interpreter: AudioPropertiesFactoryImpl): AudioProperties.Factory
|
fun audioPropertiesFactory(interpreter: AudioPropertiesFactoryImpl): AudioProperties.Factory
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
fun metadataExtractor(extractor: MetadataExtractorImpl): MetadataExtractor
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,5 @@
|
||||||
/*
|
package org.oxycblt.auxio.musikr.metadata
|
||||||
* 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
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.media.MediaMetadataRetriever
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.HandlerThread
|
import android.os.HandlerThread
|
||||||
|
@ -38,101 +18,9 @@ import androidx.media3.exoplayer.upstream.Allocator
|
||||||
import androidx.media3.exoplayer.upstream.DefaultAllocator
|
import androidx.media3.exoplayer.upstream.DefaultAllocator
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.google.common.util.concurrent.SettableFuture
|
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 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_PREPARE = 0
|
||||||
private const val MESSAGE_CONTINUE_LOADING = 1
|
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 MESSAGE_RELEASE = 3
|
||||||
private const val CHECK_INTERVAL_MS = 100
|
private const val CHECK_INTERVAL_MS = 100
|
||||||
|
|
||||||
/**
|
// TODO: Rewrite and re-integrate
|
||||||
* 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.
|
interface MetadataRetrieverExt {
|
||||||
*
|
fun retrieveMetadata(mediaItem: MediaItem): Future<TrackGroupArray>
|
||||||
* @author Media3 Team, Alexander Capehart (OxygenCobalt)
|
fun retrieve()
|
||||||
*/
|
|
||||||
private class ChunkedMetadataRetriever(private val mediaSourceFactory: MediaSource.Factory) :
|
interface Factory {
|
||||||
Handler.Callback {
|
fun create(): MetadataRetrieverExt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReusableMetadataRetrieverImpl @Inject constructor(private val mediaSourceFactory: MediaSource.Factory) :
|
||||||
|
MetadataRetrieverExt, Handler.Callback {
|
||||||
private val mediaSourceThread = HandlerThread("Auxio:ChunkedMetadataRetriever:${hashCode()}")
|
private val mediaSourceThread = HandlerThread("Auxio:ChunkedMetadataRetriever:${hashCode()}")
|
||||||
private val mediaSourceHandler: HandlerWrapper
|
private val mediaSourceHandler: HandlerWrapper
|
||||||
private var job: MetadataJob? = null
|
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(
|
private class JobData(
|
||||||
val params: JobParams,
|
val params: JobParams,
|
||||||
|
@ -167,15 +60,15 @@ private class ChunkedMetadataRetriever(private val mediaSourceFactory: MediaSour
|
||||||
mediaSourceHandler = Clock.DEFAULT.createHandler(mediaSourceThread.looper, this)
|
mediaSourceHandler = Clock.DEFAULT.createHandler(mediaSourceThread.looper, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun retrieve(uri: Uri): ListenableFuture<TrackGroupArray> {
|
override fun retrieveMetadata(mediaItem: MediaItem): Future<TrackGroupArray> {
|
||||||
val job = job
|
val job = job
|
||||||
check(job == null || job.data.params.future.isDone) { "Already working on something: $job" }
|
check(job == null || job.data.params.future.isDone) { "Already working on something: $job" }
|
||||||
val future = SettableFuture.create<TrackGroupArray>()
|
val future = SettableFuture.create<TrackGroupArray>()
|
||||||
mediaSourceHandler.obtainMessage(MESSAGE_PREPARE, JobParams(uri, future)).sendToTarget()
|
mediaSourceHandler.obtainMessage(MESSAGE_PREPARE, JobParams(mediaItem, future)).sendToTarget()
|
||||||
return future
|
return future
|
||||||
}
|
}
|
||||||
|
|
||||||
fun release() {
|
override fun retrieve() {
|
||||||
mediaSourceHandler.removeCallbacksAndMessages(null)
|
mediaSourceHandler.removeCallbacksAndMessages(null)
|
||||||
mediaSourceThread.quit()
|
mediaSourceThread.quit()
|
||||||
}
|
}
|
||||||
|
@ -186,7 +79,7 @@ private class ChunkedMetadataRetriever(private val mediaSourceFactory: MediaSour
|
||||||
val params = msg.obj as JobParams
|
val params = msg.obj as JobParams
|
||||||
|
|
||||||
val mediaSource =
|
val mediaSource =
|
||||||
mediaSourceFactory.createMediaSource(MediaItem.fromUri(params.uri))
|
mediaSourceFactory.createMediaSource(params.mediaItem)
|
||||||
val data = JobData(params, mediaSource, null)
|
val data = JobData(params, mediaSource, null)
|
||||||
val mediaSourceCaller = MediaSourceCaller(data)
|
val mediaSourceCaller = MediaSourceCaller(data)
|
||||||
mediaSource.prepareSource(
|
mediaSource.prepareSource(
|
||||||
|
@ -217,8 +110,8 @@ private class ChunkedMetadataRetriever(private val mediaSourceFactory: MediaSour
|
||||||
mediaPeriod.maybeThrowPrepareError()
|
mediaPeriod.maybeThrowPrepareError()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
L.e("Failed to extract MediaSource")
|
Timber.e("Failed to extract MediaSource")
|
||||||
L.e(e.stackTraceToString())
|
Timber.e(e.stackTraceToString())
|
||||||
mediaPeriod?.let(mediaSource::releasePeriod)
|
mediaPeriod?.let(mediaSource::releasePeriod)
|
||||||
mediaSource.releaseSource(mediaSourceCaller)
|
mediaSource.releaseSource(mediaSourceCaller)
|
||||||
job.data.params.future.setException(e)
|
job.data.params.future.setException(e)
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2024 Auxio Project
|
* Copyright (c) 2023 Auxio Project
|
||||||
* PrepareModule.kt is part of Auxio.
|
* ModelModule.kt is part of Auxio.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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/>.
|
* 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.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
@ -25,6 +25,6 @@ import dagger.hilt.components.SingletonComponent
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
interface PrepareModule {
|
interface InterpretModule {
|
||||||
@Binds fun prepare(factory: PreparerImpl): Preparer
|
@Binds fun interpreter(interpreter: ModelerImpl): Modeler
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2024 Auxio Project
|
* 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
|
* 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
|
* 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/>.
|
* 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 javax.inject.Inject
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -30,19 +30,20 @@ import kotlinx.coroutines.flow.toList
|
||||||
import org.oxycblt.auxio.music.Music
|
import org.oxycblt.auxio.music.Music
|
||||||
import org.oxycblt.auxio.musikr.tag.AudioFile
|
import org.oxycblt.auxio.musikr.tag.AudioFile
|
||||||
import org.oxycblt.auxio.musikr.playlist.PlaylistFile
|
import org.oxycblt.auxio.musikr.playlist.PlaylistFile
|
||||||
import org.oxycblt.auxio.musikr.interpret.link.AlbumLinker
|
import org.oxycblt.auxio.musikr.model.graph.AlbumLinker
|
||||||
import org.oxycblt.auxio.musikr.interpret.link.ArtistLinker
|
import org.oxycblt.auxio.musikr.model.graph.ArtistLinker
|
||||||
import org.oxycblt.auxio.musikr.interpret.link.GenreLinker
|
import org.oxycblt.auxio.musikr.model.graph.GenreLinker
|
||||||
import org.oxycblt.auxio.musikr.interpret.link.Linked
|
import org.oxycblt.auxio.musikr.model.graph.Linked
|
||||||
import org.oxycblt.auxio.musikr.interpret.link.LinkedSong
|
import org.oxycblt.auxio.musikr.model.graph.LinkedSong
|
||||||
import org.oxycblt.auxio.musikr.model.AlbumImpl
|
import org.oxycblt.auxio.musikr.model.impl.AlbumImpl
|
||||||
import org.oxycblt.auxio.musikr.model.ArtistImpl
|
import org.oxycblt.auxio.musikr.model.impl.ArtistImpl
|
||||||
import org.oxycblt.auxio.musikr.model.GenreImpl
|
import org.oxycblt.auxio.musikr.model.impl.GenreImpl
|
||||||
import org.oxycblt.auxio.musikr.model.LibraryImpl
|
import org.oxycblt.auxio.musikr.model.impl.LibraryImpl
|
||||||
import org.oxycblt.auxio.musikr.model.MutableLibrary
|
import org.oxycblt.auxio.musikr.model.impl.MutableLibrary
|
||||||
import org.oxycblt.auxio.musikr.model.SongImpl
|
import org.oxycblt.auxio.musikr.model.impl.SongImpl
|
||||||
import org.oxycblt.auxio.musikr.interpret.prepare.PreSong
|
import org.oxycblt.auxio.musikr.tag.Interpretation
|
||||||
import org.oxycblt.auxio.musikr.interpret.prepare.Preparer
|
import org.oxycblt.auxio.musikr.tag.interpret.PreSong
|
||||||
|
import org.oxycblt.auxio.musikr.tag.interpret.TagInterpreter
|
||||||
import timber.log.Timber as L
|
import timber.log.Timber as L
|
||||||
|
|
||||||
interface Modeler {
|
interface Modeler {
|
||||||
|
@ -53,14 +54,14 @@ interface Modeler {
|
||||||
): MutableLibrary
|
): MutableLibrary
|
||||||
}
|
}
|
||||||
|
|
||||||
class ModelerImpl @Inject constructor(private val preparer: Preparer) : Modeler {
|
class ModelerImpl @Inject constructor(private val tagInterpreter: TagInterpreter) : Modeler {
|
||||||
override suspend fun model(
|
override suspend fun model(
|
||||||
audioFiles: Flow<AudioFile>,
|
audioFiles: Flow<AudioFile>,
|
||||||
playlistFiles: Flow<PlaylistFile>,
|
playlistFiles: Flow<PlaylistFile>,
|
||||||
interpretation: Interpretation
|
interpretation: Interpretation
|
||||||
): MutableLibrary {
|
): MutableLibrary {
|
||||||
val preSongs =
|
val preSongs =
|
||||||
preparer
|
tagInterpreter
|
||||||
.interpret(audioFiles, interpretation)
|
.interpret(audioFiles, interpretation)
|
||||||
.flowOn(Dispatchers.Main)
|
.flowOn(Dispatchers.Main)
|
||||||
.buffer(Channel.UNLIMITED)
|
.buffer(Channel.UNLIMITED)
|
|
@ -16,13 +16,13 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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 java.util.UUID
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import org.oxycblt.auxio.musikr.model.AlbumImpl
|
import org.oxycblt.auxio.musikr.model.impl.AlbumImpl
|
||||||
import org.oxycblt.auxio.musikr.model.SongImpl
|
import org.oxycblt.auxio.musikr.model.impl.SongImpl
|
||||||
|
|
||||||
class AlbumLinker {
|
class AlbumLinker {
|
||||||
private val tree = mutableMapOf<String?, MutableMap<UUID?, AlbumLink>>()
|
private val tree = mutableMapOf<String?, MutableMap<UUID?, AlbumLink>>()
|
|
@ -16,17 +16,17 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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 java.util.UUID
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import org.oxycblt.auxio.music.Music
|
import org.oxycblt.auxio.music.Music
|
||||||
import org.oxycblt.auxio.musikr.model.AlbumImpl
|
import org.oxycblt.auxio.musikr.model.impl.AlbumImpl
|
||||||
import org.oxycblt.auxio.musikr.model.ArtistImpl
|
import org.oxycblt.auxio.musikr.model.impl.ArtistImpl
|
||||||
import org.oxycblt.auxio.musikr.model.SongImpl
|
import org.oxycblt.auxio.musikr.model.impl.SongImpl
|
||||||
import org.oxycblt.auxio.musikr.interpret.prepare.PreAlbum
|
import org.oxycblt.auxio.musikr.tag.interpret.PreAlbum
|
||||||
import org.oxycblt.auxio.musikr.interpret.prepare.PreArtist
|
import org.oxycblt.auxio.musikr.tag.interpret.PreArtist
|
||||||
|
|
||||||
class ArtistLinker {
|
class ArtistLinker {
|
||||||
private val tree = mutableMapOf<String?, MutableMap<UUID?, ArtistLink>>()
|
private val tree = mutableMapOf<String?, MutableMap<UUID?, ArtistLink>>()
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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> {
|
class Contribution<T> {
|
||||||
private val map = mutableMapOf<T, Int>()
|
private val map = mutableMapOf<T, Int>()
|
|
@ -16,14 +16,14 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import org.oxycblt.auxio.musikr.model.GenreImpl
|
import org.oxycblt.auxio.musikr.model.impl.GenreImpl
|
||||||
import org.oxycblt.auxio.musikr.model.SongImpl
|
import org.oxycblt.auxio.musikr.model.impl.SongImpl
|
||||||
import org.oxycblt.auxio.musikr.interpret.prepare.PreGenre
|
import org.oxycblt.auxio.musikr.tag.interpret.PreGenre
|
||||||
import org.oxycblt.auxio.musikr.interpret.prepare.PreSong
|
import org.oxycblt.auxio.musikr.tag.interpret.PreSong
|
||||||
|
|
||||||
class GenreLinker {
|
class GenreLinker {
|
||||||
private val tree = mutableMapOf<String?, GenreLink>()
|
private val tree = mutableMapOf<String?, GenreLink>()
|
|
@ -16,16 +16,16 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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.impl.AlbumImpl
|
||||||
import org.oxycblt.auxio.musikr.model.ArtistImpl
|
import org.oxycblt.auxio.musikr.model.impl.ArtistImpl
|
||||||
import org.oxycblt.auxio.musikr.model.GenreImpl
|
import org.oxycblt.auxio.musikr.model.impl.GenreImpl
|
||||||
import org.oxycblt.auxio.musikr.model.PlaylistImpl
|
import org.oxycblt.auxio.musikr.model.impl.PlaylistImpl
|
||||||
import org.oxycblt.auxio.musikr.model.SongImpl
|
import org.oxycblt.auxio.musikr.model.impl.SongImpl
|
||||||
import org.oxycblt.auxio.musikr.interpret.prepare.PreAlbum
|
import org.oxycblt.auxio.musikr.tag.interpret.PreAlbum
|
||||||
import org.oxycblt.auxio.musikr.interpret.prepare.PrePlaylist
|
import org.oxycblt.auxio.musikr.tag.interpret.PrePlaylist
|
||||||
import org.oxycblt.auxio.musikr.interpret.prepare.PreSong
|
import org.oxycblt.auxio.musikr.tag.interpret.PreSong
|
||||||
|
|
||||||
interface LinkedSong {
|
interface LinkedSong {
|
||||||
val preSong: PreSong
|
val preSong: PreSong
|
|
@ -16,12 +16,12 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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.Flow
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import org.oxycblt.auxio.musikr.playlist.PlaylistFile
|
import org.oxycblt.auxio.musikr.playlist.PlaylistFile
|
||||||
import org.oxycblt.auxio.musikr.model.PlaylistImpl
|
import org.oxycblt.auxio.musikr.model.impl.PlaylistImpl
|
||||||
|
|
||||||
class PlaylistLinker {
|
class PlaylistLinker {
|
||||||
fun register(
|
fun register(
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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 kotlin.math.min
|
||||||
import org.oxycblt.auxio.musikr.cover.Cover
|
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.Music
|
||||||
import org.oxycblt.auxio.music.MusicType
|
import org.oxycblt.auxio.music.MusicType
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.musikr.interpret.link.LinkedAlbum
|
import org.oxycblt.auxio.musikr.model.graph.LinkedAlbum
|
||||||
import org.oxycblt.auxio.musikr.interpret.link.LinkedSong
|
import org.oxycblt.auxio.musikr.model.graph.LinkedSong
|
||||||
import org.oxycblt.auxio.musikr.interpret.prepare.PreArtist
|
import org.oxycblt.auxio.musikr.tag.Date
|
||||||
import org.oxycblt.auxio.musikr.interpret.prepare.PreGenre
|
import org.oxycblt.auxio.musikr.tag.interpret.PreArtist
|
||||||
|
import org.oxycblt.auxio.musikr.tag.interpret.PreGenre
|
||||||
import org.oxycblt.auxio.util.update
|
import org.oxycblt.auxio.util.update
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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.Library
|
||||||
import org.oxycblt.auxio.music.Music
|
import org.oxycblt.auxio.music.Music
|
|
@ -16,11 +16,12 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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.musikr.cover.Cover
|
||||||
import org.oxycblt.auxio.music.Playlist
|
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 {
|
class PlaylistImpl(linkedPlaylist: LinkedPlaylist) : Playlist {
|
||||||
private val prePlaylist = linkedPlaylist.prePlaylist
|
private val prePlaylist = linkedPlaylist.prePlaylist
|
|
@ -28,7 +28,7 @@ import java.io.OutputStream
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import org.oxycblt.auxio.music.Playlist
|
import org.oxycblt.auxio.music.Playlist
|
||||||
import org.oxycblt.auxio.music.resolveNames
|
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.Components
|
||||||
import org.oxycblt.auxio.musikr.fs.Path
|
import org.oxycblt.auxio.musikr.fs.Path
|
||||||
import org.oxycblt.auxio.musikr.fs.Volume
|
import org.oxycblt.auxio.musikr.fs.Volume
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package org.oxycblt.auxio.musikr.tag
|
package org.oxycblt.auxio.musikr.tag
|
||||||
|
|
||||||
import org.oxycblt.auxio.musikr.model.Date
|
|
||||||
import org.oxycblt.auxio.musikr.fs.DeviceFile
|
import org.oxycblt.auxio.musikr.fs.DeviceFile
|
||||||
|
|
||||||
data class AudioFile(
|
data class AudioFile(
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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 android.content.Context
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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 android.content.Context
|
||||||
import org.oxycblt.auxio.R
|
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/>.
|
* 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 android.content.Context
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
|
@ -16,10 +16,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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.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.
|
* 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>) =
|
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.RoomDatabase
|
||||||
import androidx.room.TypeConverter
|
import androidx.room.TypeConverter
|
||||||
import androidx.room.TypeConverters
|
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.tag.AudioFile
|
||||||
import org.oxycblt.auxio.musikr.fs.DeviceFile
|
import org.oxycblt.auxio.musikr.fs.DeviceFile
|
||||||
import org.oxycblt.auxio.musikr.tag.extractor.correctWhitespace
|
import org.oxycblt.auxio.musikr.tag.util.correctWhitespace
|
||||||
import org.oxycblt.auxio.musikr.tag.extractor.splitEscaped
|
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 class TagDatabase : RoomDatabase() {
|
||||||
abstract fun cachedSongsDao(): TagDao
|
abstract fun cachedSongsDao(): TagDao
|
||||||
}
|
}
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface TagDao {
|
interface TagDao {
|
||||||
@Query("SELECT * FROM Tags WHERE uri = :uri AND dateModified = :dateModified")
|
@Query("SELECT * FROM CachedTags WHERE uri = :uri AND dateModified = :dateModified")
|
||||||
suspend fun selectTags(uri: String, dateModified: Long): Tags?
|
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
|
@Entity
|
||||||
@TypeConverters(Tags.Converters::class)
|
@TypeConverters(CachedTags.Converters::class)
|
||||||
data class Tags(
|
data class CachedTags(
|
||||||
/**
|
/**
|
||||||
* The Uri of the [AudioFile]'s audio file, obtained from SAF. This should ideally be a black
|
* The Uri of the [AudioFile]'s audio file, obtained from SAF. This should ideally be a black
|
||||||
* box only used for comparison.
|
* box only used for comparison.
|
||||||
|
@ -140,7 +140,7 @@ data class Tags(
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromAudioFile(audioFile: AudioFile) =
|
fun fromAudioFile(audioFile: AudioFile) =
|
||||||
Tags(
|
CachedTags(
|
||||||
uri = audioFile.deviceFile.uri.toString(),
|
uri = audioFile.deviceFile.uri.toString(),
|
||||||
dateModified = audioFile.deviceFile.lastModified,
|
dateModified = audioFile.deviceFile.lastModified,
|
||||||
musicBrainzId = audioFile.musicBrainzId,
|
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
|
* Copyright (c) 2024 Auxio Project
|
||||||
* InterpretModule.kt is part of Auxio.
|
* ModelModule.kt is part of Auxio.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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/>.
|
* 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.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
@ -26,5 +26,5 @@ import dagger.hilt.components.SingletonComponent
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
interface InterpretModule {
|
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/>.
|
* 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 android.net.Uri
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import org.oxycblt.auxio.music.Music
|
import org.oxycblt.auxio.music.Music
|
||||||
import org.oxycblt.auxio.music.MusicType
|
import org.oxycblt.auxio.music.MusicType
|
||||||
import org.oxycblt.auxio.musikr.model.Date
|
import org.oxycblt.auxio.musikr.tag.Date
|
||||||
import org.oxycblt.auxio.musikr.model.Disc
|
import org.oxycblt.auxio.musikr.tag.Disc
|
||||||
import org.oxycblt.auxio.musikr.model.Name
|
import org.oxycblt.auxio.musikr.tag.Name
|
||||||
import org.oxycblt.auxio.musikr.model.ReleaseType
|
import org.oxycblt.auxio.musikr.tag.ReleaseType
|
||||||
import org.oxycblt.auxio.musikr.playlist.PlaylistHandle
|
import org.oxycblt.auxio.musikr.playlist.PlaylistHandle
|
||||||
import org.oxycblt.auxio.musikr.fs.MimeType
|
import org.oxycblt.auxio.musikr.fs.MimeType
|
||||||
import org.oxycblt.auxio.musikr.fs.Path
|
import org.oxycblt.auxio.musikr.fs.Path
|
|
@ -16,10 +16,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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.util.correctWhitespace
|
||||||
import org.oxycblt.auxio.musikr.tag.extractor.splitEscaped
|
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
|
* 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
|
* 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
|
* 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
|
* 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/>.
|
* 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 javax.inject.Inject
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.musikr.model.Disc
|
import org.oxycblt.auxio.musikr.tag.Disc
|
||||||
import org.oxycblt.auxio.musikr.model.Name
|
import org.oxycblt.auxio.musikr.tag.Name
|
||||||
import org.oxycblt.auxio.musikr.model.ReleaseType
|
import org.oxycblt.auxio.musikr.tag.ReleaseType
|
||||||
import org.oxycblt.auxio.musikr.tag.AudioFile
|
import org.oxycblt.auxio.musikr.tag.AudioFile
|
||||||
import org.oxycblt.auxio.musikr.fs.MimeType
|
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.playback.replaygain.ReplayGainAdjustment
|
||||||
import org.oxycblt.auxio.util.toUuidOrNull
|
import org.oxycblt.auxio.util.toUuidOrNull
|
||||||
|
|
||||||
interface Preparer {
|
interface TagInterpreter {
|
||||||
fun interpret(audioFiles: Flow<AudioFile>, interpretation: Interpretation): Flow<PreSong>
|
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) =
|
override fun interpret(audioFiles: Flow<AudioFile>, interpretation: Interpretation) =
|
||||||
audioFiles.map { audioFile ->
|
audioFiles.map { audioFile ->
|
||||||
val individualPreArtists =
|
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/>.
|
* 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 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
|
import org.oxycblt.auxio.util.nonZeroOrNull
|
||||||
|
|
||||||
// Song
|
// Song
|
||||||
|
@ -234,9 +236,6 @@ private fun List<String>.parseR128Adjustment() =
|
||||||
private fun List<String>.parseReplayGainAdjustment() =
|
private fun List<String>.parseReplayGainAdjustment() =
|
||||||
first().replace(REPLAYGAIN_ADJUSTMENT_FILTER_REGEX, "").toFloatOrNull()?.nonZeroOrNull()
|
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:
|
* Matches non-float information from ReplayGain adjustments. Derived from vanilla music:
|
||||||
* https://github.com/vanilla-music/vanilla
|
* 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/>.
|
* 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.common.Metadata
|
||||||
import androidx.media3.extractor.metadata.id3.InternalFrame
|
import androidx.media3.extractor.metadata.id3.InternalFrame
|
||||||
import androidx.media3.extractor.metadata.id3.TextInformationFrame
|
import androidx.media3.extractor.metadata.id3.TextInformationFrame
|
||||||
import androidx.media3.extractor.metadata.vorbis.VorbisComment
|
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.
|
* Processing wrapper for [Metadata] that allows organized access to text-based audio tags.
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2024 Auxio Project
|
* 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
|
* 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
|
* 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/>.
|
* 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 ---
|
/// --- ID3v2 PARSING ---
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2022 Auxio Project
|
* 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
|
* 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
|
* 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/>.
|
* 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.util
|
||||||
|
|
||||||
import org.oxycblt.auxio.util.positiveOrNull
|
|
||||||
|
|
||||||
/// --- GENERIC PARSING ---
|
/// --- GENERIC PARSING ---
|
||||||
|
|
||||||
// TODO: Remove the escaping checks, it's too expensive to do this for every single tag.
|
// 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
|
* Split a [String] by the given selector, automatically handling escaped characters that satisfy
|
||||||
* the selector.
|
* the selector.
|
||||||
|
@ -86,49 +81,3 @@ fun String.correctWhitespace() = trim().ifBlank { null }
|
||||||
* @return A list of non-blank strings with trailing whitespace removed.
|
* @return A list of non-blank strings with trailing whitespace removed.
|
||||||
*/
|
*/
|
||||||
fun List<String>.correctWhitespace() = mapNotNull { it.correctWhitespace() }
|
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.MusicRepository
|
||||||
import org.oxycblt.auxio.music.Playlist
|
import org.oxycblt.auxio.music.Playlist
|
||||||
import org.oxycblt.auxio.music.Song
|
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.MediaSessionUID
|
||||||
import org.oxycblt.auxio.music.service.MusicBrowser
|
import org.oxycblt.auxio.music.service.MusicBrowser
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackCommand
|
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.Music
|
||||||
import org.oxycblt.auxio.music.Playlist
|
import org.oxycblt.auxio.music.Playlist
|
||||||
import org.oxycblt.auxio.music.Song
|
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
|
import timber.log.Timber as L
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,7 +22,7 @@ import java.security.MessageDigest
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import org.oxycblt.auxio.BuildConfig
|
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],
|
* 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.assertEquals
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.oxycblt.auxio.musikr.model.Date
|
import org.oxycblt.auxio.musikr.tag.Date
|
||||||
|
|
||||||
class DateTest {
|
class DateTest {
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -20,7 +20,7 @@ package org.oxycblt.auxio.music.info
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.oxycblt.auxio.musikr.model.Disc
|
import org.oxycblt.auxio.musikr.tag.Disc
|
||||||
|
|
||||||
class DiscTest {
|
class DiscTest {
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -21,8 +21,8 @@ package org.oxycblt.auxio.music.info
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNotEquals
|
import org.junit.Assert.assertNotEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.oxycblt.auxio.musikr.model.Name
|
import org.oxycblt.auxio.musikr.tag.Name
|
||||||
import org.oxycblt.auxio.musikr.model.SortToken
|
import org.oxycblt.auxio.musikr.tag.SortToken
|
||||||
|
|
||||||
class NameTest {
|
class NameTest {
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -20,7 +20,7 @@ package org.oxycblt.auxio.music.info
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.oxycblt.auxio.musikr.model.ReleaseType
|
import org.oxycblt.auxio.musikr.tag.ReleaseType
|
||||||
|
|
||||||
class ReleaseTypeTest {
|
class ReleaseTypeTest {
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -20,7 +20,7 @@ package org.oxycblt.auxio.music.metadata
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.oxycblt.auxio.musikr.interpret.Separators
|
import org.oxycblt.auxio.musikr.tag.interpret.Separators
|
||||||
|
|
||||||
class SeparatorsTest {
|
class SeparatorsTest {
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -20,11 +20,11 @@ package org.oxycblt.auxio.music.metadata
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.oxycblt.auxio.musikr.tag.extractor.correctWhitespace
|
import org.oxycblt.auxio.musikr.tag.util.correctWhitespace
|
||||||
import org.oxycblt.auxio.musikr.tag.extractor.parseId3v2PositionField
|
import org.oxycblt.auxio.musikr.tag.util.parseId3v2PositionField
|
||||||
import org.oxycblt.auxio.musikr.tag.extractor.parseVorbisPositionField
|
import org.oxycblt.auxio.musikr.tag.util.parseVorbisPositionField
|
||||||
import org.oxycblt.auxio.musikr.tag.extractor.splitEscaped
|
import org.oxycblt.auxio.musikr.tag.util.splitEscaped
|
||||||
import org.oxycblt.auxio.musikr.interpret.prepare.parseId3GenreNames
|
import org.oxycblt.auxio.musikr.tag.util.parseId3GenreNames
|
||||||
|
|
||||||
class TagUtilTest {
|
class TagUtilTest {
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -27,9 +27,9 @@ import androidx.media3.extractor.metadata.vorbis.VorbisComment
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Test
|
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
|
@Test
|
||||||
fun textTags_vorbis() {
|
fun textTags_vorbis() {
|
||||||
val textTags = TextTags(VORBIS_METADATA)
|
val textTags = TextTags(VORBIS_METADATA)
|
Loading…
Reference in a new issue