Add track types to audio streams (#1041)
This commit is contained in:
parent
44ae139d33
commit
8d1303e18f
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -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"
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue