Merge pull request #973 from FireMasterK/audio-track

Add support for extracting audio track information
This commit is contained in:
Kavin 2022-11-15 08:19:47 +00:00 committed by GitHub
commit 9bdad55508
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1690 additions and 13 deletions

View File

@ -1,5 +1,12 @@
package org.schabi.newpipe.extractor.services.youtube;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serializable;
import static org.schabi.newpipe.extractor.MediaFormat.M4A;
import static org.schabi.newpipe.extractor.MediaFormat.MPEG_4;
import static org.schabi.newpipe.extractor.MediaFormat.WEBM;
@ -10,14 +17,6 @@ import static org.schabi.newpipe.extractor.services.youtube.ItagItem.ItagType.AU
import static org.schabi.newpipe.extractor.services.youtube.ItagItem.ItagType.VIDEO;
import static org.schabi.newpipe.extractor.services.youtube.ItagItem.ItagType.VIDEO_ONLY;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serializable;
public class ItagItem implements Serializable {
/**
@ -211,18 +210,24 @@ public class ItagItem implements Serializable {
public final ItagType itagType;
// Audio fields
/** @deprecated Use {@link #getAverageBitrate()} instead. */
/**
* @deprecated Use {@link #getAverageBitrate()} instead.
*/
@Deprecated
public int avgBitrate = AVERAGE_BITRATE_UNKNOWN;
private int sampleRate = SAMPLE_RATE_UNKNOWN;
private int audioChannels = AUDIO_CHANNELS_NOT_APPLICABLE_OR_UNKNOWN;
// Video fields
/** @deprecated Use {@link #getResolutionString()} instead. */
/**
* @deprecated Use {@link #getResolutionString()} instead.
*/
@Deprecated
public String resolutionString;
/** @deprecated Use {@link #getFps()} and {@link #setFps(int)} instead. */
/**
* @deprecated Use {@link #getFps()} and {@link #setFps(int)} instead.
*/
@Deprecated
public int fps = FPS_NOT_APPLICABLE_OR_UNKNOWN;
@ -239,6 +244,8 @@ public class ItagItem implements Serializable {
private int targetDurationSec = TARGET_DURATION_SEC_UNKNOWN;
private long approxDurationMs = APPROX_DURATION_MS_UNKNOWN;
private long contentLength = CONTENT_LENGTH_UNKNOWN;
private String audioTrackId;
private String audioTrackName;
public int getBitrate() {
return bitrate;
@ -539,4 +546,42 @@ public class ItagItem implements Serializable {
public void setContentLength(final long contentLength) {
this.contentLength = contentLength > 0 ? contentLength : CONTENT_LENGTH_UNKNOWN;
}
/**
* Get the {@code audioTrackId} of the stream, if present.
*
* @return the {@code audioTrackId} of the stream or null
*/
@Nullable
public String getAudioTrackId() {
return audioTrackId;
}
/**
* Set the {@code audioTrackId} of the stream.
*
* @param audioTrackId the {@code audioTrackId} of the stream
*/
public void setAudioTrackId(@Nullable final String audioTrackId) {
this.audioTrackId = audioTrackId;
}
/**
* Get the {@code audioTrackName} of the stream, if present.
*
* @return the {@code audioTrackName} of the stream or null
*/
@Nullable
public String getAudioTrackName() {
return audioTrackName;
}
/**
* Set the {@code audioTrackName} of the stream.
*
* @param audioTrackName the {@code audioTrackName} of the stream
*/
public void setAudioTrackName(@Nullable final String audioTrackName) {
this.audioTrackName = audioTrackName;
}
}

View File

@ -1333,6 +1333,8 @@ public class YoutubeStreamExtractor extends StreamExtractor {
.setContent(itagInfo.getContent(), itagInfo.getIsUrl())
.setMediaFormat(itagItem.getMediaFormat())
.setAverageBitrate(itagItem.getAverageBitrate())
.setAudioTrackId(itagItem.getAudioTrackId())
.setAudioTrackName(itagItem.getAudioTrackName())
.setItagItem(itagItem);
if (streamType == StreamType.LIVE_STREAM
@ -1478,6 +1480,9 @@ 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"));
}

View File

@ -25,6 +25,7 @@ import org.schabi.newpipe.extractor.services.youtube.ItagItem;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;
public final class AudioStream extends Stream {
public static final int UNKNOWN_BITRATE = -1;
@ -40,6 +41,10 @@ public final class AudioStream extends Stream {
private int indexEnd;
private String quality;
private String codec;
// Fields about the audio track id/name
private String audioTrackId;
private String audioTrackName;
@Nullable
private ItagItem itagItem;
@ -58,6 +63,10 @@ public final class AudioStream extends Stream {
private String manifestUrl;
private int averageBitrate = UNKNOWN_BITRATE;
@Nullable
private String audioTrackId;
@Nullable
private String audioTrackName;
@Nullable
private ItagItem itagItem;
/**
@ -173,6 +182,28 @@ public final class AudioStream extends Stream {
return this;
}
/**
* Set the audio track id of the {@link AudioStream}.
*
* @param audioTrackId the audio track id of the {@link AudioStream}
* @return this {@link Builder} instance
*/
public Builder setAudioTrackId(@Nullable final String audioTrackId) {
this.audioTrackId = audioTrackId;
return this;
}
/**
* Set the audio track name of the {@link AudioStream}.
*
* @param audioTrackName the audio track name of the {@link AudioStream}
* @return this {@link Builder} instance
*/
public Builder setAudioTrackName(@Nullable final String audioTrackName) {
this.audioTrackName = audioTrackName;
return this;
}
/**
* Set the {@link ItagItem} corresponding to the {@link AudioStream}.
*
@ -226,7 +257,7 @@ public final class AudioStream extends Stream {
}
return new AudioStream(id, content, isUrl, mediaFormat, deliveryMethod, averageBitrate,
manifestUrl, itagItem);
manifestUrl, audioTrackId, audioTrackName, itagItem);
}
}
@ -244,6 +275,8 @@ public final class AudioStream extends Stream {
* @param deliveryMethod the {@link DeliveryMethod} of the stream
* @param averageBitrate the average bitrate of the stream (which can be unknown, see
* {@link #UNKNOWN_BITRATE})
* @param audioTrackId the id of the audio track
* @param audioTrackName the name of the audio track
* @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)
@ -256,6 +289,8 @@ public final class AudioStream extends Stream {
@Nonnull final DeliveryMethod deliveryMethod,
final int averageBitrate,
@Nullable final String manifestUrl,
@Nullable final String audioTrackId,
@Nullable final String audioTrackName,
@Nullable final ItagItem itagItem) {
super(id, content, isUrl, format, deliveryMethod, manifestUrl);
if (itagItem != null) {
@ -270,6 +305,8 @@ public final class AudioStream extends Stream {
this.codec = itagItem.getCodec();
}
this.averageBitrate = averageBitrate;
this.audioTrackId = audioTrackId;
this.audioTrackName = audioTrackName;
}
/**
@ -278,7 +315,8 @@ public final class AudioStream extends Stream {
@Override
public boolean equalStats(final Stream cmp) {
return super.equalStats(cmp) && cmp instanceof AudioStream
&& averageBitrate == ((AudioStream) cmp).averageBitrate;
&& averageBitrate == ((AudioStream) cmp).averageBitrate
&& Objects.equals(audioTrackId, ((AudioStream) cmp).audioTrackId);
}
/**
@ -372,6 +410,26 @@ public final class AudioStream extends Stream {
return codec;
}
/**
* Get the id of the audio track.
*
* @return the id of the audio track
*/
@Nullable
public String getAudioTrackId() {
return audioTrackId;
}
/**
* Get the name of the audio track.
*
* @return the name of the audio track
*/
@Nullable
public String getAudioTrackName() {
return audioTrackName;
}
/**
* {@inheritDoc}
*/

View File

@ -23,6 +23,7 @@ package org.schabi.newpipe.extractor.services.youtube.stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
import org.junit.jupiter.api.BeforeAll;
@ -42,6 +43,7 @@ import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentExcepti
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.Description;
import org.schabi.newpipe.extractor.stream.StreamExtractor;
import org.schabi.newpipe.extractor.stream.StreamSegment;
@ -464,4 +466,41 @@ public class YoutubeStreamExtractorDefaultTest {
assertEquals("Creative Commons Attribution licence (reuse allowed)", extractor.getLicence());
}
}
public static class AudioTrackLanguage {
private static final String ID = "kX3nB4PpJko";
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 + "audioTrack"));
extractor = YouTube.getStreamExtractor(URL);
extractor.fetchPage();
}
@Test
void testCheckAudioStreams() throws Exception {
assertTrue(extractor.getAudioStreams().size() > 0);
for (final AudioStream audioStream : extractor.getAudioStreams()) {
assertNotNull(audioStream.getAudioTrackName());
}
assertTrue(
extractor.getAudioStreams()
.stream()
.anyMatch(audioStream -> audioStream.getAudioTrackName().equals("English"))
);
assertTrue(
extractor.getAudioStreams()
.stream()
.anyMatch(audioStream -> audioStream.getAudioTrackName().equals("Hindi"))
);
}
}
}

View File

@ -0,0 +1,72 @@
{
"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,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\""
],
"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\""
],
"cross-origin-resource-policy": [
"cross-origin"
],
"date": [
"Sun, 13 Nov 2022 23:10:02 GMT"
],
"expires": [
"Sun, 13 Nov 2022 23:10:02 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\u003dvUst81-3DYY; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"VISITOR_INFO1_LIVE\u003dvcz7Oi5zbD4; Domain\u003d.youtube.com; Expires\u003dFri, 12-May-2023 23:10:02 GMT; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone"
],
"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\\/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",
"latestUrl": "https://www.youtube.com/iframe_api"
}
}