Add track types to audio streams (#1041)

This commit is contained in:
ThetaDev 2023-03-28 00:02:20 +02:00 committed by GitHub
parent 44ae139d33
commit 8d1303e18f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1683 additions and 1547 deletions

View File

@ -2,6 +2,7 @@ package org.schabi.newpipe.extractor.services.youtube;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.stream.AudioTrackType;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -201,7 +202,7 @@ public class ItagItem implements Serializable {
this.contentLength = itagItem.contentLength;
this.audioTrackId = itagItem.audioTrackId;
this.audioTrackName = itagItem.audioTrackName;
this.isDescriptiveAudio = itagItem.isDescriptiveAudio;
this.audioTrackType = itagItem.audioTrackType;
this.audioLocale = itagItem.audioLocale;
}
@ -251,7 +252,8 @@ public class ItagItem implements Serializable {
private long contentLength = CONTENT_LENGTH_UNKNOWN;
private String audioTrackId;
private String audioTrackName;
private boolean isDescriptiveAudio;
@Nullable
private AudioTrackType audioTrackType;
@Nullable
private Locale audioLocale;
@ -594,21 +596,22 @@ public class ItagItem implements Serializable {
}
/**
* Return whether the stream is a descriptive audio.
* Get the {@link AudioTrackType} of the stream.
*
* @return whether the stream is a descriptive audio
* @return the {@link AudioTrackType} of the stream or {@code null}
*/
public boolean isDescriptiveAudio() {
return isDescriptiveAudio;
@Nullable
public AudioTrackType getAudioTrackType() {
return audioTrackType;
}
/**
* Set whether the stream is a descriptive audio.
* Set the {@link AudioTrackType} of the stream, if present.
*
* @param isDescriptiveAudio whether the stream is a descriptive audio
* @param audioTrackType the {@link AudioTrackType} of the stream or {@code null}
*/
public void setIsDescriptiveAudio(final boolean isDescriptiveAudio) {
this.isDescriptiveAudio = isDescriptiveAudio;
public void setAudioTrackType(@Nullable final AudioTrackType audioTrackType) {
this.audioTrackType = audioTrackType;
}
/**

View File

@ -43,6 +43,7 @@ import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.localization.ContentCountry;
import org.schabi.newpipe.extractor.localization.Localization;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.AudioTrackType;
import org.schabi.newpipe.extractor.stream.Description;
import org.schabi.newpipe.extractor.utils.JsonUtils;
import org.schabi.newpipe.extractor.utils.Parser;
@ -1801,4 +1802,49 @@ public final class YoutubeParsingHelper {
public static boolean isConsentAccepted() {
return consentAccepted;
}
/**
* Extract the audio track type from a YouTube stream URL.
* <p>
* The track type is parsed from the {@code xtags} URL parameter
* (Example: {@code acont=original:lang=en}).
* </p>
* @param streamUrl YouTube stream URL
* @return {@link AudioTrackType} or {@code null} if no track type was found
*/
@Nullable
public static AudioTrackType extractAudioTrackType(final String streamUrl) {
final String xtags;
try {
xtags = Utils.getQueryValue(new URL(streamUrl), "xtags");
} catch (final MalformedURLException e) {
return null;
}
if (xtags == null) {
return null;
}
String atype = null;
for (final String param : xtags.split(":")) {
final String[] kv = param.split("=", 2);
if (kv.length > 1 && kv[0].equals("acont")) {
atype = kv[1];
break;
}
}
if (atype == null) {
return null;
}
switch (atype) {
case "original":
return AudioTrackType.ORIGINAL;
case "dubbed":
return AudioTrackType.DUBBED;
case "descriptive":
return AudioTrackType.DESCRIPTIVE;
default:
return null;
}
}
}

View File

@ -16,6 +16,7 @@ import org.schabi.newpipe.extractor.downloader.Response;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.services.youtube.DeliveryType;
import org.schabi.newpipe.extractor.services.youtube.ItagItem;
import org.schabi.newpipe.extractor.stream.AudioTrackType;
import org.schabi.newpipe.extractor.utils.ManifestCreatorCache;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
@ -31,6 +32,7 @@ import java.util.Map;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@ -278,7 +280,8 @@ public final class YoutubeDashManifestCreatorsUtils {
*
* <p>
* {@code <Role schemeIdUri="urn:mpeg:DASH:role:2011" value="VALUE"/>}, where {@code VALUE} is
* {@code main} for videos and audios and {@code alternate} for descriptive audio
* {@code main} for videos and audios, {@code description} for descriptive audio and
* {@code dub} for dubbed audio.
* </p>
*
* <p>
@ -298,8 +301,7 @@ public final class YoutubeDashManifestCreatorsUtils {
final Element roleElement = doc.createElement(ROLE);
setAttribute(roleElement, doc, "schemeIdUri", "urn:mpeg:DASH:role:2011");
setAttribute(roleElement, doc, "value", itagItem.isDescriptiveAudio()
? "alternate" : "main");
setAttribute(roleElement, doc, "value", getRoleValue(itagItem.getAudioTrackType()));
adaptationSetElement.appendChild(roleElement);
} catch (final DOMException e) {
@ -307,6 +309,28 @@ public final class YoutubeDashManifestCreatorsUtils {
}
}
/**
* Get the value of the {@code <Role>} element based on the {@link AudioTrackType} attribute
* of a stream.
* @param trackType audio track type
* @return role value
*/
private static String getRoleValue(@Nullable final AudioTrackType trackType) {
if (trackType != null) {
switch (trackType) {
case ORIGINAL:
return "main";
case DUBBED:
return "dub";
case DESCRIPTIVE:
return "description";
default:
return "alternate";
}
}
return "main";
}
/**
* Generate the {@code <Representation>} element, appended as a child of the
* {@code <AdaptationSet>} element.

View File

@ -1311,7 +1311,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
.setAudioTrackId(itagItem.getAudioTrackId())
.setAudioTrackName(itagItem.getAudioTrackName())
.setAudioLocale(itagItem.getAudioLocale())
.setIsDescriptive(itagItem.isDescriptiveAudio())
.setAudioTrackType(itagItem.getAudioTrackType())
.setItagItem(itagItem);
if (streamType == StreamType.LIVE_STREAM
@ -1484,16 +1484,11 @@ public class YoutubeStreamExtractor extends StreamExtractor {
itagItem.setAudioLocale(LocaleCompat.forLanguageTag(
audioTrackId.substring(0, audioTrackIdLastLocaleCharacter)));
}
itagItem.setAudioTrackType(YoutubeParsingHelper.extractAudioTrackType(streamUrl));
}
itagItem.setAudioTrackName(formatData.getObject("audioTrack")
.getString("displayName"));
// Descriptive audio tracks
// This information is also provided as a protobuf object in the formatData
itagItem.setIsDescriptiveAudio(streamUrl.contains("acont%3Ddescriptive")
// Support "decoded" URLs
|| streamUrl.contains("acont=descriptive"));
}
// YouTube return the content length and the approximate duration as strings

View File

@ -50,7 +50,8 @@ public final class AudioStream extends Stream {
private final String audioTrackName;
@Nullable
private final Locale audioLocale;
private final boolean isDescriptive;
@Nullable
private final AudioTrackType audioTrackType;
@Nullable
private ItagItem itagItem;
@ -75,7 +76,8 @@ public final class AudioStream extends Stream {
private String audioTrackName;
@Nullable
private Locale audioLocale;
private boolean isDescriptive;
@Nullable
private AudioTrackType audioTrackType;
@Nullable
private ItagItem itagItem;
@ -223,25 +225,17 @@ public final class AudioStream extends Stream {
}
/**
* Set whether this {@link AudioStream} is a descriptive audio.
* Set the {@link AudioTrackType} of the {@link AudioStream}.
*
* <p>
* A descriptive audio is an audio in which descriptions of visual elements of a video are
* added in the original audio, with the goal to make a video more accessible to blind and
* visually impaired people.
* The default value is {@code null}.
* </p>
*
* <p>
* The default value is {@code false}.
* </p>
*
* @param isDescriptive whether this {@link AudioStream} is a descriptive audio
* @param audioTrackType the audio track type of the {@link AudioStream}, which can be null
* @return this {@link Builder} instance
* @see <a href="https://en.wikipedia.org/wiki/Audio_description">
* https://en.wikipedia.org/wiki/Audio_description</a>
*/
public Builder setIsDescriptive(final boolean isDescriptive) {
this.isDescriptive = isDescriptive;
public Builder setAudioTrackType(final AudioTrackType audioTrackType) {
this.audioTrackType = audioTrackType;
return this;
}
@ -313,7 +307,7 @@ public final class AudioStream extends Stream {
}
return new AudioStream(id, content, isUrl, mediaFormat, deliveryMethod, averageBitrate,
manifestUrl, audioTrackId, audioTrackName, audioLocale, isDescriptive,
manifestUrl, audioTrackId, audioTrackName, audioLocale, audioTrackType,
itagItem);
}
}
@ -350,7 +344,7 @@ public final class AudioStream extends Stream {
@Nullable final String audioTrackId,
@Nullable final String audioTrackName,
@Nullable final Locale audioLocale,
final boolean isDescriptive,
@Nullable final AudioTrackType audioTrackType,
@Nullable final ItagItem itagItem) {
super(id, content, isUrl, format, deliveryMethod, manifestUrl);
if (itagItem != null) {
@ -368,7 +362,7 @@ public final class AudioStream extends Stream {
this.audioTrackId = audioTrackId;
this.audioTrackName = audioTrackName;
this.audioLocale = audioLocale;
this.isDescriptive = isDescriptive;
this.audioTrackType = audioTrackType;
}
/**
@ -379,7 +373,7 @@ public final class AudioStream extends Stream {
return super.equalStats(cmp) && cmp instanceof AudioStream
&& averageBitrate == ((AudioStream) cmp).averageBitrate
&& Objects.equals(audioTrackId, ((AudioStream) cmp).audioTrackId)
&& isDescriptive == ((AudioStream) cmp).isDescriptive
&& audioTrackType == ((AudioStream) cmp).audioTrackType
&& Objects.equals(audioLocale, ((AudioStream) cmp).audioLocale);
}
@ -507,20 +501,14 @@ public final class AudioStream extends Stream {
}
/**
* Returns whether this stream is a descriptive audio.
* Get the {@link AudioTrackType} of the stream, which is {@code null} if the track type
* is not known.
*
* <p>
* A descriptive audio is an audio in which descriptions of visual elements of a video are
* added in the original audio, with the goal to make a video more accessible to blind and
* visually impaired people.
* </p>
*
* @return {@code true} this audio stream is a descriptive audio, {@code false} otherwise
* @see <a href="https://en.wikipedia.org/wiki/Audio_description">
* https://en.wikipedia.org/wiki/Audio_description</a>
* @return the {@link AudioTrackType} of the stream or {@code null}
*/
public boolean isDescriptive() {
return isDescriptive;
@Nullable
public AudioTrackType getAudioTrackType() {
return audioTrackType;
}
/**

View File

@ -0,0 +1,33 @@
package org.schabi.newpipe.extractor.stream;
/**
* An enum representing the track type of an {@link AudioStream} extracted by a {@link
* StreamExtractor}.
*/
public enum AudioTrackType {
/**
* An original audio track of the video.
*/
ORIGINAL,
/**
* An audio track with the original voices replaced, typically in a different language.
*
* @see <a href="https://en.wikipedia.org/wiki/Dubbing">
* https://en.wikipedia.org/wiki/Dubbing</a>
*/
DUBBED,
/**
* A descriptive audio track.
* <p>
* A descriptive audio track is an audio track in which descriptions of visual elements of
* a video are added to the original audio, with the goal to make a video more accessible to
* blind and visually impaired people.
* </p>
*
* @see <a href="https://en.wikipedia.org/wiki/Audio_description">
* https://en.wikipedia.org/wiki/Audio_description</a>
*/
DESCRIPTIVE
}

View File

@ -8,6 +8,7 @@ import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.Creati
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.AudioTrackType;
import org.schabi.newpipe.extractor.stream.DeliveryMethod;
import org.schabi.newpipe.extractor.stream.Stream;
import org.w3c.dom.Document;
@ -233,7 +234,27 @@ class YoutubeDashManifestCreatorsTest {
private void assertRoleElement(@Nonnull final Document document,
@Nonnull final ItagItem itagItem) {
final Element element = assertGetElement(document, ROLE, ADAPTATION_SET);
assertAttrEquals(itagItem.isDescriptiveAudio() ? "alternate" : "main", element, "value");
final String expect;
if (itagItem.getAudioTrackType() == null) {
expect = "main";
} else {
switch (itagItem.getAudioTrackType()) {
case ORIGINAL:
expect = "main";
break;
case DUBBED:
expect = "dub";
break;
case DESCRIPTIVE:
expect = "description";
break;
default:
expect = "alternate";
}
}
assertAttrEquals(expect, element, "value");
}
private void assertRepresentationElement(@Nonnull final Document document,

View File

@ -1,17 +1,19 @@
package org.schabi.newpipe.extractor.services.youtube;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.schabi.newpipe.downloader.DownloaderFactory;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.stream.AudioTrackType;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class YoutubeParsingHelperTest {
private static final String RESOURCE_PATH = DownloaderFactory.RESOURCE_PATH + "services/youtube/";
@ -23,29 +25,42 @@ public class YoutubeParsingHelperTest {
}
@Test
public void testAreHardcodedClientVersionAndKeyValid() throws IOException, ExtractionException {
void testAreHardcodedClientVersionAndKeyValid() throws IOException, ExtractionException {
assertTrue(YoutubeParsingHelper.areHardcodedClientVersionAndKeyValid(),
"Hardcoded client version and key are not valid anymore");
}
@Test
public void testAreHardcodedYoutubeMusicKeysValid() throws IOException, ExtractionException {
void testAreHardcodedYoutubeMusicKeysValid() throws IOException, ExtractionException {
assertTrue(YoutubeParsingHelper.isHardcodedYoutubeMusicKeyValid(),
"Hardcoded YouTube Music keys are not valid anymore");
}
@Test
public void testParseDurationString() throws ParsingException {
void testParseDurationString() throws ParsingException {
assertEquals(1162567, YoutubeParsingHelper.parseDurationString("12:34:56:07"));
assertEquals(4445767, YoutubeParsingHelper.parseDurationString("1,234:56:07"));
assertEquals(754, YoutubeParsingHelper.parseDurationString("12:34 "));
}
@Test
public void testConvertFromGoogleCacheUrl() {
void testConvertFromGoogleCacheUrl() {
assertEquals("https://mohfw.gov.in/",
YoutubeParsingHelper.extractCachedUrlIfNeeded("https://webcache.googleusercontent.com/search?q=cache:https://mohfw.gov.in/"));
assertEquals("https://www.infektionsschutz.de/coronavirus-sars-cov-2.html",
YoutubeParsingHelper.extractCachedUrlIfNeeded("https://www.infektionsschutz.de/coronavirus-sars-cov-2.html"));
}
@Test
void extractAudioTrackType() {
final String originalUrl = "https://rr2---sn-4g5lzned.googlevideo.com/videoplayback?expire=1679429648&ei=sLsZZKrICIuR1gLSnYbgAg&ip=127.0.0.1&id=o-ALWn2ZwDxUXEZKzlsT_X9iuDjRMSi__SgRXVrVjKZEhc&itag=251&source=youtube&requiressl=yes&mh=nU&mm=31%2C29&mn=sn-4g5lzned%2Csn-4g5edndz&ms=au%2Crdu&mv=m&mvi=2&pl=40&initcwndbps=1740000&spc=H3gIhgXQzBxvKu2MOEmFaaEenC4DKdVUwudTeu3dtKwmq-Xv5g&vprv=1&xtags=acont%3Doriginal%3Alang%3Den&mime=audio%2Fwebm&ns=-lg0OQZL1LZRQO-dzE0W4E4L&gir=yes&clen=3513412&dur=303.681&lmt=1679342942566207&mt=1679407764&fvip=1&keepalive=yes&fexp=24007246&c=WEB&txp=5532434&n=gDLP5pImH9Vr7v&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&sig=AOq0QJ8wRAIgPFQ1yX8aoc35sz2eV2-wzNIhTQeOHGCsOmIonmo776kCIFo5k6HZ5kAQ6DycRCAG8jJgk9jNyncILGPrGZMZUuuo&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=AG3C_xAwRQIhANODPaBuc32MWI9gF3Bn1iz3byEn7EwUiXpNLuCcQqW9AiBB88Qrrz2fJCzYKg14_nnGxGQH1Uoi7i31OSrHK6_dGw%3D%3D";
final String dubbedUrl = "https://rr2---sn-4g5lzned.googlevideo.com/videoplayback?expire=1679429648&ei=sLsZZKrICIuR1gLSnYbgAg&ip=127.0.0.1&id=o-ALWn2ZwDxUXEZKzlsT_X9iuDjRMSi__SgRXVrVjKZEhc&itag=251&source=youtube&requiressl=yes&mh=nU&mm=31%2C29&mn=sn-4g5lzned%2Csn-4g5edndz&ms=au%2Crdu&mv=m&mvi=2&pl=40&initcwndbps=1740000&spc=H3gIhgXQzBxvKu2MOEmFaaEenC4DKdVUwudTeu3dtKwmq-Xv5g&vprv=1&xtags=acont%3Ddubbed%3Alang%3Den&mime=audio%2Fwebm&ns=-lg0OQZL1LZRQO-dzE0W4E4L&gir=yes&clen=3884070&dur=303.721&lmt=1679342946044954&mt=1679407764&fvip=1&keepalive=yes&fexp=24007246&c=WEB&txp=5532434&n=gDLP5pImH9Vr7v&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&sig=AOq0QJ8wRQIhAKEMLB8yLZJf2jXAu4P1Q8AVEciYsmjjr2syYAWZfJg6AiAfu-XI11zYpCLqljw_MCegh26pJHYyfatgfFGWfpL-6Q%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=AG3C_xAwRQIhANODPaBuc32MWI9gF3Bn1iz3byEn7EwUiXpNLuCcQqW9AiBB88Qrrz2fJCzYKg14_nnGxGQH1Uoi7i31OSrHK6_dGw%3D%3D";
final String descriptiveUrl = "https://rr2---sn-4g5lzned.googlevideo.com/videoplayback?expire=1679429648&ei=sLsZZKrICIuR1gLSnYbgAg&ip=127.0.0.1&id=o-ALWn2ZwDxUXEZKzlsT_X9iuDjRMSi__SgRXVrVjKZEhc&itag=251&source=youtube&requiressl=yes&mh=nU&mm=31%2C29&mn=sn-4g5lzned%2Csn-4g5edndz&ms=au%2Crdu&mv=m&mvi=2&pl=40&initcwndbps=1740000&spc=H3gIhgXQzBxvKu2MOEmFaaEenC4DKdVUwudTeu3dtKwmq-Xv5g&vprv=1&xtags=acont%3Ddescriptive%3Alang%3Den&mime=audio%2Fwebm&ns=-lg0OQZL1LZRQO-dzE0W4E4L&gir=yes&clen=4061711&dur=303.721&lmt=1679342946800120&mt=1679407764&fvip=1&keepalive=yes&fexp=24007246&c=WEB&txp=5532434&n=gDLP5pImH9Vr7v&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&sig=AOq0QJ8wRgIhAKFUzoNscV1hbNcPwcnQO3vOy47q69szj7BdLhFYS52pAiEA2oPhLZIZsrUQrx62iH4dHvTBlCloC3NieJw6edo7LL8%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=AG3C_xAwRQIhANODPaBuc32MWI9gF3Bn1iz3byEn7EwUiXpNLuCcQqW9AiBB88Qrrz2fJCzYKg14_nnGxGQH1Uoi7i31OSrHK6_dGw%3D%3D";
final String noTrackUrl = "https://rr2---sn-4g5ednz7.googlevideo.com/videoplayback?expire=1679430240&ei=AL4ZZKiXJefYx_APj_6ECA&ip=127.0.0.1&id=o-ALKVh9uHVEvurL3bZOZCEMzFod9ZmJJd6GszA6UEIuKy&itag=251&source=youtube&requiressl=yes&mh=8L&mm=31%2C26&mn=sn-4g5ednz7%2Csn-i5heen7z&ms=au%2Conr&mv=m&mvi=2&pl=40&initcwndbps=1793750&spc=H3gIhh2s06nxQJg3zEgY9pw84syUasRiagYDsQ5UHHfcu5bfTA&vprv=1&mime=audio%2Fwebm&ns=VumObYcnTZNicexX7Ek2WakL&gir=yes&clen=3711099&dur=299.201&lmt=1679334484198077&mt=1679408487&fvip=2&keepalive=yes&fexp=24007246&c=WEB&txp=3318224&n=10c-m6ZvG6C7rC&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&sig=AOq0QJ8wRQIhAODS0aHRBgdrHm5qwquqGC6zq3rU81W59y4BtV0Y9KStAiAPT8ykXXj_7GzAyZbLPgYKs-B1HWT-4bY0CppmZ2rReg%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=AG3C_xAwRQIhAL8fS6T-V9BNqrx55mdMvve5be2gcjIY8pYfxlUMPY6pAiAgiCMbqR4eSS_HvLu9KBe6cCFZeMcSTc7vzWtL9y0xvw%3D%3D";
assertEquals(AudioTrackType.ORIGINAL, YoutubeParsingHelper.extractAudioTrackType(originalUrl));
assertEquals(AudioTrackType.DUBBED, YoutubeParsingHelper.extractAudioTrackType(dubbedUrl));
assertEquals(AudioTrackType.DESCRIPTIVE, YoutubeParsingHelper.extractAudioTrackType(descriptiveUrl));
assertNull(YoutubeParsingHelper.extractAudioTrackType(noTrackUrl));
}
}

View File

@ -45,6 +45,7 @@ import org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest;
import org.schabi.newpipe.extractor.services.youtube.YoutubeTestsUtils;
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.AudioTrackType;
import org.schabi.newpipe.extractor.stream.Description;
import org.schabi.newpipe.extractor.stream.StreamExtractor;
import org.schabi.newpipe.extractor.stream.StreamSegment;
@ -567,26 +568,40 @@ public class YoutubeStreamExtractorDefaultTest {
}
}
public static class DescriptiveAudio {
private static final String ID = "TjxC-evzxdk";
public static class AudioTrackTypes {
private static final String ID = "Kn56bMZ9OE8";
private static final String URL = BASE_URL + ID;
private static StreamExtractor extractor;
@BeforeAll
public static void setUp() throws Exception {
YoutubeTestsUtils.ensureStateless();
NewPipe.init(DownloaderFactory.getDownloader(RESOURCE_PATH + "descriptiveAudio"));
NewPipe.init(DownloaderFactory.getDownloader(RESOURCE_PATH + "audioTrackType"));
extractor = YouTube.getStreamExtractor(URL);
extractor.fetchPage();
}
@Test
void testCheckDescriptiveAudio() throws Exception {
void testCheckOriginalAudio() throws Exception {
assertFalse(extractor.getAudioStreams().isEmpty());
assertTrue(extractor.getAudioStreams()
.stream()
.anyMatch(AudioStream::isDescriptive));
.anyMatch(s -> s.getAudioTrackType() == AudioTrackType.ORIGINAL));
}
@Test
void testCheckDubbedAudio() throws Exception {
assertTrue(extractor.getAudioStreams()
.stream()
.anyMatch(s -> s.getAudioTrackType() == AudioTrackType.DUBBED));
}
@Test
void testCheckDescriptiveAudio() throws Exception {
assertTrue(extractor.getAudioStreams()
.stream()
.anyMatch(s -> s.getAudioTrackType() == AudioTrackType.DESCRIPTIVE));
}
}
}

View File

@ -22,9 +22,6 @@
"cache-control": [
"private, max-age\u003d0"
],
"content-security-policy-report-only": [
"base-uri \u0027self\u0027;default-src \u0027self\u0027 https: blob:;font-src https: data:;img-src https: data: android-webview-video-poster:;media-src blob: https:;object-src \u0027none\u0027;script-src \u0027nonce-n5UoXCJvOauVo_kO_mAFJg\u0027 \u0027unsafe-inline\u0027 \u0027strict-dynamic\u0027 https: http: \u0027unsafe-eval\u0027;style-src https: \u0027unsafe-inline\u0027;report-uri /cspreport"
],
"content-type": [
"text/javascript; charset\u003dutf-8"
],
@ -35,10 +32,10 @@
"cross-origin"
],
"date": [
"Mon, 30 Jan 2023 18:31:11 GMT"
"Mon, 20 Mar 2023 22:47:00 GMT"
],
"expires": [
"Mon, 30 Jan 2023 18:31:11 GMT"
"Mon, 20 Mar 2023 22:47:00 GMT"
],
"p3p": [
"CP\u003d\"This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl\u003den-GB for more info.\""
@ -53,10 +50,9 @@
"ESF"
],
"set-cookie": [
"YSC\u003dDcMnDslDdHw; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"DEVICE_INFO\u003dChxOekU1TkRVeE5EWXlOVFkwTkRBNE16QXlNZz09EO+Z4J4GGO+Z4J4G; Domain\u003d.youtube.com; Expires\u003dSat, 29-Jul-2023 18:31:11 GMT; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"VISITOR_INFO1_LIVE\u003dx01Dzzf4XZk; Domain\u003d.youtube.com; Expires\u003dSat, 29-Jul-2023 18:31:11 GMT; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"CONSENT\u003dPENDING+527; expires\u003dWed, 29-Jan-2025 18:31:11 GMT; path\u003d/; domain\u003d.youtube.com; Secure"
"YSC\u003dB0yIqzbmW6M; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"VISITOR_INFO1_LIVE\u003dfdcgGLrzPBk; Domain\u003d.youtube.com; Expires\u003dSat, 16-Sep-2023 22:47:00 GMT; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"CONSENT\u003dPENDING+065; expires\u003dWed, 19-Mar-2025 22:47:00 GMT; path\u003d/; domain\u003d.youtube.com; Secure"
],
"strict-transport-security": [
"max-age\u003d31536000"
@ -71,7 +67,7 @@
"0"
]
},
"responseBody": "var scriptUrl \u003d \u0027https:\\/\\/www.youtube.com\\/s\\/player\\/4248d311\\/www-widgetapi.vflset\\/www-widgetapi.js\u0027;try{var ttPolicy\u003dwindow.trustedTypes.createPolicy(\"youtube-widget-api\",{createScriptURL:function(x){return x}});scriptUrl\u003dttPolicy.createScriptURL(scriptUrl)}catch(e){}var YT;if(!window[\"YT\"])YT\u003d{loading:0,loaded:0};var YTConfig;if(!window[\"YTConfig\"])YTConfig\u003d{\"host\":\"https://www.youtube.com\"};\nif(!YT.loading){YT.loading\u003d1;(function(){var l\u003d[];YT.ready\u003dfunction(f){if(YT.loaded)f();else l.push(f)};window.onYTReady\u003dfunction(){YT.loaded\u003d1;for(var i\u003d0;i\u003cl.length;i++)try{l[i]()}catch(e$0){}};YT.setConfig\u003dfunction(c){for(var k in c)if(c.hasOwnProperty(k))YTConfig[k]\u003dc[k]};var a\u003ddocument.createElement(\"script\");a.type\u003d\"text/javascript\";a.id\u003d\"www-widgetapi-script\";a.src\u003dscriptUrl;a.async\u003dtrue;var c\u003ddocument.currentScript;if(c){var n\u003dc.nonce||c.getAttribute(\"nonce\");if(n)a.setAttribute(\"nonce\",n)}var b\u003d\ndocument.getElementsByTagName(\"script\")[0];b.parentNode.insertBefore(a,b)})()};\n",
"responseBody": "var scriptUrl \u003d \u0027https:\\/\\/www.youtube.com\\/s\\/player\\/59acb1f3\\/www-widgetapi.vflset\\/www-widgetapi.js\u0027;try{var ttPolicy\u003dwindow.trustedTypes.createPolicy(\"youtube-widget-api\",{createScriptURL:function(x){return x}});scriptUrl\u003dttPolicy.createScriptURL(scriptUrl)}catch(e){}var YT;if(!window[\"YT\"])YT\u003d{loading:0,loaded:0};var YTConfig;if(!window[\"YTConfig\"])YTConfig\u003d{\"host\":\"https://www.youtube.com\"};\nif(!YT.loading){YT.loading\u003d1;(function(){var l\u003d[];YT.ready\u003dfunction(f){if(YT.loaded)f();else l.push(f)};window.onYTReady\u003dfunction(){YT.loaded\u003d1;for(var i\u003d0;i\u003cl.length;i++)try{l[i]()}catch(e$0){}};YT.setConfig\u003dfunction(c){for(var k in c)if(c.hasOwnProperty(k))YTConfig[k]\u003dc[k]};var a\u003ddocument.createElement(\"script\");a.type\u003d\"text/javascript\";a.id\u003d\"www-widgetapi-script\";a.src\u003dscriptUrl;a.async\u003dtrue;var c\u003ddocument.currentScript;if(c){var n\u003dc.nonce||c.getAttribute(\"nonce\");if(n)a.setAttribute(\"nonce\",n)}var b\u003d\ndocument.getElementsByTagName(\"script\")[0];b.parentNode.insertBefore(a,b)})()};\n",
"latestUrl": "https://www.youtube.com/iframe_api"
}
}

View File

@ -41,10 +41,10 @@
"same-origin; report-to\u003d\"youtube_main\""
],
"date": [
"Mon, 30 Jan 2023 18:31:14 GMT"
"Mon, 20 Mar 2023 22:47:00 GMT"
],
"expires": [
"Mon, 30 Jan 2023 18:31:14 GMT"
"Mon, 20 Mar 2023 22:47:00 GMT"
],
"p3p": [
"CP\u003d\"This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl\u003den-GB for more info.\""
@ -59,9 +59,9 @@
"ESF"
],
"set-cookie": [
"YSC\u003dlneu_eWU8iY; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"VISITOR_INFO1_LIVE\u003d; Domain\u003d.youtube.com; Expires\u003dTue, 05-May-2020 18:31:14 GMT; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"CONSENT\u003dPENDING+588; expires\u003dWed, 29-Jan-2025 18:31:14 GMT; path\u003d/; domain\u003d.youtube.com; Secure"
"YSC\u003dBCK2-KGAHuw; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"VISITOR_INFO1_LIVE\u003d; Domain\u003d.youtube.com; Expires\u003dTue, 23-Jun-2020 22:47:00 GMT; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"CONSENT\u003dPENDING+517; expires\u003dWed, 19-Mar-2025 22:47:00 GMT; path\u003d/; domain\u003d.youtube.com; Secure"
],
"strict-transport-security": [
"max-age\u003d31536000"