diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeDashManifestCreator.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeDashManifestCreator.java
index 8383d0e2b..8929185c1 100644
--- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeDashManifestCreator.java
+++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeDashManifestCreator.java
@@ -79,7 +79,7 @@ public final class YoutubeDashManifestCreator {
*
*
* This list is automatically cleared in the execution of
- * {@link #createDashManifestFromOtfStreamingUrl(String, ItagItem, long)}, before the DASH
+ * {@link #fromOtfStreamingUrl(String, ItagItem, long)}, before the DASH
* manifest is converted to a string.
*
*/
@@ -90,7 +90,7 @@ public final class YoutubeDashManifestCreator {
*
*
* This list is automatically cleared in the execution of
- * {@link #createDashManifestFromOtfStreamingUrl(String, ItagItem, long)}, before the DASH
+ * {@link #fromOtfStreamingUrl(String, ItagItem, long)}, before the DASH
* manifest is converted to a string.
*
*/
@@ -242,7 +242,7 @@ public final class YoutubeDashManifestCreator {
* the DASH manifest
*/
@Nonnull
- public static String createDashManifestFromOtfStreamingUrl(
+ public static String fromOtfStreamingUrl(
@Nonnull final String otfBaseStreamingUrl,
@Nonnull final ItagItem itagItem,
final long durationSecondsFallback) throws YoutubeDashManifestCreationException {
@@ -376,7 +376,7 @@ public final class YoutubeDashManifestCreator {
* the DASH manifest
*/
@Nonnull
- public static String createDashManifestFromPostLiveStreamDvrStreamingUrl(
+ public static String fromPostLiveStreamDvrStreamingUrl(
@Nonnull final String postLiveStreamDvrStreamingUrl,
@Nonnull final ItagItem itagItem,
final int targetDurationSec,
@@ -505,7 +505,7 @@ public final class YoutubeDashManifestCreator {
* the DASH manifest
*/
@Nonnull
- public static String createDashManifestFromProgressiveStreamingUrl(
+ public static String fromProgressiveStreamingUrl(
@Nonnull final String progressiveStreamingBaseUrl,
@Nonnull final ItagItem itagItem,
final long durationSecondsFallback) throws YoutubeDashManifestCreationException {
diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/ExtractorAsserts.java b/extractor/src/test/java/org/schabi/newpipe/extractor/ExtractorAsserts.java
index bdbd59530..124d998d0 100644
--- a/extractor/src/test/java/org/schabi/newpipe/extractor/ExtractorAsserts.java
+++ b/extractor/src/test/java/org/schabi/newpipe/extractor/ExtractorAsserts.java
@@ -15,6 +15,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.schabi.newpipe.extractor.utils.Utils;
+
public class ExtractorAsserts {
public static void assertEmptyErrors(String message, List errors) {
if (!errors.isEmpty()) {
@@ -64,6 +66,14 @@ public class ExtractorAsserts {
}
}
+ public static void assertNotBlank(String stringToCheck) {
+ assertNotBlank(stringToCheck, null);
+ }
+
+ public static void assertNotBlank(String stringToCheck, @Nullable String message) {
+ assertFalse(Utils.isBlank(stringToCheck), message);
+ }
+
public static void assertGreater(final long expected, final long actual) {
assertGreater(expected, actual, actual + " is not > " + expected);
}
diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeDashManifestCreatorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeDashManifestCreatorTest.java
index db06307d1..c5fdf71af 100644
--- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeDashManifestCreatorTest.java
+++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeDashManifestCreatorTest.java
@@ -1,5 +1,18 @@
package org.schabi.newpipe.extractor.services.youtube;
+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.utils.Utils.isBlank;
+
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.schabi.newpipe.downloader.DownloaderTestImpl;
@@ -7,33 +20,25 @@ import org.schabi.newpipe.extractor.NewPipe;
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.schabi.newpipe.extractor.stream.VideoStream;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
+import java.io.StringReader;
+import java.util.List;
+import java.util.Random;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
import javax.annotation.Nonnull;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
-import java.io.StringReader;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.List;
-import java.util.Random;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
-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.ServiceList.YouTube;
-import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
/**
- * Test for {@link YoutubeDashManifestCreator}.
+ * Test for {@link YoutubeDashManifestCreator}. Tests the generation of OTF and Progressive
+ * manifests.
*
*
* We cannot test the generation of DASH manifests for ended livestreams because these videos will
@@ -58,570 +63,280 @@ import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
*/
class YoutubeDashManifestCreatorTest {
// Setting a higher number may let Google video servers return a lot of 403s
- private static final int MAXIMUM_NUMBER_OF_STREAMS_TO_TEST = 3;
-
- public static class TestGenerationOfOtfAndProgressiveManifests {
- private static final String url = "https://www.youtube.com/watch?v=DJ8GQUNUXGM";
- private static YoutubeStreamExtractor extractor;
-
- @BeforeAll
- public static void setUp() throws Exception {
- YoutubeParsingHelper.resetClientVersionAndKey();
- YoutubeParsingHelper.setNumberGenerator(new Random(1));
- NewPipe.init(DownloaderTestImpl.getInstance());
- extractor = (YoutubeStreamExtractor) YouTube.getStreamExtractor(url);
- extractor.fetchPage();
- }
-
- @Test
- void testOtfStreamsANewEraOfOpen() throws Exception {
- testStreams(DeliveryMethod.DASH,
- extractor.getVideoOnlyStreams());
- testStreams(DeliveryMethod.DASH,
- extractor.getAudioStreams());
- // This should not happen because there are no video stream with audio which use the
- // DASH delivery method (YouTube OTF stream type)
- try {
- testStreams(DeliveryMethod.DASH,
- extractor.getVideoStreams());
- } catch (final Exception e) {
- assertEquals(YoutubeDashManifestCreator.YoutubeDashManifestCreationException.class,
- e.getClass(), "The exception thrown was not the one excepted: "
- + e.getClass().getName()
- + "was thrown instead of YoutubeDashManifestCreationException");
- }
- }
-
- @Test
- void testProgressiveStreamsANewEraOfOpen() throws Exception {
- testStreams(DeliveryMethod.PROGRESSIVE_HTTP,
- extractor.getVideoOnlyStreams());
- testStreams(DeliveryMethod.PROGRESSIVE_HTTP,
- extractor.getAudioStreams());
- // This exception should be always thrown, as we are not able to generate DASH
- // manifests of video formats with audio
- final List videoStreams = extractor.getVideoStreams();
- if (!videoStreams.isEmpty()) {
- assertThrows(YoutubeDashManifestCreator.YoutubeDashManifestCreationException.class,
- () -> testStreams(DeliveryMethod.PROGRESSIVE_HTTP, videoStreams),
- "The exception thrown for the generation of DASH manifests for YouTube "
- + "progressive video streams with audio was not the one excepted");
- }
- }
-
- private void testStreams(@Nonnull final DeliveryMethod deliveryMethodToTest,
- @Nonnull final List extends Stream> streamList)
- throws Exception {
- int i = 0;
- final int streamListSize = streamList.size();
- final boolean isDeliveryMethodToTestProgressiveHttpDeliveryMethod =
- deliveryMethodToTest == DeliveryMethod.PROGRESSIVE_HTTP;
- final long videoLength = extractor.getLength();
-
- // Test at most the first five streams we found
- while (i <= YoutubeDashManifestCreatorTest.MAXIMUM_NUMBER_OF_STREAMS_TO_TEST
- && i < streamListSize) {
- final Stream stream = streamList.get(i);
- if (stream.getDeliveryMethod() == deliveryMethodToTest) {
- final String baseUrl = stream.getContent();
- assertFalse(isBlank(baseUrl), "The base URL of the stream is empty");
-
- final ItagItem itagItem = stream.getItagItem();
- assertNotNull(itagItem, "The itagItem is null");
-
- final String dashManifest;
- if (isDeliveryMethodToTestProgressiveHttpDeliveryMethod) {
- dashManifest = YoutubeDashManifestCreator
- .createDashManifestFromProgressiveStreamingUrl(baseUrl, itagItem,
- videoLength);
- } else if (deliveryMethodToTest == DeliveryMethod.DASH) {
- dashManifest = YoutubeDashManifestCreator
- .createDashManifestFromOtfStreamingUrl(baseUrl, itagItem,
- videoLength);
- } else {
- throw new IllegalArgumentException(
- "The delivery method provided is not the progressive HTTP or the DASH delivery method");
- }
- testManifestGenerated(dashManifest, itagItem,
- isDeliveryMethodToTestProgressiveHttpDeliveryMethod);
- assertFalse(isBlank(dashManifest), "The DASH manifest is null or empty: "
- + dashManifest);
- }
- ++i;
- }
- }
-
- private void testManifestGenerated(final String dashManifest,
- @Nonnull final ItagItem itagItem,
- final boolean isAProgressiveStreamingUrl)
- throws Exception {
- final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
- .newInstance();
- final DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
- final Document document = documentBuilder.parse(new InputSource(
- new StringReader(dashManifest)));
-
- testMpdElement(document);
- testPeriodElement(document);
- testAdaptationSetElement(document, itagItem);
- testRoleElement(document);
- testRepresentationElement(document, itagItem);
- if (itagItem.itagType.equals(ItagItem.ItagType.AUDIO)) {
- testAudioChannelConfigurationElement(document, itagItem);
- }
- if (isAProgressiveStreamingUrl) {
- testBaseUrlElement(document);
- testSegmentBaseElement(document, itagItem);
- testInitializationElement(document, itagItem);
- } else {
- testSegmentTemplateElement(document);
- testSegmentTimelineAndSElements(document);
- }
- }
-
- private void testMpdElement(@Nonnull final Document document) {
- final Element mpdElement = (Element) document.getElementsByTagName("MPD")
- .item(0);
- assertNotNull(mpdElement, "The MPD element doesn't exist");
- assertNull(mpdElement.getParentNode().getNodeValue(), "The MPD element has a parent element");
-
- final String mediaPresentationDurationValue = mpdElement
- .getAttribute("mediaPresentationDuration");
- assertNotNull(mediaPresentationDurationValue,
- "The value of the mediaPresentationDuration attribute is empty or the corresponding attribute doesn't exist");
- assertTrue(mediaPresentationDurationValue.startsWith("PT"),
- "The mediaPresentationDuration attribute of the DASH manifest is not valid");
- }
-
- private void testPeriodElement(@Nonnull final Document document) {
- final Element periodElement = (Element) document.getElementsByTagName("Period")
- .item(0);
- assertNotNull(periodElement, "The Period element doesn't exist");
- assertTrue(periodElement.getParentNode().isEqualNode(
- document.getElementsByTagName("MPD").item(0)),
- "The MPD element doesn't contain a Period element");
- }
-
- private void testAdaptationSetElement(@Nonnull final Document document,
- @Nonnull final ItagItem itagItem) {
- final Element adaptationSetElement = (Element) document
- .getElementsByTagName("AdaptationSet").item(0);
- assertNotNull(adaptationSetElement, "The AdaptationSet element doesn't exist");
- assertTrue(adaptationSetElement.getParentNode().isEqualNode(
- document.getElementsByTagName("Period").item(0)),
- "The Period element doesn't contain an AdaptationSet element");
-
- final String mimeTypeDashManifestValue = adaptationSetElement
- .getAttribute("mimeType");
- assertFalse(isBlank(mimeTypeDashManifestValue),
- "The value of the mimeType attribute is empty or the corresponding attribute doesn't exist");
-
- final String mimeTypeItagItemValue = itagItem.getMediaFormat().getMimeType();
- assertFalse(isBlank(mimeTypeItagItemValue), "The mimeType of the ItagItem is empty");
-
- assertEquals(mimeTypeDashManifestValue, mimeTypeItagItemValue,
- "The mimeType attribute of the DASH manifest (" + mimeTypeItagItemValue
- + ") is not equal to the mimeType set in the ItagItem object ("
- + mimeTypeItagItemValue + ")");
- }
-
- private void testRoleElement(@Nonnull final Document document) {
- final Element roleElement = (Element) document.getElementsByTagName("Role")
- .item(0);
- assertNotNull(roleElement, "The Role element doesn't exist");
- assertTrue(roleElement.getParentNode().isEqualNode(
- document.getElementsByTagName("AdaptationSet").item(0)),
- "The AdaptationSet element doesn't contain a Role element");
- }
-
- private void testRepresentationElement(@Nonnull final Document document,
- @Nonnull final ItagItem itagItem) {
- final Element representationElement = (Element) document
- .getElementsByTagName("Representation").item(0);
- assertNotNull(representationElement, "The Representation element doesn't exist");
- assertTrue(representationElement.getParentNode().isEqualNode(
- document.getElementsByTagName("AdaptationSet").item(0)),
- "The AdaptationSet element doesn't contain a Representation element");
-
- final String bandwidthDashManifestValue = representationElement
- .getAttribute("bandwidth");
- assertFalse(isBlank(bandwidthDashManifestValue),
- "The value of the bandwidth attribute is empty or the corresponding attribute doesn't exist");
-
- final int bandwidthDashManifest;
- try {
- bandwidthDashManifest = Integer.parseInt(bandwidthDashManifestValue);
- } catch (final NumberFormatException e) {
- throw new AssertionError("The value of the bandwidth attribute is not an integer",
- e);
- }
- assertTrue(bandwidthDashManifest > 0,
- "The value of the bandwidth attribute is less than or equal to 0");
-
- final int bitrateItagItem = itagItem.getBitrate();
- assertTrue(bitrateItagItem > 0,
- "The bitrate of the ItagItem is less than or equal to 0");
-
- assertEquals(bandwidthDashManifest, bitrateItagItem,
- "The value of the bandwidth attribute of the DASH manifest ("
- + bandwidthDashManifest
- + ") is not equal to the bitrate value set in the ItagItem object ("
- + bitrateItagItem + ")");
-
- final String codecsDashManifestValue = representationElement.getAttribute("codecs");
- assertFalse(isBlank(codecsDashManifestValue),
- "The value of the codecs attribute is empty or the corresponding attribute doesn't exist");
-
- final String codecsItagItemValue = itagItem.getCodec();
- assertFalse(isBlank(codecsItagItemValue), "The codec of the ItagItem is empty");
-
- assertEquals(codecsDashManifestValue, codecsItagItemValue,
- "The value of the codecs attribute of the DASH manifest ("
- + codecsDashManifestValue
- + ") is not equal to the codecs value set in the ItagItem object ("
- + codecsItagItemValue + ")");
-
- if (itagItem.itagType == ItagItem.ItagType.VIDEO_ONLY
- || itagItem.itagType == ItagItem.ItagType.VIDEO) {
- testVideoItagItemAttributes(representationElement, itagItem);
- }
-
- final String idDashManifestValue = representationElement.getAttribute("id");
- assertFalse(isBlank(idDashManifestValue),
- "The value of the id attribute is empty or the corresponding attribute doesn't exist");
-
- final int idDashManifest;
- try {
- idDashManifest = Integer.parseInt(idDashManifestValue);
- } catch (final NumberFormatException e) {
- throw new AssertionError("The value of the id attribute is not an integer",
- e);
- }
- assertTrue(idDashManifest > 0, "The value of the id attribute is less than or equal to 0");
-
- final int idItagItem = itagItem.id;
- assertTrue(idItagItem > 0, "The id of the ItagItem is less than or equal to 0");
- assertEquals(idDashManifest, idItagItem,
- "The value of the id attribute of the DASH manifest (" + idDashManifestValue
- + ") is not equal to the id of the ItagItem object (" + idItagItem
- + ")");
- }
-
- private void testVideoItagItemAttributes(@Nonnull final Element representationElement,
- @Nonnull final ItagItem itagItem) {
- final String frameRateDashManifestValue = representationElement
- .getAttribute("frameRate");
- assertFalse(isBlank(frameRateDashManifestValue),
- "The value of the frameRate attribute is empty or the corresponding attribute doesn't exist");
-
- final int frameRateDashManifest;
- try {
- frameRateDashManifest = Integer.parseInt(frameRateDashManifestValue);
- } catch (final NumberFormatException e) {
- throw new AssertionError("The value of the frameRate attribute is not an integer",
- e);
- }
- assertTrue(frameRateDashManifest > 0,
- "The value of the frameRate attribute is less than or equal to 0");
-
- final int fpsItagItem = itagItem.getFps();
- assertTrue(fpsItagItem > 0, "The fps of the ItagItem is unknown");
-
- assertEquals(frameRateDashManifest, fpsItagItem,
- "The value of the frameRate attribute of the DASH manifest ("
- + frameRateDashManifest
- + ") is not equal to the frame rate value set in the ItagItem object ("
- + fpsItagItem + ")");
-
- final String heightDashManifestValue = representationElement.getAttribute("height");
- assertFalse(isBlank(heightDashManifestValue),
- "The value of the height attribute is empty or the corresponding attribute doesn't exist");
-
- final int heightDashManifest;
- try {
- heightDashManifest = Integer.parseInt(heightDashManifestValue);
- } catch (final NumberFormatException e) {
- throw new AssertionError("The value of the height attribute is not an integer",
- e);
- }
- assertTrue(heightDashManifest > 0,
- "The value of the height attribute is less than or equal to 0");
-
- final int heightItagItem = itagItem.getHeight();
- assertTrue(heightItagItem > 0,
- "The height of the ItagItem is less than or equal to 0");
-
- assertEquals(heightDashManifest, heightItagItem,
- "The value of the height attribute of the DASH manifest ("
- + heightDashManifest
- + ") is not equal to the height value set in the ItagItem object ("
- + heightItagItem + ")");
-
- final String widthDashManifestValue = representationElement.getAttribute("width");
- assertFalse(isBlank(widthDashManifestValue),
- "The value of the width attribute is empty or the corresponding attribute doesn't exist");
-
- final int widthDashManifest;
- try {
- widthDashManifest = Integer.parseInt(widthDashManifestValue);
- } catch (final NumberFormatException e) {
- throw new AssertionError("The value of the width attribute is not an integer",
- e);
- }
- assertTrue(widthDashManifest > 0,
- "The value of the width attribute is less than or equal to 0");
-
- final int widthItagItem = itagItem.getWidth();
- assertTrue(widthItagItem > 0, "The width of the ItagItem is less than or equal to 0");
-
- assertEquals(widthDashManifest, widthItagItem,
- "The value of the width attribute of the DASH manifest (" + widthDashManifest
- + ") is not equal to the width value set in the ItagItem object ("
- + widthItagItem + ")");
- }
-
- private void testAudioChannelConfigurationElement(@Nonnull final Document document,
- @Nonnull final ItagItem itagItem) {
- final Element audioChannelConfigurationElement = (Element) document
- .getElementsByTagName("AudioChannelConfiguration").item(0);
- assertNotNull(audioChannelConfigurationElement,
- "The AudioChannelConfiguration element doesn't exist");
- assertTrue(audioChannelConfigurationElement.getParentNode().isEqualNode(
- document.getElementsByTagName("Representation").item(0)),
- "The Representation element doesn't contain an AudioChannelConfiguration element");
-
- final String audioChannelsDashManifestValue = audioChannelConfigurationElement
- .getAttribute("value");
- assertFalse(isBlank(audioChannelsDashManifestValue),
- "The value of the value attribute is empty or the corresponding attribute doesn't exist");
-
- final int audioChannelsDashManifest;
- try {
- audioChannelsDashManifest = Integer.parseInt(audioChannelsDashManifestValue);
- } catch (final NumberFormatException e) {
- throw new AssertionError(
- "The number of audio channels (the value attribute) is not an integer",
- e);
- }
- assertTrue(audioChannelsDashManifest > 0,
- "The number of audio channels (the value attribute) is less than or equal to 0");
-
- final int audioChannelsItagItem = itagItem.getAudioChannels();
- assertTrue(audioChannelsItagItem > 0,
- "The number of audio channels of the ItagItem is less than or equal to 0");
-
- assertEquals(audioChannelsDashManifest, audioChannelsItagItem,
- "The value of the value attribute of the DASH manifest ("
- + audioChannelsDashManifest
- + ") is not equal to the number of audio channels set in the ItagItem object ("
- + audioChannelsItagItem + ")");
- }
-
- private void testSegmentTemplateElement(@Nonnull final Document document) {
- final Element segmentTemplateElement = (Element) document
- .getElementsByTagName("SegmentTemplate").item(0);
- assertNotNull(segmentTemplateElement, "The SegmentTemplate element doesn't exist");
- assertTrue(segmentTemplateElement.getParentNode().isEqualNode(
- document.getElementsByTagName("Representation").item(0)),
- "The Representation element doesn't contain a SegmentTemplate element");
-
- final String initializationValue = segmentTemplateElement
- .getAttribute("initialization");
- assertFalse(isBlank(initializationValue),
- "The value of the initialization attribute is empty or the corresponding attribute doesn't exist");
- try {
- new URL(initializationValue);
- } catch (final MalformedURLException e) {
- throw new AssertionError("The value of the initialization attribute is not an URL",
- e);
- }
- assertTrue(initializationValue.endsWith("&sq=0"),
- "The value of the initialization attribute doesn't end with &sq=0");
-
- final String mediaValue = segmentTemplateElement.getAttribute("media");
- assertFalse(isBlank(mediaValue),
- "The value of the media attribute is empty or the corresponding attribute doesn't exist");
- try {
- new URL(mediaValue);
- } catch (final MalformedURLException e) {
- throw new AssertionError("The value of the media attribute is not an URL",
- e);
- }
- assertTrue(mediaValue.endsWith("&sq=$Number$"),
- "The value of the media attribute doesn't end with &sq=$Number$");
-
- final String startNumberValue = segmentTemplateElement.getAttribute("startNumber");
- assertFalse(isBlank(startNumberValue),
- "The value of the startNumber attribute is empty or the corresponding attribute doesn't exist");
- assertEquals("1", startNumberValue,
- "The value of the startNumber attribute is not equal to 1");
- }
-
- private void testSegmentTimelineAndSElements(@Nonnull final Document document) {
- final Element segmentTimelineElement = (Element) document
- .getElementsByTagName("SegmentTimeline").item(0);
- assertNotNull(segmentTimelineElement, "The SegmentTimeline element doesn't exist");
- assertTrue(segmentTimelineElement.getParentNode().isEqualNode(
- document.getElementsByTagName("SegmentTemplate").item(0)),
- "The SegmentTemplate element doesn't contain a SegmentTimeline element");
- testSElements(segmentTimelineElement);
- }
-
- private void testSElements(@Nonnull final Element segmentTimelineElement) {
- final NodeList segmentTimelineElementChildren = segmentTimelineElement.getChildNodes();
- final int segmentTimelineElementChildrenLength = segmentTimelineElementChildren
- .getLength();
- assertNotEquals(0, segmentTimelineElementChildrenLength,
- "The DASH manifest doesn't have a segment element (S) in the SegmentTimeLine element");
-
- for (int i = 0; i < segmentTimelineElementChildrenLength; i++) {
- final Element sElement = (Element) segmentTimelineElement.getElementsByTagName("S")
- .item(i);
-
- final String dValue = sElement.getAttribute("d");
- assertFalse(isBlank(dValue),
- "The value of the duration of this segment (the d attribute of this S element) is empty or the corresponding attribute doesn't exist");
-
- final int d;
- try {
- d = Integer.parseInt(dValue);
- } catch (final NumberFormatException e) {
- throw new AssertionError("The value of the d attribute is not an integer", e);
- }
- assertTrue(d > 0, "The value of the d attribute is less than or equal to 0");
-
- final String rValue = sElement.getAttribute("r");
- // A segment duration can or can't be repeated, so test the next segment if there
- // is no r attribute
- if (!isBlank(rValue)) {
- final int r;
- try {
- r = Integer.parseInt(dValue);
- } catch (final NumberFormatException e) {
- throw new AssertionError("The value of the r attribute is not an integer",
- e);
- }
- assertTrue(r > 0, "The value of the r attribute is less than or equal to 0");
- }
- }
- }
-
- private void testBaseUrlElement(@Nonnull final Document document) {
- final Element baseURLElement = (Element) document
- .getElementsByTagName("BaseURL").item(0);
- assertNotNull(baseURLElement, "The BaseURL element doesn't exist");
- assertTrue(baseURLElement.getParentNode().isEqualNode(
- document.getElementsByTagName("Representation").item(0)),
- "The Representation element doesn't contain a BaseURL element");
-
- final String baseURLElementContentValue = baseURLElement
- .getTextContent();
- assertFalse(isBlank(baseURLElementContentValue),
- "The content of the BaseURL element is empty or the corresponding element has no content");
-
- try {
- new URL(baseURLElementContentValue);
- } catch (final MalformedURLException e) {
- throw new AssertionError("The content of the BaseURL element is not an URL", e);
- }
- }
-
- private void testSegmentBaseElement(@Nonnull final Document document,
- @Nonnull final ItagItem itagItem) {
- final Element segmentBaseElement = (Element) document
- .getElementsByTagName("SegmentBase").item(0);
- assertNotNull(segmentBaseElement, "The SegmentBase element doesn't exist");
- assertTrue(segmentBaseElement.getParentNode().isEqualNode(
- document.getElementsByTagName("Representation").item(0)),
- "The Representation element doesn't contain a SegmentBase element");
-
- final String indexRangeValue = segmentBaseElement
- .getAttribute("indexRange");
- assertFalse(isBlank(indexRangeValue),
- "The value of the indexRange attribute is empty or the corresponding attribute doesn't exist");
- final String[] indexRangeParts = indexRangeValue.split("-");
- assertEquals(2, indexRangeParts.length,
- "The value of the indexRange attribute is not valid");
-
- final int dashManifestIndexStart;
- try {
- dashManifestIndexStart = Integer.parseInt(indexRangeParts[0]);
- } catch (final NumberFormatException e) {
- throw new AssertionError("The value of the indexRange attribute is not valid", e);
- }
-
- final int itagItemIndexStart = itagItem.getIndexStart();
- assertTrue(itagItemIndexStart > 0,
- "The indexStart of the ItagItem is less than or equal to 0");
- assertEquals(dashManifestIndexStart, itagItemIndexStart,
- "The indexStart value of the indexRange attribute of the DASH manifest ("
- + dashManifestIndexStart
- + ") is not equal to the indexStart of the ItagItem object ("
- + itagItemIndexStart + ")");
-
- final int dashManifestIndexEnd;
- try {
- dashManifestIndexEnd = Integer.parseInt(indexRangeParts[1]);
- } catch (final NumberFormatException e) {
- throw new AssertionError("The value of the indexRange attribute is not valid", e);
- }
-
- final int itagItemIndexEnd = itagItem.getIndexEnd();
- assertTrue(itagItemIndexEnd > 0,
- "The indexEnd of the ItagItem is less than or equal to 0");
-
- assertEquals(dashManifestIndexEnd, itagItemIndexEnd,
- "The indexEnd value of the indexRange attribute of the DASH manifest ("
- + dashManifestIndexEnd
- + ") is not equal to the indexEnd of the ItagItem object ("
- + itagItemIndexEnd + ")");
- }
-
- private void testInitializationElement(@Nonnull final Document document,
- @Nonnull final ItagItem itagItem) {
- final Element initializationElement = (Element) document
- .getElementsByTagName("Initialization").item(0);
- assertNotNull(initializationElement, "The Initialization element doesn't exist");
- assertTrue(initializationElement.getParentNode().isEqualNode(
- document.getElementsByTagName("SegmentBase").item(0)),
- "The SegmentBase element doesn't contain an Initialization element");
-
- final String rangeValue = initializationElement
- .getAttribute("range");
- assertFalse(isBlank(rangeValue),
- "The value of the range attribute is empty or the corresponding attribute doesn't exist");
- final String[] rangeParts = rangeValue.split("-");
- assertEquals(2, rangeParts.length, "The value of the range attribute is not valid");
-
- final int dashManifestInitStart;
- try {
- dashManifestInitStart = Integer.parseInt(rangeParts[0]);
- } catch (final NumberFormatException e) {
- throw new AssertionError("The value of the range attribute is not valid", e);
- }
-
- final int itagItemInitStart = itagItem.getInitStart();
- assertTrue(itagItemInitStart >= 0, "The initStart of the ItagItem is less than 0");
- assertEquals(dashManifestInitStart, itagItemInitStart,
- "The initStart value of the range attribute of the DASH manifest ("
- + dashManifestInitStart
- + ") is not equal to the initStart of the ItagItem object ("
- + itagItemInitStart + ")");
-
- final int dashManifestInitEnd;
- try {
- dashManifestInitEnd = Integer.parseInt(rangeParts[1]);
- } catch (final NumberFormatException e) {
- throw new AssertionError("The value of the indexRange attribute is not valid", e);
- }
-
- final int itagItemInitEnd = itagItem.getInitEnd();
- assertTrue(itagItemInitEnd > 0, "The indexEnd of the ItagItem is less than or equal to 0");
- assertEquals(dashManifestInitEnd, itagItemInitEnd,
- "The initEnd value of the range attribute of the DASH manifest ("
- + dashManifestInitEnd
- + ") is not equal to the initEnd of the ItagItem object ("
- + itagItemInitEnd + ")");
+ 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.resetClientVersionAndKey();
+ 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(YoutubeDashManifestCreator.YoutubeDashManifestCreationException.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 = YoutubeDashManifestCreator.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 = YoutubeDashManifestCreator.fromProgressiveStreamingUrl(
+ stream.getContent(), stream.getItagItem(), videoLength);
+ assertNotBlank(manifest);
+
+ assertManifestGenerated(
+ manifest,
+ stream.getItagItem(),
+ document -> assertAll(
+ () -> assertBaseUrlElement(document),
+ () -> assertSegmentBaseElement(document, stream.getItagItem()),
+ () -> assertInitializationElement(document, stream.getItagItem())
+ )
+ );
+ }
+ }
+
+ private List extends Stream> assertFilterStreams(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 additionalAsserts)
+ throws Exception {
+
+ final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
+ .newInstance();
+ final DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+ final Document document = documentBuilder.parse(new InputSource(
+ new StringReader(dashManifest)));
+
+ assertAll(
+ () -> assertMpdElement(document),
+ () -> assertPeriodElement(document),
+ () -> assertAdaptationSetElement(document, itagItem),
+ () -> assertRoleElement(document),
+ () -> assertRepresentationElement(document, itagItem),
+ () -> {
+ if (itagItem.itagType.equals(ItagItem.ItagType.AUDIO)) {
+ assertAudioChannelConfigurationElement(document, itagItem);
+ }
+ },
+ () -> additionalAsserts.accept(document)
+ );
+ }
+
+ private void assertMpdElement(@Nonnull final Document document) {
+ final Element element = (Element) document.getElementsByTagName("MPD").item(0);
+ assertNotNull(element);
+ assertNull(element.getParentNode().getNodeValue());
+
+ final String mediaPresentationDuration = element.getAttribute("mediaPresentationDuration");
+ assertNotNull(mediaPresentationDuration);
+ assertTrue(mediaPresentationDuration.startsWith("PT"));
+ }
+
+ private void assertPeriodElement(@Nonnull final Document document) {
+ assertGetElement(document, "Period", "MPD");
+ }
+
+ private void assertAdaptationSetElement(@Nonnull final Document document,
+ @Nonnull final ItagItem itagItem) {
+ final Element element = assertGetElement(document, "AdaptationSet", "Period");
+ assertAttrEquals(itagItem.getMediaFormat().getMimeType(), element, "mimeType");
+ }
+
+ private void assertRoleElement(@Nonnull final Document document) {
+ assertGetElement(document, "Role", "AdaptationSet");
+ }
+
+ private void assertRepresentationElement(@Nonnull final Document document,
+ @Nonnull final ItagItem itagItem) {
+ final Element element = assertGetElement(document, "Representation", "AdaptationSet");
+
+ assertAttrEquals(itagItem.getBitrate(), element, "bandwidth");
+ assertAttrEquals(itagItem.getCodec(), element, "codecs");
+
+ if (itagItem.itagType == ItagItem.ItagType.VIDEO_ONLY
+ || itagItem.itagType == ItagItem.ItagType.VIDEO) {
+ assertAttrEquals(itagItem.getFps(), element, "frameRate");
+ assertAttrEquals(itagItem.getHeight(), element, "height");
+ assertAttrEquals(itagItem.getWidth(), element, "width");
+ }
+
+ assertAttrEquals(itagItem.id, element, "id");
+ }
+
+ private void assertAudioChannelConfigurationElement(@Nonnull final Document document,
+ @Nonnull final ItagItem itagItem) {
+ final Element element = assertGetElement(document,
+ "AudioChannelConfiguration", "Representation");
+ assertAttrEquals(itagItem.getAudioChannels(), element, "value");
+ }
+
+ private void assertSegmentTemplateElement(@Nonnull final Document document) {
+ final Element element = assertGetElement(document, "SegmentTemplate", "Representation");
+
+ final String initializationValue = element.getAttribute("initialization");
+ assertIsValidUrl(initializationValue);
+ assertTrue(initializationValue.endsWith("&sq=0"));
+
+ final String mediaValue = element.getAttribute("media");
+ assertIsValidUrl(mediaValue);
+ assertTrue(mediaValue.endsWith("&sq=$Number$"));
+
+ assertEquals("1", element.getAttribute("startNumber"));
+ }
+
+ private void assertSegmentTimelineAndSElements(@Nonnull final Document document) {
+ final Element element = assertGetElement(document, "SegmentTimeline", "SegmentTemplate");
+ final NodeList childNodes = element.getChildNodes();
+ assertGreater(0, childNodes.getLength());
+
+ assertAll(IntStream.range(0, childNodes.getLength())
+ .mapToObj(childNodes::item)
+ .map(Element.class::cast)
+ .flatMap(sElement -> java.util.stream.Stream.of(
+ () -> assertEquals("S", sElement.getTagName()),
+ () -> assertGreater(0, Integer.parseInt(sElement.getAttribute("d"))),
+ () -> {
+ final String rValue = sElement.getAttribute("r");
+ // A segment duration can or can't be repeated, so test the next segment
+ // if there is no r attribute
+ if (!isBlank(rValue)) {
+ assertGreater(0, Integer.parseInt(rValue));
+ }
+ }
+ )
+ )
+ );
+ }
+
+ private void assertBaseUrlElement(@Nonnull final Document document) {
+ final Element element = assertGetElement(document, "BaseURL", "Representation");
+ assertIsValidUrl(element.getTextContent());
+ }
+
+ private void assertSegmentBaseElement(@Nonnull final Document document,
+ @Nonnull final ItagItem itagItem) {
+ final Element element = assertGetElement(document, "SegmentBase", "Representation");
+ assertRangeEquals(itagItem.getIndexStart(), itagItem.getIndexEnd(), element, "indexRange");
+ }
+
+ private void assertInitializationElement(@Nonnull final Document document,
+ @Nonnull final ItagItem itagItem) {
+ final Element element = assertGetElement(document, "Initialization", "SegmentBase");
+ assertRangeEquals(itagItem.getInitStart(), itagItem.getInitEnd(), element, "range");
+ }
+
+
+ private void assertAttrEquals(final int expected,
+ final Element element,
+ final String attribute) {
+
+ final int actual = Integer.parseInt(element.getAttribute(attribute));
+ assertAll(
+ () -> assertGreater(0, actual),
+ () -> assertEquals(expected, actual)
+ );
+ }
+
+ private void assertAttrEquals(final String expected,
+ final Element element,
+ final String attribute) {
+ final String actual = element.getAttribute(attribute);
+ assertAll(
+ () -> assertNotBlank(actual),
+ () -> assertEquals(expected, actual)
+ );
+ }
+
+ private void assertRangeEquals(final int expectedStart,
+ final int expectedEnd,
+ final Element element,
+ final String attribute) {
+ final String range = element.getAttribute(attribute);
+ assertNotBlank(range);
+ final String[] rangeParts = range.split("-");
+ assertEquals(2, rangeParts.length);
+
+ final int actualStart = Integer.parseInt(rangeParts[0]);
+ final int actualEnd = Integer.parseInt(rangeParts[1]);
+
+ assertAll(
+ () -> assertGreaterOrEqual(0, actualStart),
+ () -> assertEquals(expectedStart, actualStart),
+ () -> assertGreater(0, actualEnd),
+ () -> assertEquals(expectedEnd, actualEnd)
+ );
+ }
+
+ private Element assertGetElement(final Document document,
+ final String tagName,
+ final String expectedParentTagName) {
+
+ final Element element = (Element) document.getElementsByTagName(tagName).item(0);
+ assertNotNull(element);
+ assertTrue(element.getParentNode().isEqualNode(
+ document.getElementsByTagName(expectedParentTagName).item(0)),
+ "Element with tag name \"" + tagName + "\" does not have a parent node"
+ + " with tag name \"" + expectedParentTagName + "\"");
+ return element;
+ }
}