music: add texttags tests

Add tests for the TextTags processing wrapper.
This commit is contained in:
Alexander Capehart 2023-01-05 14:24:30 -07:00
parent 502dd8ccc4
commit 782b570b38
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
5 changed files with 113 additions and 14 deletions

View file

@ -30,7 +30,7 @@ jobs:
- name: Grant execute permission for gradlew - name: Grant execute permission for gradlew
run: chmod +x gradlew run: chmod +x gradlew
- name: Test app with Gradle - name: Test app with Gradle
run: ./gradlew app:test run: ./gradlew app:testDebug
- name: Build debug APK with Gradle - name: Build debug APK with Gradle
run: ./gradlew app:packageDebug run: ./gradlew app:packageDebug
- name: Upload debug APK artifact - name: Upload debug APK artifact

View file

@ -160,9 +160,9 @@ class Task(context: Context, private val raw: Song.Raw) {
val metadata = format.metadata val metadata = format.metadata
if (metadata != null) { if (metadata != null) {
val tags = Tags(metadata) val textTags = TextTags(metadata)
populateWithId3v2(tags.id3v2) populateWithId3v2(textTags.id3v2)
populateWithVorbis(tags.vorbis) populateWithVorbis(textTags.vorbis)
} else { } else {
logD("No metadata could be extracted for ${raw.name}") logD("No metadata could be extracted for ${raw.name}")
} }

View file

@ -24,11 +24,11 @@ import com.google.android.exoplayer2.metadata.vorbis.VorbisComment
import org.oxycblt.auxio.music.parsing.correctWhitespace import org.oxycblt.auxio.music.parsing.correctWhitespace
/** /**
* Processing wrapper for [Metadata] that allows access to more organized music tags. * Processing wrapper for [Metadata] that allows organized access to text-based audio tags.
* @param metadata The [Metadata] to wrap. * @param metadata The [Metadata] to wrap.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class Tags(metadata: Metadata) { class TextTags(metadata: Metadata) {
private val _id3v2 = mutableMapOf<String, List<String>>() private val _id3v2 = mutableMapOf<String, List<String>>()
/** The ID3v2 text identification frames found in the file. Can have more than one value. */ /** The ID3v2 text identification frames found in the file. Can have more than one value. */
val id3v2: Map<String, List<String>> val id3v2: Map<String, List<String>>
@ -65,6 +65,10 @@ class Tags(metadata: Metadata) {
is VorbisComment -> { is VorbisComment -> {
// Vorbis comment keys can be in any case, make them uppercase for simplicity. // Vorbis comment keys can be in any case, make them uppercase for simplicity.
val id = tag.key.sanitize().lowercase() val id = tag.key.sanitize().lowercase()
if (id == "metadata_block_picture") {
// Picture, we don't care about these
continue
}
val value = tag.value.sanitize().correctWhitespace() val value = tag.value.sanitize().correctWhitespace()
if (value != null) { if (value != null) {
_vorbis.getOrPut(id) { mutableListOf() }.add(value) _vorbis.getOrPut(id) { mutableListOf() }.add(value)

View file

@ -30,7 +30,7 @@ import java.nio.ByteBuffer
import kotlin.math.pow import kotlin.math.pow
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.extractor.Tags import org.oxycblt.auxio.music.extractor.TextTags
import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -166,23 +166,23 @@ class ReplayGainAudioProcessor(private val context: Context) :
* @return A [Adjustment] adjustment, or null if there were no valid adjustments. * @return A [Adjustment] adjustment, or null if there were no valid adjustments.
*/ */
private fun parseReplayGain(format: Format): Adjustment? { private fun parseReplayGain(format: Format): Adjustment? {
val tags = Tags(format.metadata ?: return null) val textTags = TextTags(format.metadata ?: return null)
var trackGain = 0f var trackGain = 0f
var albumGain = 0f var albumGain = 0f
// Most ReplayGain tags are formatted as a simple decibel adjustment in a custom // Most ReplayGain tags are formatted as a simple decibel adjustment in a custom
// replaygain_*_gain tag. // replaygain_*_gain tag.
if (format.sampleMimeType != MimeTypes.AUDIO_OPUS) { if (format.sampleMimeType != MimeTypes.AUDIO_OPUS) {
tags.id3v2["TXXX:$TAG_RG_TRACK_GAIN"] textTags.id3v2["TXXX:$TAG_RG_TRACK_GAIN"]
?.run { first().parseReplayGainAdjustment() } ?.run { first().parseReplayGainAdjustment() }
?.let { trackGain = it } ?.let { trackGain = it }
tags.id3v2["TXXX:$TAG_RG_ALBUM_GAIN"] textTags.id3v2["TXXX:$TAG_RG_ALBUM_GAIN"]
?.run { first().parseReplayGainAdjustment() } ?.run { first().parseReplayGainAdjustment() }
?.let { albumGain = it } ?.let { albumGain = it }
tags.vorbis[TAG_RG_ALBUM_GAIN] textTags.vorbis[TAG_RG_ALBUM_GAIN]
?.run { first().parseReplayGainAdjustment() } ?.run { first().parseReplayGainAdjustment() }
?.let { trackGain = it } ?.let { trackGain = it }
tags.vorbis[TAG_RG_TRACK_GAIN] textTags.vorbis[TAG_RG_TRACK_GAIN]
?.run { first().parseReplayGainAdjustment() } ?.run { first().parseReplayGainAdjustment() }
?.let { albumGain = it } ?.let { albumGain = it }
} else { } else {
@ -191,10 +191,10 @@ class ReplayGainAudioProcessor(private val context: Context) :
// intrinsic to the format to create the normalized adjustment. That base adjustment // intrinsic to the format to create the normalized adjustment. That base adjustment
// is already handled by the media framework, so we just need to apply the more // is already handled by the media framework, so we just need to apply the more
// specific adjustments. // specific adjustments.
tags.vorbis[TAG_R128_TRACK_GAIN] textTags.vorbis[TAG_R128_TRACK_GAIN]
?.run { first().parseReplayGainAdjustment() } ?.run { first().parseReplayGainAdjustment() }
?.let { trackGain = it / 256f } ?.let { trackGain = it / 256f }
tags.vorbis[TAG_R128_ALBUM_GAIN] textTags.vorbis[TAG_R128_ALBUM_GAIN]
?.run { first().parseReplayGainAdjustment() } ?.run { first().parseReplayGainAdjustment() }
?.let { albumGain = it / 256f } ?.let { albumGain = it / 256f }
} }

View file

@ -0,0 +1,95 @@
/*
* Copyright (c) 2023 Auxio Project
*
* 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.music.extractor
import com.google.android.exoplayer2.metadata.Metadata
import com.google.android.exoplayer2.metadata.flac.PictureFrame
import com.google.android.exoplayer2.metadata.id3.ApicFrame
import com.google.android.exoplayer2.metadata.id3.InternalFrame
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame
import com.google.android.exoplayer2.metadata.vorbis.VorbisComment
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
class TextTagsTest {
@Test
fun textTags_vorbis() {
val textTags = TextTags(VORBIS_METADATA)
assertTrue(textTags.id3v2.isEmpty())
assertEquals(listOf("Wheel"), textTags.vorbis["title"])
assertEquals(listOf("Paraglow"), textTags.vorbis["album"])
assertEquals(listOf("Parannoul", "Asian Glow"), textTags.vorbis["artist"])
assertEquals(listOf("2022"), textTags.vorbis["date"])
assertEquals(listOf("ep"), textTags.vorbis["releasetype"])
assertEquals(listOf("+2 dB"), textTags.vorbis["replaygain_track_gain"])
}
@Test
fun textTags_id3v2() {
val textTags = TextTags(ID3V2_METADATA)
assertTrue(textTags.vorbis.isEmpty())
assertEquals(listOf("Wheel"), textTags.id3v2["TIT2"])
assertEquals(listOf("Paraglow"), textTags.id3v2["TALB"])
assertEquals(listOf("Parannoul", "Asian Glow"), textTags.id3v2["TPE1"])
assertEquals(listOf("2022"), textTags.id3v2["TDRC"])
assertEquals(listOf("ep"), textTags.id3v2["TXXX:musicbrainz album type"])
assertEquals(listOf("+2 dB"), textTags.id3v2["TXXX:replaygain_track_gain"])
}
@Test
fun textTags_combined() {
val textTags = TextTags(VORBIS_METADATA.copyWithAppendedEntriesFrom(ID3V2_METADATA))
assertEquals(listOf("Wheel"), textTags.vorbis["title"])
assertEquals(listOf("Paraglow"), textTags.vorbis["album"])
assertEquals(listOf("Parannoul", "Asian Glow"), textTags.vorbis["artist"])
assertEquals(listOf("2022"), textTags.vorbis["date"])
assertEquals(listOf("ep"), textTags.vorbis["releasetype"])
assertEquals(listOf("+2 dB"), textTags.vorbis["replaygain_track_gain"])
assertEquals(listOf("Wheel"), textTags.id3v2["TIT2"])
assertEquals(listOf("Paraglow"), textTags.id3v2["TALB"])
assertEquals(listOf("Parannoul", "Asian Glow"), textTags.id3v2["TPE1"])
assertEquals(listOf("2022"), textTags.id3v2["TDRC"])
assertEquals(listOf("ep"), textTags.id3v2["TXXX:musicbrainz album type"])
assertEquals(listOf("+2 dB"), textTags.id3v2["TXXX:replaygain_track_gain"])
}
companion object {
private val VORBIS_METADATA =
Metadata(
VorbisComment("TITLE", "Wheel"),
VorbisComment("ALBUM", "Paraglow"),
VorbisComment("ARTIST", "Parannoul"),
VorbisComment("ARTIST", "Asian Glow"),
VorbisComment("DATE", "2022"),
VorbisComment("RELEASETYPE", "ep"),
VorbisComment("METADATA_BLOCK_PICTURE", ""),
VorbisComment("REPLAYGAIN_TRACK_GAIN", "+2 dB"),
PictureFrame(0, "", "", 0, 0, 0, 0, byteArrayOf()))
private val ID3V2_METADATA =
Metadata(
TextInformationFrame("TIT2", null, listOf("Wheel")),
TextInformationFrame("TALB", null, listOf("Paraglow")),
TextInformationFrame("TPE1", null, listOf("Parannoul", "Asian Glow")),
TextInformationFrame("TDRC", null, listOf("2022")),
TextInformationFrame("TXXX", "MusicBrainz Album Type", listOf("ep")),
InternalFrame("com.apple.iTunes", "replaygain_track_gain", "+2 dB"),
ApicFrame("", "", 0, byteArrayOf()))
}
}