diff --git a/src/main/kotlin/dev/typetype/server/services/ManifestService.kt b/src/main/kotlin/dev/typetype/server/services/ManifestService.kt index 0959bc1..4ef82de 100644 --- a/src/main/kotlin/dev/typetype/server/services/ManifestService.kt +++ b/src/main/kotlin/dev/typetype/server/services/ManifestService.kt @@ -19,9 +19,8 @@ class ManifestService(private val streamService: StreamService) { } private fun compatibleVideoStreams(streams: List): List = - streams.filter { it.codec?.startsWith("av01") != true && it.url.isNotBlank() && !it.codec.isNullOrBlank() } + streams.filter { it.url.isNotBlank() && !it.codec.isNullOrBlank() } .sortedWith(compareBy({ codecPriority(it.codec ?: "") }, { -(it.bitrate ?: bwFromUrl(it.url) ?: 0) })) - private fun compatibleAudioStreams(streams: List, preferredTrackId: String?): List = streams.filter { it.url.isNotBlank() && !it.codec.isNullOrBlank() } .sortedWith(compareBy { preferredTrackId != null && it.audioTrackId != preferredTrackId } @@ -101,6 +100,7 @@ class ManifestService(private val streamService: StreamService) { private fun codecFamily(codec: String): String = when { codec.startsWith("avc1") -> "avc" codec.startsWith("vp9") || codec.startsWith("vp09") -> "vp9" + codec.startsWith("av01") -> "av1" else -> "other" } diff --git a/src/test/kotlin/dev/typetype/server/ManifestServiceAudioTest.kt b/src/test/kotlin/dev/typetype/server/ManifestServiceAudioTest.kt new file mode 100644 index 0000000..52dfad4 --- /dev/null +++ b/src/test/kotlin/dev/typetype/server/ManifestServiceAudioTest.kt @@ -0,0 +1,62 @@ +package dev.typetype.server + +import dev.typetype.server.models.ExtractionResult +import dev.typetype.server.services.ManifestService +import dev.typetype.server.services.StreamService +import io.mockk.coEvery +import io.mockk.mockk +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class ManifestServiceAudioTest { + private val streamService: StreamService = mockk() + private val service = ManifestService(streamService) + + @Test + fun `mono-track audio produces one AdaptationSet per mimeType without lang or label`() = runBlocking { + val aac = testAudioStream(format = "M4A", codec = "mp4a.40.2") + val opus = testAudioStream(format = "WEBM", codec = "opus", url = "https://example.com/opus") + coEvery { streamService.getStreamInfo(any()) } returns + ExtractionResult.Success(testStreamResponse(audioStreams = listOf(aac, opus))) + + val xml = (service.dashManifest("https://youtube.com/watch?v=test") as ExtractionResult.Success).data + + val sets = Regex("]*mimeType").findAll(xml).count() + assertTrue(sets >= 2) + assertTrue(!xml.contains("lang=")) + assertTrue(!xml.contains("label=")) + } + + @Test + fun `multi-track audio produces one AdaptationSet per trackId and mimeType with lang and label`() = runBlocking { + val en = testAudioStream(format = "M4A", codec = "mp4a.40.2", url = "https://example.com/en", + audioTrackId = "en.0", audioTrackName = "English", audioLocale = "en") + val fr = testAudioStream(format = "M4A", codec = "mp4a.40.2", url = "https://example.com/fr", + audioTrackId = "fr.0", audioTrackName = "French", audioLocale = "fr") + coEvery { streamService.getStreamInfo(any()) } returns + ExtractionResult.Success(testStreamResponse(audioStreams = listOf(en, fr))) + + val xml = (service.dashManifest("https://youtube.com/watch?v=test") as ExtractionResult.Success).data + + assertTrue(xml.contains("lang=\"en\"")) + assertTrue(xml.contains("lang=\"fr\"")) + assertTrue(xml.contains("label=\"English\"")) + assertTrue(xml.contains("label=\"French\"")) + val sets = Regex("]*mimeType").findAll(xml).count() + assertTrue(sets >= 2) + } + + @Test + fun `null audioLocale and audioTrackName produce no lang or label attributes`() = runBlocking { + val track = testAudioStream(format = "M4A", codec = "mp4a.40.2", + audioTrackId = "en.0", audioTrackName = null, audioLocale = null) + coEvery { streamService.getStreamInfo(any()) } returns + ExtractionResult.Success(testStreamResponse(audioStreams = listOf(track))) + + val xml = (service.dashManifest("https://youtube.com/watch?v=test") as ExtractionResult.Success).data + + assertTrue(!xml.contains("lang=")) + assertTrue(!xml.contains("label=")) + } +} diff --git a/src/test/kotlin/dev/typetype/server/ManifestServiceTest.kt b/src/test/kotlin/dev/typetype/server/ManifestServiceTest.kt index 872b4a5..41b9d90 100644 --- a/src/test/kotlin/dev/typetype/server/ManifestServiceTest.kt +++ b/src/test/kotlin/dev/typetype/server/ManifestServiceTest.kt @@ -16,7 +16,7 @@ class ManifestServiceTest { private val service = ManifestService(streamService) @Test - fun `av01 streams are excluded from manifest`() = runBlocking { + fun `av01 streams are included in manifest`() = runBlocking { val av01 = testVideoStream(codec = "av01.0.05M.08") val avc = testVideoStream(codec = "avc1.42c01e") coEvery { streamService.getStreamInfo(any()) } returns @@ -26,7 +26,7 @@ class ManifestServiceTest { assertTrue(result is ExtractionResult.Success) val xml = (result as ExtractionResult.Success).data - assertTrue(!xml.contains("av01")) + assertTrue(xml.contains("av01")) assertTrue(xml.contains("avc1")) } @@ -82,13 +82,12 @@ class ManifestServiceTest { @Test fun `returns Failure when no compatible streams`() = runBlocking { - val av01 = testVideoStream(codec = "av01.0.05M.08") val noCodec = testVideoStream(codec = null) val blankUrl = testVideoStream(url = "") coEvery { streamService.getStreamInfo(any()) } returns ExtractionResult.Success( testStreamResponse( - videoOnlyStreams = listOf(av01, noCodec, blankUrl), + videoOnlyStreams = listOf(noCodec, blankUrl), audioStreams = emptyList(), ) ) @@ -109,50 +108,4 @@ class ManifestServiceTest { assertEquals("Extraction failed", (result as ExtractionResult.Failure).message) } - @Test - fun `mono-track audio produces one AdaptationSet per mimeType without lang or label`() = runBlocking { - val aac = testAudioStream(format = "M4A", codec = "mp4a.40.2") - val opus = testAudioStream(format = "WEBM", codec = "opus", url = "https://example.com/opus") - coEvery { streamService.getStreamInfo(any()) } returns - ExtractionResult.Success(testStreamResponse(audioStreams = listOf(aac, opus))) - - val xml = (service.dashManifest("https://youtube.com/watch?v=test") as ExtractionResult.Success).data - - val sets = Regex("]*mimeType").findAll(xml).count() - assertTrue(sets >= 2) - assertTrue(!xml.contains("lang=")) - assertTrue(!xml.contains("label=")) - } - - @Test - fun `multi-track audio produces one AdaptationSet per trackId+mimeType with lang and label`() = runBlocking { - val en = testAudioStream(format = "M4A", codec = "mp4a.40.2", url = "https://example.com/en", - audioTrackId = "en.0", audioTrackName = "English", audioLocale = "en") - val fr = testAudioStream(format = "M4A", codec = "mp4a.40.2", url = "https://example.com/fr", - audioTrackId = "fr.0", audioTrackName = "French", audioLocale = "fr") - coEvery { streamService.getStreamInfo(any()) } returns - ExtractionResult.Success(testStreamResponse(audioStreams = listOf(en, fr))) - - val xml = (service.dashManifest("https://youtube.com/watch?v=test") as ExtractionResult.Success).data - - assertTrue(xml.contains("lang=\"en\"")) - assertTrue(xml.contains("lang=\"fr\"")) - assertTrue(xml.contains("label=\"English\"")) - assertTrue(xml.contains("label=\"French\"")) - val sets = Regex("]*mimeType").findAll(xml).count() - assertTrue(sets >= 2) - } - - @Test - fun `null audioLocale and audioTrackName produce no lang or label attributes`() = runBlocking { - val track = testAudioStream(format = "M4A", codec = "mp4a.40.2", - audioTrackId = "en.0", audioTrackName = null, audioLocale = null) - coEvery { streamService.getStreamInfo(any()) } returns - ExtractionResult.Success(testStreamResponse(audioStreams = listOf(track))) - - val xml = (service.dashManifest("https://youtube.com/watch?v=test") as ExtractionResult.Success).data - - assertTrue(!xml.contains("lang=")) - assertTrue(!xml.contains("label=")) - } }