Compare commits

..

4 commits
dev ... master

Author SHA1 Message Date
Alexander Capehart
2b51b836af
Merge pull request #1061 from OxygenCobalt/dev
v4.0.4
2025-03-18 16:08:37 -06:00
Alexander Capehart
2e212af77b
Merge pull request #1058 from OxygenCobalt/dev
v4.0.3 (with FSCovers fix)
2025-03-18 13:49:15 -06:00
Alexander Capehart
8f41f35bc9
Merge pull request #1057 from OxygenCobalt/dev
v4.0.3 (with backported fix)
2025-03-18 13:15:19 -06:00
Alexander Capehart
9c6798a450
Merge pull request #1056 from OxygenCobalt/dev
v4.0.3
2025-03-18 12:38:05 -06:00
5 changed files with 7 additions and 802 deletions

View file

@ -41,12 +41,12 @@ abstract class Naming {
}
}
data object IntelligentNaming : Naming() {
override fun name(raw: String, sort: String?): Name.Known = IntelligentKnownName(raw, sort)
private data object IntelligentNaming : Naming() {
override fun name(raw: String, sort: String?) = IntelligentKnownName(raw, sort)
}
data object SimpleNaming : Naming() {
override fun name(raw: String, sort: String?): Name.Known = SimpleKnownName(raw, sort)
private data object SimpleNaming : Naming() {
override fun name(raw: String, sort: String?) = SimpleKnownName(raw, sort)
}
private val collator: Collator = Collator.getInstance().apply { strength = Collator.PRIMARY }

View file

@ -16,16 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.musikr.tag.interpret
package org.oxycblt.musikr.tag
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Test
import org.oxycblt.musikr.tag.Name
import org.oxycblt.musikr.tag.Placeholder
import org.oxycblt.musikr.tag.Token
import org.oxycblt.musikr.tag.interpret.Naming
class NamingTest {
class NameTest {
@Test
fun name_simple_withoutPunct() {
val name = Naming.simple().name("Loveless", null)

View file

@ -1,335 +0,0 @@
/*
* Copyright (c) 2024 Auxio Project
* TagFieldsTest.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.tag.parse
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.oxycblt.musikr.metadata.Metadata
import org.oxycblt.musikr.metadata.Properties
import org.oxycblt.musikr.tag.Date
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [30])
class TagFieldsTest {
@Test
fun metadata_name_formats() {
// Test ID3v2 format
var metadata = createTestMetadata(id3v2Tags = mapOf("TIT2" to listOf("ID3 Title")))
assertEquals("ID3 Title", metadata.name())
// Test MP4 format
metadata = createTestMetadata(mp4Tags = mapOf("©nam" to listOf("MP4 Title")))
assertEquals("MP4 Title", metadata.name())
// Test Xiph format
metadata = createTestMetadata(xiphTags = mapOf("TITLE" to listOf("Xiph Title")))
assertEquals("Xiph Title", metadata.name())
// Test priority (xiph > mp4 > id3v2)
metadata = createTestMetadata(
id3v2Tags = mapOf("TIT2" to listOf("ID3 Title")),
mp4Tags = mapOf("©nam" to listOf("MP4 Title")),
xiphTags = mapOf("TITLE" to listOf("Xiph Title"))
)
assertEquals("Xiph Title", metadata.name())
}
@Test
fun metadata_track_formats() {
// Test ID3v2 format
var metadata = createTestMetadata(id3v2Tags = mapOf("TRCK" to listOf("5/10")))
assertEquals(5, metadata.track())
// Test ID3v2 format without total
metadata = createTestMetadata(id3v2Tags = mapOf("TRCK" to listOf("5")))
assertEquals(5, metadata.track())
// Test MP4 format
metadata = createTestMetadata(mp4Tags = mapOf("trkn" to listOf("7/12")))
assertEquals(7, metadata.track())
// Test Xiph format
metadata = createTestMetadata(
xiphTags = mapOf(
"TRACKNUMBER" to listOf("9"),
"TOTALTRACKS" to listOf("15")
)
)
assertEquals(9, metadata.track())
// Test Xiph alternative total tracks
metadata = createTestMetadata(
xiphTags = mapOf(
"TRACKNUMBER" to listOf("8"),
"TRACKTOTAL" to listOf("16")
)
)
assertEquals(8, metadata.track())
}
@Test
fun metadata_disc_formats() {
// Test ID3v2 format
var metadata = createTestMetadata(id3v2Tags = mapOf("TPOS" to listOf("2/3")))
assertEquals(2, metadata.disc())
// Test MP4 format
metadata = createTestMetadata(mp4Tags = mapOf("disk" to listOf("1/2")))
assertEquals(1, metadata.disc())
// Test Xiph format
metadata = createTestMetadata(
xiphTags = mapOf(
"DISCNUMBER" to listOf("3"),
"TOTALDISCS" to listOf("4")
)
)
assertEquals(3, metadata.disc())
// Test Xiph alternative total discs
metadata = createTestMetadata(
xiphTags = mapOf(
"DISCNUMBER" to listOf("2"),
"DISCTOTAL" to listOf("5")
)
)
assertEquals(2, metadata.disc())
}
@Test
fun metadata_subtitle() {
var metadata = createTestMetadata(id3v2Tags = mapOf("TSST" to listOf("Bonus Disc")))
assertEquals("Bonus Disc", metadata.subtitle())
metadata = createTestMetadata(xiphTags = mapOf("DISCSUBTITLE" to listOf("Rarities")))
assertEquals("Rarities", metadata.subtitle())
// Test priority
metadata = createTestMetadata(
id3v2Tags = mapOf("TSST" to listOf("ID3 Subtitle")),
xiphTags = mapOf("DISCSUBTITLE" to listOf("Xiph Subtitle"))
)
assertEquals("Xiph Subtitle", metadata.subtitle())
}
@Test
fun metadata_date_formats() {
// Test simple year
var metadata = createTestMetadata(id3v2Tags = mapOf("TDRC" to listOf("2022")))
assertEquals("2022", metadata.date().toString())
// Test ISO-8601 date
metadata = createTestMetadata(id3v2Tags = mapOf("TDRC" to listOf("2022-05-17")))
assertEquals("2022-05-17", metadata.date().toString())
// Test ISO-8601 datetime
metadata = createTestMetadata(id3v2Tags = mapOf("TDRC" to listOf("2022-05-17T14:30:00")))
assertEquals("2022-05-17T14:30:00Z", metadata.date().toString())
// Test Xiph date
metadata = createTestMetadata(xiphTags = mapOf("DATE" to listOf("2021-12-25")))
assertEquals("2021-12-25", metadata.date().toString())
// Test date priority (ORIGINALDATE > DATE)
metadata = createTestMetadata(
xiphTags = mapOf(
"DATE" to listOf("2023-01-01"),
"ORIGINALDATE" to listOf("2021-01-01")
)
)
assertEquals("2021-01-01", metadata.date().toString())
// Test MP4 date
metadata = createTestMetadata(mp4Tags = mapOf("©day" to listOf("2020-11-30")))
assertEquals("2020-11-30", metadata.date().toString())
}
@Test
fun metadata_id3v23Date() {
// Test ID3v2.3 date components
val metadata = createTestMetadata(
id3v2Tags = mapOf(
"TYER" to listOf("2019"),
"TDAT" to listOf("0523"), // May 23
"TIME" to listOf("2145") // 21:45
)
)
val date = metadata.date()
assertEquals(2019, date?.year)
assertEquals(5, date?.month)
assertEquals(23, date?.day)
assertEquals(21, date?.hour)
assertEquals(45, date?.minute)
}
@Test
fun metadata_albumInfo() {
val metadata = createTestMetadata(
id3v2Tags = mapOf(
"TALB" to listOf("Album Name"),
"TSOA" to listOf("Sort Album Name"),
"TXXX:RELEASETYPE" to listOf("compilation")
)
)
assertEquals("Album Name", metadata.albumName())
assertEquals("Sort Album Name", metadata.albumSortName())
assertEquals(listOf("compilation"), metadata.releaseTypes())
}
@Test
fun metadata_artistInfo() {
val metadata = createTestMetadata(
id3v2Tags = mapOf(
"TPE1" to listOf("Artist 1"),
"TPE2" to listOf("Album Artist 1"),
"TSOP" to listOf("Artist 1 Sort"),
"TSO2" to listOf("Album Artist 1 Sort")
)
)
assertEquals(listOf("Artist 1"), metadata.artistNames())
assertEquals(listOf("Album Artist 1"), metadata.albumArtistNames())
assertEquals(listOf("Artist 1 Sort"), metadata.artistSortNames())
assertEquals(listOf("Album Artist 1 Sort"), metadata.albumArtistSortNames())
}
@Test
fun metadata_musicBrainzIds() {
// Test different formats and variants of MusicBrainz identifiers
val metadata = createTestMetadata(
xiphTags = mapOf(
"MUSICBRAINZ_RELEASETRACKID" to listOf("track-mb-id"),
"MUSICBRAINZ_ALBUMID" to listOf("album-mb-id")
)
)
assertEquals("track-mb-id", metadata.musicBrainzId())
assertEquals("album-mb-id", metadata.albumMusicBrainzId())
// Test Apple iTunes MP4 variants
val metadata2 = createTestMetadata(
mp4Tags = mapOf(
"----:COM.APPLE.ITUNES:MUSICBRAINZ RELEASE TRACK ID" to listOf("track-mb-id-2"),
"----:COM.APPLE.ITUNES:MUSICBRAINZ ALBUM ID" to listOf("album-mb-id-2")
)
)
assertEquals("track-mb-id-2", metadata2.musicBrainzId())
assertEquals("album-mb-id-2", metadata2.albumMusicBrainzId())
}
@Test
fun metadata_isCompilation() {
// Test various compilation flags
var metadata = createTestMetadata(
id3v2Tags = mapOf("TCMP" to listOf("1"))
)
assertTrue(metadata.isCompilation())
metadata = createTestMetadata(
id3v2Tags = mapOf("TCMP" to listOf("0"))
)
assertFalse(metadata.isCompilation())
metadata = createTestMetadata(
xiphTags = mapOf("COMPILATION" to listOf("1"))
)
assertTrue(metadata.isCompilation())
metadata = createTestMetadata(
mp4Tags = mapOf("cpil" to listOf("1"))
)
assertTrue(metadata.isCompilation())
// Test invalid value
metadata = createTestMetadata(
id3v2Tags = mapOf("TCMP" to listOf("yes"))
)
assertFalse(metadata.isCompilation())
}
@Test
fun metadata_replayGain() {
// Test ReplayGain track adjustment
var metadata = createTestMetadata(
xiphTags = mapOf("REPLAYGAIN_TRACK_GAIN" to listOf("-5.5 dB"))
)
assertEquals(-5.5f, metadata.replayGainTrackAdjustment())
// Test ReplayGain album adjustment
metadata = createTestMetadata(
xiphTags = mapOf("REPLAYGAIN_ALBUM_GAIN" to listOf("3.2 dB"))
)
assertEquals(3.2f, metadata.replayGainAlbumAdjustment())
// Test R128 track gain
metadata = createTestMetadata(
xiphTags = mapOf("R128_TRACK_GAIN" to listOf("-2560"))
)
// R128 is converted to match ReplayGain scale (-2560/256 + 5 = -5.0)
assertEquals(-5.0f, metadata.replayGainTrackAdjustment())
// Test zero value
metadata = createTestMetadata(
xiphTags = mapOf("REPLAYGAIN_TRACK_GAIN" to listOf("0.0 dB"))
)
assertNull(metadata.replayGainTrackAdjustment())
}
@Test
fun metadata_genreNames() {
var metadata = createTestMetadata(
id3v2Tags = mapOf("TCON" to listOf("Rock", "Electronic"))
)
assertEquals(listOf("Rock", "Electronic"), metadata.genreNames())
metadata = createTestMetadata(
mp4Tags = mapOf("©gen" to listOf("Hip Hop", "Jazz"))
)
assertEquals(listOf("Hip Hop", "Jazz"), metadata.genreNames())
metadata = createTestMetadata(
xiphTags = mapOf("GENRE" to listOf("Classical", "Ambient"))
)
assertEquals(listOf("Classical", "Ambient"), metadata.genreNames())
}
private fun createTestMetadata(
id3v2Tags: Map<String, List<String>> = emptyMap(),
xiphTags: Map<String, List<String>> = emptyMap(),
mp4Tags: Map<String, List<String>> = emptyMap(),
cover: ByteArray? = null,
durationMs: Long = 1000,
bitrateKbps: Int = 320,
sampleRateHz: Int = 44100,
mimeType: String = "audio/mpeg"
): Metadata {
val properties = Properties(mimeType, durationMs, bitrateKbps, sampleRateHz)
return Metadata(id3v2Tags, xiphTags, mp4Tags, cover, properties)
}
}

View file

@ -1,178 +0,0 @@
/*
* Copyright (c) 2024 Auxio Project
* TagParserTest.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.tag.parse
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
import org.oxycblt.musikr.metadata.Metadata
import org.oxycblt.musikr.metadata.Properties
import org.oxycblt.musikr.tag.Date
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [30])
class TagParserTest {
private val tagParser = TagParser.new()
@Test
fun tagParser_parse_basic() {
val metadata = createTestMetadata(
id3v2Tags = mapOf(
"TIT2" to listOf("Test Song"),
"TALB" to listOf("Test Album"),
"TPE1" to listOf("Test Artist"),
"TPE2" to listOf("Test Album Artist"),
"TYER" to listOf("2020"),
"TRCK" to listOf("1/10"),
"TPOS" to listOf("1/2"),
"TCON" to listOf("Rock", "Electronic")
)
)
val tags = tagParser.parse(metadata)
assertEquals("Test Song", tags.name)
assertEquals("Test Album", tags.albumName)
assertEquals(listOf("Test Artist"), tags.artistNames)
assertEquals(listOf("Test Album Artist"), tags.albumArtistNames)
assertEquals(2020, tags.date?.year)
assertEquals(1, tags.track)
assertEquals(1, tags.disc)
assertEquals(listOf("Rock", "Electronic"), tags.genreNames)
}
@Test
fun tagParser_parse_multipleFormats() {
// Test priority handling between different tag formats
val metadata = createTestMetadata(
id3v2Tags = mapOf(
"TIT2" to listOf("ID3 Song"),
"TALB" to listOf("ID3 Album")
),
xiphTags = mapOf(
"TITLE" to listOf("Xiph Song"),
"ALBUM" to listOf("Xiph Album")
),
mp4Tags = mapOf(
"©nam" to listOf("MP4 Song"),
"©alb" to listOf("MP4 Album")
)
)
val tags = tagParser.parse(metadata)
// Check priority: xiph > mp4 > id3v2
assertEquals("Xiph Song", tags.name)
assertEquals("Xiph Album", tags.albumName)
}
@Test
fun tagParser_parse_compilation() {
// Test compilation album behavior
val metadata = createTestMetadata(
id3v2Tags = mapOf(
"TCMP" to listOf("1")
)
)
val tags = tagParser.parse(metadata)
assertEquals(listOf("Various Artists"), tags.albumArtistNames)
assertEquals(listOf("compilation"), tags.releaseTypes)
}
@Test
fun tagParser_parse_compilationWithReleaseType() {
// Test compilation album with explicit release type
val metadata = createTestMetadata(
id3v2Tags = mapOf(
"TCMP" to listOf("1"),
"TXXX:RELEASETYPE" to listOf("soundtrack")
)
)
val tags = tagParser.parse(metadata)
assertEquals(listOf("Various Artists"), tags.albumArtistNames)
assertEquals(listOf("soundtrack"), tags.releaseTypes)
}
@Test
fun tagParser_parse_empty() {
// Test handling of empty metadata
val metadata = createTestMetadata()
val tags = tagParser.parse(metadata)
assertNull(tags.name)
assertNull(tags.albumName)
assertEquals(emptyList<String>(), tags.artistNames)
}
@Test
fun tagParser_musicBrainzIds() {
val metadata = createTestMetadata(
id3v2Tags = mapOf(
"TXXX:MUSICBRAINZ RELEASE TRACK ID" to listOf("track-id-123"),
"TXXX:MUSICBRAINZ ALBUM ID" to listOf("album-id-456"),
"TXXX:MUSICBRAINZ ARTIST ID" to listOf("artist-id-789"),
"TXXX:MUSICBRAINZ ALBUM ARTIST ID" to listOf("album-artist-id-012")
)
)
val tags = tagParser.parse(metadata)
assertEquals("track-id-123", tags.musicBrainzId)
assertEquals("album-id-456", tags.albumMusicBrainzId)
assertEquals(listOf("artist-id-789"), tags.artistMusicBrainzIds)
assertEquals(listOf("album-artist-id-012"), tags.albumArtistMusicBrainzIds)
}
@Test
fun tagParser_replayGain() {
val metadata = createTestMetadata(
xiphTags = mapOf(
"REPLAYGAIN_TRACK_GAIN" to listOf("-3.5 dB"),
"REPLAYGAIN_ALBUM_GAIN" to listOf("-2.1 dB")
)
)
val tags = tagParser.parse(metadata)
assertEquals(-3.5f, tags.replayGainTrackAdjustment)
assertEquals(-2.1f, tags.replayGainAlbumAdjustment)
}
private fun createTestMetadata(
id3v2Tags: Map<String, List<String>> = emptyMap(),
xiphTags: Map<String, List<String>> = emptyMap(),
mp4Tags: Map<String, List<String>> = emptyMap(),
cover: ByteArray? = null,
durationMs: Long = 1000,
bitrateKbps: Int = 320,
sampleRateHz: Int = 44100,
mimeType: String = "audio/mpeg"
): Metadata {
val properties = Properties(mimeType, durationMs, bitrateKbps, sampleRateHz)
return Metadata(id3v2Tags, xiphTags, mp4Tags, cover, properties)
}
}

View file

@ -1,280 +0,0 @@
/*
* Copyright (c) 2024 Auxio Project
* TagParsingEdgeCasesTest.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.tag.parse
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.oxycblt.musikr.metadata.Metadata
import org.oxycblt.musikr.metadata.Properties
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [30])
class TagParsingEdgeCasesTest {
private val tagParser = TagParser.new()
@Test
fun tagParser_emptyTags() {
// Test behavior with completely empty tag maps
val metadata = createTestMetadata()
val parsedTags = tagParser.parse(metadata)
// Check that all fields have their default values
assertNull(parsedTags.name)
assertNull(parsedTags.sortName)
assertNull(parsedTags.track)
assertNull(parsedTags.disc)
assertNull(parsedTags.subtitle)
assertNull(parsedTags.date)
assertNull(parsedTags.albumName)
assertNull(parsedTags.albumSortName)
assertTrue(parsedTags.releaseTypes.isEmpty())
assertTrue(parsedTags.artistNames.isEmpty())
assertTrue(parsedTags.artistSortNames.isEmpty())
assertTrue(parsedTags.albumArtistNames.isEmpty())
assertTrue(parsedTags.albumArtistSortNames.isEmpty())
assertTrue(parsedTags.genreNames.isEmpty())
}
@Test
fun tagParser_malformedTags() {
// Test behavior with malformed tags
val metadata = createTestMetadata(
id3v2Tags = mapOf(
"TRCK" to listOf("not-a-number"),
"TPOS" to listOf("also-not-a-number"),
"TYER" to listOf("invalid-year"),
"REPLAYGAIN_TRACK_GAIN" to listOf("not-a-float dB")
)
)
val parsedTags = tagParser.parse(metadata)
// Verify all numeric fields are null when given invalid values
assertNull(parsedTags.track)
assertNull(parsedTags.disc)
assertNull(parsedTags.date)
assertNull(parsedTags.replayGainTrackAdjustment)
}
@Test
fun tagParser_priorityHandling() {
// Test that field priority is handled correctly when multiple formats have the same field
val metadata = createTestMetadata(
id3v2Tags = mapOf(
"TIT2" to listOf("ID3 Title"),
"TALB" to listOf("ID3 Album")
),
mp4Tags = mapOf(
"©nam" to listOf("MP4 Title"),
"©alb" to listOf("MP4 Album")
),
xiphTags = mapOf(
"TITLE" to listOf("Xiph Title"),
"ALBUM" to listOf("Xiph Album")
)
)
// Individual field extraction
assertEquals("Xiph Title", metadata.name())
assertEquals("Xiph Album", metadata.albumName())
// Full tag parsing
val parsedTags = tagParser.parse(metadata)
assertEquals("Xiph Title", parsedTags.name)
assertEquals("Xiph Album", parsedTags.albumName)
}
@Test
fun tagParser_fieldBackoffHandling() {
// Test that when a format doesn't have a field, it falls back to the next format
val metadata = createTestMetadata(
id3v2Tags = mapOf(
"TIT2" to listOf("ID3 Title"),
"TALB" to listOf("ID3 Album")
),
mp4Tags = mapOf(
"©nam" to listOf("MP4 Title")
// No album in MP4
),
xiphTags = mapOf(
// No title in Xiph
"ALBUM" to listOf("Xiph Album")
)
)
// Individual field extraction
assertEquals("MP4 Title", metadata.name()) // Xiph missing, falls back to MP4
assertEquals("Xiph Album", metadata.albumName()) // Xiph has it, no fallback needed
// Full tag parsing
val parsedTags = tagParser.parse(metadata)
assertEquals("MP4 Title", parsedTags.name)
assertEquals("Xiph Album", parsedTags.albumName)
}
@Test
fun tagParser_compilationBehavior() {
// Test the special behavior for compilation albums
// Case 1: Compilation flag only
var metadata = createTestMetadata(
id3v2Tags = mapOf(
"TCMP" to listOf("1")
)
)
var tags = tagParser.parse(metadata)
assertEquals(listOf("Various Artists"), tags.albumArtistNames)
assertEquals(listOf("compilation"), tags.releaseTypes)
// Case 2: Compilation flag with explicit album artist
metadata = createTestMetadata(
id3v2Tags = mapOf(
"TCMP" to listOf("1"),
"TPE2" to listOf("Custom Album Artist")
)
)
tags = tagParser.parse(metadata)
assertEquals(listOf("Custom Album Artist"), tags.albumArtistNames)
assertEquals(listOf("compilation"), tags.releaseTypes)
// Case 3: Compilation flag with explicit release type
metadata = createTestMetadata(
id3v2Tags = mapOf(
"TCMP" to listOf("1"),
"TXXX:RELEASETYPE" to listOf("soundtrack")
)
)
tags = tagParser.parse(metadata)
assertEquals(listOf("Various Artists"), tags.albumArtistNames)
assertEquals(listOf("soundtrack"), tags.releaseTypes)
}
@Test
fun tagFields_replayGainEdgeCases() {
// Test various ReplayGain edge cases
// Non-numeric characters filtered out
var metadata = createTestMetadata(
xiphTags = mapOf(
"REPLAYGAIN_TRACK_GAIN" to listOf("+2.5 dB some text")
)
)
assertEquals(2.5f, metadata.replayGainTrackAdjustment())
// Zero values filtered
metadata = createTestMetadata(
xiphTags = mapOf(
"REPLAYGAIN_ALBUM_GAIN" to listOf("0.0 dB")
)
)
assertNull(metadata.replayGainAlbumAdjustment())
// R128 value with special parsing
metadata = createTestMetadata(
xiphTags = mapOf(
"R128_TRACK_GAIN" to listOf("-1280")
)
)
assertEquals(0.0f, metadata.replayGainTrackAdjustment()) // -1280/256 + 5 = 0.0
// Completely invalid value
metadata = createTestMetadata(
xiphTags = mapOf(
"REPLAYGAIN_TRACK_GAIN" to listOf("not a number")
)
)
assertNull(metadata.replayGainTrackAdjustment())
}
@Test
fun tagFields_id3v23DateEdgeCases() {
// Test ID3v2.3 date edge cases
// Just year
var metadata = createTestMetadata(
id3v2Tags = mapOf(
"TYER" to listOf("2018")
)
)
assertEquals("2018", metadata.date().toString())
// Year and date
metadata = createTestMetadata(
id3v2Tags = mapOf(
"TYER" to listOf("2018"),
"TDAT" to listOf("0315") // March 15
)
)
assertEquals("2018-03-15", metadata.date().toString())
// Year, date and time
metadata = createTestMetadata(
id3v2Tags = mapOf(
"TYER" to listOf("2018"),
"TDAT" to listOf("0315"), // March 15
"TIME" to listOf("1422") // 14:22
)
)
assertEquals("2018-03-15T14:22Z", metadata.date().toString())
// Original year
metadata = createTestMetadata(
id3v2Tags = mapOf(
"TORY" to listOf("1995")
)
)
assertEquals("1995", metadata.date().toString())
// Invalid date components
metadata = createTestMetadata(
id3v2Tags = mapOf(
"TYER" to listOf("2018"),
"TDAT" to listOf("invalid"),
"TIME" to listOf("invalid")
)
)
assertEquals("2018", metadata.date().toString())
// Non-numeric date components
metadata = createTestMetadata(
id3v2Tags = mapOf(
"TYER" to listOf("year")
)
)
assertNull(metadata.date())
}
private fun createTestMetadata(
id3v2Tags: Map<String, List<String>> = emptyMap(),
xiphTags: Map<String, List<String>> = emptyMap(),
mp4Tags: Map<String, List<String>> = emptyMap(),
cover: ByteArray? = null,
durationMs: Long = 1000,
bitrateKbps: Int = 320,
sampleRateHz: Int = 44100,
mimeType: String = "audio/mpeg"
): Metadata {
val properties = Properties(mimeType, durationMs, bitrateKbps, sampleRateHz)
return Metadata(id3v2Tags, xiphTags, mp4Tags, cover, properties)
}
}