@@ -49,16 +49,23 @@ private const val TAG = "SemanticLyrics"
4949 * We completely ignore all ID3 tags from the header as MediaStore is our source of truth.
5050 */
5151
52+ // This does not encode the full range of possible information in the TTML format, only what's used
53+ // for actual rendering. This could change in the future, but for now it's like this.
5254@Parcelize
5355enum class SpeakerEntity (
54- val isVoice2 : Boolean = false ,
55- val isGroup : Boolean = false ,
56- val isBackground : Boolean = false
56+ val isVoice2 : Boolean = false , // align opposite
57+ val isGroup : Boolean = false , // center
58+ val isBackground : Boolean = false , // small
59+ val isWidthLimited : Boolean = false ,
5760) : Parcelable {
58- Voice1 ,
59- Background (isBackground = true ),
60- Voice2 (isVoice2 = true ),
61- Voice2Background (isVoice2 = true , isBackground = true ),
61+ // Voice is used if there should be no width limit as it's the only person, Voice1 is used if
62+ // there are more and hence width limit should be applied.
63+ Voice ,
64+ VoiceBackground (isBackground = true ),
65+ Voice1 (isWidthLimited = true ),
66+ Voice1Background (isWidthLimited = true , isBackground = true ),
67+ Voice2 (isWidthLimited = true , isVoice2 = true ),
68+ Voice2Background (isWidthLimited = true , isVoice2 = true , isBackground = true ),
6269 Group (isGroup = true ),
6370 GroupBackground (isGroup = true , isBackground = true )
6471}
@@ -144,7 +151,9 @@ private sealed class SyntacticLrc {
144151 // but hey, we tried. Can't do much about it.
145152 // If you want to write something that looks like a timestamp into your lyrics,
146153 // you'll probably have to delete the following three lines.
147- if (! (out .lastOrNull() is NewLine ? || out .lastOrNull() is SyncPoint ))
154+ val lastOrNull = out .lastOrNull()
155+ if (! (lastOrNull is NewLine ? || lastOrNull is SyncPoint
156+ || (lastOrNull is SpeakerTag && lastOrNull.speaker.isBackground)))
148157 out .add(NewLine .SyntheticNewLine ())
149158 out .add(SyncPoint (parseTime(tmMatch)))
150159 pos + = tmMatch.value.length
@@ -212,7 +221,7 @@ private sealed class SyntacticLrc {
212221 when {
213222 lastSpeaker?.isGroup == true -> SpeakerEntity .GroupBackground
214223 lastSpeaker?.isVoice2 == true -> SpeakerEntity .Voice2Background
215- else -> SpeakerEntity .Background
224+ else -> SpeakerEntity .Voice1Background
216225 }
217226 )
218227 )
@@ -518,6 +527,7 @@ fun parseLrc(lyricText: String, trimEnabled: Boolean, multiLineEnabled: Boolean)
518527 var lastSyncPoint: ULong? = null
519528 var lastWordSyncPoint: ULong? = null
520529 var speaker: SpeakerEntity ? = null
530+ var hadVoice2 = false
521531 var hadLyricSinceWordSync = true
522532 var hadWordSyncSinceNewLine = false
523533 val currentLine = mutableListOf<Pair <ULong , String ?>>()
@@ -547,6 +557,9 @@ fun parseLrc(lyricText: String, trimEnabled: Boolean, multiLineEnabled: Boolean)
547557
548558 is SyntacticLrc .SpeakerTag -> {
549559 speaker = element.speaker
560+ if (element.speaker.isVoice2) {
561+ hadVoice2 = true
562+ }
550563 }
551564
552565 is SyntacticLrc .WordSyncPoint -> {
@@ -683,6 +696,13 @@ fun parseLrc(lyricText: String, trimEnabled: Boolean, multiLineEnabled: Boolean)
683696 out .sortBy { it.start }
684697 var previousLyric: LyricLine ? = null
685698 out .forEach { lyric ->
699+ if (! hadVoice2) { // If there is no v2: tag, we should avoid width limit.
700+ lyric.speaker = when (lyric.speaker) {
701+ SpeakerEntity .Voice1 -> SpeakerEntity .Voice
702+ SpeakerEntity .Voice1Background -> SpeakerEntity .VoiceBackground
703+ else -> lyric.speaker
704+ }
705+ }
686706 val mainEnd = if (lyric.start == previousLyric?.start) out .firstOrNull {
687707 it.start == lyric.start && ! it.endIsImplicit
688708 }?.end else null
@@ -1501,22 +1521,28 @@ fun parseTtml(audioMimeType: String?, lyricText: String): SemanticLyrics? {
15011521 } while (cur != - 1 )
15021522 out
15031523 }
1524+ val hasAtLeastTwoPeople = people[" person" ]?.let { it.size > 1 } == true
15041525 if (paragraphs.find { it.time != null } == null ) {
15051526 return UnsyncedLyrics (paragraphs.map {
15061527 val text = it.texts.joinToString(" " ) { it.text }
15071528 val isBg = it.role == " x-bg"
15081529 val isGroup = peopleToType[it.agent] == " group"
1530+ val isOther = peopleToType[it.agent] == " other"
1531+ // first person goes left, second right, third left, fourth right, and so on.
1532+ // and the same goes for "other" except that we start on the right here.
15091533 val isVoice2 =
15101534 it.agent != null && (people[peopleToType[it.agent]] ? : throw NullPointerException (
15111535 " expected to find ${it.agent} (${peopleToType[it.agent]} ) in $people "
1512- )).indexOf(it.agent) % 2 == 1
1536+ )).indexOf(it.agent) % 2 == ( if (isOther) 0 else 1 )
15131537 val speaker = when {
15141538 isGroup && isBg -> SpeakerEntity .GroupBackground
15151539 isGroup -> SpeakerEntity .Group
15161540 isVoice2 && isBg -> SpeakerEntity .Voice2Background
15171541 isVoice2 -> SpeakerEntity .Voice2
1518- isBg -> SpeakerEntity .Background
1519- else -> SpeakerEntity .Voice1
1542+ hasAtLeastTwoPeople && isBg -> SpeakerEntity .Voice1Background
1543+ hasAtLeastTwoPeople -> SpeakerEntity .Voice1
1544+ isBg -> SpeakerEntity .VoiceBackground
1545+ else -> SpeakerEntity .Voice
15201546 }
15211547 Pair (text, speaker)
15221548 })
@@ -1536,17 +1562,22 @@ fun parseTtml(audioMimeType: String?, lyricText: String): SemanticLyrics? {
15361562 ?.toMutableList()
15371563 val isBg = it.role == " x-bg"
15381564 val isGroup = peopleToType[it.agent] == " group"
1565+ val isOther = peopleToType[it.agent] == " other"
1566+ // first person goes left, second right, third left, fourth right, and so on.
1567+ // and the same goes for "other" except that we start on the right here.
15391568 val isVoice2 =
15401569 it.agent != null && (people[peopleToType[it.agent]] ? : throw NullPointerException (
15411570 " expected to find ${it.agent} (${peopleToType[it.agent]} ) in $people "
1542- )).indexOf(it.agent) % 2 == 1
1571+ )).indexOf(it.agent) % 2 == ( if (isOther) 0 else 1 )
15431572 val speaker = when {
15441573 isGroup && isBg -> SpeakerEntity .GroupBackground
15451574 isGroup -> SpeakerEntity .Group
15461575 isVoice2 && isBg -> SpeakerEntity .Voice2Background
15471576 isVoice2 -> SpeakerEntity .Voice2
1548- isBg -> SpeakerEntity .Background
1549- else -> SpeakerEntity .Voice1
1577+ hasAtLeastTwoPeople && isBg -> SpeakerEntity .Voice1Background
1578+ hasAtLeastTwoPeople -> SpeakerEntity .Voice1
1579+ isBg -> SpeakerEntity .VoiceBackground
1580+ else -> SpeakerEntity .Voice
15501581 }
15511582 if (it.time == null ) {
15521583 throw IllegalArgumentException (" it.time == null but some other P has non-null time" )
0 commit comments