Skip to content

Commit 1b30dd8

Browse files
committed
supports head/body-less ttml files
1 parent d66f78b commit 1b30dd8

File tree

1 file changed

+120
-109
lines changed

1 file changed

+120
-109
lines changed

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

Lines changed: 120 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,129 +1304,140 @@ fun parseTtml(audioMimeType: String?, lyricText: String): SemanticLyrics? {
13041304
val itunesTranslations = hashMapOf<String, HashMap<String, String>>()
13051305
val timer = TtmlTimeTracker(parser, hasItunesNamespace)
13061306
parser.nextTag()
1307-
parser.require(XmlPullParser.START_TAG, tt, "head")
1308-
// TODO parse and reject based on https://www.w3.org/TR/2018/REC-ttml2-20181108/#feature-profile-version-2 to be compliant
1309-
while (parser.nextTag() != XmlPullParser.END_TAG) {
1310-
if (parser.name == "metadata") {
1311-
while (parser.nextTag() != XmlPullParser.END_TAG) {
1312-
if (parser.namespace == ttm && parser.name == "agent") {
1313-
val id = parser.getAttributeValue("http://www.w3.org/XML/1998/namespace", "id")
1314-
val type = parser.getAttributeValue("", "type")
1315-
people.getOrPut(type) { mutableListOf() }.add(id)
1316-
peopleToType[id] = type
1317-
while (parser.nextTag() != XmlPullParser.END_TAG) {
1318-
if (parser.namespace == ttm && parser.name == "name") {
1319-
// val type = parser.getAttributeValue("", "type")
1320-
parser.nextAndThrowIfNotText()
1321-
// val name = parser.text
1322-
parser.nextAndThrowIfNotEnd()
1323-
} else {
1324-
throw XmlPullParserException(
1325-
"expected <ttm:name>, got " +
1326-
"<${(parser.prefix?.plus(":") ?: "") + parser.name}> " +
1327-
"in <ttm:agent> in <metadata>"
1328-
)
1307+
if (parser.eventType == XmlPullParser.END_TAG && parser.namespace == tt && parser.name == "tt") {
1308+
return null // a valid ttml file could have neither <head> or <body>, but it would be a weird one
1309+
}
1310+
parser.require(XmlPullParser.START_TAG, tt, null)
1311+
if (parser.name != "body") { // <head> is optional
1312+
parser.require(XmlPullParser.START_TAG, tt, "head")
1313+
// TODO parse and reject based on https://www.w3.org/TR/2018/REC-ttml2-20181108/#feature-profile-version-2 to be compliant
1314+
while (parser.nextTag() != XmlPullParser.END_TAG) {
1315+
if (parser.name == "metadata") {
1316+
while (parser.nextTag() != XmlPullParser.END_TAG) {
1317+
if (parser.namespace == ttm && parser.name == "agent") {
1318+
val id =
1319+
parser.getAttributeValue("http://www.w3.org/XML/1998/namespace", "id")
1320+
val type = parser.getAttributeValue("", "type")
1321+
people.getOrPut(type) { mutableListOf() }.add(id)
1322+
peopleToType[id] = type
1323+
while (parser.nextTag() != XmlPullParser.END_TAG) {
1324+
if (parser.namespace == ttm && parser.name == "name") {
1325+
// val type = parser.getAttributeValue("", "type")
1326+
parser.nextAndThrowIfNotText()
1327+
// val name = parser.text
1328+
parser.nextAndThrowIfNotEnd()
1329+
} else {
1330+
throw XmlPullParserException(
1331+
"expected <ttm:name>, got " +
1332+
"<${(parser.prefix?.plus(":") ?: "") + parser.name}> " +
1333+
"in <ttm:agent> in <metadata>"
1334+
)
1335+
}
13291336
}
1330-
}
1331-
} else if (parser.name == "iTunesMetadata") {
1332-
while (parser.nextTag() != XmlPullParser.END_TAG) {
1333-
when (parser.name) {
1334-
"songwriters" -> {
1335-
while (parser.nextTag() != XmlPullParser.END_TAG) {
1336-
if (parser.name == "songwriter") {
1337-
parser.nextAndThrowIfNotText()
1338-
// val songwriter = parser.text
1339-
parser.nextAndThrowIfNotEnd()
1340-
} else {
1341-
throw XmlPullParserException(
1342-
"expected <songwriter>, got " +
1343-
"<${(parser.prefix?.plus(":") ?: "") + parser.name}> " +
1344-
"in <songwriters> in <iTunesMetadata>"
1345-
)
1337+
} else if (parser.name == "iTunesMetadata") {
1338+
while (parser.nextTag() != XmlPullParser.END_TAG) {
1339+
when (parser.name) {
1340+
"songwriters" -> {
1341+
while (parser.nextTag() != XmlPullParser.END_TAG) {
1342+
if (parser.name == "songwriter") {
1343+
parser.nextAndThrowIfNotText()
1344+
// val songwriter = parser.text
1345+
parser.nextAndThrowIfNotEnd()
1346+
} else {
1347+
throw XmlPullParserException(
1348+
"expected <songwriter>, got " +
1349+
"<${(parser.prefix?.plus(":") ?: "") + parser.name}> " +
1350+
"in <songwriters> in <iTunesMetadata>"
1351+
)
1352+
}
13461353
}
13471354
}
1348-
}
13491355

1350-
"audio" -> {
1351-
val role = parser.getAttributeValue(null, "role")
1352-
if (when (role) {
1353-
"spatial" -> audioMimeType == MimeTypes.AUDIO_AC3 ||
1354-
audioMimeType == MimeTypes.AUDIO_E_AC3 ||
1355-
audioMimeType == MimeTypes.AUDIO_AC4
1356-
// ext- are Gramophone extensions
1357-
"ext-not-spatial" -> !(audioMimeType == MimeTypes.AUDIO_AC3 ||
1358-
audioMimeType == MimeTypes.AUDIO_E_AC3 ||
1359-
audioMimeType == MimeTypes.AUDIO_AC4)
1360-
1361-
"ext-always" -> true
1362-
else -> throw XmlPullParserException(
1363-
"unsupported offset " +
1364-
"role $role, can't decide whether to apply offset"
1356+
"audio" -> {
1357+
val role = parser.getAttributeValue(null, "role")
1358+
if (when (role) {
1359+
"spatial" -> audioMimeType == MimeTypes.AUDIO_AC3 ||
1360+
audioMimeType == MimeTypes.AUDIO_E_AC3 ||
1361+
audioMimeType == MimeTypes.AUDIO_AC4
1362+
// ext- are Gramophone extensions
1363+
"ext-not-spatial" -> !(audioMimeType == MimeTypes.AUDIO_AC3 ||
1364+
audioMimeType == MimeTypes.AUDIO_E_AC3 ||
1365+
audioMimeType == MimeTypes.AUDIO_AC4)
1366+
1367+
"ext-always" -> true
1368+
else -> throw XmlPullParserException(
1369+
"unsupported offset " +
1370+
"role $role, can't decide whether to apply offset"
1371+
)
1372+
}
1373+
) {
1374+
val parsed = timer.parseTimestampMs(
1375+
parser.getAttributeValue(
1376+
null,
1377+
"lyricOffset"
1378+
), 0L, true
13651379
)
1380+
if (timer.audioOffset != null)
1381+
timer.audioOffset = timer.audioOffset!! + (parsed ?: 0L)
1382+
else
1383+
timer.audioOffset = parsed
13661384
}
1367-
) {
1368-
val parsed = timer.parseTimestampMs(
1369-
parser.getAttributeValue(
1370-
null,
1371-
"lyricOffset"
1372-
), 0L, true
1373-
)
1374-
if (timer.audioOffset != null)
1375-
timer.audioOffset = timer.audioOffset!! + (parsed ?: 0L)
1376-
else
1377-
timer.audioOffset = parsed
1385+
parser.nextAndThrowIfNotEnd()
13781386
}
1379-
parser.nextAndThrowIfNotEnd()
1380-
}
13811387

1382-
"translations" -> {
1383-
while (parser.nextTag() != XmlPullParser.END_TAG) {
1384-
if (parser.name == "translation") {
1385-
val type = parser.getAttributeValue(null, "type")
1386-
if (type != "subtitle") {
1387-
throw XmlPullParserException("unsupported translation type $type")
1388-
}
1389-
val lang = parser.getAttributeValue(
1390-
"http://www.w3.org/XML/1998/namespace",
1391-
"lang"
1392-
)
1393-
val out = hashMapOf<String, String>()
1394-
while (parser.nextTag() != XmlPullParser.END_TAG) {
1395-
if (parser.name == "text") {
1396-
val `for` = parser.getAttributeValue(null, "for")
1397-
?: throw XmlPullParserException("missing attribute for at $parser")
1398-
parser.nextAndThrowIfNotText()
1399-
out[`for`] = parser.text
1400-
parser.nextAndThrowIfNotEnd()
1401-
} else {
1402-
throw XmlPullParserException(
1403-
"expected <text>, got " +
1404-
"<${(parser.prefix?.plus(":") ?: "") + parser.name}> " +
1405-
"in <translation> in <translations> in <iTunesMetadata>"
1406-
)
1388+
"translations" -> {
1389+
while (parser.nextTag() != XmlPullParser.END_TAG) {
1390+
if (parser.name == "translation") {
1391+
val type = parser.getAttributeValue(null, "type")
1392+
if (type != "subtitle") {
1393+
throw XmlPullParserException("unsupported translation type $type")
1394+
}
1395+
val lang = parser.getAttributeValue(
1396+
"http://www.w3.org/XML/1998/namespace",
1397+
"lang"
1398+
)
1399+
val out = hashMapOf<String, String>()
1400+
while (parser.nextTag() != XmlPullParser.END_TAG) {
1401+
if (parser.name == "text") {
1402+
val `for` =
1403+
parser.getAttributeValue(null, "for")
1404+
?: throw XmlPullParserException("missing attribute for at $parser")
1405+
parser.nextAndThrowIfNotText()
1406+
out[`for`] = parser.text
1407+
parser.nextAndThrowIfNotEnd()
1408+
} else {
1409+
throw XmlPullParserException(
1410+
"expected <text>, got " +
1411+
"<${(parser.prefix?.plus(":") ?: "") + parser.name}> " +
1412+
"in <translation> in <translations> in <iTunesMetadata>"
1413+
)
1414+
}
14071415
}
1416+
itunesTranslations[lang] = out
1417+
} else {
1418+
throw XmlPullParserException(
1419+
"expected <translation>, got " +
1420+
"<${(parser.prefix?.plus(":") ?: "") + parser.name}> " +
1421+
"in <translations> in <iTunesMetadata>"
1422+
)
14081423
}
1409-
itunesTranslations[lang] = out
1410-
} else {
1411-
throw XmlPullParserException(
1412-
"expected <translation>, got " +
1413-
"<${(parser.prefix?.plus(":") ?: "") + parser.name}> " +
1414-
"in <translations> in <iTunesMetadata>"
1415-
)
14161424
}
14171425
}
1418-
}
14191426

1420-
else -> parser.skipToEndOfTag()
1421-
} // there are some others
1422-
}
1423-
} else parser.skipToEndOfTag()
1424-
}
1425-
} else // probably <styling> or <layout>
1426-
parser.skipToEndOfTag()
1427+
else -> parser.skipToEndOfTag()
1428+
} // there are some others
1429+
}
1430+
} else parser.skipToEndOfTag()
1431+
}
1432+
} else // probably <styling> or <layout>
1433+
parser.skipToEndOfTag()
1434+
}
1435+
parser.require(XmlPullParser.END_TAG, tt, "head")
1436+
parser.nextTag()
1437+
if (parser.eventType == XmlPullParser.END_TAG && parser.namespace == tt && parser.name == "tt") {
1438+
return null // a valid ttml file could have no <body>, but it would be a weird one
1439+
}
14271440
}
1428-
parser.require(XmlPullParser.END_TAG, tt, "head")
1429-
parser.nextTag()
14301441
parser.require(XmlPullParser.START_TAG, tt, "body")
14311442
val state = TtmlParserState(parser, timer)
14321443
state.parse()

0 commit comments

Comments
 (0)