Skip to content

Commit 8d1303e

Browse files
authored
Add track types to audio streams (#1041)
1 parent 44ae139 commit 8d1303e

21 files changed

Lines changed: 1683 additions & 1547 deletions

File tree

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/ItagItem.java

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.schabi.newpipe.extractor.MediaFormat;
44
import org.schabi.newpipe.extractor.exceptions.ParsingException;
5+
import org.schabi.newpipe.extractor.stream.AudioTrackType;
56

67
import javax.annotation.Nonnull;
78
import javax.annotation.Nullable;
@@ -201,7 +202,7 @@ public ItagItem(@Nonnull final ItagItem itagItem) {
201202
this.contentLength = itagItem.contentLength;
202203
this.audioTrackId = itagItem.audioTrackId;
203204
this.audioTrackName = itagItem.audioTrackName;
204-
this.isDescriptiveAudio = itagItem.isDescriptiveAudio;
205+
this.audioTrackType = itagItem.audioTrackType;
205206
this.audioLocale = itagItem.audioLocale;
206207
}
207208

@@ -251,7 +252,8 @@ public MediaFormat getMediaFormat() {
251252
private long contentLength = CONTENT_LENGTH_UNKNOWN;
252253
private String audioTrackId;
253254
private String audioTrackName;
254-
private boolean isDescriptiveAudio;
255+
@Nullable
256+
private AudioTrackType audioTrackType;
255257
@Nullable
256258
private Locale audioLocale;
257259

@@ -594,21 +596,22 @@ public void setAudioTrackName(@Nullable final String audioTrackName) {
594596
}
595597

596598
/**
597-
* Return whether the stream is a descriptive audio.
599+
* Get the {@link AudioTrackType} of the stream.
598600
*
599-
* @return whether the stream is a descriptive audio
601+
* @return the {@link AudioTrackType} of the stream or {@code null}
600602
*/
601-
public boolean isDescriptiveAudio() {
602-
return isDescriptiveAudio;
603+
@Nullable
604+
public AudioTrackType getAudioTrackType() {
605+
return audioTrackType;
603606
}
604607

605608
/**
606-
* Set whether the stream is a descriptive audio.
609+
* Set the {@link AudioTrackType} of the stream, if present.
607610
*
608-
* @param isDescriptiveAudio whether the stream is a descriptive audio
611+
* @param audioTrackType the {@link AudioTrackType} of the stream or {@code null}
609612
*/
610-
public void setIsDescriptiveAudio(final boolean isDescriptiveAudio) {
611-
this.isDescriptiveAudio = isDescriptiveAudio;
613+
public void setAudioTrackType(@Nullable final AudioTrackType audioTrackType) {
614+
this.audioTrackType = audioTrackType;
612615
}
613616

614617
/**

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.schabi.newpipe.extractor.localization.ContentCountry;
4444
import org.schabi.newpipe.extractor.localization.Localization;
4545
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
46+
import org.schabi.newpipe.extractor.stream.AudioTrackType;
4647
import org.schabi.newpipe.extractor.stream.Description;
4748
import org.schabi.newpipe.extractor.utils.JsonUtils;
4849
import org.schabi.newpipe.extractor.utils.Parser;
@@ -1801,4 +1802,49 @@ public static void setConsentAccepted(final boolean accepted) {
18011802
public static boolean isConsentAccepted() {
18021803
return consentAccepted;
18031804
}
1805+
1806+
/**
1807+
* Extract the audio track type from a YouTube stream URL.
1808+
* <p>
1809+
* The track type is parsed from the {@code xtags} URL parameter
1810+
* (Example: {@code acont=original:lang=en}).
1811+
* </p>
1812+
* @param streamUrl YouTube stream URL
1813+
* @return {@link AudioTrackType} or {@code null} if no track type was found
1814+
*/
1815+
@Nullable
1816+
public static AudioTrackType extractAudioTrackType(final String streamUrl) {
1817+
final String xtags;
1818+
try {
1819+
xtags = Utils.getQueryValue(new URL(streamUrl), "xtags");
1820+
} catch (final MalformedURLException e) {
1821+
return null;
1822+
}
1823+
if (xtags == null) {
1824+
return null;
1825+
}
1826+
1827+
String atype = null;
1828+
for (final String param : xtags.split(":")) {
1829+
final String[] kv = param.split("=", 2);
1830+
if (kv.length > 1 && kv[0].equals("acont")) {
1831+
atype = kv[1];
1832+
break;
1833+
}
1834+
}
1835+
if (atype == null) {
1836+
return null;
1837+
}
1838+
1839+
switch (atype) {
1840+
case "original":
1841+
return AudioTrackType.ORIGINAL;
1842+
case "dubbed":
1843+
return AudioTrackType.DUBBED;
1844+
case "descriptive":
1845+
return AudioTrackType.DESCRIPTIVE;
1846+
default:
1847+
return null;
1848+
}
1849+
}
18041850
}

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/dashmanifestcreators/YoutubeDashManifestCreatorsUtils.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
1717
import org.schabi.newpipe.extractor.services.youtube.DeliveryType;
1818
import org.schabi.newpipe.extractor.services.youtube.ItagItem;
19+
import org.schabi.newpipe.extractor.stream.AudioTrackType;
1920
import org.schabi.newpipe.extractor.utils.ManifestCreatorCache;
2021
import org.w3c.dom.Attr;
2122
import org.w3c.dom.DOMException;
@@ -31,6 +32,7 @@
3132
import java.util.Objects;
3233

3334
import javax.annotation.Nonnull;
35+
import javax.annotation.Nullable;
3436
import javax.xml.XMLConstants;
3537
import javax.xml.parsers.DocumentBuilder;
3638
import javax.xml.parsers.DocumentBuilderFactory;
@@ -278,7 +280,8 @@ public static void generateAdaptationSetElement(@Nonnull final Document doc,
278280
*
279281
* <p>
280282
* {@code <Role schemeIdUri="urn:mpeg:DASH:role:2011" value="VALUE"/>}, where {@code VALUE} is
281-
* {@code main} for videos and audios and {@code alternate} for descriptive audio
283+
* {@code main} for videos and audios, {@code description} for descriptive audio and
284+
* {@code dub} for dubbed audio.
282285
* </p>
283286
*
284287
* <p>
@@ -298,15 +301,36 @@ public static void generateRoleElement(@Nonnull final Document doc,
298301
final Element roleElement = doc.createElement(ROLE);
299302

300303
setAttribute(roleElement, doc, "schemeIdUri", "urn:mpeg:DASH:role:2011");
301-
setAttribute(roleElement, doc, "value", itagItem.isDescriptiveAudio()
302-
? "alternate" : "main");
304+
setAttribute(roleElement, doc, "value", getRoleValue(itagItem.getAudioTrackType()));
303305

304306
adaptationSetElement.appendChild(roleElement);
305307
} catch (final DOMException e) {
306308
throw CreationException.couldNotAddElement(ROLE, e);
307309
}
308310
}
309311

312+
/**
313+
* Get the value of the {@code <Role>} element based on the {@link AudioTrackType} attribute
314+
* of a stream.
315+
* @param trackType audio track type
316+
* @return role value
317+
*/
318+
private static String getRoleValue(@Nullable final AudioTrackType trackType) {
319+
if (trackType != null) {
320+
switch (trackType) {
321+
case ORIGINAL:
322+
return "main";
323+
case DUBBED:
324+
return "dub";
325+
case DESCRIPTIVE:
326+
return "description";
327+
default:
328+
return "alternate";
329+
}
330+
}
331+
return "main";
332+
}
333+
310334
/**
311335
* Generate the {@code <Representation>} element, appended as a child of the
312336
* {@code <AdaptationSet>} element.

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,7 +1311,7 @@ private java.util.function.Function<ItagInfo, AudioStream> getAudioStreamBuilder
13111311
.setAudioTrackId(itagItem.getAudioTrackId())
13121312
.setAudioTrackName(itagItem.getAudioTrackName())
13131313
.setAudioLocale(itagItem.getAudioLocale())
1314-
.setIsDescriptive(itagItem.isDescriptiveAudio())
1314+
.setAudioTrackType(itagItem.getAudioTrackType())
13151315
.setItagItem(itagItem);
13161316

13171317
if (streamType == StreamType.LIVE_STREAM
@@ -1484,16 +1484,11 @@ private ItagInfo buildAndAddItagInfoToList(
14841484
itagItem.setAudioLocale(LocaleCompat.forLanguageTag(
14851485
audioTrackId.substring(0, audioTrackIdLastLocaleCharacter)));
14861486
}
1487+
itagItem.setAudioTrackType(YoutubeParsingHelper.extractAudioTrackType(streamUrl));
14871488
}
14881489

14891490
itagItem.setAudioTrackName(formatData.getObject("audioTrack")
14901491
.getString("displayName"));
1491-
1492-
// Descriptive audio tracks
1493-
// This information is also provided as a protobuf object in the formatData
1494-
itagItem.setIsDescriptiveAudio(streamUrl.contains("acont%3Ddescriptive")
1495-
// Support "decoded" URLs
1496-
|| streamUrl.contains("acont=descriptive"));
14971492
}
14981493

14991494
// YouTube return the content length and the approximate duration as strings

extractor/src/main/java/org/schabi/newpipe/extractor/stream/AudioStream.java

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ public final class AudioStream extends Stream {
5050
private final String audioTrackName;
5151
@Nullable
5252
private final Locale audioLocale;
53-
private final boolean isDescriptive;
53+
@Nullable
54+
private final AudioTrackType audioTrackType;
5455

5556
@Nullable
5657
private ItagItem itagItem;
@@ -75,7 +76,8 @@ public static final class Builder {
7576
private String audioTrackName;
7677
@Nullable
7778
private Locale audioLocale;
78-
private boolean isDescriptive;
79+
@Nullable
80+
private AudioTrackType audioTrackType;
7981
@Nullable
8082
private ItagItem itagItem;
8183

@@ -223,25 +225,17 @@ public Builder setAudioTrackName(@Nullable final String audioTrackName) {
223225
}
224226

225227
/**
226-
* Set whether this {@link AudioStream} is a descriptive audio.
227-
*
228-
* <p>
229-
* A descriptive audio is an audio in which descriptions of visual elements of a video are
230-
* added in the original audio, with the goal to make a video more accessible to blind and
231-
* visually impaired people.
232-
* </p>
228+
* Set the {@link AudioTrackType} of the {@link AudioStream}.
233229
*
234230
* <p>
235-
* The default value is {@code false}.
231+
* The default value is {@code null}.
236232
* </p>
237233
*
238-
* @param isDescriptive whether this {@link AudioStream} is a descriptive audio
234+
* @param audioTrackType the audio track type of the {@link AudioStream}, which can be null
239235
* @return this {@link Builder} instance
240-
* @see <a href="https://en.wikipedia.org/wiki/Audio_description">
241-
* https://en.wikipedia.org/wiki/Audio_description</a>
242236
*/
243-
public Builder setIsDescriptive(final boolean isDescriptive) {
244-
this.isDescriptive = isDescriptive;
237+
public Builder setAudioTrackType(final AudioTrackType audioTrackType) {
238+
this.audioTrackType = audioTrackType;
245239
return this;
246240
}
247241

@@ -313,7 +307,7 @@ public AudioStream build() {
313307
}
314308

315309
return new AudioStream(id, content, isUrl, mediaFormat, deliveryMethod, averageBitrate,
316-
manifestUrl, audioTrackId, audioTrackName, audioLocale, isDescriptive,
310+
manifestUrl, audioTrackId, audioTrackName, audioLocale, audioTrackType,
317311
itagItem);
318312
}
319313
}
@@ -350,7 +344,7 @@ private AudioStream(@Nonnull final String id,
350344
@Nullable final String audioTrackId,
351345
@Nullable final String audioTrackName,
352346
@Nullable final Locale audioLocale,
353-
final boolean isDescriptive,
347+
@Nullable final AudioTrackType audioTrackType,
354348
@Nullable final ItagItem itagItem) {
355349
super(id, content, isUrl, format, deliveryMethod, manifestUrl);
356350
if (itagItem != null) {
@@ -368,7 +362,7 @@ private AudioStream(@Nonnull final String id,
368362
this.audioTrackId = audioTrackId;
369363
this.audioTrackName = audioTrackName;
370364
this.audioLocale = audioLocale;
371-
this.isDescriptive = isDescriptive;
365+
this.audioTrackType = audioTrackType;
372366
}
373367

374368
/**
@@ -379,7 +373,7 @@ public boolean equalStats(final Stream cmp) {
379373
return super.equalStats(cmp) && cmp instanceof AudioStream
380374
&& averageBitrate == ((AudioStream) cmp).averageBitrate
381375
&& Objects.equals(audioTrackId, ((AudioStream) cmp).audioTrackId)
382-
&& isDescriptive == ((AudioStream) cmp).isDescriptive
376+
&& audioTrackType == ((AudioStream) cmp).audioTrackType
383377
&& Objects.equals(audioLocale, ((AudioStream) cmp).audioLocale);
384378
}
385379

@@ -507,20 +501,14 @@ public Locale getAudioLocale() {
507501
}
508502

509503
/**
510-
* Returns whether this stream is a descriptive audio.
504+
* Get the {@link AudioTrackType} of the stream, which is {@code null} if the track type
505+
* is not known.
511506
*
512-
* <p>
513-
* A descriptive audio is an audio in which descriptions of visual elements of a video are
514-
* added in the original audio, with the goal to make a video more accessible to blind and
515-
* visually impaired people.
516-
* </p>
517-
*
518-
* @return {@code true} this audio stream is a descriptive audio, {@code false} otherwise
519-
* @see <a href="https://en.wikipedia.org/wiki/Audio_description">
520-
* https://en.wikipedia.org/wiki/Audio_description</a>
507+
* @return the {@link AudioTrackType} of the stream or {@code null}
521508
*/
522-
public boolean isDescriptive() {
523-
return isDescriptive;
509+
@Nullable
510+
public AudioTrackType getAudioTrackType() {
511+
return audioTrackType;
524512
}
525513

526514
/**
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.schabi.newpipe.extractor.stream;
2+
3+
/**
4+
* An enum representing the track type of an {@link AudioStream} extracted by a {@link
5+
* StreamExtractor}.
6+
*/
7+
public enum AudioTrackType {
8+
/**
9+
* An original audio track of the video.
10+
*/
11+
ORIGINAL,
12+
13+
/**
14+
* An audio track with the original voices replaced, typically in a different language.
15+
*
16+
* @see <a href="https://en.wikipedia.org/wiki/Dubbing">
17+
* https://en.wikipedia.org/wiki/Dubbing</a>
18+
*/
19+
DUBBED,
20+
21+
/**
22+
* A descriptive audio track.
23+
* <p>
24+
* A descriptive audio track is an audio track in which descriptions of visual elements of
25+
* a video are added to the original audio, with the goal to make a video more accessible to
26+
* blind and visually impaired people.
27+
* </p>
28+
*
29+
* @see <a href="https://en.wikipedia.org/wiki/Audio_description">
30+
* https://en.wikipedia.org/wiki/Audio_description</a>
31+
*/
32+
DESCRIPTIVE
33+
}

extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeDashManifestCreatorsTest.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeOtfDashManifestCreator;
99
import org.schabi.newpipe.extractor.services.youtube.dashmanifestcreators.YoutubeProgressiveDashManifestCreator;
1010
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
11+
import org.schabi.newpipe.extractor.stream.AudioTrackType;
1112
import org.schabi.newpipe.extractor.stream.DeliveryMethod;
1213
import org.schabi.newpipe.extractor.stream.Stream;
1314
import org.w3c.dom.Document;
@@ -233,7 +234,27 @@ private void assertAdaptationSetElement(@Nonnull final Document document,
233234
private void assertRoleElement(@Nonnull final Document document,
234235
@Nonnull final ItagItem itagItem) {
235236
final Element element = assertGetElement(document, ROLE, ADAPTATION_SET);
236-
assertAttrEquals(itagItem.isDescriptiveAudio() ? "alternate" : "main", element, "value");
237+
238+
final String expect;
239+
if (itagItem.getAudioTrackType() == null) {
240+
expect = "main";
241+
} else {
242+
switch (itagItem.getAudioTrackType()) {
243+
case ORIGINAL:
244+
expect = "main";
245+
break;
246+
case DUBBED:
247+
expect = "dub";
248+
break;
249+
case DESCRIPTIVE:
250+
expect = "description";
251+
break;
252+
default:
253+
expect = "alternate";
254+
}
255+
}
256+
257+
assertAttrEquals(expect, element, "value");
237258
}
238259

239260
private void assertRepresentationElement(@Nonnull final Document document,

0 commit comments

Comments
 (0)