music: add support for date-encoding years
Add support for date-encoding years such as "YYYYMMDD". This is a semi-common timestamp edge-case, it seems, primarily due to taggers wanting to encode date information in older tag formats.
This commit is contained in:
parent
a29875b5bf
commit
3502af33e7
4 changed files with 28 additions and 8 deletions
|
@ -182,14 +182,25 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
|
|||
*/
|
||||
private val ISO8601_REGEX =
|
||||
Regex(
|
||||
"""^(\d{4,})([-.](\d{2})([-.](\d{2})([T ](\d{2})([:.](\d{2})([:.](\d{2})(Z)?)?)?)?)?)?$""")
|
||||
"""^(\d{4})([-.](\d{2})([-.](\d{2})([T ](\d{2})([:.](\d{2})([:.](\d{2})(Z)?)?)?)?)?)?$""")
|
||||
|
||||
/**
|
||||
* Create a [Date] from a year component.
|
||||
* @param year The year component.
|
||||
* @return A new [Date] of the given component, or null if the component is invalid.
|
||||
*/
|
||||
fun from(year: Int) = fromTokens(listOf(year))
|
||||
fun from(year: Int) =
|
||||
if (year in 10000000..100000000) {
|
||||
// Year is actually more likely to be a separated date timestamp. Interpret
|
||||
// it as such.
|
||||
val stringYear = year.toString()
|
||||
from(
|
||||
stringYear.substring(0..3).toInt(),
|
||||
stringYear.substring(4..5).toInt(),
|
||||
stringYear.substring(6..7).toInt())
|
||||
} else {
|
||||
fromTokens(listOf(year))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [Date] from a date component.
|
||||
|
@ -222,8 +233,9 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
|
|||
*/
|
||||
fun from(timestamp: String): Date? {
|
||||
val tokens =
|
||||
// Match the input with the timestamp regex
|
||||
(ISO8601_REGEX.matchEntire(timestamp) ?: return null)
|
||||
// Match the input with the timestamp regex. If there is no match, see if we can
|
||||
// fall back to some kind of year value.
|
||||
(ISO8601_REGEX.matchEntire(timestamp) ?: return timestamp.toIntOrNull()?.let(::from))
|
||||
.groupValues
|
||||
// Filter to the specific tokens we want and convert them to integer tokens.
|
||||
.mapIndexedNotNull { index, s -> if (index % 2 != 0) s.toIntOrNull() else null }
|
||||
|
@ -238,7 +250,7 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
|
|||
*/
|
||||
private fun fromTokens(tokens: List<Int>): Date? {
|
||||
val validated = mutableListOf<Int>()
|
||||
validateTokens(tokens, validated)
|
||||
transformTokens(tokens, validated)
|
||||
if (validated.isEmpty()) {
|
||||
// No token was valid, return null.
|
||||
return null
|
||||
|
@ -252,7 +264,7 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
|
|||
* @param src The input tokens to validate.
|
||||
* @param dst The destination list to add valid tokens to.
|
||||
*/
|
||||
private fun validateTokens(src: List<Int>, dst: MutableList<Int>) {
|
||||
private fun transformTokens(src: List<Int>, dst: MutableList<Int>) {
|
||||
dst.add(src.getOrNull(0)?.nonZeroOrNull() ?: return)
|
||||
dst.add(src.getOrNull(1)?.inRangeOrNull(1..12) ?: return)
|
||||
dst.add(src.getOrNull(2)?.inRangeOrNull(1..31) ?: return)
|
||||
|
@ -260,5 +272,7 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
|
|||
dst.add(src.getOrNull(4)?.inRangeOrNull(0..59) ?: return)
|
||||
dst.add(src.getOrNull(5)?.inRangeOrNull(0..59) ?: return)
|
||||
}
|
||||
|
||||
private fun transformYearToken(src: List<Int>, dst: MutableList<Int>) {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -305,7 +305,7 @@ abstract class MediaStoreExtractor(
|
|||
// MediaStore only exposes the year value of a file. This is actually worse than it
|
||||
// seems, as it means that it will not read ID3v2 TDRC tags or Vorbis DATE comments.
|
||||
// This is one of the major weaknesses of using MediaStore, hence the redundancy layers.
|
||||
raw.date = cursor.getIntOrNull(yearIndex)?.let(Date::from)
|
||||
raw.date = cursor.getStringOrNull(yearIndex)?.let(Date::from)
|
||||
// A non-existent album name should theoretically be the name of the folder it contained
|
||||
// in, but in practice it is more often "0" (as in /storage/emulated/0), even when it the
|
||||
// file is not actually in the root internal storage directory. We can't do anything to
|
||||
|
|
|
@ -293,7 +293,7 @@ class Task(context: Context, private val raw: Song.Raw) {
|
|||
// date tag that android supports, so it must be 15 years old or more!)
|
||||
(comments["originaldate"]?.run { Date.from(first()) }
|
||||
?: comments["date"]?.run { Date.from(first()) }
|
||||
?: comments["year"]?.run { first().toIntOrNull()?.let(Date::from) })
|
||||
?: comments["year"]?.run { Date.from(first()) })
|
||||
?.let { raw.date = it }
|
||||
|
||||
// Album
|
||||
|
|
|
@ -124,6 +124,12 @@ class DateTest {
|
|||
assertEquals(Date.from(0), null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun date_fromYearDate() {
|
||||
assertEquals("2016", Date.from(2016).toString())
|
||||
assertEquals("2016", Date.from("2016").toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dateRange_fromDates() {
|
||||
val range =
|
||||
|
|
Loading…
Reference in a new issue