Skip to content

Commit 9bdad55

Browse files
authored
Merge pull request #973 from FireMasterK/audio-track
Add support for extracting audio track information
2 parents 43d1c1f + 86f06b3 commit 9bdad55

File tree

10 files changed

+1690
-13
lines changed

10 files changed

+1690
-13
lines changed

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

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
package org.schabi.newpipe.extractor.services.youtube;
22

3+
import org.schabi.newpipe.extractor.MediaFormat;
4+
import org.schabi.newpipe.extractor.exceptions.ParsingException;
5+
6+
import javax.annotation.Nonnull;
7+
import javax.annotation.Nullable;
8+
import java.io.Serializable;
9+
310
import static org.schabi.newpipe.extractor.MediaFormat.M4A;
411
import static org.schabi.newpipe.extractor.MediaFormat.MPEG_4;
512
import static org.schabi.newpipe.extractor.MediaFormat.WEBM;
@@ -10,14 +17,6 @@
1017
import static org.schabi.newpipe.extractor.services.youtube.ItagItem.ItagType.VIDEO;
1118
import static org.schabi.newpipe.extractor.services.youtube.ItagItem.ItagType.VIDEO_ONLY;
1219

13-
import org.schabi.newpipe.extractor.MediaFormat;
14-
import org.schabi.newpipe.extractor.exceptions.ParsingException;
15-
16-
import javax.annotation.Nonnull;
17-
import javax.annotation.Nullable;
18-
19-
import java.io.Serializable;
20-
2120
public class ItagItem implements Serializable {
2221

2322
/**
@@ -211,18 +210,24 @@ public MediaFormat getMediaFormat() {
211210
public final ItagType itagType;
212211

213212
// Audio fields
214-
/** @deprecated Use {@link #getAverageBitrate()} instead. */
213+
/**
214+
* @deprecated Use {@link #getAverageBitrate()} instead.
215+
*/
215216
@Deprecated
216217
public int avgBitrate = AVERAGE_BITRATE_UNKNOWN;
217218
private int sampleRate = SAMPLE_RATE_UNKNOWN;
218219
private int audioChannels = AUDIO_CHANNELS_NOT_APPLICABLE_OR_UNKNOWN;
219220

220221
// Video fields
221-
/** @deprecated Use {@link #getResolutionString()} instead. */
222+
/**
223+
* @deprecated Use {@link #getResolutionString()} instead.
224+
*/
222225
@Deprecated
223226
public String resolutionString;
224227

225-
/** @deprecated Use {@link #getFps()} and {@link #setFps(int)} instead. */
228+
/**
229+
* @deprecated Use {@link #getFps()} and {@link #setFps(int)} instead.
230+
*/
226231
@Deprecated
227232
public int fps = FPS_NOT_APPLICABLE_OR_UNKNOWN;
228233

@@ -239,6 +244,8 @@ public MediaFormat getMediaFormat() {
239244
private int targetDurationSec = TARGET_DURATION_SEC_UNKNOWN;
240245
private long approxDurationMs = APPROX_DURATION_MS_UNKNOWN;
241246
private long contentLength = CONTENT_LENGTH_UNKNOWN;
247+
private String audioTrackId;
248+
private String audioTrackName;
242249

243250
public int getBitrate() {
244251
return bitrate;
@@ -539,4 +546,42 @@ public long getContentLength() {
539546
public void setContentLength(final long contentLength) {
540547
this.contentLength = contentLength > 0 ? contentLength : CONTENT_LENGTH_UNKNOWN;
541548
}
549+
550+
/**
551+
* Get the {@code audioTrackId} of the stream, if present.
552+
*
553+
* @return the {@code audioTrackId} of the stream or null
554+
*/
555+
@Nullable
556+
public String getAudioTrackId() {
557+
return audioTrackId;
558+
}
559+
560+
/**
561+
* Set the {@code audioTrackId} of the stream.
562+
*
563+
* @param audioTrackId the {@code audioTrackId} of the stream
564+
*/
565+
public void setAudioTrackId(@Nullable final String audioTrackId) {
566+
this.audioTrackId = audioTrackId;
567+
}
568+
569+
/**
570+
* Get the {@code audioTrackName} of the stream, if present.
571+
*
572+
* @return the {@code audioTrackName} of the stream or null
573+
*/
574+
@Nullable
575+
public String getAudioTrackName() {
576+
return audioTrackName;
577+
}
578+
579+
/**
580+
* Set the {@code audioTrackName} of the stream.
581+
*
582+
* @param audioTrackName the {@code audioTrackName} of the stream
583+
*/
584+
public void setAudioTrackName(@Nullable final String audioTrackName) {
585+
this.audioTrackName = audioTrackName;
586+
}
542587
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,6 +1333,8 @@ private java.util.function.Function<ItagInfo, AudioStream> getAudioStreamBuilder
13331333
.setContent(itagInfo.getContent(), itagInfo.getIsUrl())
13341334
.setMediaFormat(itagItem.getMediaFormat())
13351335
.setAverageBitrate(itagItem.getAverageBitrate())
1336+
.setAudioTrackId(itagItem.getAudioTrackId())
1337+
.setAudioTrackName(itagItem.getAudioTrackName())
13361338
.setItagItem(itagItem);
13371339

13381340
if (streamType == StreamType.LIVE_STREAM
@@ -1478,6 +1480,9 @@ private ItagInfo buildAndAddItagInfoToList(
14781480
itagItem.setQuality(formatData.getString("quality"));
14791481
itagItem.setCodec(codec);
14801482

1483+
itagItem.setAudioTrackId(formatData.getObject("audioTrack").getString("id"));
1484+
itagItem.setAudioTrackName(formatData.getObject("audioTrack").getString("displayName"));
1485+
14811486
if (streamType == StreamType.LIVE_STREAM || streamType == StreamType.POST_LIVE_STREAM) {
14821487
itagItem.setTargetDurationSec(formatData.getInt("targetDurationSec"));
14831488
}

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

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import javax.annotation.Nonnull;
2727
import javax.annotation.Nullable;
28+
import java.util.Objects;
2829

2930
public final class AudioStream extends Stream {
3031
public static final int UNKNOWN_BITRATE = -1;
@@ -40,6 +41,10 @@ public final class AudioStream extends Stream {
4041
private int indexEnd;
4142
private String quality;
4243
private String codec;
44+
45+
// Fields about the audio track id/name
46+
private String audioTrackId;
47+
private String audioTrackName;
4348
@Nullable
4449
private ItagItem itagItem;
4550

@@ -58,6 +63,10 @@ public static final class Builder {
5863
private String manifestUrl;
5964
private int averageBitrate = UNKNOWN_BITRATE;
6065
@Nullable
66+
private String audioTrackId;
67+
@Nullable
68+
private String audioTrackName;
69+
@Nullable
6170
private ItagItem itagItem;
6271

6372
/**
@@ -173,6 +182,28 @@ public Builder setAverageBitrate(final int averageBitrate) {
173182
return this;
174183
}
175184

185+
/**
186+
* Set the audio track id of the {@link AudioStream}.
187+
*
188+
* @param audioTrackId the audio track id of the {@link AudioStream}
189+
* @return this {@link Builder} instance
190+
*/
191+
public Builder setAudioTrackId(@Nullable final String audioTrackId) {
192+
this.audioTrackId = audioTrackId;
193+
return this;
194+
}
195+
196+
/**
197+
* Set the audio track name of the {@link AudioStream}.
198+
*
199+
* @param audioTrackName the audio track name of the {@link AudioStream}
200+
* @return this {@link Builder} instance
201+
*/
202+
public Builder setAudioTrackName(@Nullable final String audioTrackName) {
203+
this.audioTrackName = audioTrackName;
204+
return this;
205+
}
206+
176207
/**
177208
* Set the {@link ItagItem} corresponding to the {@link AudioStream}.
178209
*
@@ -226,7 +257,7 @@ public AudioStream build() {
226257
}
227258

228259
return new AudioStream(id, content, isUrl, mediaFormat, deliveryMethod, averageBitrate,
229-
manifestUrl, itagItem);
260+
manifestUrl, audioTrackId, audioTrackName, itagItem);
230261
}
231262
}
232263

@@ -244,6 +275,8 @@ public AudioStream build() {
244275
* @param deliveryMethod the {@link DeliveryMethod} of the stream
245276
* @param averageBitrate the average bitrate of the stream (which can be unknown, see
246277
* {@link #UNKNOWN_BITRATE})
278+
* @param audioTrackId the id of the audio track
279+
* @param audioTrackName the name of the audio track
247280
* @param itagItem the {@link ItagItem} corresponding to the stream, which cannot be null
248281
* @param manifestUrl the URL of the manifest this stream comes from (if applicable,
249282
* otherwise null)
@@ -256,6 +289,8 @@ private AudioStream(@Nonnull final String id,
256289
@Nonnull final DeliveryMethod deliveryMethod,
257290
final int averageBitrate,
258291
@Nullable final String manifestUrl,
292+
@Nullable final String audioTrackId,
293+
@Nullable final String audioTrackName,
259294
@Nullable final ItagItem itagItem) {
260295
super(id, content, isUrl, format, deliveryMethod, manifestUrl);
261296
if (itagItem != null) {
@@ -270,6 +305,8 @@ private AudioStream(@Nonnull final String id,
270305
this.codec = itagItem.getCodec();
271306
}
272307
this.averageBitrate = averageBitrate;
308+
this.audioTrackId = audioTrackId;
309+
this.audioTrackName = audioTrackName;
273310
}
274311

275312
/**
@@ -278,7 +315,8 @@ private AudioStream(@Nonnull final String id,
278315
@Override
279316
public boolean equalStats(final Stream cmp) {
280317
return super.equalStats(cmp) && cmp instanceof AudioStream
281-
&& averageBitrate == ((AudioStream) cmp).averageBitrate;
318+
&& averageBitrate == ((AudioStream) cmp).averageBitrate
319+
&& Objects.equals(audioTrackId, ((AudioStream) cmp).audioTrackId);
282320
}
283321

284322
/**
@@ -372,6 +410,26 @@ public String getCodec() {
372410
return codec;
373411
}
374412

413+
/**
414+
* Get the id of the audio track.
415+
*
416+
* @return the id of the audio track
417+
*/
418+
@Nullable
419+
public String getAudioTrackId() {
420+
return audioTrackId;
421+
}
422+
423+
/**
424+
* Get the name of the audio track.
425+
*
426+
* @return the name of the audio track
427+
*/
428+
@Nullable
429+
public String getAudioTrackName() {
430+
return audioTrackName;
431+
}
432+
375433
/**
376434
* {@inheritDoc}
377435
*/

extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/stream/YoutubeStreamExtractorDefaultTest.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import static org.junit.jupiter.api.Assertions.assertEquals;
2424
import static org.junit.jupiter.api.Assertions.assertNotNull;
2525
import static org.junit.jupiter.api.Assertions.assertThrows;
26+
import static org.junit.jupiter.api.Assertions.assertTrue;
2627
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
2728

2829
import org.junit.jupiter.api.BeforeAll;
@@ -42,6 +43,7 @@
4243
import org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest;
4344
import org.schabi.newpipe.extractor.services.youtube.YoutubeTestsUtils;
4445
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
46+
import org.schabi.newpipe.extractor.stream.AudioStream;
4547
import org.schabi.newpipe.extractor.stream.Description;
4648
import org.schabi.newpipe.extractor.stream.StreamExtractor;
4749
import org.schabi.newpipe.extractor.stream.StreamSegment;
@@ -464,4 +466,41 @@ void testGetLicence() throws ParsingException {
464466
assertEquals("Creative Commons Attribution licence (reuse allowed)", extractor.getLicence());
465467
}
466468
}
469+
470+
public static class AudioTrackLanguage {
471+
472+
private static final String ID = "kX3nB4PpJko";
473+
private static final String URL = BASE_URL + ID;
474+
private static StreamExtractor extractor;
475+
476+
@BeforeAll
477+
public static void setUp() throws Exception {
478+
YoutubeTestsUtils.ensureStateless();
479+
NewPipe.init(DownloaderFactory.getDownloader(RESOURCE_PATH + "audioTrack"));
480+
extractor = YouTube.getStreamExtractor(URL);
481+
extractor.fetchPage();
482+
}
483+
484+
@Test
485+
void testCheckAudioStreams() throws Exception {
486+
assertTrue(extractor.getAudioStreams().size() > 0);
487+
488+
for (final AudioStream audioStream : extractor.getAudioStreams()) {
489+
assertNotNull(audioStream.getAudioTrackName());
490+
}
491+
492+
assertTrue(
493+
extractor.getAudioStreams()
494+
.stream()
495+
.anyMatch(audioStream -> audioStream.getAudioTrackName().equals("English"))
496+
);
497+
498+
assertTrue(
499+
extractor.getAudioStreams()
500+
.stream()
501+
.anyMatch(audioStream -> audioStream.getAudioTrackName().equals("Hindi"))
502+
);
503+
}
504+
505+
}
467506
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{
2+
"request": {
3+
"httpMethod": "GET",
4+
"url": "https://www.youtube.com/iframe_api",
5+
"headers": {
6+
"Accept-Language": [
7+
"en-GB, en;q\u003d0.9"
8+
]
9+
},
10+
"localization": {
11+
"languageCode": "en",
12+
"countryCode": "GB"
13+
}
14+
},
15+
"response": {
16+
"responseCode": 200,
17+
"responseMessage": "",
18+
"responseHeaders": {
19+
"alt-svc": [
20+
"h3\u003d\":443\"; ma\u003d2592000,h3-29\u003d\":443\"; ma\u003d2592000,h3-Q050\u003d\":443\"; ma\u003d2592000,h3-Q046\u003d\":443\"; ma\u003d2592000,h3-Q043\u003d\":443\"; ma\u003d2592000,quic\u003d\":443\"; ma\u003d2592000; v\u003d\"46,43\""
21+
],
22+
"cache-control": [
23+
"private, max-age\u003d0"
24+
],
25+
"content-type": [
26+
"text/javascript; charset\u003dutf-8"
27+
],
28+
"cross-origin-opener-policy-report-only": [
29+
"same-origin; report-to\u003d\"youtube_main\""
30+
],
31+
"cross-origin-resource-policy": [
32+
"cross-origin"
33+
],
34+
"date": [
35+
"Sun, 13 Nov 2022 23:10:02 GMT"
36+
],
37+
"expires": [
38+
"Sun, 13 Nov 2022 23:10:02 GMT"
39+
],
40+
"p3p": [
41+
"CP\u003d\"This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl\u003den-GB for more info.\""
42+
],
43+
"permissions-policy": [
44+
"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*"
45+
],
46+
"report-to": [
47+
"{\"group\":\"youtube_main\",\"max_age\":2592000,\"endpoints\":[{\"url\":\"https://csp.withgoogle.com/csp/report-to/youtube_main\"}]}"
48+
],
49+
"server": [
50+
"ESF"
51+
],
52+
"set-cookie": [
53+
"YSC\u003dvUst81-3DYY; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
54+
"VISITOR_INFO1_LIVE\u003dvcz7Oi5zbD4; Domain\u003d.youtube.com; Expires\u003dFri, 12-May-2023 23:10:02 GMT; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone"
55+
],
56+
"strict-transport-security": [
57+
"max-age\u003d31536000"
58+
],
59+
"x-content-type-options": [
60+
"nosniff"
61+
],
62+
"x-frame-options": [
63+
"SAMEORIGIN"
64+
],
65+
"x-xss-protection": [
66+
"0"
67+
]
68+
},
69+
"responseBody": "var scriptUrl \u003d \u0027https:\\/\\/www.youtube.com\\/s\\/player\\/c4225c42\\/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",
70+
"latestUrl": "https://www.youtube.com/iframe_api"
71+
}
72+
}

0 commit comments

Comments
 (0)