├── .idea
├── artifacts
│ └── VideoHelper_jar.xml
├── misc.xml
├── modules.xml
├── uiDesigner.xml
├── vcs.xml
└── workspace.xml
├── LICENSE
├── README.md
├── VideoHelper.iml
├── VideoHelper.jar
├── VideoHelperGUI.jar
├── lib
├── dom4j-2.1.1.jar
├── json-20180130.jar
└── jsoup-1.11.3.jar
├── out
├── artifacts
│ └── VideoHelper_jar
│ │ └── VideoHelper.jar
└── production
│ └── VideoHelper
│ ├── META-INF
│ └── MANIFEST.MF
│ ├── Main.class
│ ├── Site.class
│ ├── UrlRecognition.class
│ ├── bean
│ ├── BilibiliBean.class
│ └── VideoBean.class
│ ├── util
│ ├── DownloadUtil.class
│ ├── HttpUtil.class
│ ├── MD5Encoder.class
│ └── PrintUtil.class
│ └── website
│ ├── BaseSite.class
│ ├── Bilibili.class
│ ├── Diyishipin.class
│ └── Zhihu.class
├── screenshots
├── .pic
├── Screenshot from 2018-09-17 20-21-16.png
├── TIM截图20181027201653.png
└── TIM截图20181027212558.png
└── src
├── META-INF
└── MANIFEST.MF
├── Main.java
├── Site.java
├── UrlRecognition.java
├── bean
├── BilibiliBean.java
└── VideoBean.java
├── util
├── DownloadUtil.java
├── HttpUtil.java
├── MD5Encoder.java
└── PrintUtil.java
└── website
├── BaseSite.java
├── Bilibili.java
├── Diyishipin.java
└── Zhihu.java
/.idea/artifacts/VideoHelper_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | $PROJECT_DIR$/out/artifacts/VideoHelper_jar
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/uiDesigner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | -
6 |
7 |
8 | -
9 |
10 |
11 | -
12 |
13 |
14 | -
15 |
16 |
17 | -
18 |
19 |
20 |
21 |
22 |
23 | -
24 |
25 |
26 |
27 |
28 |
29 | -
30 |
31 |
32 |
33 |
34 |
35 | -
36 |
37 |
38 |
39 |
40 |
41 | -
42 |
43 |
44 |
45 |
46 | -
47 |
48 |
49 |
50 |
51 | -
52 |
53 |
54 |
55 |
56 | -
57 |
58 |
59 |
60 |
61 | -
62 |
63 |
64 |
65 |
66 | -
67 |
68 |
69 |
70 |
71 | -
72 |
73 |
74 | -
75 |
76 |
77 |
78 |
79 | -
80 |
81 |
82 |
83 |
84 | -
85 |
86 |
87 |
88 |
89 | -
90 |
91 |
92 |
93 |
94 | -
95 |
96 |
97 |
98 |
99 | -
100 |
101 |
102 | -
103 |
104 |
105 | -
106 |
107 |
108 | -
109 |
110 |
111 | -
112 |
113 |
114 |
115 |
116 | -
117 |
118 |
119 | -
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
147 |
148 |
149 |
150 | serialList
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 | true
168 | DEFINITION_ORDER
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 | 1537153983171
266 |
267 |
268 | 1537153983171
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Asche
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VideoHelper
2 |
3 | ### 主要功能:完成一些主流网站的视频下载任务
4 | ### 相应的功能在爬虫的首选语言python都有实现,这里的目的是只是对java的挚爱以及对爬虫的练习
5 | ### 目前支持:Bilibili(支持av和ep等类型)、知乎、第一视频网
6 |
7 | ## 使用方式一:代码中使用,导入[VideoHelper.jar](https://github.com/asche910/VideoHelper/raw/master/VideoHelper.jar)
8 | 然后:
9 | ```JAVA
10 | // 首先完成初始化;这里playUrl就是视频的播放地址, outputDir则是视频下载的保存目录
11 | // 其中 outputDir = System.getProperty("user.dir"); 可以保存至运行目录或自定义
12 | BaseSite base = UrlRecognition.reconize(playUrl, outputDir);
13 | // 然后再下载
14 | base.downloadByUrl(playUrl, outputDir);
15 |
16 | ```
17 |
18 | ## 使用方式二:cmd或bash,下载[VideoHelper.jar](https://github.com/asche910/VideoHelper/raw/master/VideoHelper.jar)
19 | 然后:
20 | cmd或bash里面
21 | ```
22 | // 后面outputDir可选
23 | java -jar VideoHelper.jar playUrl outputDir
24 |
25 | ```
26 | 如图:
27 |
28 | 
29 |
30 | ## 使用方式三:图形界面,运行[VideoHelperGUI.jar](https://github.com/asche910/VideoHelper/raw/master/VideoHelperGUI.jar)
31 | 运行方式同上(但要注意这均需要java运行环境jre)
32 | 如图:
33 |
34 | 
35 |
36 |
37 |
38 |
39 | 知乎教程介绍参照这里:
40 | https://www.cnblogs.com/asche/p/9664987.html
41 |
42 |
--------------------------------------------------------------------------------
/VideoHelper.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/VideoHelper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/VideoHelper.jar
--------------------------------------------------------------------------------
/VideoHelperGUI.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/VideoHelperGUI.jar
--------------------------------------------------------------------------------
/lib/dom4j-2.1.1.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/lib/dom4j-2.1.1.jar
--------------------------------------------------------------------------------
/lib/json-20180130.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/lib/json-20180130.jar
--------------------------------------------------------------------------------
/lib/jsoup-1.11.3.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/lib/jsoup-1.11.3.jar
--------------------------------------------------------------------------------
/out/artifacts/VideoHelper_jar/VideoHelper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/out/artifacts/VideoHelper_jar/VideoHelper.jar
--------------------------------------------------------------------------------
/out/production/VideoHelper/META-INF/MANIFEST.MF:
--------------------------------------------------------------------------------
1 | Manifest-Version: 1.0
2 | Main-Class: Main
3 |
4 |
--------------------------------------------------------------------------------
/out/production/VideoHelper/Main.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/out/production/VideoHelper/Main.class
--------------------------------------------------------------------------------
/out/production/VideoHelper/Site.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/out/production/VideoHelper/Site.class
--------------------------------------------------------------------------------
/out/production/VideoHelper/UrlRecognition.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/out/production/VideoHelper/UrlRecognition.class
--------------------------------------------------------------------------------
/out/production/VideoHelper/bean/BilibiliBean.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/out/production/VideoHelper/bean/BilibiliBean.class
--------------------------------------------------------------------------------
/out/production/VideoHelper/bean/VideoBean.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/out/production/VideoHelper/bean/VideoBean.class
--------------------------------------------------------------------------------
/out/production/VideoHelper/util/DownloadUtil.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/out/production/VideoHelper/util/DownloadUtil.class
--------------------------------------------------------------------------------
/out/production/VideoHelper/util/HttpUtil.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/out/production/VideoHelper/util/HttpUtil.class
--------------------------------------------------------------------------------
/out/production/VideoHelper/util/MD5Encoder.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/out/production/VideoHelper/util/MD5Encoder.class
--------------------------------------------------------------------------------
/out/production/VideoHelper/util/PrintUtil.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/out/production/VideoHelper/util/PrintUtil.class
--------------------------------------------------------------------------------
/out/production/VideoHelper/website/BaseSite.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/out/production/VideoHelper/website/BaseSite.class
--------------------------------------------------------------------------------
/out/production/VideoHelper/website/Bilibili.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/out/production/VideoHelper/website/Bilibili.class
--------------------------------------------------------------------------------
/out/production/VideoHelper/website/Diyishipin.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/out/production/VideoHelper/website/Diyishipin.class
--------------------------------------------------------------------------------
/out/production/VideoHelper/website/Zhihu.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/out/production/VideoHelper/website/Zhihu.class
--------------------------------------------------------------------------------
/screenshots/.pic:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/screenshots/Screenshot from 2018-09-17 20-21-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/screenshots/Screenshot from 2018-09-17 20-21-16.png
--------------------------------------------------------------------------------
/screenshots/TIM截图20181027201653.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/screenshots/TIM截图20181027201653.png
--------------------------------------------------------------------------------
/screenshots/TIM截图20181027212558.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asche910/VideoHelper/26aaaa6454a6faff97edb50cfb6308c1cb0d0704/screenshots/TIM截图20181027212558.png
--------------------------------------------------------------------------------
/src/META-INF/MANIFEST.MF:
--------------------------------------------------------------------------------
1 | Manifest-Version: 1.0
2 | Main-Class: Main
3 |
4 |
--------------------------------------------------------------------------------
/src/Main.java:
--------------------------------------------------------------------------------
1 | import website.BaseSite;
2 |
3 | import java.io.File;
4 |
5 | import static util.PrintUtil.println;
6 |
7 | public class Main {
8 | private static String playUrl = "https://www.bilibili.com/bangumi/play/ss25733/";
9 | private static String outputDir;
10 |
11 | public static void main(String[] args) {
12 |
13 | handleArgs(args);
14 |
15 | try {
16 | BaseSite baseSite = UrlRecognition.reconize(playUrl, outputDir);
17 |
18 | baseSite.downloadByUrl(playUrl, outputDir);
19 |
20 | } catch (Exception e) {
21 | e.printStackTrace();
22 | }
23 | }
24 |
25 | private static void handleArgs(String[] args){
26 |
27 | if (args.length == 1) {
28 | playUrl = args[0];
29 |
30 | outputDir = System.getProperty("user.dir");
31 | println("OutputDir: " + outputDir);
32 | }else if (args.length == 2){
33 | playUrl = args[0];
34 | outputDir = args[1];
35 |
36 | println("OutputDir: " + args[1]);
37 |
38 | File file = new File(outputDir);
39 | if (!file.isDirectory()){
40 | println("请输入有效的输出目录!");
41 | return;
42 | }
43 | }else {
44 | outputDir = System.getProperty("user.dir");
45 | println("OutputDir: " + outputDir);
46 | }
47 | }
48 |
49 | public static String getOutputDir(){
50 | if (outputDir == null || outputDir.equals("")){
51 | outputDir = System.getProperty("user.dir");
52 | }
53 | return outputDir;
54 | }
55 | }
56 |
57 |
--------------------------------------------------------------------------------
/src/Site.java:
--------------------------------------------------------------------------------
1 |
2 | public class Site {
3 |
4 | static String[] Bilibili = new String[]{"bilibili"};
5 |
6 | static String[] Zhihu = new String[]{"zhihu"};
7 |
8 | static String[] Diyishipin = new String[]{"v1.cn"};
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/UrlRecognition.java:
--------------------------------------------------------------------------------
1 | import website.BaseSite;
2 | import website.Bilibili;
3 | import website.Diyishipin;
4 | import website.Zhihu;
5 |
6 | public class UrlRecognition {
7 |
8 | private static String url;
9 |
10 | public static BaseSite reconize(String url, String outputDir){
11 |
12 | UrlRecognition.url = url;
13 |
14 | if (contains(Site.Bilibili))
15 | return new Bilibili(url, outputDir);
16 | if (contains(Site.Zhihu))
17 | return new Zhihu(url, outputDir);
18 | if (contains(Site.Diyishipin))
19 | return new Diyishipin(url, outputDir);
20 |
21 | return null;
22 | }
23 |
24 | private static boolean contains(String[] array){
25 | for(String keyword: array)
26 | if (url.contains(keyword))
27 | return true;
28 | return false;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/bean/BilibiliBean.java:
--------------------------------------------------------------------------------
1 | package bean;
2 |
3 |
4 | public class BilibiliBean {
5 | private int aid;
6 | private int cid;
7 | private int duration;
8 | private int epId;
9 | private String index;
10 | private String indexTitle;
11 |
12 | public BilibiliBean(int aid, int cid, int duration, int epid, String index, String indexTitle) {
13 | this.aid = aid;
14 | this.cid = cid;
15 | this.duration = duration;
16 | this.epId = epid;
17 | this.index = index;
18 | this.indexTitle = indexTitle;
19 | }
20 |
21 | public int getAid() {
22 | return aid;
23 | }
24 |
25 | public void setAid(int aid) {
26 | this.aid = aid;
27 | }
28 |
29 | public int getCid() {
30 | return cid;
31 | }
32 |
33 | public void setCid(int cid) {
34 | this.cid = cid;
35 | }
36 |
37 | public int getDuration() {
38 | return duration;
39 | }
40 |
41 | public void setDuration(int duration) {
42 | this.duration = duration;
43 | }
44 |
45 | public int getEpId() {
46 | return epId;
47 | }
48 |
49 | public void setEpId(int epId) {
50 | this.epId = epId;
51 | }
52 |
53 | public String getIndex() {
54 | return index;
55 | }
56 |
57 | public void setIndex(String index) {
58 | this.index = index;
59 | }
60 |
61 | public String getIndexTitle() {
62 | return indexTitle;
63 | }
64 |
65 | public void setIndexTitle(String indexTitle) {
66 | this.indexTitle = indexTitle;
67 | }
68 |
69 | @Override
70 | public String toString() {
71 | return "{\"aid\":" + aid + ", " +
72 | "\"cid\":" + cid + ", " +
73 | "\"duration\":" + duration + ", " +
74 | "\"ep_id\":" + epId + ", " +
75 | "\"index\":" + index + ", " +
76 | "\"index_title\":" + indexTitle + " " +
77 | "}";
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/bean/VideoBean.java:
--------------------------------------------------------------------------------
1 | package bean;
2 |
3 | public class VideoBean {
4 | private String title;
5 | // 解析后的timeLength, 行如2:30
6 | private String timeLength;
7 | // 单位为MB
8 | private int size;
9 |
10 | public VideoBean(String title, String timeLength, int size) {
11 | this.title = title;
12 | this.timeLength = timeLength;
13 | this.size = size;
14 | }
15 |
16 | public VideoBean() {
17 | }
18 |
19 | public String getTitle() {
20 | return title;
21 | }
22 |
23 | public void setTitle(String title) {
24 | this.title = title;
25 | }
26 |
27 | public String getTimeLength() {
28 | return timeLength;
29 | }
30 |
31 | public void setTimeLength(String timeLength) {
32 | this.timeLength = timeLength;
33 | }
34 |
35 | public int getSize() {
36 | return size;
37 | }
38 |
39 | public void setSize(int size) {
40 | this.size = size;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/util/DownloadUtil.java:
--------------------------------------------------------------------------------
1 | package util;
2 |
3 | import java.io.File;
4 | import java.io.FileOutputStream;
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.net.HttpURLConnection;
8 | import java.net.URL;
9 | import java.util.Iterator;
10 | import java.util.List;
11 | import java.util.Map;
12 | import java.util.regex.Matcher;
13 | import java.util.regex.Pattern;
14 |
15 | import static util.PrintUtil.println;
16 |
17 | public class DownloadUtil {
18 |
19 | public static void downloadVideo(String url, String fileDir) throws IOException {
20 | downloadVideo(url, fileDir, null);
21 | }
22 |
23 | @SuppressWarnings("Duplicates")
24 | public static void downloadVideo(String url, String fileDir, Map> headerMap) throws IOException {
25 |
26 | HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
27 | connection.setRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36");
28 | connection.setRequestProperty("Connection", "keep-alive");
29 |
30 | if (headerMap != null) {
31 | Iterator>> iterator = headerMap.entrySet().iterator();
32 | while (iterator.hasNext()){
33 | Map.Entry> entry = iterator.next();
34 | List values = entry.getValue();
35 | for (String value: values)
36 | connection.setRequestProperty(entry.getKey(), value);
37 | }
38 | }
39 |
40 | println(fileDir);
41 |
42 | File file = new File(fileDir);
43 |
44 | // 文件存在则重命名
45 | while (file.exists()){
46 | Matcher matcher = Pattern.compile("\\.\\w+$").matcher(fileDir);
47 | while (matcher.find()){
48 | int index = matcher.start();
49 |
50 | String startStr = fileDir.substring(0, index);
51 | String format = fileDir.substring(index);
52 |
53 | if (startStr.matches(".*\\(\\d+\\)$")){
54 | int indexStart = startStr.lastIndexOf('(');
55 | int indexEnd = startStr.lastIndexOf(')');
56 |
57 | int num = Integer.parseInt(startStr.substring(indexStart + 1, indexEnd));
58 | startStr = startStr.substring(0, indexStart) + "(" + ++num + ")";
59 | }else {
60 | startStr += "(1)";
61 | }
62 | fileDir = startStr + format;
63 | }
64 | file = new File(fileDir);
65 | }
66 |
67 | FileOutputStream outputStream = new FileOutputStream(file);
68 | InputStream inputStream = connection.getInputStream();
69 |
70 | System.out.println("Start downloading...");
71 |
72 | byte[] bytes = new byte[1024];
73 | int n;
74 | int curSize = 0, count = 0;
75 | while ((n = inputStream.read(bytes)) != -1) {
76 | outputStream.write(bytes, 0, n);
77 | curSize += n;
78 |
79 | if (count++ % 500 == 0) {
80 | System.out.println(String.format("Downloading... %.2f M", curSize / 1024.00 / 1024.00));
81 | }
82 | }
83 | outputStream.close();
84 | inputStream.close();
85 |
86 | int code = connection.getResponseCode();
87 | Map map = connection.getHeaderFields();
88 | // println(code + "\n" + map);
89 |
90 | println("Download finished!");
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/src/util/HttpUtil.java:
--------------------------------------------------------------------------------
1 | package util;
2 |
3 | import java.io.*;
4 | import java.net.URL;
5 | import java.net.URLConnection;
6 | import java.util.Iterator;
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | /**
11 | * 该类提供一些Http的一些基本操作
12 | *
13 | * @author As_
14 | * @date 2018-09-17 20:29:12
15 | * @github https://github.com/asche910
16 | */
17 | @SuppressWarnings("Duplicates")
18 | public class HttpUtil {
19 |
20 | /**
21 | * 读取输入流并写到输出流中,该函数用于合并分散的视频文件
22 | *
23 | * @param in
24 | * @param out
25 | * @throws IOException
26 | */
27 | public static void combineStream(InputStream in, OutputStream out) throws IOException {
28 | byte[] bytes = new byte[1024];
29 | int n;
30 | while ((n = in.read(bytes)) != -1) {
31 | out.write(bytes, 0, n);
32 | }
33 | in.close();
34 | }
35 |
36 | public static InputStream getInputStream(String url) throws IOException {
37 | return getInputStream(url, null);
38 | }
39 |
40 | public static InputStream getInputStream(String url, Map> headerMap) throws IOException {
41 | URLConnection connection = new URL(url).openConnection();
42 | connection.setRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36");
43 |
44 | if (headerMap != null) {
45 | Iterator>> iterator = headerMap.entrySet().iterator();
46 | while (iterator.hasNext()) {
47 | Map.Entry> entry = iterator.next();
48 | List values = entry.getValue();
49 | for (String value : values)
50 | connection.setRequestProperty(entry.getKey(), value);
51 | }
52 | }
53 | return connection.getInputStream();
54 | }
55 |
56 | public static String getResponseContent(String url) throws IOException {
57 |
58 | return getResponseContent(url, null);
59 | }
60 |
61 | public static String getResponseContent(String url, Map> headerMap) throws IOException {
62 | URLConnection connection = new URL(url).openConnection();
63 | connection.setRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36");
64 |
65 | if (headerMap != null) {
66 | Iterator>> iterator = headerMap.entrySet().iterator();
67 | while (iterator.hasNext()) {
68 | Map.Entry> entry = iterator.next();
69 | List values = entry.getValue();
70 | for (String value : values)
71 | connection.setRequestProperty(entry.getKey(), value);
72 | }
73 | }
74 |
75 | InputStream inputStream = connection.getInputStream();
76 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
77 |
78 | StringBuilder stringBuilder = new StringBuilder();
79 | String line;
80 | while ((line = reader.readLine()) != null) {
81 | stringBuilder.append(line);
82 | }
83 | reader.close();
84 | inputStream.close();
85 | return stringBuilder.toString();
86 | }
87 | }
--------------------------------------------------------------------------------
/src/util/MD5Encoder.java:
--------------------------------------------------------------------------------
1 | package util;
2 |
3 | import java.io.UnsupportedEncodingException;
4 | import java.security.MessageDigest;
5 | import java.security.NoSuchAlgorithmException;
6 |
7 | public class MD5Encoder {
8 |
9 | /**
10 | *
11 | * @param str 待加密的字符串
12 | * @return MD5加密后的字符串(默认大写)
13 | * @throws NoSuchAlgorithmException
14 | * @throws UnsupportedEncodingException
15 | */
16 | public static String md5(String str) throws NoSuchAlgorithmException, UnsupportedEncodingException {
17 | return md5(str, "utf-8");
18 | }
19 |
20 | /**
21 | *
22 | * @param str 待加密的字符串
23 | * @param charset 加密编码
24 | * @return MD5加密后的字符串(默认大写)
25 | * @throws NoSuchAlgorithmException
26 | * @throws UnsupportedEncodingException
27 | */
28 | public static String md5(String str, String charset) throws NoSuchAlgorithmException, UnsupportedEncodingException {
29 | MessageDigest md5 = MessageDigest.getInstance("MD5");
30 | return toHex(md5.digest(str.getBytes(charset)));
31 | }
32 |
33 | private static String toHex(byte[] bytes) {
34 | final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
35 | StringBuilder ret = new StringBuilder(bytes.length * 2);
36 | for (int i=0; i> 4) & 0x0f]);
38 | ret.append(HEX_DIGITS[bytes[i] & 0x0f]);
39 | }
40 | return ret.toString();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/util/PrintUtil.java:
--------------------------------------------------------------------------------
1 | package util;
2 |
3 | public class PrintUtil {
4 |
5 | public static void println(String content){
6 | System.out.println(content);
7 | }
8 |
9 | public static void println(){
10 | System.out.println();
11 | }
12 |
13 | public static void println(Object obj){
14 | System.out.println(obj);
15 | }
16 |
17 | public static void print(Object obj){
18 | System.out.print(obj);
19 | }
20 |
21 | public static void printf(String format, Object... args){
22 | System.out.printf(format, args);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/website/BaseSite.java:
--------------------------------------------------------------------------------
1 | package website;
2 |
3 | import bean.VideoBean;
4 |
5 | import java.io.IOException;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | abstract public class BaseSite {
10 |
11 | // 解析出的下载地址
12 | public List urls = new ArrayList<>();
13 |
14 | abstract public void downloadByUrl(String playUrl, String outputDir);
15 |
16 | abstract void download(List videoSrcs, String outputDir) throws IOException;
17 |
18 | abstract public VideoBean getInfo();
19 | }
20 |
--------------------------------------------------------------------------------
/src/website/Bilibili.java:
--------------------------------------------------------------------------------
1 | package website;
2 |
3 | import bean.BilibiliBean;
4 | import bean.VideoBean;
5 | import org.dom4j.DocumentException;
6 | import org.dom4j.io.SAXReader;
7 | import org.json.JSONArray;
8 | import org.json.JSONObject;
9 | import org.jsoup.Jsoup;
10 | import org.jsoup.nodes.Document;
11 | import org.jsoup.nodes.Element;
12 | import util.DownloadUtil;
13 | import util.HttpUtil;
14 | import util.MD5Encoder;
15 |
16 | import java.io.ByteArrayInputStream;
17 | import java.io.File;
18 | import java.io.IOException;
19 | import java.util.*;
20 |
21 | import static util.PrintUtil.println;
22 |
23 |
24 | /**
25 | * 哔哩哔哩: https://www.bilibili.com/
26 | *
27 | * @author Asche
28 | * @date 2018-10-20 18:02:29
29 | * @github https://github.com/asche910
30 | */
31 | public class Bilibili extends BaseSite {
32 | // from aid to cids
33 | private String ApiGetList = "https://www.bilibili.com/widget/getPageList?aid=";
34 | private String AvApi = "http://interface.bilibili.com/v2/playurl?";
35 | private String EpApi = "http://bangumi.bilibili.com/player/web_api/playurl?";
36 | private String SEC_1 = "94aba54af9065f71de72f5508f1cd42e";
37 | private String SEC_2 = "9b288147e5474dd2aa67085f716c560d";
38 |
39 | // quality
40 | private final int RESOLUTION_1080 = 112;
41 | private final int RESOLUTION_720 = 64;
42 | private final int RESOLUTION_480 = 32;
43 | private final int RESOLUTION_360 = 15;
44 |
45 | private int quality = RESOLUTION_1080;
46 |
47 | // private List urls = new ArrayList<>();
48 | private String playUrl;
49 | private String fileName;
50 | private int timeLength;
51 | private int fileSize = 0;
52 | private int aid;
53 | private int cid;
54 |
55 | // 视频类型
56 | private final int AV_VIDEO = 1;
57 | private final int EP_VIDEO = 2;
58 | private final int SS_VIDEO = 3;
59 |
60 | private int type = AV_VIDEO;
61 | private boolean isSupported;
62 |
63 | // ep的关联系列
64 | private List serialList = new ArrayList<>();
65 |
66 | // 是否已经解析
67 | private boolean isResolved;
68 |
69 |
70 | public Bilibili() {
71 | }
72 |
73 | /**
74 | * 先获取信息再决定是否下载
75 | * @param playUrl
76 | * @param outputDir
77 | */
78 | public Bilibili(String playUrl, String outputDir) {
79 | if (!isResolved) {
80 | this.playUrl = playUrl;
81 |
82 | String[] strs = playUrl.split("/");
83 |
84 | for (String str : strs) {
85 | if (str.matches("av\\d{4,}")) {
86 | aid = Integer.parseInt(str.substring(2));
87 | isSupported = true;
88 | break;
89 | } else if(str.matches("ep\\d{4,}")){
90 | type = EP_VIDEO;
91 | isSupported = true;
92 | break;
93 | } else if(str.matches("ss\\d{4,}")){
94 | type = SS_VIDEO;
95 | isSupported = true;
96 | break;
97 | }
98 | }
99 |
100 | try {
101 | switch (type) {
102 | case SS_VIDEO:
103 | case EP_VIDEO:
104 | initEp();
105 |
106 | String epApi = generateEpApi(EpApi, cid, quality);
107 | println(epApi);
108 |
109 | parseEpApiResponse(epApi);
110 | break;
111 | case AV_VIDEO:
112 | initAv();
113 |
114 | String avApi = generateAvApi(AvApi, cid, quality);
115 | println(avApi);
116 |
117 | parseAvApiResponse(avApi);
118 | break;
119 | }
120 |
121 | } catch (Exception e) {
122 | e.printStackTrace();
123 | }
124 | isResolved = true;
125 | }
126 | }
127 |
128 | @Override
129 | public void downloadByUrl(String playUrl, String outputDir) {
130 | println("Bilibili start: ");
131 |
132 | this.playUrl = playUrl;
133 | String[] strs = playUrl.split("/");
134 |
135 | for (String str : strs) {
136 | if (str.matches("av\\d{4,}")) {
137 | aid = Integer.parseInt(str.substring(2));
138 | isSupported = true;
139 | break;
140 | } else if(str.matches("ep\\d{4,}")){
141 | type = EP_VIDEO;
142 | isSupported = true;
143 | break;
144 | } else if(str.matches("ss\\d{4,}")){
145 | type = SS_VIDEO;
146 | isSupported = true;
147 | break;
148 | }
149 | }
150 |
151 | try {
152 |
153 | if (!isResolved) {
154 | switch (type) {
155 | case SS_VIDEO:
156 | case EP_VIDEO:
157 | initEp();
158 |
159 | String epApi = generateEpApi(EpApi, cid, quality);
160 | println(epApi);
161 |
162 | parseEpApiResponse(epApi);
163 | break;
164 | case AV_VIDEO:
165 | initAv();
166 |
167 | String avApi = generateAvApi(AvApi, cid, quality);
168 | println(avApi);
169 |
170 | parseAvApiResponse(avApi);
171 | break;
172 | }
173 | isResolved = true;
174 | }
175 |
176 | println("# Title: " + fileName);
177 | println(" -TimeLength: " + timeLength / 1000 / 60 + ":" + String.format("%02d", timeLength / 1000 % 60));
178 | println(" -File Size: " + fileSize / 1024 / 1024 + " M");
179 |
180 | download(urls, outputDir);
181 |
182 | } catch (Exception e) {
183 | e.printStackTrace();
184 | }
185 | }
186 |
187 | /**
188 | * 内部下载入口
189 | *
190 | * @param videoSrcs
191 | * @param outputDir
192 | */
193 | @Override
194 | public void download(List videoSrcs, String outputDir) throws IOException {
195 | Map> headerMap = new HashMap<>();
196 | // 缺失Referer会导致453错误
197 | headerMap.put("Referer", Collections.singletonList("http://interface.bilibili.com/v2/playurl?appkey=84956560bc028eb7&cid=59389212&otype=json&qn=3&quality=3&type=&sign=4c841d687bb7e479e3111428c6a4d3b8"));
198 |
199 | int index = 0;
200 |
201 | for (String src : videoSrcs) {
202 | println("Download: " + ++index + "/" + videoSrcs.size());
203 |
204 | String fileDir;
205 | if (videoSrcs.size() == 1) {
206 |
207 | fileDir = outputDir + File.separatorChar + fileName.replaceAll("[/|\\\\]", "") + ".flv";
208 | } else {
209 | fileDir = outputDir + File.separatorChar + fileName.replaceAll("[/|\\\\]", "") + "【" + index + "】.flv";
210 | }
211 |
212 | DownloadUtil.downloadVideo(src, fileDir, headerMap);
213 | }
214 | println("Download: All Done!");
215 | }
216 |
217 | @Override
218 | public VideoBean getInfo() {
219 | VideoBean bean = new VideoBean();
220 | bean.setTitle(fileName);
221 | bean.setTimeLength(timeLength / 1000 / 60 + ":" + String.format("%02d", timeLength / 1000 % 60));
222 | bean.setSize(fileSize / 1024 / 1024);
223 | return bean;
224 | }
225 |
226 | public List getSerialList(){
227 | return serialList;
228 | }
229 |
230 | /**
231 | * cid, fileName
232 | *
233 | * @throws IOException
234 | */
235 | private void initAv() throws IOException {
236 | String result = HttpUtil.getResponseContent(ApiGetList + aid);
237 | JSONObject jb = (JSONObject) new JSONArray(result).get(0);
238 | cid = jb.getInt("cid");
239 |
240 | Document doc = Jsoup.connect(playUrl).get();
241 |
242 | Element ele = doc.selectFirst("div[id=viewbox_report]").selectFirst("h1");
243 | if (ele.hasAttr("title"))
244 | fileName = ele.attr("title");
245 |
246 | }
247 |
248 | /**
249 | * cid, fileName and related eps
250 | *
251 | * @throws IOException
252 | */
253 | private void initEp() throws IOException {
254 | Document doc = Jsoup.connect(playUrl).get();
255 | Element ele = doc.body().child(2);
256 |
257 | String preResult = ele.toString();
258 | // println(preResult);
259 |
260 | String result = preResult.substring(preResult.indexOf("__=") + 3, preResult.indexOf(";(function()"));
261 | // println(result);
262 |
263 | JSONObject object = new JSONObject(result);
264 |
265 | JSONObject curEpInfo = object.getJSONObject("epInfo");
266 |
267 | fileName = object.getJSONObject("mediaInfo").getString("title");
268 |
269 | cid = curEpInfo.getInt("cid");
270 |
271 |
272 | JSONArray ja = object.getJSONArray("epList");
273 |
274 | for (Object obj : ja) {
275 | JSONObject epObject = (JSONObject) obj;
276 |
277 | int aid = epObject.getInt("aid");
278 | int cid = epObject.getInt("cid");
279 | int duration = epObject.getInt("duration");
280 | int epId = epObject.getInt("ep_id");
281 |
282 | String index = epObject.getString("index");
283 | String indexTitle = epObject.getString("index_title");
284 |
285 | BilibiliBean bean = new BilibiliBean(aid, cid, duration, epId, index, indexTitle);
286 |
287 | serialList.add(bean);
288 |
289 | println(bean.toString());
290 | }
291 | }
292 |
293 | /**
294 | * timeLength, fileSize, urls
295 | *
296 | * @param avReqApi
297 | * @throws IOException
298 | */
299 | private void parseAvApiResponse(String avReqApi) throws IOException {
300 | String result = HttpUtil.getResponseContent(avReqApi);
301 |
302 | // println(result);
303 |
304 | JSONObject jsonObject = new JSONObject(result);
305 | timeLength = jsonObject.getInt("timelength");
306 |
307 | JSONArray ja = jsonObject.getJSONArray("durl");
308 |
309 | Iterator