@@ -85,6 +85,7 @@ public class SubtitlesException extends ContentNotAvailableException {
8585 private JsonObject playerArgs ;
8686 @ Nonnull
8787 private final Map <String , String > videoInfoPage = new HashMap <>();
88+ private JsonObject playerResponse ;
8889
8990 @ Nonnull
9091 private List <SubtitlesInfo > subtitlesInfos = new ArrayList <>();
@@ -253,20 +254,6 @@ public int getAgeLimit() throws ParsingException {
253254 public long getLength () throws ParsingException {
254255 assertPageFetched ();
255256
256- final JsonObject playerResponse ;
257- try {
258- final String pr ;
259- if (playerArgs != null ) {
260- pr = playerArgs .getString ("player_response" );
261- } else {
262- pr = videoInfoPage .get ("player_response" );
263- }
264- playerResponse = JsonParser .object ()
265- .from (pr );
266- } catch (Exception e ) {
267- throw new ParsingException ("Could not get playerResponse" , e );
268- }
269-
270257 // try getting duration from playerargs
271258 try {
272259 String durationMs = playerResponse
@@ -442,31 +429,24 @@ public String getDashMpdUrl() throws ParsingException {
442429 @ Override
443430 public String getHlsUrl () throws ParsingException {
444431 assertPageFetched ();
445- try {
446- String hlsvp = "" ;
447- if (playerArgs != null ) {
448- if ( playerArgs .isString ("hlsvp" ) ) {
449- hlsvp = playerArgs .getString ("hlsvp" , "" );
450- }else {
451- hlsvp = JsonParser .object ()
452- .from (playerArgs .getString ("player_response" , "{}" ))
453- .getObject ("streamingData" , new JsonObject ())
454- .getString ("hlsManifestUrl" , "" );
455- }
456- }
457432
458- return hlsvp ;
433+ try {
434+ return playerResponse .getObject ("streamingData" ).getString ("hlsManifestUrl" );
459435 } catch (Exception e ) {
460- throw new ParsingException ("Could not get hls manifest url" , e );
436+ if (playerArgs != null && playerArgs .isString ("hlsvp" )) {
437+ return playerArgs .getString ("hlsvp" );
438+ } else {
439+ throw new ParsingException ("Could not get hls manifest url" , e );
440+ }
461441 }
462442 }
463443
464444 @ Override
465- public List <AudioStream > getAudioStreams () throws IOException , ExtractionException {
445+ public List <AudioStream > getAudioStreams () throws ExtractionException {
466446 assertPageFetched ();
467447 List <AudioStream > audioStreams = new ArrayList <>();
468448 try {
469- for (Map .Entry <String , ItagItem > entry : getItags (ADAPTIVE_FMTS , ItagItem .ItagType .AUDIO ).entrySet ()) {
449+ for (Map .Entry <String , ItagItem > entry : getItags (ADAPTIVE_FORMATS , ItagItem .ItagType .AUDIO ).entrySet ()) {
470450 ItagItem itag = entry .getValue ();
471451
472452 AudioStream audioStream = new AudioStream (entry .getKey (), itag .getMediaFormat (), itag .avgBitrate );
@@ -482,11 +462,11 @@ public List<AudioStream> getAudioStreams() throws IOException, ExtractionExcepti
482462 }
483463
484464 @ Override
485- public List <VideoStream > getVideoStreams () throws IOException , ExtractionException {
465+ public List <VideoStream > getVideoStreams () throws ExtractionException {
486466 assertPageFetched ();
487467 List <VideoStream > videoStreams = new ArrayList <>();
488468 try {
489- for (Map .Entry <String , ItagItem > entry : getItags (URL_ENCODED_FMT_STREAM_MAP , ItagItem .ItagType .VIDEO ).entrySet ()) {
469+ for (Map .Entry <String , ItagItem > entry : getItags (FORMATS , ItagItem .ItagType .VIDEO ).entrySet ()) {
490470 ItagItem itag = entry .getValue ();
491471
492472 VideoStream videoStream = new VideoStream (entry .getKey (), itag .getMediaFormat (), itag .resolutionString );
@@ -506,7 +486,7 @@ public List<VideoStream> getVideoOnlyStreams() throws ExtractionException {
506486 assertPageFetched ();
507487 List <VideoStream > videoOnlyStreams = new ArrayList <>();
508488 try {
509- for (Map .Entry <String , ItagItem > entry : getItags (ADAPTIVE_FMTS , ItagItem .ItagType .VIDEO_ONLY ).entrySet ()) {
489+ for (Map .Entry <String , ItagItem > entry : getItags (ADAPTIVE_FORMATS , ItagItem .ItagType .VIDEO_ONLY ).entrySet ()) {
510490 ItagItem itag = entry .getValue ();
511491
512492 VideoStream videoStream = new VideoStream (entry .getKey (), itag .getMediaFormat (), itag .resolutionString , true );
@@ -543,7 +523,7 @@ public StreamType getStreamType() throws ParsingException {
543523 assertPageFetched ();
544524 try {
545525 if (playerArgs != null && (playerArgs .has ("ps" ) && playerArgs .get ("ps" ).toString ().equals ("live" ) ||
546- playerArgs . get ( URL_ENCODED_FMT_STREAM_MAP ). toString (). isEmpty ( ))) {
526+ (! playerResponse . getObject ( "streamingData" ). has ( FORMATS ) ))) {
547527 return StreamType .LIVE_STREAM ;
548528 }
549529 } catch (Exception e ) {
@@ -595,21 +575,26 @@ public StreamInfoItemsCollector getRelatedStreams() throws IOException, Extracti
595575 */
596576 @ Override
597577 public String getErrorMessage () {
598- String errorMessage = doc .select ("h1[id=\" unavailable-message\" ]" ).first ().text ();
599578 StringBuilder errorReason ;
579+ Element errorElement = doc .select ("h1[id=\" unavailable-message\" ]" ).first ();
600580
601- if (errorMessage == null || errorMessage . isEmpty () ) {
581+ if (errorElement == null ) {
602582 errorReason = null ;
603- } else if (errorMessage .contains ("GEMA" )) {
604- // Gema sometimes blocks youtube music content in germany:
605- // https://www.gema.de/en/
606- // Detailed description:
607- // https://en.wikipedia.org/wiki/GEMA_%28German_organization%29
608- errorReason = new StringBuilder ("GEMA" );
609583 } else {
610- errorReason = new StringBuilder (errorMessage );
611- errorReason .append (" " );
612- errorReason .append (doc .select ("[id=\" unavailable-submessage\" ]" ).first ().text ());
584+ String errorMessage = errorElement .text ();
585+ if (errorMessage == null || errorMessage .isEmpty ()) {
586+ errorReason = null ;
587+ } else if (errorMessage .contains ("GEMA" )) {
588+ // Gema sometimes blocks youtube music content in germany:
589+ // https://www.gema.de/en/
590+ // Detailed description:
591+ // https://en.wikipedia.org/wiki/GEMA_%28German_organization%29
592+ errorReason = new StringBuilder ("GEMA" );
593+ } else {
594+ errorReason = new StringBuilder (errorMessage );
595+ errorReason .append (" " );
596+ errorReason .append (doc .select ("[id=\" unavailable-submessage\" ]" ).first ().text ());
597+ }
613598 }
614599
615600 return errorReason != null ? errorReason .toString () : null ;
@@ -619,8 +604,8 @@ public String getErrorMessage() {
619604 // Fetch page
620605 //////////////////////////////////////////////////////////////////////////*/
621606
622- private static final String URL_ENCODED_FMT_STREAM_MAP = "url_encoded_fmt_stream_map " ;
623- private static final String ADAPTIVE_FMTS = "adaptive_fmts " ;
607+ private static final String FORMATS = "formats " ;
608+ private static final String ADAPTIVE_FORMATS = "adaptiveFormats " ;
624609 private static final String HTTPS = "https:" ;
625610 private static final String CONTENT = "content" ;
626611 private static final String DECRYPTION_FUNC_NAME = "decrypt" ;
@@ -667,6 +652,7 @@ public void onFetchPage(@Nonnull Downloader downloader) throws IOException, Extr
667652 playerUrl = getPlayerUrl (ytPlayerConfig );
668653 isAgeRestricted = false ;
669654 }
655+ playerResponse = getPlayerResponse ();
670656
671657 if (decryptionCode .isEmpty ()) {
672658 decryptionCode = loadDecryptionCode (playerUrl );
@@ -728,6 +714,20 @@ private String getPlayerUrl(JsonObject playerConfig) throws ParsingException {
728714 }
729715 }
730716
717+ private JsonObject getPlayerResponse () throws ParsingException {
718+ try {
719+ String playerResponseStr ;
720+ if (playerArgs != null ) {
721+ playerResponseStr = playerArgs .getString ("player_response" );
722+ } else {
723+ playerResponseStr = videoInfoPage .get ("player_response" );
724+ }
725+ return JsonParser .object ().from (playerResponseStr );
726+ } catch (Exception e ) {
727+ throw new ParsingException ("Could not parse yt player response" , e );
728+ }
729+ }
730+
731731 @ Nonnull
732732 private EmbeddedInfo getEmbeddedInfo () throws ParsingException , ReCaptchaException {
733733 try {
@@ -843,19 +843,13 @@ private List<SubtitlesInfo> getAvailableSubtitlesInfo() throws SubtitlesExceptio
843843 } catch (IOException | ExtractionException e ) {
844844 throw new SubtitlesException ("Unable to download player configs" , e );
845845 }
846- final String playerResponse = playerConfig .getObject ("args" , new JsonObject ())
847- .getString ("player_response" );
848846
849847 final JsonObject captions ;
850- try {
851- if (playerResponse == null || !JsonParser .object ().from (playerResponse ).has ("captions" )) {
852- // Captions does not exist
853- return Collections .emptyList ();
854- }
855- captions = JsonParser .object ().from (playerResponse ).getObject ("captions" );
856- } catch (JsonParserException e ) {
857- throw new SubtitlesException ("Unable to parse subtitles listing" , e );
848+ if (!playerResponse .has ("captions" )) {
849+ // Captions does not exist
850+ return Collections .emptyList ();
858851 }
852+ captions = playerResponse .getObject ("captions" );
859853
860854 final JsonObject renderer = captions .getObject ("playerCaptionsTracklistRenderer" , new JsonObject ());
861855 final JsonArray captionsArray = renderer .getArray ("captionTracks" , new JsonArray ());
@@ -924,45 +918,36 @@ private static String getVideoInfoUrl(final String id, final String sts) {
924918 "&sts=" + sts + "&ps=default&gl=US&hl=en" ;
925919 }
926920
927- private Map <String , ItagItem > getItags (String encodedUrlMapKey , ItagItem .ItagType itagTypeWanted ) throws ParsingException {
921+ private Map <String , ItagItem > getItags (String streamingDataKey , ItagItem .ItagType itagTypeWanted ) throws ParsingException {
928922 Map <String , ItagItem > urlAndItags = new LinkedHashMap <>();
929-
930- String encodedUrlMap = "" ;
931- if (playerArgs != null && playerArgs .isString (encodedUrlMapKey )) {
932- encodedUrlMap = playerArgs .getString (encodedUrlMapKey , "" );
933- } else if (videoInfoPage .containsKey (encodedUrlMapKey )) {
934- encodedUrlMap = videoInfoPage .get (encodedUrlMapKey );
923+ JsonObject streamingData = playerResponse .getObject ("streamingData" );
924+ if (!streamingData .has (streamingDataKey )) {
925+ return urlAndItags ;
935926 }
936927
937- for (String url_data_str : encodedUrlMap .split ("," )) {
938- try {
939- // This loop iterates through multiple streams, therefore tags
940- // is related to one and the same stream at a time.
941- Map <String , String > tags = Parser .compatParseMap (
942- org .jsoup .parser .Parser .unescapeEntities (url_data_str , true ));
943-
944- int itag = Integer .parseInt (tags .get ("itag" ));
928+ JsonArray formats = streamingData .getArray (streamingDataKey );
929+ for (int i = 0 ; i != formats .size (); ++i ) {
930+ JsonObject formatData = formats .getObject (i );
931+ int itag = formatData .getInt ("itag" );
945932
946- if (ItagItem .isSupported (itag )) {
933+ if (ItagItem .isSupported (itag )) {
934+ try {
947935 ItagItem itagItem = ItagItem .getItag (itag );
948936 if (itagItem .itagType == itagTypeWanted ) {
949- String streamUrl = tags .get ("url" );
950- // if video has a signature: decrypt it and add it to the url
951- if (tags .get ("s" ) != null ) {
952- if (tags .get ("sp" ) == null ) {
953- // fallback for urls not conaining the "sp" tag
954- streamUrl = streamUrl + "&signature=" + decryptSignature (tags .get ("s" ), decryptionCode );
955- }
956- else {
957- streamUrl = streamUrl + "&" + tags .get ("sp" ) + "=" + decryptSignature (tags .get ("s" ), decryptionCode );
958- }
937+ String streamUrl ;
938+ if (formatData .has ("url" )) {
939+ streamUrl = formatData .getString ("url" );
940+ } else {
941+ // this url has an encrypted signature
942+ Map <String , String > cipher = Parser .compatParseMap (formatData .getString ("cipher" ));
943+ streamUrl = cipher .get ("url" ) + "&" + cipher .get ("sp" ) + "=" + decryptSignature (cipher .get ("s" ), decryptionCode );
959944 }
945+
960946 urlAndItags .put (streamUrl , itagItem );
961947 }
948+ } catch (UnsupportedEncodingException ignored ) {
949+
962950 }
963- } catch (DecryptException e ) {
964- throw e ;
965- } catch (Exception ignored ) {
966951 }
967952 }
968953
0 commit comments