Skip to content

Commit

Permalink
[YouTube] Catch any exception in YoutubeThrottlingDecrypter.apply and…
Browse files Browse the repository at this point in the history
… improve docs

This will prevent any future extractor break due to decryption failure, like it was excepted to be the case before.

Some documentation about the throttling decryption has been also improved.
  • Loading branch information
AudricV committed Aug 12, 2022
1 parent 52ded6e commit 5b54834
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,24 @@
import java.util.regex.Pattern;

/**
* YouTube's streaming URLs of HTML5 clients are protected with a cipher, which modifies their
* {@code n} query parameter.
*
* <p>
* YouTube's media is protected with a cipher,
* which modifies the "n" query parameter of it's video playback urls.
* This class handles extracting that "n" query parameter,
* applying the cipher on it and returning the resulting url which is not throttled.
* This class handles extracting that {@code n} query parameter, applying the cipher on it and
* returning the resulting URL which is not throttled.
* </p>
*
* <pre>
* https://r5---sn-4g5ednsz.googlevideo.com/videoplayback?n=VVF2xyZLVRZZxHXZ&amp;other=other
* </pre>
* <p>
* For instance,
* {@code https://r5---sn-4g5ednsz.googlevideo.com/videoplayback?n=VVF2xyZLVRZZxHXZ&other=other}
* becomes
* <pre>
* https://r5---sn-4g5ednsz.googlevideo.com/videoplayback?n=iHywZkMipkszqA&amp;other=other
* </pre>
* <br>
* {@code https://r5---sn-4g5ednsz.googlevideo.com/videoplayback?n=iHywZkMipkszqA&other=other}.
* </p>
*
* <p>
* Decoding the "n" parameter is time intensive. For this reason, the results are cached.
* The cache can be cleared using {@link #clearCache()}
* Decoding the {@code n} parameter is time intensive. For this reason, the results are cached.
* The cache can be cleared using {@link #clearCache()}.
* </p>
*
*/
Expand Down Expand Up @@ -73,13 +73,35 @@ public YoutubeThrottlingDecrypter() throws ParsingException {
}

/**
* Try to decrypt a YouTube streaming URL protected with a throttling parameter.
*
* <p>
* The videoId is only used to fetch the decryption function.
* It can be a constant value of any existing video.
* A constant value is discouraged, because it could allow tracking.
* If the streaming URL provided doesn't contain a throttling parameter, it is returned as it
* is; otherwise, the encrypted value is decrypted and this value is replaced by the decrypted
* one.
* </p>
*
* <p>
* If the JavaScript code has been not extracted, it is extracted with the given video ID using
* {@link YoutubeJavaScriptExtractor#extractJavaScriptCode(String)}.
* </p>
*
* @param streamingUrl The streaming URL to decrypt, if needed.
* @param videoId A video ID, used to fetch the JavaScript code to get the decryption
* function. It can be a constant value of any existing video, but a
* constant value is discouraged, because it could allow tracking.
* @return A streaming URL with the decrypted parameter or the streaming URL itself if no
* throttling parameter has been found
* @throws ParsingException If the streaming URL contains a throttling parameter and its
* decryption failed
*/
public static String apply(final String url, final String videoId) throws ParsingException {
if (containsNParam(url)) {
public static String apply(@Nonnull final String streamingUrl,
@Nonnull final String videoId) throws ParsingException {
if (!containsNParam(streamingUrl)) {
return streamingUrl;
}

try {
if (FUNCTION == null) {
final String playerJsCode
= YoutubeJavaScriptExtractor.extractJavaScriptCode(videoId);
Expand All @@ -88,11 +110,11 @@ public static String apply(final String url, final String videoId) throws Parsin
FUNCTION = parseDecodeFunction(playerJsCode, FUNCTION_NAME);
}

final String oldNParam = parseNParam(url);
final String oldNParam = parseNParam(streamingUrl);
final String newNParam = decryptNParam(FUNCTION, FUNCTION_NAME, oldNParam);
return replaceNParam(url, oldNParam, newNParam);
} else {
return url;
return replaceNParam(streamingUrl, oldNParam, newNParam);
} catch (final Exception e) {
throw new ParsingException("Could not parse, decrypt or replace n parameter", e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -602,15 +602,21 @@ public List<VideoStream> getVideoOnlyStreams() throws ExtractionException {
}

/**
* Try to decrypt url and fallback to given url, because decryption is not
* always needed.
* Try to decrypt a streaming URL and fallback to the given URL, because decryption may fail if
* YouTube do breaking changes.
*
* <p>
* This way a breaking change from YouTube does not result in a broken extractor.
* </p>
*
* @param streamingUrl the streaming URL to decrypt with {@link YoutubeThrottlingDecrypter}
* @param videoId the video ID to use when extracting JavaScript player code, if needed
*/
private String tryDecryptUrl(final String url, final String videoId) {
private String tryDecryptUrl(final String streamingUrl, final String videoId) {
try {
return YoutubeThrottlingDecrypter.apply(url, videoId);
return YoutubeThrottlingDecrypter.apply(streamingUrl, videoId);
} catch (final ParsingException e) {
return url;
return streamingUrl;
}
}

Expand Down

0 comments on commit 5b54834

Please sign in to comment.