Skip to content

Commit 6bdd698

Browse files
authored
Merge pull request #1026 from AudricV/audio-streams-descriptive-and-locale-properties
Add descriptive and locale properties to audio streams
2 parents 19e4b21 + 95b3f5e commit 6bdd698

15 files changed

Lines changed: 1907 additions & 47 deletions

File tree

extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.schabi.newpipe.extractor.stream.StreamType;
2525
import org.schabi.newpipe.extractor.stream.VideoStream;
2626
import org.schabi.newpipe.extractor.utils.JsonUtils;
27+
import org.schabi.newpipe.extractor.utils.LocaleCompat;
2728

2829
import java.io.IOException;
2930
import java.util.ArrayList;
@@ -114,15 +115,24 @@ public List<AudioStream> getAudioStreams() throws ExtractionException {
114115
mediaFormat = null;
115116
}
116117

117-
// Not checking containsSimilarStream here, since MediaCCC does not provide enough
118-
// information to decide whether two streams are similar. Hence that method would
119-
// always return false, e.g. even for different language variations.
120-
audioStreams.add(new AudioStream.Builder()
118+
final AudioStream.Builder builder = new AudioStream.Builder()
121119
.setId(recording.getString("filename", ID_UNKNOWN))
122120
.setContent(recording.getString("recording_url"), true)
123121
.setMediaFormat(mediaFormat)
124-
.setAverageBitrate(UNKNOWN_BITRATE)
125-
.build());
122+
.setAverageBitrate(UNKNOWN_BITRATE);
123+
124+
final String language = recording.getString("language");
125+
// If the language contains a - symbol, this means that the stream has an audio
126+
// track with multiple languages, so there is no specific language for this stream
127+
// Don't set the audio language in this case
128+
if (language != null && !language.contains("-")) {
129+
builder.setAudioLocale(LocaleCompat.forLanguageTag(language));
130+
}
131+
132+
// Not checking containsSimilarStream here, since MediaCCC does not provide enough
133+
// information to decide whether two streams are similar. Hence that method would
134+
// always return false, e.g. even for different language variations.
135+
audioStreams.add(builder.build());
126136
}
127137
}
128138
return audioStreams;

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

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import javax.annotation.Nonnull;
77
import javax.annotation.Nullable;
88
import java.io.Serializable;
9+
import java.util.Locale;
910

1011
import static org.schabi.newpipe.extractor.MediaFormat.M4A;
1112
import static org.schabi.newpipe.extractor.MediaFormat.MPEG_4;
@@ -198,6 +199,10 @@ public ItagItem(@Nonnull final ItagItem itagItem) {
198199
this.targetDurationSec = itagItem.targetDurationSec;
199200
this.approxDurationMs = itagItem.approxDurationMs;
200201
this.contentLength = itagItem.contentLength;
202+
this.audioTrackId = itagItem.audioTrackId;
203+
this.audioTrackName = itagItem.audioTrackName;
204+
this.isDescriptiveAudio = itagItem.isDescriptiveAudio;
205+
this.audioLocale = itagItem.audioLocale;
201206
}
202207

203208
public MediaFormat getMediaFormat() {
@@ -246,6 +251,9 @@ public MediaFormat getMediaFormat() {
246251
private long contentLength = CONTENT_LENGTH_UNKNOWN;
247252
private String audioTrackId;
248253
private String audioTrackName;
254+
private boolean isDescriptiveAudio;
255+
@Nullable
256+
private Locale audioLocale;
249257

250258
public int getBitrate() {
251259
return bitrate;
@@ -569,19 +577,61 @@ public void setAudioTrackId(@Nullable final String audioTrackId) {
569577
/**
570578
* Get the {@code audioTrackName} of the stream, if present.
571579
*
572-
* @return the {@code audioTrackName} of the stream or null
580+
* @return the {@code audioTrackName} of the stream or {@code null}
573581
*/
574582
@Nullable
575583
public String getAudioTrackName() {
576584
return audioTrackName;
577585
}
578586

579587
/**
580-
* Set the {@code audioTrackName} of the stream.
588+
* Set the {@code audioTrackName} of the stream, if present.
581589
*
582-
* @param audioTrackName the {@code audioTrackName} of the stream
590+
* @param audioTrackName the {@code audioTrackName} of the stream or {@code null}
583591
*/
584592
public void setAudioTrackName(@Nullable final String audioTrackName) {
585593
this.audioTrackName = audioTrackName;
586594
}
595+
596+
/**
597+
* Return whether the stream is a descriptive audio.
598+
*
599+
* @return whether the stream is a descriptive audio
600+
*/
601+
public boolean isDescriptiveAudio() {
602+
return isDescriptiveAudio;
603+
}
604+
605+
/**
606+
* Set whether the stream is a descriptive audio.
607+
*
608+
* @param isDescriptiveAudio whether the stream is a descriptive audio
609+
*/
610+
public void setIsDescriptiveAudio(final boolean isDescriptiveAudio) {
611+
this.isDescriptiveAudio = isDescriptiveAudio;
612+
}
613+
614+
/**
615+
* Return the audio {@link Locale} of the stream, if known.
616+
*
617+
* @return the audio {@link Locale} of the stream, if known, or {@code null} if that's not the
618+
* case
619+
*/
620+
@Nullable
621+
public Locale getAudioLocale() {
622+
return audioLocale;
623+
}
624+
625+
/**
626+
* Set the audio {@link Locale} of the stream.
627+
*
628+
* <p>
629+
* If it is unknown, {@code null} could be passed, which is the default value.
630+
* </p>
631+
*
632+
* @param audioLocale the audio {@link Locale} of the stream, which could be {@code null}
633+
*/
634+
public void setAudioLocale(@Nullable final Locale audioLocale) {
635+
this.audioLocale = audioLocale;
636+
}
587637
}

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

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public static void setAttribute(final Element element,
124124
* <li>{@code Period} (using {@link #generatePeriodElement(Document)});</li>
125125
* <li>{@code AdaptationSet} (using {@link #generateAdaptationSetElement(Document,
126126
* ItagItem)});</li>
127-
* <li>{@code Role} (using {@link #generateRoleElement(Document)});</li>
127+
* <li>{@code Role} (using {@link #generateRoleElement(Document, ItagItem)});</li>
128128
* <li>{@code Representation} (using {@link #generateRepresentationElement(Document,
129129
* ItagItem)});</li>
130130
* <li>and, for audio streams, {@code AudioChannelConfiguration} (using
@@ -144,7 +144,7 @@ public static Document generateDocumentAndDoCommonElementsGeneration(
144144

145145
generatePeriodElement(doc);
146146
generateAdaptationSetElement(doc, itagItem);
147-
generateRoleElement(doc);
147+
generateRoleElement(doc, itagItem);
148148
generateRepresentationElement(doc, itagItem);
149149
if (itagItem.itagType == ItagItem.ItagType.AUDIO) {
150150
generateAudioChannelConfigurationElement(doc, itagItem);
@@ -208,7 +208,7 @@ public static Document generateDocumentAndMpdElement(final long duration)
208208
* {@link #generateDocumentAndMpdElement(long)}.
209209
* </p>
210210
*
211-
* @param doc the {@link Document} on which the the {@code <Period>} element will be appended
211+
* @param doc the {@link Document} on which the {@code <Period>} element will be appended
212212
*/
213213
public static void generatePeriodElement(@Nonnull final Document doc)
214214
throws CreationException {
@@ -249,6 +249,16 @@ public static void generateAdaptationSetElement(@Nonnull final Document doc,
249249
"the MediaFormat or its mime type is null or empty");
250250
}
251251

252+
if (itagItem.itagType == ItagItem.ItagType.AUDIO) {
253+
final Locale audioLocale = itagItem.getAudioLocale();
254+
if (audioLocale != null) {
255+
final String audioLanguage = audioLocale.getLanguage();
256+
if (!audioLanguage.isEmpty()) {
257+
setAttribute(adaptationSetElement, doc, "lang", audioLanguage);
258+
}
259+
}
260+
}
261+
252262
setAttribute(adaptationSetElement, doc, "mimeType", mediaFormat.getMimeType());
253263
setAttribute(adaptationSetElement, doc, "subsegmentAlignment", "true");
254264

@@ -267,25 +277,29 @@ public static void generateAdaptationSetElement(@Nonnull final Document doc,
267277
* </p>
268278
*
269279
* <p>
270-
* {@code <Role schemeIdUri="urn:mpeg:DASH:role:2011" value="main"/>}
280+
* {@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
271282
* </p>
272283
*
273284
* <p>
274285
* The {@code <AdaptationSet>} element needs to be generated before this element with
275286
* {@link #generateAdaptationSetElement(Document, ItagItem)}).
276287
* </p>
277288
*
278-
* @param doc the {@link Document} on which the the {@code <Role>} element will be appended
289+
* @param doc the {@link Document} on which the {@code <Role>} element will be appended
290+
* @param itagItem the {@link ItagItem} corresponding to the stream, which must not be null
279291
*/
280-
public static void generateRoleElement(@Nonnull final Document doc)
292+
public static void generateRoleElement(@Nonnull final Document doc,
293+
@Nonnull final ItagItem itagItem)
281294
throws CreationException {
282295
try {
283296
final Element adaptationSetElement = (Element) doc.getElementsByTagName(
284297
ADAPTATION_SET).item(0);
285298
final Element roleElement = doc.createElement(ROLE);
286299

287300
setAttribute(roleElement, doc, "schemeIdUri", "urn:mpeg:DASH:role:2011");
288-
setAttribute(roleElement, doc, "value", "main");
301+
setAttribute(roleElement, doc, "value", itagItem.isDescriptiveAudio()
302+
? "alternate" : "main");
289303

290304
adaptationSetElement.appendChild(roleElement);
291305
} catch (final DOMException e) {
@@ -302,7 +316,7 @@ public static void generateRoleElement(@Nonnull final Document doc)
302316
* {@link #generateAdaptationSetElement(Document, ItagItem)}).
303317
* </p>
304318
*
305-
* @param doc the {@link Document} on which the the {@code <SegmentTimeline>} element will be
319+
* @param doc the {@link Document} on which the {@code <SegmentTimeline>} element will be
306320
* appended
307321
* @param itagItem the {@link ItagItem} to use, which must not be null
308322
*/
@@ -522,7 +536,7 @@ public static void generateSegmentTemplateElement(@Nonnull final Document doc,
522536
* {@link #generateSegmentTemplateElement(Document, String, DeliveryType)}.
523537
* </p>
524538
*
525-
* @param doc the {@link Document} on which the the {@code <SegmentTimeline>} element will be
539+
* @param doc the {@link Document} on which the {@code <SegmentTimeline>} element will be
526540
* appended
527541
*/
528542
public static void generateSegmentTimelineElement(@Nonnull final Document doc)

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

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
8383
import org.schabi.newpipe.extractor.stream.VideoStream;
8484
import org.schabi.newpipe.extractor.utils.JsonUtils;
85+
import org.schabi.newpipe.extractor.utils.LocaleCompat;
8586
import org.schabi.newpipe.extractor.utils.Pair;
8687
import org.schabi.newpipe.extractor.utils.Parser;
8788
import org.schabi.newpipe.extractor.utils.Utils;
@@ -1309,6 +1310,8 @@ private java.util.function.Function<ItagInfo, AudioStream> getAudioStreamBuilder
13091310
.setAverageBitrate(itagItem.getAverageBitrate())
13101311
.setAudioTrackId(itagItem.getAudioTrackId())
13111312
.setAudioTrackName(itagItem.getAudioTrackName())
1313+
.setAudioLocale(itagItem.getAudioLocale())
1314+
.setIsDescriptive(itagItem.isDescriptiveAudio())
13121315
.setItagItem(itagItem);
13131316

13141317
if (streamType == StreamType.LIVE_STREAM
@@ -1454,9 +1457,6 @@ private ItagInfo buildAndAddItagInfoToList(
14541457
itagItem.setQuality(formatData.getString("quality"));
14551458
itagItem.setCodec(codec);
14561459

1457-
itagItem.setAudioTrackId(formatData.getObject("audioTrack").getString("id"));
1458-
itagItem.setAudioTrackName(formatData.getObject("audioTrack").getString("displayName"));
1459-
14601460
if (streamType == StreamType.LIVE_STREAM || streamType == StreamType.POST_LIVE_STREAM) {
14611461
itagItem.setTargetDurationSec(formatData.getInt("targetDurationSec"));
14621462
}
@@ -1473,6 +1473,27 @@ private ItagInfo buildAndAddItagInfoToList(
14731473
// AudioChannelConfiguration element of DASH manifests of audio streams in
14741474
// YoutubeDashManifestCreatorUtils
14751475
2));
1476+
1477+
final String audioTrackId = formatData.getObject("audioTrack")
1478+
.getString("id");
1479+
if (!isNullOrEmpty(audioTrackId)) {
1480+
itagItem.setAudioTrackId(audioTrackId);
1481+
final int audioTrackIdLastLocaleCharacter = audioTrackId.indexOf(".");
1482+
if (audioTrackIdLastLocaleCharacter != -1) {
1483+
// Audio tracks IDs are in the form LANGUAGE_CODE.TRACK_NUMBER
1484+
itagItem.setAudioLocale(LocaleCompat.forLanguageTag(
1485+
audioTrackId.substring(0, audioTrackIdLastLocaleCharacter)));
1486+
}
1487+
}
1488+
1489+
itagItem.setAudioTrackName(formatData.getObject("audioTrack")
1490+
.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"));
14761497
}
14771498

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

0 commit comments

Comments
 (0)