package org.schabi.newpipe.extractor.services.youtube; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.schabi.newpipe.downloader.DownloaderTestImpl; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.CreationException; import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeOtfDashManifestCreator; import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeProgressiveDashManifestCreator; import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor; import org.schabi.newpipe.extractor.stream.DeliveryMethod; import org.schabi.newpipe.extractor.stream.Stream; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import javax.annotation.Nonnull; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.StringReader; import java.util.List; import java.util.Locale; import java.util.Random; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.IntStream; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.schabi.newpipe.extractor.ExtractorAsserts.assertGreater; import static org.schabi.newpipe.extractor.ExtractorAsserts.assertGreaterOrEqual; import static org.schabi.newpipe.extractor.ExtractorAsserts.assertIsValidUrl; import static org.schabi.newpipe.extractor.ExtractorAsserts.assertNotBlank; import static org.schabi.newpipe.extractor.ServiceList.YouTube; import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.ADAPTATION_SET; import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.AUDIO_CHANNEL_CONFIGURATION; import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.BASE_URL; import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.INITIALIZATION; import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.MPD; import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.PERIOD; import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.REPRESENTATION; import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.ROLE; import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.SEGMENT_BASE; import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.SEGMENT_TEMPLATE; import static org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeDashManifestCreatorsUtils.SEGMENT_TIMELINE; import static org.schabi.newpipe.extractor.utils.Utils.isBlank; /** * Test for YouTube DASH manifest creators. * *
* Tests the generation of OTF and progressive manifests. *
* ** We cannot test the generation of DASH manifests for ended livestreams because these videos will * be re-encoded as normal videos later, so we can't use a specific video. *
* ** The generation of DASH manifests for OTF streams, which can be tested, uses a video licenced * under the Creative Commons Attribution licence (reuse allowed): {@code A New Era of Open? * COVID-19 and the Pursuit for Equitable Solutions} (https://www.youtube.com/watch?v=DJ8GQUNUXGM) *
* ** We couldn't use mocks for these tests because the streaming URLs needs to fetched and the IP * address used to get these URLs is required (used as a param in the URLs; without it, video * servers return 403/Forbidden HTTP response code). *
* ** So the real downloader will be used everytime on this test class. *
*/ class YoutubeDashManifestCreatorsTest { // Setting a higher number may let Google video servers return 403s private static final int MAX_STREAMS_TO_TEST_PER_METHOD = 3; private static final String url = "https://www.youtube.com/watch?v=DJ8GQUNUXGM"; private static YoutubeStreamExtractor extractor; private static long videoLength; @BeforeAll public static void setUp() throws Exception { YoutubeParsingHelper.resetClientVersion(); YoutubeParsingHelper.setNumberGenerator(new Random(1)); NewPipe.init(DownloaderTestImpl.getInstance()); extractor = (YoutubeStreamExtractor) YouTube.getStreamExtractor(url); extractor.fetchPage(); videoLength = extractor.getLength(); } @Test void testOtfStreams() throws Exception { assertDashStreams(extractor.getVideoOnlyStreams()); assertDashStreams(extractor.getAudioStreams()); // no video stream with audio uses the DASH delivery method (YouTube OTF stream type) assertEquals(0, assertFilterStreams(extractor.getVideoStreams(), DeliveryMethod.DASH).size()); } @Test void testProgressiveStreams() throws Exception { assertProgressiveStreams(extractor.getVideoOnlyStreams()); assertProgressiveStreams(extractor.getAudioStreams()); // we are not able to generate DASH manifests of video formats with audio assertThrows(CreationException.class, () -> assertProgressiveStreams(extractor.getVideoStreams())); } private void assertDashStreams(final List extends Stream> streams) throws Exception { for (final Stream stream : assertFilterStreams(streams, DeliveryMethod.DASH)) { //noinspection ConstantConditions final String manifest = YoutubeOtfDashManifestCreator.fromOtfStreamingUrl( stream.getContent(), stream.getItagItem(), videoLength); assertNotBlank(manifest); assertManifestGenerated( manifest, stream.getItagItem(), document -> assertAll( () -> assertSegmentTemplateElement(document), () -> assertSegmentTimelineAndSElements(document) ) ); } } private void assertProgressiveStreams(final List extends Stream> streams) throws Exception { for (final Stream stream : assertFilterStreams(streams, DeliveryMethod.PROGRESSIVE_HTTP)) { //noinspection ConstantConditions final String manifest = YoutubeProgressiveDashManifestCreator.fromProgressiveStreamingUrl( stream.getContent(), stream.getItagItem(), videoLength); assertNotBlank(manifest); assertManifestGenerated( manifest, stream.getItagItem(), document -> assertAll( () -> assertBaseUrlElement(document), () -> assertSegmentBaseElement(document, stream.getItagItem()), () -> assertInitializationElement(document, stream.getItagItem()) ) ); } } @Nonnull private List extends Stream> assertFilterStreams( @Nonnull final List extends Stream> streams, final DeliveryMethod deliveryMethod) { final List extends Stream> filteredStreams = streams.stream() .filter(stream -> stream.getDeliveryMethod() == deliveryMethod) .limit(MAX_STREAMS_TO_TEST_PER_METHOD) .collect(Collectors.toList()); assertAll(filteredStreams.stream() .flatMap(stream -> java.util.stream.Stream.of( () -> assertNotBlank(stream.getContent()), () -> assertNotNull(stream.getItagItem()) )) ); return filteredStreams; } private void assertManifestGenerated(final String dashManifest, final ItagItem itagItem, final Consumer