Compare commits
3 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e2918d3a95 | ||
![]() |
01a159754d | ||
![]() |
6a42f7c5d2 |
5 changed files with 802 additions and 7 deletions
|
@ -41,12 +41,12 @@ abstract class Naming {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private data object IntelligentNaming : Naming() {
|
data object IntelligentNaming : Naming() {
|
||||||
override fun name(raw: String, sort: String?) = IntelligentKnownName(raw, sort)
|
override fun name(raw: String, sort: String?): Name.Known = IntelligentKnownName(raw, sort)
|
||||||
}
|
}
|
||||||
|
|
||||||
private data object SimpleNaming : Naming() {
|
data object SimpleNaming : Naming() {
|
||||||
override fun name(raw: String, sort: String?) = SimpleKnownName(raw, sort)
|
override fun name(raw: String, sort: String?): Name.Known = SimpleKnownName(raw, sort)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val collator: Collator = Collator.getInstance().apply { strength = Collator.PRIMARY }
|
private val collator: Collator = Collator.getInstance().apply { strength = Collator.PRIMARY }
|
||||||
|
|
|
@ -16,14 +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.musikr.tag
|
package org.oxycblt.musikr.tag.interpret
|
||||||
|
|
||||||
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.musikr.tag.interpret.Naming
|
import org.oxycblt.musikr.tag.Name
|
||||||
|
import org.oxycblt.musikr.tag.Placeholder
|
||||||
|
import org.oxycblt.musikr.tag.Token
|
||||||
|
|
||||||
class NameTest {
|
class NamingTest {
|
||||||
@Test
|
@Test
|
||||||
fun name_simple_withoutPunct() {
|
fun name_simple_withoutPunct() {
|
||||||
val name = Naming.simple().name("Loveless", null)
|
val name = Naming.simple().name("Loveless", null)
|
|
@ -0,0 +1,335 @@
|
||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,280 @@
|
||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue