|
9 | 9 | import org.mozilla.javascript.Function; |
10 | 10 | import org.mozilla.javascript.ScriptableObject; |
11 | 11 | import org.schabi.newpipe.extractor.AbstractStreamInfo; |
12 | | -import org.schabi.newpipe.extractor.exceptions.ExtractionException; |
13 | | -import org.schabi.newpipe.extractor.exceptions.ParsingException; |
14 | | -import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; |
15 | | -import org.schabi.newpipe.extractor.stream_info.AudioStream; |
16 | 12 | import org.schabi.newpipe.extractor.Downloader; |
| 13 | +import org.schabi.newpipe.extractor.MediaFormat; |
17 | 14 | import org.schabi.newpipe.extractor.NewPipe; |
18 | 15 | import org.schabi.newpipe.extractor.Parser; |
19 | 16 | import org.schabi.newpipe.extractor.UrlIdHandler; |
20 | | -import org.schabi.newpipe.extractor.MediaFormat; |
| 17 | +import org.schabi.newpipe.extractor.exceptions.ExtractionException; |
| 18 | +import org.schabi.newpipe.extractor.exceptions.ParsingException; |
| 19 | +import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; |
| 20 | +import org.schabi.newpipe.extractor.stream_info.AudioStream; |
21 | 21 | import org.schabi.newpipe.extractor.stream_info.StreamExtractor; |
22 | 22 | import org.schabi.newpipe.extractor.stream_info.StreamInfo; |
23 | 23 | import org.schabi.newpipe.extractor.stream_info.StreamInfoItemCollector; |
@@ -105,49 +105,84 @@ public ItagItem(int id, ItagType type, MediaFormat format, String res, int fps) |
105 | 105 | this.resolutionString = res; |
106 | 106 | this.fps = fps; |
107 | 107 | } |
| 108 | + |
108 | 109 | public ItagItem(int id, ItagType type, MediaFormat format, int samplingRate, int bandWidth) { |
| 110 | + this(id, type, format, 0, samplingRate, bandWidth); |
| 111 | + } |
| 112 | + |
| 113 | + public ItagItem(int id, ItagType type, MediaFormat format, int avgBitrate, int samplingRate, int bandWidth) { |
109 | 114 | this.id = id; |
110 | 115 | this.itagType = type; |
111 | 116 | this.mediaFormatId = format.id; |
| 117 | + this.avgBitrate = avgBitrate; |
112 | 118 | this.samplingRate = samplingRate; |
113 | 119 | this.bandWidth = bandWidth; |
114 | 120 | } |
| 121 | + |
115 | 122 | public int id; |
116 | 123 | public ItagType itagType; |
117 | 124 | public int mediaFormatId; |
118 | 125 | public String resolutionString; |
119 | 126 | public int fps = -1; |
| 127 | + public int avgBitrate = -1; |
120 | 128 | public int samplingRate = -1; |
121 | 129 | public int bandWidth = -1; |
122 | 130 | } |
123 | 131 |
|
124 | 132 | private static final ItagItem[] itagList = { |
125 | | - // video streams |
126 | | - // id, ItagType, MediaFormat, Resolution, fps |
127 | | - new ItagItem(17, ItagType.VIDEO, MediaFormat.v3GPP, "144p", 12), |
128 | | - new ItagItem(18, ItagType.VIDEO, MediaFormat.MPEG_4, "360p", 24), |
129 | | - new ItagItem(22, ItagType.VIDEO, MediaFormat.MPEG_4, "720p", 24), |
130 | | - new ItagItem(36, ItagType.VIDEO, MediaFormat.v3GPP, "240p", 24), |
131 | | - new ItagItem(37, ItagType.VIDEO, MediaFormat.MPEG_4, "1080p", 24), |
132 | | - new ItagItem(38, ItagType.VIDEO, MediaFormat.MPEG_4, "1080p", 24), |
133 | | - new ItagItem(43, ItagType.VIDEO, MediaFormat.WEBM, "360p", 24), |
134 | | - new ItagItem(44, ItagType.VIDEO, MediaFormat.WEBM, "480p", 24), |
135 | | - new ItagItem(45, ItagType.VIDEO, MediaFormat.WEBM, "720p", 24), |
136 | | - new ItagItem(46, ItagType.VIDEO, MediaFormat.WEBM, "1080p", 24), |
137 | | - // audio streams |
138 | | - // id, ItagType, MediaFormat, samplingR, bandwidth |
139 | | - new ItagItem(249, ItagType.AUDIO, MediaFormat.WEBMA, 0, 0), // bandwith/samplingR 0 because not known |
140 | | - new ItagItem(250, ItagType.AUDIO, MediaFormat.WEBMA, 0, 0), |
141 | | - new ItagItem(171, ItagType.AUDIO, MediaFormat.WEBMA, 0, 0), |
142 | | - new ItagItem(140, ItagType.AUDIO, MediaFormat.M4A, 0, 0), |
143 | | - new ItagItem(251, ItagType.AUDIO, MediaFormat.WEBMA, 0, 0), |
144 | | - // video only streams |
145 | | - new ItagItem(160, ItagType.VIDEO_ONLY, MediaFormat.MPEG_4, "144p", 24), |
146 | | - new ItagItem(133, ItagType.VIDEO_ONLY, MediaFormat.MPEG_4, "240p", 24), |
147 | | - new ItagItem(134, ItagType.VIDEO_ONLY, MediaFormat.MPEG_4, "360p", 24), |
148 | | - new ItagItem(135, ItagType.VIDEO_ONLY, MediaFormat.MPEG_4, "480p", 24), |
149 | | - new ItagItem(136, ItagType.VIDEO_ONLY, MediaFormat.MPEG_4, "720p", 24), |
150 | | - new ItagItem(137, ItagType.VIDEO_ONLY, MediaFormat.MPEG_4, "1080p", 24), |
| 133 | + ////////////////////////////////////////////////////////////////////////// |
| 134 | + // VIDEO ID ItagType Format Resolution FPS /// |
| 135 | + //////////////////////////////////////////////////////////////////////// |
| 136 | + new ItagItem(17, ItagType.VIDEO, MediaFormat.v3GPP, "144p" , 12), |
| 137 | + new ItagItem(18, ItagType.VIDEO, MediaFormat.MPEG_4, "360p" , 24), |
| 138 | + new ItagItem(22, ItagType.VIDEO, MediaFormat.MPEG_4, "720p" , 24), |
| 139 | + new ItagItem(36, ItagType.VIDEO, MediaFormat.v3GPP, "240p" , 24), |
| 140 | + new ItagItem(37, ItagType.VIDEO, MediaFormat.MPEG_4, "1080p" , 24), |
| 141 | + new ItagItem(38, ItagType.VIDEO, MediaFormat.MPEG_4, "1080p" , 24), |
| 142 | + new ItagItem(43, ItagType.VIDEO, MediaFormat.WEBM, "360p" , 24), |
| 143 | + new ItagItem(44, ItagType.VIDEO, MediaFormat.WEBM, "480p" , 24), |
| 144 | + new ItagItem(45, ItagType.VIDEO, MediaFormat.WEBM, "720p" , 24), |
| 145 | + new ItagItem(46, ItagType.VIDEO, MediaFormat.WEBM, "1080p" , 24), |
| 146 | + |
| 147 | + ////////////////////////////////////////////////////////////////////////////////////////// |
| 148 | + // AUDIO ID ItagType Format Bitrate SamplingR Bandwidth /// |
| 149 | + //////////////////////////////////////////////////////////////////////////////////////// |
| 150 | + new ItagItem(249, ItagType.AUDIO, MediaFormat.WEBMA, 50, 0, 0), |
| 151 | + new ItagItem(250, ItagType.AUDIO, MediaFormat.WEBMA, 70, 0, 0), |
| 152 | + new ItagItem(251, ItagType.AUDIO, MediaFormat.WEBMA, 160, 0, 0), |
| 153 | + new ItagItem(171, ItagType.AUDIO, MediaFormat.WEBMA, 128, 0, 0), |
| 154 | + new ItagItem(172, ItagType.AUDIO, MediaFormat.WEBMA, 256, 0, 0), |
| 155 | + new ItagItem(140, ItagType.AUDIO, MediaFormat.M4A, 128, 0, 0), |
| 156 | + new ItagItem(141, ItagType.AUDIO, MediaFormat.M4A, 256, 0, 0), |
| 157 | + |
| 158 | + /// VIDEO ONLY /////////////////////////////////////////////////////////////////// |
| 159 | + // ID ItagType Format Resolution FPS /// |
| 160 | + //////////////////////////////////////////////////////////////////////////////// |
| 161 | + // Don't add VideoOnly streams that have normal variants |
| 162 | +// new ItagItem(160, ItagType.VIDEO_ONLY, MediaFormat.MPEG_4, "144p" , 24), |
| 163 | +// new ItagItem(133, ItagType.VIDEO_ONLY, MediaFormat.MPEG_4, "240p" , 24), |
| 164 | +// new ItagItem(134, ItagType.VIDEO_ONLY, MediaFormat.MPEG_4, "360p" , 24), |
| 165 | + new ItagItem(135, ItagType.VIDEO_ONLY, MediaFormat.MPEG_4, "480p" , 30), |
| 166 | +// new ItagItem(136, ItagType.VIDEO_ONLY, MediaFormat.MPEG_4, "720p" , 30), |
| 167 | + new ItagItem(298, ItagType.VIDEO_ONLY, MediaFormat.MPEG_4, "720p60" , 60), |
| 168 | + new ItagItem(137, ItagType.VIDEO_ONLY, MediaFormat.MPEG_4, "1080p" , 30), |
| 169 | + new ItagItem(299, ItagType.VIDEO_ONLY, MediaFormat.MPEG_4, "1080p60" , 60), |
| 170 | + new ItagItem(266, ItagType.VIDEO_ONLY, MediaFormat.MPEG_4, "2160p" , 30), |
| 171 | + |
| 172 | +// new ItagItem(243, ItagType.VIDEO_ONLY, MediaFormat.WEBM, "360p" , 30), |
| 173 | + new ItagItem(244, ItagType.VIDEO_ONLY, MediaFormat.WEBM, "480p" , 30), |
| 174 | + new ItagItem(245, ItagType.VIDEO_ONLY, MediaFormat.WEBM, "480p" , 30), |
| 175 | + new ItagItem(246, ItagType.VIDEO_ONLY, MediaFormat.WEBM, "480p" , 30), |
| 176 | + new ItagItem(247, ItagType.VIDEO_ONLY, MediaFormat.WEBM, "720p" , 30), |
| 177 | + new ItagItem(248, ItagType.VIDEO_ONLY, MediaFormat.WEBM, "1080p" , 30), |
| 178 | + new ItagItem(271, ItagType.VIDEO_ONLY, MediaFormat.WEBM, "1440p" , 30), |
| 179 | + // #272 is either 3840x2160 (e.g. RtoitU2A-3E) or 7680x4320 (sLprVF6d7Ug) |
| 180 | + new ItagItem(272, ItagType.VIDEO_ONLY, MediaFormat.WEBM, "2160p" , 30), |
| 181 | + new ItagItem(302, ItagType.VIDEO_ONLY, MediaFormat.WEBM, "720p60" , 60), |
| 182 | + new ItagItem(303, ItagType.VIDEO_ONLY, MediaFormat.WEBM, "1080p60" , 60), |
| 183 | + new ItagItem(308, ItagType.VIDEO_ONLY, MediaFormat.WEBM, "1440p60" , 60), |
| 184 | + new ItagItem(313, ItagType.VIDEO_ONLY, MediaFormat.WEBM, "2160p" , 30), |
| 185 | + new ItagItem(315, ItagType.VIDEO_ONLY, MediaFormat.WEBM, "2160p60" , 60) |
151 | 186 | }; |
152 | 187 |
|
153 | 188 | /**These lists only contain itag formats that are supported by the common Android Video player. |
@@ -483,6 +518,7 @@ public List<AudioStream> getAudioStreams() throws ParsingException { |
483 | 518 |
|
484 | 519 | audioStreams.add(new AudioStream(streamUrl, |
485 | 520 | itagItem.mediaFormatId, |
| 521 | + itagItem.avgBitrate, |
486 | 522 | itagItem.bandWidth, |
487 | 523 | itagItem.samplingRate)); |
488 | 524 | } |
@@ -549,7 +585,58 @@ public List<VideoStream> getVideoStreams() throws ParsingException { |
549 | 585 |
|
550 | 586 | @Override |
551 | 587 | public List<VideoStream> getVideoOnlyStreams() throws ParsingException { |
552 | | - return null; |
| 588 | + Vector<VideoStream> videoOnlyStreams = new Vector<>(); |
| 589 | + |
| 590 | + try { |
| 591 | + String encodedUrlMap; |
| 592 | + // playerArgs could be null if the video is age restricted |
| 593 | + if (playerArgs == null) { |
| 594 | + if (videoInfoPage.containsKey("adaptive_fmts")) { |
| 595 | + encodedUrlMap = videoInfoPage.get("adaptive_fmts"); |
| 596 | + } else { |
| 597 | + return null; |
| 598 | + } |
| 599 | + } else { |
| 600 | + if (playerArgs.has("adaptive_fmts")) { |
| 601 | + encodedUrlMap = playerArgs.getString("adaptive_fmts"); |
| 602 | + } else { |
| 603 | + return null; |
| 604 | + } |
| 605 | + } |
| 606 | + for (String url_data_str : encodedUrlMap.split(",")) { |
| 607 | + // This loop iterates through multiple streams, therefor tags |
| 608 | + // is related to one and the same stream at a time. |
| 609 | + Map<String, String> tags = Parser.compatParseMap( |
| 610 | + org.jsoup.parser.Parser.unescapeEntities(url_data_str, true)); |
| 611 | + |
| 612 | + int itag = Integer.parseInt(tags.get("itag")); |
| 613 | + |
| 614 | + if (itagIsSupported(itag)) { |
| 615 | + ItagItem itagItem = getItagItem(itag); |
| 616 | + if (itagItem.itagType == ItagType.VIDEO_ONLY) { |
| 617 | + String streamUrl = tags.get("url"); |
| 618 | + // if video has a signature: decrypt it and add it to the url |
| 619 | + if (tags.get("s") != null) { |
| 620 | + streamUrl = streamUrl + "&signature=" |
| 621 | + + decryptSignature(tags.get("s"), decryptionCode); |
| 622 | + } |
| 623 | + |
| 624 | + videoOnlyStreams.add(new VideoStream( |
| 625 | + true, //isVideoOnly |
| 626 | + streamUrl, |
| 627 | + itagItem.mediaFormatId, |
| 628 | + itagItem.resolutionString)); |
| 629 | + } |
| 630 | + } |
| 631 | + } |
| 632 | + } catch (Exception e) { |
| 633 | + throw new ParsingException("Failed to get video only streams", e); |
| 634 | + } |
| 635 | + |
| 636 | + if (videoOnlyStreams.isEmpty()) { |
| 637 | + throw new ParsingException("Failed to get any video only stream"); |
| 638 | + } |
| 639 | + return videoOnlyStreams; |
553 | 640 | } |
554 | 641 |
|
555 | 642 | /**Attempts to parse (and return) the offset to start playing the video from. |
|
0 commit comments