Skip to content

Commit 20f068b

Browse files
committed
Word will now prefer next line ts over estimation, unless duration would go negative
1 parent 028c4f8 commit 20f068b

File tree

3 files changed

+40
-28
lines changed

3 files changed

+40
-28
lines changed

app/src/main/java/org/akanework/gramophone/logic/utils/SemanticLyrics.kt

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -456,10 +456,16 @@ sealed class SemanticLyrics : Parcelable {
456456

457457
@Parcelize
458458
data class Word(
459-
var timeRange: @WriteWith<ULongRangeParceler>() ULongRange,
459+
var begin: ULong,
460+
var endInclusive: ULong?,
460461
var charRange: @WriteWith<IntRangeParceler>() IntRange,
461462
var isRtl: Boolean
462-
) : Parcelable
463+
) : Parcelable {
464+
constructor(timeRange: ULongRange, charRange: IntRange, isRtl: Boolean)
465+
: this(timeRange.first, timeRange.last, charRange, isRtl)
466+
val timeRange
467+
get() = begin..endInclusive!!
468+
}
463469

464470
object ULongRangeParceler : Parceler<ULongRange> {
465471
override fun create(parcel: Parcel) =
@@ -595,23 +601,12 @@ fun parseLrc(lyricText: String, trimEnabled: Boolean, multiLineEnabled: Boolean)
595601
// If we have a dedicated sync point just for the last word,
596602
// use it. Similar to dummy words but for the last word only
597603
lastWordSyncPoint - 1uL // minus 1ms for consistency
598-
} else {
599-
// Estimate how long this word will take based on character
600-
// to time ratio. To avoid this estimation, add a last word
601-
// sync point to the line after the text :)
602-
current.first + (wout.map {
603-
it.timeRange.count() /
604-
it.charRange.count().toFloat()
605-
}.average().let {
606-
if (it.isNaN()) 100.0 else it
607-
} *
608-
textWithoutWhitespaces.length).toULong()
609-
}
610-
if (endInclusive > current.first)
604+
} else null /* filled later */
605+
if (endInclusive == null || endInclusive > current.first)
611606
// isRtl is filled in later in splitBidirectionalWords
612607
wout.add(
613608
Word(
614-
current.first..endInclusive,
609+
current.first, endInclusive,
615610
startIndex..<endIndex,
616611
isRtl = false
617612
)
@@ -642,22 +637,20 @@ fun parseLrc(lyricText: String, trimEnabled: Boolean, multiLineEnabled: Boolean)
642637
else lastWordSyncPoint ?: lastSyncPoint!!
643638
// if we had trailing word sync point after the last word was already ended,
644639
// it's an explicit line end ts, so use it. otherwise, use the last word's sync
645-
// point (possibly estimated) as end time. otherwise we will fill it later based
646-
// on next (temporal, not file order) line.
640+
// point as end time. otherwise we will fill it later based on next (temporal,
641+
// not file order) line.
647642
// TODO(ASAP): but, do we REALLY want to use that estimation? not next line start?
648643
val lastWordSyncForEnd = lastWordSyncPoint?.let { it - 1uL }
649-
val lastWordForEnd = words?.lastOrNull()?.timeRange?.last
650-
val end = if (lastWordSyncForEnd != null && lastWordForEnd != null)
651-
max(lastWordForEnd, lastWordSyncForEnd)
652-
else lastWordSyncForEnd ?: lastWordForEnd
644+
val lastWordBegin = words?.lastOrNull()?.begin
645+
val end = if (lastWordSyncForEnd != null && lastWordBegin != null &&
646+
lastWordBegin < lastWordSyncForEnd) lastWordSyncForEnd else null
653647
out.add(LyricLine(text, start, end ?: 0uL /* filled later */,
654648
end == null, words, speaker, false /* filled later */))
655649
compressed.forEach {
656650
val diff = it - start
657651
out.add(out.last().copy(start = it, words = words?.map {
658-
it.copy(
659-
timeRange = it.timeRange.first + diff..it.timeRange.last + diff
660-
)
652+
it.copy(begin = it.begin + diff,
653+
endInclusive = it.endInclusive?.plus(diff))
661654
}?.toMutableList()))
662655
}
663656
}
@@ -678,13 +671,30 @@ fun parseLrc(lyricText: String, trimEnabled: Boolean, multiLineEnabled: Boolean)
678671
out.forEach { lyric ->
679672
val mainEnd = if (lyric.start == previousLyric?.start) out.firstOrNull {
680673
it.start == lyric.start && !it.endIsImplicit }?.end else null
674+
val wordWithoutEnd = lyric.words?.lastOrNull()
675+
if (wordWithoutEnd != null && wordWithoutEnd.endInclusive == null) {
676+
wordWithoutEnd.endInclusive = mainEnd?.takeIf { it > wordWithoutEnd.begin }
677+
?: out.find { it.start > lyric.start }?.start?.minus(1uL)
678+
?.takeIf { it > wordWithoutEnd.begin }
679+
?: run {
680+
// Estimate how long this word will take based on character
681+
// to time ratio. To avoid this estimation, add a last word
682+
// sync point to the line after the text :)
683+
wordWithoutEnd.begin + (lyric.words.subList(0, lyric.words.size - 1)
684+
.map { it.timeRange.count() / it.charRange.count().toFloat() }
685+
.average().let { if (it.isNaN()) 100.0 else it } *
686+
lyric.text.substring(wordWithoutEnd.charRange).length).toULong()
687+
}
688+
}
681689
if (lyric.endIsImplicit) {
682690
if (mainEnd != null) {
683691
lyric.end = mainEnd
684692
lyric.endIsImplicit = false
685693
} else {
686-
lyric.end = out.find { it.start > lyric.start }?.start?.minus(1uL)
694+
lyric.end = wordWithoutEnd?.endInclusive
695+
?: out.find { it.start > lyric.start }?.start?.minus(1uL)
687696
?: Long.MAX_VALUE.toULong()
697+
lyric.endIsImplicit = wordWithoutEnd == null // TODO: should this just stay true?
688698
}
689699
}
690700
lyric.isTranslated = lyric.start == previousLyric?.start &&

app/src/test/java/org/akanework/gramophone/LrcTestData.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1033,7 +1033,7 @@ object LrcTestData2 {
10331033
LyricLine(start = 2333uL, text = """بببببببب """, words = mutableListOf(SemanticLyrics.Word(timeRange = 2333uL..4087uL, charRange = 0..0, isRtl = true), SemanticLyrics.Word(timeRange = 4088uL..5556uL, charRange = 1..1, isRtl = true), SemanticLyrics.Word(timeRange = 5557uL..6824uL, charRange = 2..2, isRtl = true), SemanticLyrics.Word(timeRange = 6825uL..8129uL, charRange = 3..3, isRtl = true), SemanticLyrics.Word(timeRange = 8130uL..9267uL, charRange = 4..4, isRtl = true), SemanticLyrics.Word(timeRange = 9268uL..10442uL, charRange = 5..5, isRtl = true), SemanticLyrics.Word(timeRange = 10443uL..11651uL, charRange = 6..6, isRtl = true), SemanticLyrics.Word(timeRange = 11652uL..13040uL, charRange = 7..7, isRtl = true)), speaker = null, end = 13040uL, isTranslated = false, endIsImplicit = false),
10341034
LyricLine(start = 4000uL, text = """ تست persian before english""", words = mutableListOf(SemanticLyrics.Word(timeRange = 4000uL..4999uL, charRange = 1..3, isRtl = true), SemanticLyrics.Word(timeRange = 5000uL..6599uL, charRange = 5..26, isRtl = false)), speaker = null, end = 6599uL, isTranslated = false, endIsImplicit = false),
10351035
LyricLine(start = 7000uL, text = """ persian after english تست""", words = mutableListOf(SemanticLyrics.Word(timeRange = 7000uL..8099uL, charRange = 1..21, isRtl = false), SemanticLyrics.Word(timeRange = 8100uL..8799uL, charRange = 23..25, isRtl = true)), speaker = null, end = 8799uL, isTranslated = false, endIsImplicit = false),
1036-
LyricLine(start = 9000uL, text = """ one دو three چهار""", words = mutableListOf(SemanticLyrics.Word(timeRange = 9000uL..9999uL, charRange = 1..3, isRtl = false), SemanticLyrics.Word(timeRange = 10000uL..10999uL, charRange = 5..6, isRtl = true), SemanticLyrics.Word(timeRange = 11000uL..11999uL, charRange = 8..12, isRtl = false), SemanticLyrics.Word(timeRange = 12000uL..13377uL, charRange = 14..17, isRtl = true)), speaker = null, end = 13377uL, isTranslated = false, endIsImplicit = false),
1036+
LyricLine(start = 9000uL, text = """ one دو three چهار""", words = mutableListOf(SemanticLyrics.Word(timeRange = 9000uL..9999uL, charRange = 1..3, isRtl = false), SemanticLyrics.Word(timeRange = 10000uL..10999uL, charRange = 5..6, isRtl = true), SemanticLyrics.Word(timeRange = 11000uL..11999uL, charRange = 8..12, isRtl = false), SemanticLyrics.Word(timeRange = 12000uL..12999uL, charRange = 14..17, isRtl = true)), speaker = null, end = 12999uL, isTranslated = false, endIsImplicit = false),
10371037
LyricLine(start = 13000uL, text = """ یکtwo سه four""", words = mutableListOf(SemanticLyrics.Word(timeRange = 13000uL..16399uL, charRange = 1..2, isRtl = true), SemanticLyrics.Word(timeRange = 16400uL..19999uL, charRange = 3..5, isRtl = false), SemanticLyrics.Word(timeRange = 20000uL..23999uL, charRange = 7..8, isRtl = true), SemanticLyrics.Word(timeRange = 24000uL..30800uL, charRange = 10..13, isRtl = false)), speaker = null, end = 30800uL, isTranslated = false, endIsImplicit = false),
10381038
LyricLine(start = 17000uL, text = """ و کل متن در پارسی""", words = mutableListOf(SemanticLyrics.Word(timeRange = 27000uL..29999uL, charRange = 1..4, isRtl = true), SemanticLyrics.Word(timeRange = 30000uL..33499uL, charRange = 6..8, isRtl = true), SemanticLyrics.Word(timeRange = 33500uL..35999uL, charRange = 10..11, isRtl = true), SemanticLyrics.Word(timeRange = 36000uL..38999uL, charRange = 13..17, isRtl = true)), speaker = null, end = 38999uL, isTranslated = false, endIsImplicit = false),
10391039
LyricLine(start = 40000uL, text = """ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaasaaaaasdfghjklöoiuztrewqasdfghjklknbvfdrtzuikjnbvcfdewsdcfghjidopldksmnbgdfczusiopds0987vztgbsdnvklpdsvlknbdsgftzs7uio""", words = mutableListOf(SemanticLyrics.Word(timeRange = 40010uL..44999uL, charRange = 1..148, isRtl = false)), speaker = null, end = 44999uL, isTranslated = false, endIsImplicit = false),

app/src/test/java/org/akanework/gramophone/LrcUtilsTest.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,14 +309,16 @@ class LrcUtilsTest {
309309
assertEquals(2, lrc!!.size)
310310
assertEquals(100uL, lrc[0].start)
311311
assertEquals(10100uL, lrc[1].start)
312+
assertEquals(10099uL, lrc[0].end)
313+
assertEquals(11270uL, lrc[1].end)
312314
assertNotNull(lrc[0].words)
313315
assertEquals(3, lrc[0].words!!.size)
314316
assertEquals(100uL, lrc[0].words!![0].timeRange.first)
315317
assertEquals(200uL - 1uL, lrc[0].words!![0].timeRange.last)
316318
assertEquals(200uL, lrc[0].words!![1].timeRange.first)
317319
assertEquals(1000uL - 1uL, lrc[0].words!![1].timeRange.last)
318320
assertEquals(1000uL, lrc[0].words!![2].timeRange.first)
319-
assertEquals(1270uL, lrc[0].words!![2].timeRange.last)
321+
assertEquals(10099uL, lrc[0].words!![2].timeRange.last)
320322
assertEquals(10100uL, lrc[1].start)
321323
assertNotNull(lrc[1].words)
322324
assertEquals(3, lrc[1].words!!.size)

0 commit comments

Comments
 (0)