musikr: replace mimetype w/format
First property now derived from taglib.
This commit is contained in:
parent
e16b23f34e
commit
9ab4dc5595
13 changed files with 162 additions and 148 deletions
|
@ -104,9 +104,9 @@ class SongDetailDialog : ViewBindingMaterialDialogFragment<DialogSongDetailBindi
|
||||||
add(SongProperty(R.string.lbl_disc, zipped))
|
add(SongProperty(R.string.lbl_disc, zipped))
|
||||||
}
|
}
|
||||||
add(SongProperty(R.string.lbl_path, song.path.resolve(context)))
|
add(SongProperty(R.string.lbl_path, song.path.resolve(context)))
|
||||||
info.resolvedMimeType.resolveName(context)?.let {
|
// info.format.resolveName(context)?.let {
|
||||||
add(SongProperty(R.string.lbl_format, it))
|
// add(SongProperty(R.string.lbl_format, it))
|
||||||
}
|
// }
|
||||||
add(
|
add(
|
||||||
SongProperty(
|
SongProperty(
|
||||||
R.string.lbl_size, Formatter.formatFileSize(context, song.size)))
|
R.string.lbl_size, Formatter.formatFileSize(context, song.size)))
|
||||||
|
|
|
@ -320,7 +320,7 @@ fun Context.share(songs: Collection<Song>) {
|
||||||
val mimeTypes = mutableSetOf<String>()
|
val mimeTypes = mutableSetOf<String>()
|
||||||
for (song in songs) {
|
for (song in songs) {
|
||||||
builder.addStream(song.uri)
|
builder.addStream(song.uri)
|
||||||
mimeTypes.add(song.mimeType.fromFormat ?: song.mimeType.fromExtension)
|
mimeTypes.add(song.format.mimeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setType(mimeTypes.singleOrNull() ?: "audio/*").startChooser()
|
builder.setType(mimeTypes.singleOrNull() ?: "audio/*").startChooser()
|
||||||
|
|
|
@ -33,7 +33,7 @@ import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment
|
||||||
import org.oxycblt.auxio.util.concatLocalized
|
import org.oxycblt.auxio.util.concatLocalized
|
||||||
import org.oxycblt.auxio.util.toUuidOrNull
|
import org.oxycblt.auxio.util.toUuidOrNull
|
||||||
import org.oxycblt.musikr.cover.Cover
|
import org.oxycblt.musikr.cover.Cover
|
||||||
import org.oxycblt.musikr.fs.MimeType
|
import org.oxycblt.musikr.fs.Format
|
||||||
import org.oxycblt.musikr.fs.Path
|
import org.oxycblt.musikr.fs.Path
|
||||||
import org.oxycblt.musikr.tag.Date
|
import org.oxycblt.musikr.tag.Date
|
||||||
import org.oxycblt.musikr.tag.Disc
|
import org.oxycblt.musikr.tag.Disc
|
||||||
|
@ -252,8 +252,8 @@ interface Song : Music {
|
||||||
* instead for accessing the audio file.
|
* instead for accessing the audio file.
|
||||||
*/
|
*/
|
||||||
val path: Path
|
val path: Path
|
||||||
/** The [MimeType] of the audio file. Only intended for display. */
|
/** The [Format] of the audio file. Only intended for display. */
|
||||||
val mimeType: MimeType
|
val format: Format
|
||||||
/** The size of the audio file, in bytes. */
|
/** The size of the audio file, in bytes. */
|
||||||
val size: Long
|
val size: Long
|
||||||
/** The duration of the audio file, in milliseconds. */
|
/** The duration of the audio file, in milliseconds. */
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
package org.oxycblt.musikr.cache
|
package org.oxycblt.musikr.cache
|
||||||
|
|
||||||
|
import org.oxycblt.ktaglib.Properties
|
||||||
import org.oxycblt.musikr.cover.Cover
|
import org.oxycblt.musikr.cover.Cover
|
||||||
import org.oxycblt.musikr.fs.query.DeviceFile
|
import org.oxycblt.musikr.fs.query.DeviceFile
|
||||||
import org.oxycblt.musikr.tag.parse.ParsedTags
|
import org.oxycblt.musikr.tag.parse.ParsedTags
|
||||||
|
@ -34,7 +35,11 @@ interface Cache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class CachedSong(val parsedTags: ParsedTags, val cover: Cover.Single?)
|
data class CachedSong(
|
||||||
|
val parsedTags: ParsedTags,
|
||||||
|
val cover: Cover.Single?,
|
||||||
|
val properties: Properties
|
||||||
|
)
|
||||||
|
|
||||||
private class FullCache(private val cacheInfoDao: CacheInfoDao) : Cache {
|
private class FullCache(private val cacheInfoDao: CacheInfoDao) : Cache {
|
||||||
override suspend fun read(file: DeviceFile) =
|
override suspend fun read(file: DeviceFile) =
|
||||||
|
|
|
@ -30,6 +30,7 @@ import androidx.room.Room
|
||||||
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.ktaglib.Properties
|
||||||
import org.oxycblt.musikr.cover.Cover
|
import org.oxycblt.musikr.cover.Cover
|
||||||
import org.oxycblt.musikr.fs.query.DeviceFile
|
import org.oxycblt.musikr.fs.query.DeviceFile
|
||||||
import org.oxycblt.musikr.tag.Date
|
import org.oxycblt.musikr.tag.Date
|
||||||
|
@ -67,7 +68,6 @@ internal data class CachedInfo(
|
||||||
*/
|
*/
|
||||||
@PrimaryKey val uri: String,
|
@PrimaryKey val uri: String,
|
||||||
val dateModified: Long,
|
val dateModified: Long,
|
||||||
val durationMs: Long,
|
|
||||||
val replayGainTrackAdjustment: Float?,
|
val replayGainTrackAdjustment: Float?,
|
||||||
val replayGainAlbumAdjustment: Float?,
|
val replayGainAlbumAdjustment: Float?,
|
||||||
val musicBrainzId: String?,
|
val musicBrainzId: String?,
|
||||||
|
@ -88,7 +88,11 @@ internal data class CachedInfo(
|
||||||
val albumArtistNames: List<String>,
|
val albumArtistNames: List<String>,
|
||||||
val albumArtistSortNames: List<String>,
|
val albumArtistSortNames: List<String>,
|
||||||
val genreNames: List<String>,
|
val genreNames: List<String>,
|
||||||
val cover: Cover.Single? = null
|
val cover: Cover.Single?,
|
||||||
|
val mimeType: String,
|
||||||
|
val durationMs: Long,
|
||||||
|
val bitrate: Int,
|
||||||
|
val sampleRate: Int,
|
||||||
) {
|
) {
|
||||||
fun intoCachedSong() =
|
fun intoCachedSong() =
|
||||||
CachedSong(
|
CachedSong(
|
||||||
|
@ -114,7 +118,8 @@ internal data class CachedInfo(
|
||||||
albumArtistNames = albumArtistNames,
|
albumArtistNames = albumArtistNames,
|
||||||
albumArtistSortNames = albumArtistSortNames,
|
albumArtistSortNames = albumArtistSortNames,
|
||||||
genreNames = genreNames),
|
genreNames = genreNames),
|
||||||
cover)
|
cover,
|
||||||
|
Properties(mimeType, durationMs, bitrate, sampleRate))
|
||||||
|
|
||||||
object Converters {
|
object Converters {
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
|
@ -159,6 +164,9 @@ internal data class CachedInfo(
|
||||||
albumArtistNames = cachedSong.parsedTags.albumArtistNames,
|
albumArtistNames = cachedSong.parsedTags.albumArtistNames,
|
||||||
albumArtistSortNames = cachedSong.parsedTags.albumArtistSortNames,
|
albumArtistSortNames = cachedSong.parsedTags.albumArtistSortNames,
|
||||||
genreNames = cachedSong.parsedTags.genreNames,
|
genreNames = cachedSong.parsedTags.genreNames,
|
||||||
cover = cachedSong.cover)
|
cover = cachedSong.cover,
|
||||||
|
mimeType = cachedSong.properties.mimeType,
|
||||||
|
bitrate = cachedSong.properties.bitrate,
|
||||||
|
sampleRate = cachedSong.properties.sampleRate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
112
app/src/main/java/org/oxycblt/musikr/fs/Format.kt
Normal file
112
app/src/main/java/org/oxycblt/musikr/fs/Format.kt
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Auxio Project
|
||||||
|
* Format.kt is part of Auxio.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.oxycblt.musikr.fs
|
||||||
|
|
||||||
|
import android.webkit.MimeTypeMap
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
|
sealed interface Format {
|
||||||
|
val mimeType: String
|
||||||
|
|
||||||
|
data object MPEG3 : Format {
|
||||||
|
override val mimeType = "audio/mpeg"
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MPEG4(val containing: Format?) : Format {
|
||||||
|
override val mimeType = "audio/mp4"
|
||||||
|
}
|
||||||
|
|
||||||
|
data object AAC : Format {
|
||||||
|
override val mimeType = "audio/aac"
|
||||||
|
}
|
||||||
|
|
||||||
|
data object ALAC : Format {
|
||||||
|
override val mimeType = "audio/alac"
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Ogg(val containing: Format?) : Format {
|
||||||
|
override val mimeType = "audio/ogg"
|
||||||
|
}
|
||||||
|
|
||||||
|
data object Opus : Format {
|
||||||
|
override val mimeType = "audio/opus"
|
||||||
|
}
|
||||||
|
|
||||||
|
data object Vorbis : Format {
|
||||||
|
override val mimeType = "audio/vorbis"
|
||||||
|
}
|
||||||
|
|
||||||
|
data object FLAC : Format {
|
||||||
|
override val mimeType = "audio/flac"
|
||||||
|
}
|
||||||
|
|
||||||
|
data object Wav : Format {
|
||||||
|
override val mimeType = "audio/wav"
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Unknown(override val mimeType: String) : Format {
|
||||||
|
val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)?.uppercase()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val CODEC_MAP =
|
||||||
|
mapOf(
|
||||||
|
"audio/mpeg" to MPEG3,
|
||||||
|
"audio/mp3" to MPEG3,
|
||||||
|
"audio/aac" to AAC,
|
||||||
|
"audio/aacp" to AAC,
|
||||||
|
"audio/3gpp" to AAC,
|
||||||
|
"audio/3gpp2" to AAC,
|
||||||
|
"audio/alac" to ALAC,
|
||||||
|
"audio/opus" to Opus,
|
||||||
|
"audio/vorbis" to Vorbis,
|
||||||
|
"audio/flac" to FLAC,
|
||||||
|
"audio/wav" to Wav,
|
||||||
|
"audio/raw" to Wav,
|
||||||
|
"audio/x-wav" to Wav,
|
||||||
|
"audio/vnd.wave" to Wav,
|
||||||
|
"audio/wave" to Wav,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun infer(containerMimeType: String, codecMimeType: String): Format {
|
||||||
|
val codecFormat = CODEC_MAP[codecMimeType]
|
||||||
|
if (codecFormat != null) {
|
||||||
|
// Codec found, possibly wrap in container.
|
||||||
|
return unlikelyToBeNull(wrapInContainer(containerMimeType, codecFormat))
|
||||||
|
}
|
||||||
|
val extensionFormat = CODEC_MAP[containerMimeType]
|
||||||
|
if (extensionFormat != null) {
|
||||||
|
// Standalone container of some codec.
|
||||||
|
return extensionFormat
|
||||||
|
}
|
||||||
|
return wrapInContainer(containerMimeType, null) ?: Unknown(containerMimeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun wrapInContainer(containerMimeType: String, format: Format?) =
|
||||||
|
when (containerMimeType) {
|
||||||
|
"audio/mp4",
|
||||||
|
"audio/mp4a-latm",
|
||||||
|
"audio/mpeg4-generic" -> MPEG4(format)
|
||||||
|
"audio/ogg",
|
||||||
|
"application/ogg",
|
||||||
|
"application/x-ogg" -> Ogg(format)
|
||||||
|
else -> format
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,102 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2024 Auxio Project
|
|
||||||
* MimeType.kt is part of Auxio.
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.oxycblt.musikr.fs
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.media.MediaFormat
|
|
||||||
import android.webkit.MimeTypeMap
|
|
||||||
import org.oxycblt.auxio.R
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mime type of a file. Only intended for display.
|
|
||||||
*
|
|
||||||
* @param fromExtension The mime type obtained by analyzing the file extension.
|
|
||||||
* @param fromFormat The mime type obtained by analyzing the file format. Null if could not be
|
|
||||||
* obtained.
|
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
|
||||||
*
|
|
||||||
* TODO: Get around to simplifying this
|
|
||||||
*/
|
|
||||||
data class MimeType(val fromExtension: String, val fromFormat: String?) {
|
|
||||||
/**
|
|
||||||
* Resolve the mime type into a human-readable format name, such as "Ogg Vorbis".
|
|
||||||
*
|
|
||||||
* @param context [Context] required to obtain human-readable strings.
|
|
||||||
* @return A human-readable name for this mime type. Will first try [fromFormat], then falling
|
|
||||||
* back to [fromExtension], and then null if that fails.
|
|
||||||
*/
|
|
||||||
fun resolveName(context: Context): String? {
|
|
||||||
// We try our best to produce a more readable name for the common audio formats.
|
|
||||||
val formatName =
|
|
||||||
when (fromFormat) {
|
|
||||||
// We start with the extracted mime types, as they are more consistent. Note that
|
|
||||||
// we do not include container formats at all with these names. It is only the
|
|
||||||
// inner codec that we bother with.
|
|
||||||
MediaFormat.MIMETYPE_AUDIO_MPEG -> R.string.cdc_mp3
|
|
||||||
MediaFormat.MIMETYPE_AUDIO_AAC -> R.string.cdc_aac
|
|
||||||
MediaFormat.MIMETYPE_AUDIO_VORBIS -> R.string.cdc_vorbis
|
|
||||||
MediaFormat.MIMETYPE_AUDIO_OPUS -> R.string.cdc_opus
|
|
||||||
MediaFormat.MIMETYPE_AUDIO_FLAC -> R.string.cdc_flac
|
|
||||||
// TODO: Add ALAC to this as soon as I can stop using MediaFormat for
|
|
||||||
// extracting metadata and just use ExoPlayer.
|
|
||||||
// We don't give a name to more unpopular formats.
|
|
||||||
else -> -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formatName > -1) {
|
|
||||||
return context.getString(formatName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to the file extension in the case that we have no mime type or
|
|
||||||
// a useless "audio/raw" mime type. Here:
|
|
||||||
// - We return names for container formats instead of the inner format, as we
|
|
||||||
// cannot parse the file.
|
|
||||||
// - We are at the mercy of the Android OS, hence we check for every possible mime
|
|
||||||
// type for a particular format according to Wikipedia.
|
|
||||||
val extensionName =
|
|
||||||
when (fromExtension) {
|
|
||||||
"audio/mpeg",
|
|
||||||
"audio/mp3" -> R.string.cdc_mp3
|
|
||||||
"audio/mp4",
|
|
||||||
"audio/mp4a-latm",
|
|
||||||
"audio/mpeg4-generic" -> R.string.cdc_mp4
|
|
||||||
"audio/aac",
|
|
||||||
"audio/aacp",
|
|
||||||
"audio/3gpp",
|
|
||||||
"audio/3gpp2" -> R.string.cdc_aac
|
|
||||||
"audio/ogg",
|
|
||||||
"application/ogg",
|
|
||||||
"application/x-ogg" -> R.string.cdc_ogg
|
|
||||||
"audio/flac" -> R.string.cdc_flac
|
|
||||||
"audio/wav",
|
|
||||||
"audio/x-wav",
|
|
||||||
"audio/wave",
|
|
||||||
"audio/vnd.wave" -> R.string.cdc_wav
|
|
||||||
"audio/x-matroska" -> R.string.cdc_mka
|
|
||||||
else -> -1
|
|
||||||
}
|
|
||||||
|
|
||||||
return if (extensionName > -1) {
|
|
||||||
context.getString(extensionName)
|
|
||||||
} else {
|
|
||||||
// Fall back to the extension if we can't find a special name for this format.
|
|
||||||
MimeTypeMap.getSingleton().getExtensionFromMimeType(fromExtension)?.uppercase()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -24,7 +24,6 @@ import android.media.MediaFormat
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import org.oxycblt.musikr.Song
|
import org.oxycblt.musikr.Song
|
||||||
import org.oxycblt.musikr.fs.MimeType
|
|
||||||
import timber.log.Timber as L
|
import timber.log.Timber as L
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,14 +31,9 @@ import timber.log.Timber as L
|
||||||
*
|
*
|
||||||
* @param bitrateKbps The bit rate, in kilobytes-per-second. Null if it could not be parsed.
|
* @param bitrateKbps The bit rate, in kilobytes-per-second. Null if it could not be parsed.
|
||||||
* @param sampleRateHz The sample rate, in hertz.
|
* @param sampleRateHz The sample rate, in hertz.
|
||||||
* @param resolvedMimeType The known mime type of the [Song] after it's file format was determined.
|
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
data class AudioProperties(
|
data class AudioProperties(val bitrateKbps: Int?, val sampleRateHz: Int?) {
|
||||||
val bitrateKbps: Int?,
|
|
||||||
val sampleRateHz: Int?,
|
|
||||||
val resolvedMimeType: MimeType
|
|
||||||
) {
|
|
||||||
/** Implements the process of extracting [AudioProperties] from a given [Song]. */
|
/** Implements the process of extracting [AudioProperties] from a given [Song]. */
|
||||||
interface Factory {
|
interface Factory {
|
||||||
/**
|
/**
|
||||||
|
@ -75,7 +69,7 @@ constructor(@ApplicationContext private val context: Context) : AudioProperties.
|
||||||
// that we can show.
|
// that we can show.
|
||||||
L.w("Unable to extract song attributes.")
|
L.w("Unable to extract song attributes.")
|
||||||
L.w(e.stackTraceToString())
|
L.w(e.stackTraceToString())
|
||||||
return AudioProperties(null, null, song.mimeType)
|
return AudioProperties(null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the first track from the extractor (This is basically always the only
|
// Get the first track from the extractor (This is basically always the only
|
||||||
|
@ -102,23 +96,10 @@ constructor(@ApplicationContext private val context: Context) : AudioProperties.
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
// The song's mime type won't have a populated format field right now, try to
|
|
||||||
// extract it ourselves.
|
|
||||||
val formatMimeType =
|
|
||||||
try {
|
|
||||||
format.getString(MediaFormat.KEY_MIME)
|
|
||||||
} catch (e: NullPointerException) {
|
|
||||||
L.e("Unable to extract mime type field")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
extractor.release()
|
extractor.release()
|
||||||
|
|
||||||
L.d("Finished extracting audio properties")
|
L.d("Finished extracting audio properties")
|
||||||
|
|
||||||
return AudioProperties(
|
return AudioProperties(bitrate, sampleRate)
|
||||||
bitrate,
|
|
||||||
sampleRate,
|
|
||||||
MimeType(fromExtension = song.mimeType.fromExtension, fromFormat = formatMimeType))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ class SongImpl(private val handle: SongCore) : Song {
|
||||||
override val date = preSong.date
|
override val date = preSong.date
|
||||||
override val uri = preSong.uri
|
override val uri = preSong.uri
|
||||||
override val path = preSong.path
|
override val path = preSong.path
|
||||||
override val mimeType = preSong.mimeType
|
override val format = preSong.format
|
||||||
override val size = preSong.size
|
override val size = preSong.size
|
||||||
override val durationMs = preSong.durationMs
|
override val durationMs = preSong.durationMs
|
||||||
override val replayGainAdjustment = preSong.replayGainAdjustment
|
override val replayGainAdjustment = preSong.replayGainAdjustment
|
||||||
|
|
|
@ -53,7 +53,10 @@ private class EvaluateStepImpl(
|
||||||
val preSongs =
|
val preSongs =
|
||||||
extractedMusic
|
extractedMusic
|
||||||
.filterIsInstance<ExtractedMusic.Song>()
|
.filterIsInstance<ExtractedMusic.Song>()
|
||||||
.map { tagInterpreter.interpret(it.file, it.tags, it.cover, interpretation) }
|
.map {
|
||||||
|
tagInterpreter.interpret(
|
||||||
|
it.file, it.tags, it.cover, it.properties, interpretation)
|
||||||
|
}
|
||||||
.flowOn(Dispatchers.Main)
|
.flowOn(Dispatchers.Main)
|
||||||
.buffer(Channel.UNLIMITED)
|
.buffer(Channel.UNLIMITED)
|
||||||
val graphBuilder = MusicGraph.builder()
|
val graphBuilder = MusicGraph.builder()
|
||||||
|
|
|
@ -28,6 +28,7 @@ import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.mapNotNull
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
import kotlinx.coroutines.flow.merge
|
import kotlinx.coroutines.flow.merge
|
||||||
|
import org.oxycblt.ktaglib.Properties
|
||||||
import org.oxycblt.musikr.Storage
|
import org.oxycblt.musikr.Storage
|
||||||
import org.oxycblt.musikr.cache.CachedSong
|
import org.oxycblt.musikr.cache.CachedSong
|
||||||
import org.oxycblt.musikr.cover.Cover
|
import org.oxycblt.musikr.cover.Cover
|
||||||
|
@ -63,7 +64,7 @@ private class ExtractStepImpl(
|
||||||
val (cachedSongs, uncachedSongs) =
|
val (cachedSongs, uncachedSongs) =
|
||||||
cacheResults.mapPartition {
|
cacheResults.mapPartition {
|
||||||
it.cachedSong?.let { song ->
|
it.cachedSong?.let { song ->
|
||||||
ExtractedMusic.Song(it.file, song.parsedTags, song.cover)
|
ExtractedMusic.Song(it.file, song.properties, song.parsedTags, song.cover)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val split = uncachedSongs.distribute(16)
|
val split = uncachedSongs.distribute(16)
|
||||||
|
@ -73,10 +74,9 @@ private class ExtractStepImpl(
|
||||||
.mapNotNull { node ->
|
.mapNotNull { node ->
|
||||||
val metadata =
|
val metadata =
|
||||||
metadataExtractor.extract(node.file) ?: return@mapNotNull null
|
metadataExtractor.extract(node.file) ?: return@mapNotNull null
|
||||||
L.d("Extracted tags for ${metadata.id3v2}")
|
|
||||||
val tags = tagParser.parse(node.file, metadata)
|
val tags = tagParser.parse(node.file, metadata)
|
||||||
val cover = metadata.cover?.let { storage.storedCovers.write(it) }
|
val cover = metadata.cover?.let { storage.storedCovers.write(it) }
|
||||||
ExtractedMusic.Song(node.file, tags, cover)
|
ExtractedMusic.Song(node.file, metadata.properties, tags, cover)
|
||||||
}
|
}
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
.buffer(Channel.UNLIMITED)
|
.buffer(Channel.UNLIMITED)
|
||||||
|
@ -84,7 +84,7 @@ private class ExtractStepImpl(
|
||||||
val writtenSongs =
|
val writtenSongs =
|
||||||
merge(*extractedSongs)
|
merge(*extractedSongs)
|
||||||
.map {
|
.map {
|
||||||
storage.cache.write(it.file, CachedSong(it.tags, it.cover))
|
storage.cache.write(it.file, CachedSong(it.tags, it.cover, it.properties))
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
|
@ -100,6 +100,10 @@ private class ExtractStepImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface ExtractedMusic {
|
sealed interface ExtractedMusic {
|
||||||
data class Song(val file: DeviceFile, val tags: ParsedTags, val cover: Cover.Single?) :
|
data class Song(
|
||||||
ExtractedMusic
|
val file: DeviceFile,
|
||||||
|
val properties: Properties,
|
||||||
|
val tags: ParsedTags,
|
||||||
|
val cover: Cover.Single?
|
||||||
|
) : ExtractedMusic
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment
|
||||||
import org.oxycblt.auxio.util.update
|
import org.oxycblt.auxio.util.update
|
||||||
import org.oxycblt.musikr.Music
|
import org.oxycblt.musikr.Music
|
||||||
import org.oxycblt.musikr.cover.Cover
|
import org.oxycblt.musikr.cover.Cover
|
||||||
import org.oxycblt.musikr.fs.MimeType
|
import org.oxycblt.musikr.fs.Format
|
||||||
import org.oxycblt.musikr.fs.Path
|
import org.oxycblt.musikr.fs.Path
|
||||||
import org.oxycblt.musikr.playlist.PlaylistHandle
|
import org.oxycblt.musikr.playlist.PlaylistHandle
|
||||||
import org.oxycblt.musikr.tag.Date
|
import org.oxycblt.musikr.tag.Date
|
||||||
|
@ -42,7 +42,7 @@ data class PreSong(
|
||||||
val date: Date?,
|
val date: Date?,
|
||||||
val uri: Uri,
|
val uri: Uri,
|
||||||
val path: Path,
|
val path: Path,
|
||||||
val mimeType: MimeType,
|
val format: Format,
|
||||||
val size: Long,
|
val size: Long,
|
||||||
val durationMs: Long,
|
val durationMs: Long,
|
||||||
val replayGainAdjustment: ReplayGainAdjustment,
|
val replayGainAdjustment: ReplayGainAdjustment,
|
||||||
|
|
|
@ -21,9 +21,10 @@ package org.oxycblt.musikr.tag.interpret
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
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
|
||||||
|
import org.oxycblt.ktaglib.Properties
|
||||||
import org.oxycblt.musikr.Interpretation
|
import org.oxycblt.musikr.Interpretation
|
||||||
import org.oxycblt.musikr.cover.Cover
|
import org.oxycblt.musikr.cover.Cover
|
||||||
import org.oxycblt.musikr.fs.MimeType
|
import org.oxycblt.musikr.fs.Format
|
||||||
import org.oxycblt.musikr.fs.query.DeviceFile
|
import org.oxycblt.musikr.fs.query.DeviceFile
|
||||||
import org.oxycblt.musikr.tag.Disc
|
import org.oxycblt.musikr.tag.Disc
|
||||||
import org.oxycblt.musikr.tag.Name
|
import org.oxycblt.musikr.tag.Name
|
||||||
|
@ -36,6 +37,7 @@ interface TagInterpreter {
|
||||||
file: DeviceFile,
|
file: DeviceFile,
|
||||||
parsedTags: ParsedTags,
|
parsedTags: ParsedTags,
|
||||||
cover: Cover.Single?,
|
cover: Cover.Single?,
|
||||||
|
properties: Properties,
|
||||||
interpretation: Interpretation
|
interpretation: Interpretation
|
||||||
): PreSong
|
): PreSong
|
||||||
|
|
||||||
|
@ -49,6 +51,7 @@ private data object TagInterpreterImpl : TagInterpreter {
|
||||||
file: DeviceFile,
|
file: DeviceFile,
|
||||||
parsedTags: ParsedTags,
|
parsedTags: ParsedTags,
|
||||||
cover: Cover.Single?,
|
cover: Cover.Single?,
|
||||||
|
properties: Properties,
|
||||||
interpretation: Interpretation
|
interpretation: Interpretation
|
||||||
): PreSong {
|
): PreSong {
|
||||||
val individualPreArtists =
|
val individualPreArtists =
|
||||||
|
@ -79,7 +82,6 @@ private data object TagInterpreterImpl : TagInterpreter {
|
||||||
date = parsedTags.date,
|
date = parsedTags.date,
|
||||||
uri = uri,
|
uri = uri,
|
||||||
path = file.path,
|
path = file.path,
|
||||||
mimeType = MimeType(file.mimeType, null),
|
|
||||||
size = file.size,
|
size = file.size,
|
||||||
durationMs = parsedTags.durationMs,
|
durationMs = parsedTags.durationMs,
|
||||||
replayGainAdjustment =
|
replayGainAdjustment =
|
||||||
|
@ -87,6 +89,7 @@ private data object TagInterpreterImpl : TagInterpreter {
|
||||||
parsedTags.replayGainTrackAdjustment,
|
parsedTags.replayGainTrackAdjustment,
|
||||||
parsedTags.replayGainAlbumAdjustment,
|
parsedTags.replayGainAlbumAdjustment,
|
||||||
),
|
),
|
||||||
|
format = Format.infer(file.mimeType, properties.mimeType),
|
||||||
lastModified = file.lastModified,
|
lastModified = file.lastModified,
|
||||||
// TODO: Figure out what to do with date added
|
// TODO: Figure out what to do with date added
|
||||||
dateAdded = file.lastModified,
|
dateAdded = file.lastModified,
|
||||||
|
|
Loading…
Reference in a new issue