[YouTube] Catch any exception in YoutubeThrottlingDecrypter.apply and 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.
This commit is contained in:
AudricV 2022-08-12 16:17:13 +02:00
parent 52ded6e3d7
commit 5b548340e8
No known key found for this signature in database
GPG Key ID: DA92EC7905614198
2 changed files with 56 additions and 28 deletions

View File

@ -12,24 +12,24 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; 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> * <p>
* YouTube's media is protected with a cipher, * This class handles extracting that {@code n} query parameter, applying the cipher on it and
* which modifies the "n" query parameter of it's video playback urls. * returning the resulting URL which is not throttled.
* This class handles extracting that "n" query parameter,
* applying the cipher on it and returning the resulting url which is not throttled.
* </p> * </p>
* *
* <pre>
* https://r5---sn-4g5ednsz.googlevideo.com/videoplayback?n=VVF2xyZLVRZZxHXZ&amp;other=other
* </pre>
* becomes
* <pre>
* https://r5---sn-4g5ednsz.googlevideo.com/videoplayback?n=iHywZkMipkszqA&amp;other=other
* </pre>
* <br>
* <p> * <p>
* Decoding the "n" parameter is time intensive. For this reason, the results are cached. * For instance,
* The cache can be cleared using {@link #clearCache()} * {@code https://r5---sn-4g5ednsz.googlevideo.com/videoplayback?n=VVF2xyZLVRZZxHXZ&other=other}
* becomes
* {@code https://r5---sn-4g5ednsz.googlevideo.com/videoplayback?n=iHywZkMipkszqA&other=other}.
* </p>
*
* <p>
* Decoding the {@code n} parameter is time intensive. For this reason, the results are cached.
* The cache can be cleared using {@link #clearCache()}.
* </p> * </p>
* *
*/ */
@ -73,13 +73,35 @@ public class YoutubeThrottlingDecrypter {
} }
/** /**
* Try to decrypt a YouTube streaming URL protected with a throttling parameter.
*
* <p> * <p>
* The videoId is only used to fetch the decryption function. * If the streaming URL provided doesn't contain a throttling parameter, it is returned as it
* It can be a constant value of any existing video. * is; otherwise, the encrypted value is decrypted and this value is replaced by the decrypted
* A constant value is discouraged, because it could allow tracking. * 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 { public static String apply(@Nonnull final String streamingUrl,
if (containsNParam(url)) { @Nonnull final String videoId) throws ParsingException {
if (!containsNParam(streamingUrl)) {
return streamingUrl;
}
try {
if (FUNCTION == null) { if (FUNCTION == null) {
final String playerJsCode final String playerJsCode
= YoutubeJavaScriptExtractor.extractJavaScriptCode(videoId); = YoutubeJavaScriptExtractor.extractJavaScriptCode(videoId);
@ -88,11 +110,11 @@ public class YoutubeThrottlingDecrypter {
FUNCTION = parseDecodeFunction(playerJsCode, FUNCTION_NAME); FUNCTION = parseDecodeFunction(playerJsCode, FUNCTION_NAME);
} }
final String oldNParam = parseNParam(url); final String oldNParam = parseNParam(streamingUrl);
final String newNParam = decryptNParam(FUNCTION, FUNCTION_NAME, oldNParam); final String newNParam = decryptNParam(FUNCTION, FUNCTION_NAME, oldNParam);
return replaceNParam(url, oldNParam, newNParam); return replaceNParam(streamingUrl, oldNParam, newNParam);
} else { } catch (final Exception e) {
return url; throw new ParsingException("Could not parse, decrypt or replace n parameter", e);
} }
} }

View File

@ -602,15 +602,21 @@ public class YoutubeStreamExtractor extends StreamExtractor {
} }
/** /**
* Try to decrypt url and fallback to given url, because decryption is not * Try to decrypt a streaming URL and fallback to the given URL, because decryption may fail if
* always needed. * YouTube do breaking changes.
*
* <p>
* This way a breaking change from YouTube does not result in a broken extractor. * 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 { try {
return YoutubeThrottlingDecrypter.apply(url, videoId); return YoutubeThrottlingDecrypter.apply(streamingUrl, videoId);
} catch (final ParsingException e) { } catch (final ParsingException e) {
return url; return streamingUrl;
} }
} }