mirror of
https://github.com/TeamNewPipe/NewPipeExtractor.git
synced 2024-12-12 21:30:33 +05:30
Merge pull request #973 from FireMasterK/audio-track
Add support for extracting audio track information
This commit is contained in:
commit
9bdad55508
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
|
@ -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}
|
||||
*/
|
||||
|
@ -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"))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
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
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user