Merge pull request #1026 from AudricV/audio-streams-descriptive-and-locale-properties
Add descriptive and locale properties to audio streams
This commit is contained in:
commit
6bdd698c25
|
@ -24,6 +24,7 @@ import org.schabi.newpipe.extractor.stream.StreamExtractor;
|
|||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
||||
import org.schabi.newpipe.extractor.utils.LocaleCompat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -114,15 +115,24 @@ public class MediaCCCStreamExtractor extends StreamExtractor {
|
|||
mediaFormat = null;
|
||||
}
|
||||
|
||||
// Not checking containsSimilarStream here, since MediaCCC does not provide enough
|
||||
// information to decide whether two streams are similar. Hence that method would
|
||||
// always return false, e.g. even for different language variations.
|
||||
audioStreams.add(new AudioStream.Builder()
|
||||
final AudioStream.Builder builder = new AudioStream.Builder()
|
||||
.setId(recording.getString("filename", ID_UNKNOWN))
|
||||
.setContent(recording.getString("recording_url"), true)
|
||||
.setMediaFormat(mediaFormat)
|
||||
.setAverageBitrate(UNKNOWN_BITRATE)
|
||||
.build());
|
||||
.setAverageBitrate(UNKNOWN_BITRATE);
|
||||
|
||||
final String language = recording.getString("language");
|
||||
// If the language contains a - symbol, this means that the stream has an audio
|
||||
// track with multiple languages, so there is no specific language for this stream
|
||||
// Don't set the audio language in this case
|
||||
if (language != null && !language.contains("-")) {
|
||||
builder.setAudioLocale(LocaleCompat.forLanguageTag(language));
|
||||
}
|
||||
|
||||
// Not checking containsSimilarStream here, since MediaCCC does not provide enough
|
||||
// information to decide whether two streams are similar. Hence that method would
|
||||
// always return false, e.g. even for different language variations.
|
||||
audioStreams.add(builder.build());
|
||||
}
|
||||
}
|
||||
return audioStreams;
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
|||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.Serializable;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.schabi.newpipe.extractor.MediaFormat.M4A;
|
||||
import static org.schabi.newpipe.extractor.MediaFormat.MPEG_4;
|
||||
|
@ -198,6 +199,10 @@ public class ItagItem implements Serializable {
|
|||
this.targetDurationSec = itagItem.targetDurationSec;
|
||||
this.approxDurationMs = itagItem.approxDurationMs;
|
||||
this.contentLength = itagItem.contentLength;
|
||||
this.audioTrackId = itagItem.audioTrackId;
|
||||
this.audioTrackName = itagItem.audioTrackName;
|
||||
this.isDescriptiveAudio = itagItem.isDescriptiveAudio;
|
||||
this.audioLocale = itagItem.audioLocale;
|
||||
}
|
||||
|
||||
public MediaFormat getMediaFormat() {
|
||||
|
@ -246,6 +251,9 @@ public class ItagItem implements Serializable {
|
|||
private long contentLength = CONTENT_LENGTH_UNKNOWN;
|
||||
private String audioTrackId;
|
||||
private String audioTrackName;
|
||||
private boolean isDescriptiveAudio;
|
||||
@Nullable
|
||||
private Locale audioLocale;
|
||||
|
||||
public int getBitrate() {
|
||||
return bitrate;
|
||||
|
@ -569,7 +577,7 @@ public class ItagItem implements Serializable {
|
|||
/**
|
||||
* Get the {@code audioTrackName} of the stream, if present.
|
||||
*
|
||||
* @return the {@code audioTrackName} of the stream or null
|
||||
* @return the {@code audioTrackName} of the stream or {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
public String getAudioTrackName() {
|
||||
|
@ -577,11 +585,53 @@ public class ItagItem implements Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set the {@code audioTrackName} of the stream.
|
||||
* Set the {@code audioTrackName} of the stream, if present.
|
||||
*
|
||||
* @param audioTrackName the {@code audioTrackName} of the stream
|
||||
* @param audioTrackName the {@code audioTrackName} of the stream or {@code null}
|
||||
*/
|
||||
public void setAudioTrackName(@Nullable final String audioTrackName) {
|
||||
this.audioTrackName = audioTrackName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the stream is a descriptive audio.
|
||||
*
|
||||
* @return whether the stream is a descriptive audio
|
||||
*/
|
||||
public boolean isDescriptiveAudio() {
|
||||
return isDescriptiveAudio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the stream is a descriptive audio.
|
||||
*
|
||||
* @param isDescriptiveAudio whether the stream is a descriptive audio
|
||||
*/
|
||||
public void setIsDescriptiveAudio(final boolean isDescriptiveAudio) {
|
||||
this.isDescriptiveAudio = isDescriptiveAudio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the audio {@link Locale} of the stream, if known.
|
||||
*
|
||||
* @return the audio {@link Locale} of the stream, if known, or {@code null} if that's not the
|
||||
* case
|
||||
*/
|
||||
@Nullable
|
||||
public Locale getAudioLocale() {
|
||||
return audioLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the audio {@link Locale} of the stream.
|
||||
*
|
||||
* <p>
|
||||
* If it is unknown, {@code null} could be passed, which is the default value.
|
||||
* </p>
|
||||
*
|
||||
* @param audioLocale the audio {@link Locale} of the stream, which could be {@code null}
|
||||
*/
|
||||
public void setAudioLocale(@Nullable final Locale audioLocale) {
|
||||
this.audioLocale = audioLocale;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ public final class YoutubeDashManifestCreatorsUtils {
|
|||
* <li>{@code Period} (using {@link #generatePeriodElement(Document)});</li>
|
||||
* <li>{@code AdaptationSet} (using {@link #generateAdaptationSetElement(Document,
|
||||
* ItagItem)});</li>
|
||||
* <li>{@code Role} (using {@link #generateRoleElement(Document)});</li>
|
||||
* <li>{@code Role} (using {@link #generateRoleElement(Document, ItagItem)});</li>
|
||||
* <li>{@code Representation} (using {@link #generateRepresentationElement(Document,
|
||||
* ItagItem)});</li>
|
||||
* <li>and, for audio streams, {@code AudioChannelConfiguration} (using
|
||||
|
@ -144,7 +144,7 @@ public final class YoutubeDashManifestCreatorsUtils {
|
|||
|
||||
generatePeriodElement(doc);
|
||||
generateAdaptationSetElement(doc, itagItem);
|
||||
generateRoleElement(doc);
|
||||
generateRoleElement(doc, itagItem);
|
||||
generateRepresentationElement(doc, itagItem);
|
||||
if (itagItem.itagType == ItagItem.ItagType.AUDIO) {
|
||||
generateAudioChannelConfigurationElement(doc, itagItem);
|
||||
|
@ -208,7 +208,7 @@ public final class YoutubeDashManifestCreatorsUtils {
|
|||
* {@link #generateDocumentAndMpdElement(long)}.
|
||||
* </p>
|
||||
*
|
||||
* @param doc the {@link Document} on which the the {@code <Period>} element will be appended
|
||||
* @param doc the {@link Document} on which the {@code <Period>} element will be appended
|
||||
*/
|
||||
public static void generatePeriodElement(@Nonnull final Document doc)
|
||||
throws CreationException {
|
||||
|
@ -249,6 +249,16 @@ public final class YoutubeDashManifestCreatorsUtils {
|
|||
"the MediaFormat or its mime type is null or empty");
|
||||
}
|
||||
|
||||
if (itagItem.itagType == ItagItem.ItagType.AUDIO) {
|
||||
final Locale audioLocale = itagItem.getAudioLocale();
|
||||
if (audioLocale != null) {
|
||||
final String audioLanguage = audioLocale.getLanguage();
|
||||
if (!audioLanguage.isEmpty()) {
|
||||
setAttribute(adaptationSetElement, doc, "lang", audioLanguage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setAttribute(adaptationSetElement, doc, "mimeType", mediaFormat.getMimeType());
|
||||
setAttribute(adaptationSetElement, doc, "subsegmentAlignment", "true");
|
||||
|
||||
|
@ -267,7 +277,8 @@ public final class YoutubeDashManifestCreatorsUtils {
|
|||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* {@code <Role schemeIdUri="urn:mpeg:DASH:role:2011" value="main"/>}
|
||||
* {@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
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
|
@ -275,9 +286,11 @@ public final class YoutubeDashManifestCreatorsUtils {
|
|||
* {@link #generateAdaptationSetElement(Document, ItagItem)}).
|
||||
* </p>
|
||||
*
|
||||
* @param doc the {@link Document} on which the the {@code <Role>} element will be appended
|
||||
* @param doc the {@link Document} on which the {@code <Role>} element will be appended
|
||||
* @param itagItem the {@link ItagItem} corresponding to the stream, which must not be null
|
||||
*/
|
||||
public static void generateRoleElement(@Nonnull final Document doc)
|
||||
public static void generateRoleElement(@Nonnull final Document doc,
|
||||
@Nonnull final ItagItem itagItem)
|
||||
throws CreationException {
|
||||
try {
|
||||
final Element adaptationSetElement = (Element) doc.getElementsByTagName(
|
||||
|
@ -285,7 +298,8 @@ public final class YoutubeDashManifestCreatorsUtils {
|
|||
final Element roleElement = doc.createElement(ROLE);
|
||||
|
||||
setAttribute(roleElement, doc, "schemeIdUri", "urn:mpeg:DASH:role:2011");
|
||||
setAttribute(roleElement, doc, "value", "main");
|
||||
setAttribute(roleElement, doc, "value", itagItem.isDescriptiveAudio()
|
||||
? "alternate" : "main");
|
||||
|
||||
adaptationSetElement.appendChild(roleElement);
|
||||
} catch (final DOMException e) {
|
||||
|
@ -302,7 +316,7 @@ public final class YoutubeDashManifestCreatorsUtils {
|
|||
* {@link #generateAdaptationSetElement(Document, ItagItem)}).
|
||||
* </p>
|
||||
*
|
||||
* @param doc the {@link Document} on which the the {@code <SegmentTimeline>} element will be
|
||||
* @param doc the {@link Document} on which the {@code <SegmentTimeline>} element will be
|
||||
* appended
|
||||
* @param itagItem the {@link ItagItem} to use, which must not be null
|
||||
*/
|
||||
|
@ -522,7 +536,7 @@ public final class YoutubeDashManifestCreatorsUtils {
|
|||
* {@link #generateSegmentTemplateElement(Document, String, DeliveryType)}.
|
||||
* </p>
|
||||
*
|
||||
* @param doc the {@link Document} on which the the {@code <SegmentTimeline>} element will be
|
||||
* @param doc the {@link Document} on which the {@code <SegmentTimeline>} element will be
|
||||
* appended
|
||||
*/
|
||||
public static void generateSegmentTimelineElement(@Nonnull final Document doc)
|
||||
|
|
|
@ -82,6 +82,7 @@ import org.schabi.newpipe.extractor.stream.StreamType;
|
|||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
||||
import org.schabi.newpipe.extractor.utils.LocaleCompat;
|
||||
import org.schabi.newpipe.extractor.utils.Pair;
|
||||
import org.schabi.newpipe.extractor.utils.Parser;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
@ -1309,6 +1310,8 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||
.setAverageBitrate(itagItem.getAverageBitrate())
|
||||
.setAudioTrackId(itagItem.getAudioTrackId())
|
||||
.setAudioTrackName(itagItem.getAudioTrackName())
|
||||
.setAudioLocale(itagItem.getAudioLocale())
|
||||
.setIsDescriptive(itagItem.isDescriptiveAudio())
|
||||
.setItagItem(itagItem);
|
||||
|
||||
if (streamType == StreamType.LIVE_STREAM
|
||||
|
@ -1454,9 +1457,6 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||
itagItem.setQuality(formatData.getString("quality"));
|
||||
itagItem.setCodec(codec);
|
||||
|
||||
itagItem.setAudioTrackId(formatData.getObject("audioTrack").getString("id"));
|
||||
itagItem.setAudioTrackName(formatData.getObject("audioTrack").getString("displayName"));
|
||||
|
||||
if (streamType == StreamType.LIVE_STREAM || streamType == StreamType.POST_LIVE_STREAM) {
|
||||
itagItem.setTargetDurationSec(formatData.getInt("targetDurationSec"));
|
||||
}
|
||||
|
@ -1473,6 +1473,27 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||
// AudioChannelConfiguration element of DASH manifests of audio streams in
|
||||
// YoutubeDashManifestCreatorUtils
|
||||
2));
|
||||
|
||||
final String audioTrackId = formatData.getObject("audioTrack")
|
||||
.getString("id");
|
||||
if (!isNullOrEmpty(audioTrackId)) {
|
||||
itagItem.setAudioTrackId(audioTrackId);
|
||||
final int audioTrackIdLastLocaleCharacter = audioTrackId.indexOf(".");
|
||||
if (audioTrackIdLastLocaleCharacter != -1) {
|
||||
// Audio tracks IDs are in the form LANGUAGE_CODE.TRACK_NUMBER
|
||||
itagItem.setAudioLocale(LocaleCompat.forLanguageTag(
|
||||
audioTrackId.substring(0, audioTrackIdLastLocaleCharacter)));
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.schabi.newpipe.extractor.services.youtube.ItagItem;
|
|||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class AudioStream extends Stream {
|
||||
|
@ -43,8 +44,14 @@ public final class AudioStream extends Stream {
|
|||
private String codec;
|
||||
|
||||
// Fields about the audio track id/name
|
||||
private String audioTrackId;
|
||||
private String audioTrackName;
|
||||
@Nullable
|
||||
private final String audioTrackId;
|
||||
@Nullable
|
||||
private final String audioTrackName;
|
||||
@Nullable
|
||||
private final Locale audioLocale;
|
||||
private final boolean isDescriptive;
|
||||
|
||||
@Nullable
|
||||
private ItagItem itagItem;
|
||||
|
||||
|
@ -67,6 +74,9 @@ public final class AudioStream extends Stream {
|
|||
@Nullable
|
||||
private String audioTrackName;
|
||||
@Nullable
|
||||
private Locale audioLocale;
|
||||
private boolean isDescriptive;
|
||||
@Nullable
|
||||
private ItagItem itagItem;
|
||||
|
||||
/**
|
||||
|
@ -185,7 +195,11 @@ public final class AudioStream extends Stream {
|
|||
/**
|
||||
* Set the audio track id of the {@link AudioStream}.
|
||||
*
|
||||
* @param audioTrackId the audio track id of the {@link AudioStream}
|
||||
* <p>
|
||||
* The default value is {@code null}.
|
||||
* </p>
|
||||
*
|
||||
* @param audioTrackId the audio track id of the {@link AudioStream}, which can be null
|
||||
* @return this {@link Builder} instance
|
||||
*/
|
||||
public Builder setAudioTrackId(@Nullable final String audioTrackId) {
|
||||
|
@ -196,7 +210,11 @@ public final class AudioStream extends Stream {
|
|||
/**
|
||||
* Set the audio track name of the {@link AudioStream}.
|
||||
*
|
||||
* @param audioTrackName the audio track name of the {@link AudioStream}
|
||||
* <p>
|
||||
* The default value is {@code null}.
|
||||
* </p>
|
||||
*
|
||||
* @param audioTrackName the audio track name of the {@link AudioStream}, which can be null
|
||||
* @return this {@link Builder} instance
|
||||
*/
|
||||
public Builder setAudioTrackName(@Nullable final String audioTrackName) {
|
||||
|
@ -204,6 +222,44 @@ public final class AudioStream extends Stream {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this {@link AudioStream} is a descriptive audio.
|
||||
*
|
||||
* <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>
|
||||
*
|
||||
* <p>
|
||||
* The default value is {@code false}.
|
||||
* </p>
|
||||
*
|
||||
* @param isDescriptive whether this {@link AudioStream} is a descriptive audio
|
||||
* @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;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link Locale} of the audio which represents its language.
|
||||
*
|
||||
* <p>
|
||||
* The default value is {@code null}, which means that the {@link Locale} is unknown.
|
||||
* </p>
|
||||
*
|
||||
* @param audioLocale the {@link Locale} of the audio, which could be {@code null}
|
||||
* @return this {@link Builder} instance
|
||||
*/
|
||||
public Builder setAudioLocale(@Nullable final Locale audioLocale) {
|
||||
this.audioLocale = audioLocale;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ItagItem} corresponding to the {@link AudioStream}.
|
||||
*
|
||||
|
@ -257,7 +313,8 @@ public final class AudioStream extends Stream {
|
|||
}
|
||||
|
||||
return new AudioStream(id, content, isUrl, mediaFormat, deliveryMethod, averageBitrate,
|
||||
manifestUrl, audioTrackId, audioTrackName, itagItem);
|
||||
manifestUrl, audioTrackId, audioTrackName, audioLocale, isDescriptive,
|
||||
itagItem);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -277,6 +334,7 @@ public final class AudioStream extends Stream {
|
|||
* {@link #UNKNOWN_BITRATE})
|
||||
* @param audioTrackId the id of the audio track
|
||||
* @param audioTrackName the name of the audio track
|
||||
* @param audioLocale the {@link Locale} of the audio stream, representing its language
|
||||
* @param itagItem the {@link ItagItem} corresponding to the stream, which cannot be null
|
||||
* @param manifestUrl the URL of the manifest this stream comes from (if applicable,
|
||||
* otherwise null)
|
||||
|
@ -291,6 +349,8 @@ public final class AudioStream extends Stream {
|
|||
@Nullable final String manifestUrl,
|
||||
@Nullable final String audioTrackId,
|
||||
@Nullable final String audioTrackName,
|
||||
@Nullable final Locale audioLocale,
|
||||
final boolean isDescriptive,
|
||||
@Nullable final ItagItem itagItem) {
|
||||
super(id, content, isUrl, format, deliveryMethod, manifestUrl);
|
||||
if (itagItem != null) {
|
||||
|
@ -307,6 +367,8 @@ public final class AudioStream extends Stream {
|
|||
this.averageBitrate = averageBitrate;
|
||||
this.audioTrackId = audioTrackId;
|
||||
this.audioTrackName = audioTrackName;
|
||||
this.audioLocale = audioLocale;
|
||||
this.isDescriptive = isDescriptive;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -316,7 +378,9 @@ public final class AudioStream extends Stream {
|
|||
public boolean equalStats(final Stream cmp) {
|
||||
return super.equalStats(cmp) && cmp instanceof AudioStream
|
||||
&& averageBitrate == ((AudioStream) cmp).averageBitrate
|
||||
&& Objects.equals(audioTrackId, ((AudioStream) cmp).audioTrackId);
|
||||
&& Objects.equals(audioTrackId, ((AudioStream) cmp).audioTrackId)
|
||||
&& isDescriptive == ((AudioStream) cmp).isDescriptive
|
||||
&& Objects.equals(audioLocale, ((AudioStream) cmp).audioLocale);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -421,15 +485,44 @@ public final class AudioStream extends Stream {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the name of the audio track.
|
||||
* Get the name of the audio track, which may be {@code null} if this information is not
|
||||
* provided by the service.
|
||||
*
|
||||
* @return the name of the audio track
|
||||
* @return the name of the audio track or {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
public String getAudioTrackName() {
|
||||
return audioTrackName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Locale} of the audio representing the language of the stream, which is
|
||||
* {@code null} if the audio language of this stream is not known.
|
||||
*
|
||||
* @return the {@link Locale} of the audio or {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
public Locale getAudioLocale() {
|
||||
return audioLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this stream is a descriptive audio.
|
||||
*
|
||||
* <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>
|
||||
*/
|
||||
public boolean isDescriptive() {
|
||||
return isDescriptive;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
|
@ -7,16 +7,20 @@ import org.schabi.newpipe.extractor.NewPipe;
|
|||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCStreamExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.extractor.utils.LocaleCompat;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.schabi.newpipe.extractor.ServiceList.MediaCCC;
|
||||
|
||||
/**
|
||||
|
@ -85,7 +89,11 @@ public class MediaCCCStreamExtractorTest {
|
|||
@Test
|
||||
public void testAudioStreams() throws Exception {
|
||||
super.testAudioStreams();
|
||||
assertEquals(2, extractor.getAudioStreams().size());
|
||||
final List<AudioStream> audioStreams = extractor.getAudioStreams();
|
||||
assertEquals(2, audioStreams.size());
|
||||
final Locale expectedLocale = LocaleCompat.forLanguageTag("deu");
|
||||
assertTrue(audioStreams.stream().allMatch(audioStream ->
|
||||
Objects.equals(audioStream.getAudioLocale(), expectedLocale)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,7 +163,11 @@ public class MediaCCCStreamExtractorTest {
|
|||
@Test
|
||||
public void testAudioStreams() throws Exception {
|
||||
super.testAudioStreams();
|
||||
assertEquals(2, extractor.getAudioStreams().size());
|
||||
final List<AudioStream> audioStreams = extractor.getAudioStreams();
|
||||
assertEquals(2, audioStreams.size());
|
||||
final Locale expectedLocale = LocaleCompat.forLanguageTag("eng");
|
||||
assertTrue(audioStreams.stream().allMatch(audioStream ->
|
||||
Objects.equals(audioStream.getAudioLocale(), expectedLocale)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -20,6 +20,7 @@ import javax.xml.parsers.DocumentBuilder;
|
|||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import java.io.StringReader;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -191,7 +192,7 @@ class YoutubeDashManifestCreatorsTest {
|
|||
() -> assertMpdElement(document),
|
||||
() -> assertPeriodElement(document),
|
||||
() -> assertAdaptationSetElement(document, itagItem),
|
||||
() -> assertRoleElement(document),
|
||||
() -> assertRoleElement(document, itagItem),
|
||||
() -> assertRepresentationElement(document, itagItem),
|
||||
() -> {
|
||||
if (itagItem.itagType.equals(ItagItem.ItagType.AUDIO)) {
|
||||
|
@ -220,10 +221,19 @@ class YoutubeDashManifestCreatorsTest {
|
|||
@Nonnull final ItagItem itagItem) {
|
||||
final Element element = assertGetElement(document, ADAPTATION_SET, PERIOD);
|
||||
assertAttrEquals(itagItem.getMediaFormat().getMimeType(), element, "mimeType");
|
||||
|
||||
if (itagItem.itagType == ItagItem.ItagType.AUDIO) {
|
||||
final Locale itagAudioLocale = itagItem.getAudioLocale();
|
||||
if (itagAudioLocale != null) {
|
||||
assertAttrEquals(itagAudioLocale.getLanguage(), element, "lang");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertRoleElement(@Nonnull final Document document) {
|
||||
assertGetElement(document, ROLE, ADAPTATION_SET);
|
||||
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");
|
||||
}
|
||||
|
||||
private void assertRepresentationElement(@Nonnull final Document document,
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
package org.schabi.newpipe.extractor.services.youtube.stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -48,6 +49,7 @@ import org.schabi.newpipe.extractor.stream.Description;
|
|||
import org.schabi.newpipe.extractor.stream.StreamExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.StreamSegment;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.extractor.utils.LocaleCompat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
|
@ -55,6 +57,8 @@ import java.net.URL;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
@ -546,24 +550,43 @@ public class YoutubeStreamExtractorDefaultTest {
|
|||
|
||||
@Test
|
||||
void testCheckAudioStreams() throws Exception {
|
||||
assertTrue(extractor.getAudioStreams().size() > 0);
|
||||
final List<AudioStream> audioStreams = extractor.getAudioStreams();
|
||||
assertFalse(audioStreams.isEmpty());
|
||||
|
||||
for (final AudioStream audioStream : extractor.getAudioStreams()) {
|
||||
assertNotNull(audioStream.getAudioTrackName());
|
||||
for (final AudioStream stream : audioStreams) {
|
||||
assertNotNull(stream.getAudioTrackName());
|
||||
}
|
||||
|
||||
assertTrue(
|
||||
extractor.getAudioStreams()
|
||||
.stream()
|
||||
.anyMatch(audioStream -> audioStream.getAudioTrackName().equals("English"))
|
||||
);
|
||||
assertTrue(audioStreams.stream()
|
||||
.anyMatch(audioStream -> "English".equals(audioStream.getAudioTrackName())));
|
||||
|
||||
assertTrue(
|
||||
extractor.getAudioStreams()
|
||||
.stream()
|
||||
.anyMatch(audioStream -> audioStream.getAudioTrackName().equals("Hindi"))
|
||||
);
|
||||
final Locale hindiLocale = LocaleCompat.forLanguageTag("hi");
|
||||
assertTrue(audioStreams.stream()
|
||||
.anyMatch(audioStream ->
|
||||
Objects.equals(audioStream.getAudioLocale(), hindiLocale)));
|
||||
}
|
||||
}
|
||||
|
||||
public static class DescriptiveAudio {
|
||||
private static final String ID = "TjxC-evzxdk";
|
||||
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"));
|
||||
extractor = YouTube.getStreamExtractor(URL);
|
||||
extractor.fetchPage();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCheckDescriptiveAudio() throws Exception {
|
||||
assertFalse(extractor.getAudioStreams().isEmpty());
|
||||
|
||||
assertTrue(extractor.getAudioStreams()
|
||||
.stream()
|
||||
.anyMatch(AudioStream::isDescriptive));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"request": {
|
||||
"httpMethod": "GET",
|
||||
"url": "https://www.youtube.com/iframe_api",
|
||||
"headers": {
|
||||
"Accept-Language": [
|
||||
"en-GB, en;q\u003d0.9"
|
||||
]
|
||||
},
|
||||
"localization": {
|
||||
"languageCode": "en",
|
||||
"countryCode": "GB"
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"responseCode": 200,
|
||||
"responseMessage": "",
|
||||
"responseHeaders": {
|
||||
"alt-svc": [
|
||||
"h3\u003d\":443\"; ma\u003d2592000,h3-29\u003d\":443\"; ma\u003d2592000"
|
||||
],
|
||||
"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"
|
||||
],
|
||||
"cross-origin-opener-policy-report-only": [
|
||||
"same-origin; report-to\u003d\"youtube_main\""
|
||||
],
|
||||
"cross-origin-resource-policy": [
|
||||
"cross-origin"
|
||||
],
|
||||
"date": [
|
||||
"Mon, 30 Jan 2023 18:31:11 GMT"
|
||||
],
|
||||
"expires": [
|
||||
"Mon, 30 Jan 2023 18:31:11 GMT"
|
||||
],
|
||||
"p3p": [
|
||||
"CP\u003d\"This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl\u003den-GB for more info.\""
|
||||
],
|
||||
"permissions-policy": [
|
||||
"ch-ua-arch\u003d*, ch-ua-bitness\u003d*, ch-ua-full-version\u003d*, ch-ua-full-version-list\u003d*, ch-ua-model\u003d*, ch-ua-wow64\u003d*, ch-ua-platform\u003d*, ch-ua-platform-version\u003d*"
|
||||
],
|
||||
"report-to": [
|
||||
"{\"group\":\"youtube_main\",\"max_age\":2592000,\"endpoints\":[{\"url\":\"https://csp.withgoogle.com/csp/report-to/youtube_main\"}]}"
|
||||
],
|
||||
"server": [
|
||||
"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"
|
||||
],
|
||||
"strict-transport-security": [
|
||||
"max-age\u003d31536000"
|
||||
],
|
||||
"x-content-type-options": [
|
||||
"nosniff"
|
||||
],
|
||||
"x-frame-options": [
|
||||
"SAMEORIGIN"
|
||||
],
|
||||
"x-xss-protection": [
|
||||
"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",
|
||||
"latestUrl": "https://www.youtube.com/iframe_api"
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,82 @@
|
|||
{
|
||||
"request": {
|
||||
"httpMethod": "GET",
|
||||
"url": "https://www.youtube.com/sw.js",
|
||||
"headers": {
|
||||
"Referer": [
|
||||
"https://www.youtube.com"
|
||||
],
|
||||
"Origin": [
|
||||
"https://www.youtube.com"
|
||||
],
|
||||
"Accept-Language": [
|
||||
"en-GB, en;q\u003d0.9"
|
||||
]
|
||||
},
|
||||
"localization": {
|
||||
"languageCode": "en",
|
||||
"countryCode": "GB"
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"responseCode": 200,
|
||||
"responseMessage": "",
|
||||
"responseHeaders": {
|
||||
"access-control-allow-credentials": [
|
||||
"true"
|
||||
],
|
||||
"access-control-allow-origin": [
|
||||
"https://www.youtube.com"
|
||||
],
|
||||
"alt-svc": [
|
||||
"h3\u003d\":443\"; ma\u003d2592000,h3-29\u003d\":443\"; ma\u003d2592000"
|
||||
],
|
||||
"cache-control": [
|
||||
"private, max-age\u003d0"
|
||||
],
|
||||
"content-type": [
|
||||
"text/javascript; charset\u003dutf-8"
|
||||
],
|
||||
"cross-origin-opener-policy-report-only": [
|
||||
"same-origin; report-to\u003d\"youtube_main\""
|
||||
],
|
||||
"date": [
|
||||
"Mon, 30 Jan 2023 18:31:14 GMT"
|
||||
],
|
||||
"expires": [
|
||||
"Mon, 30 Jan 2023 18:31:14 GMT"
|
||||
],
|
||||
"p3p": [
|
||||
"CP\u003d\"This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl\u003den-GB for more info.\""
|
||||
],
|
||||
"permissions-policy": [
|
||||
"ch-ua-arch\u003d*, ch-ua-bitness\u003d*, ch-ua-full-version\u003d*, ch-ua-full-version-list\u003d*, ch-ua-model\u003d*, ch-ua-wow64\u003d*, ch-ua-platform\u003d*, ch-ua-platform-version\u003d*"
|
||||
],
|
||||
"report-to": [
|
||||
"{\"group\":\"youtube_main\",\"max_age\":2592000,\"endpoints\":[{\"url\":\"https://csp.withgoogle.com/csp/report-to/youtube_main\"}]}"
|
||||
],
|
||||
"server": [
|
||||
"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"
|
||||
],
|
||||
"strict-transport-security": [
|
||||
"max-age\u003d31536000"
|
||||
],
|
||||
"x-content-type-options": [
|
||||
"nosniff"
|
||||
],
|
||||
"x-frame-options": [
|
||||
"SAMEORIGIN"
|
||||
],
|
||||
"x-xss-protection": [
|
||||
"0"
|
||||
]
|
||||
},
|
||||
"responseBody": "\n self.addEventListener(\u0027install\u0027, event \u003d\u003e {\n event.waitUntil(self.skipWaiting());\n });\n self.addEventListener(\u0027activate\u0027, event \u003d\u003e {\n event.waitUntil(\n self.clients.claim().then(() \u003d\u003e self.registration.unregister()));\n });\n ",
|
||||
"latestUrl": "https://www.youtube.com/sw.js"
|
||||
}
|
||||
}
|
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