├── .gitignore
├── README.md
├── common
├── cfdownloader.py
├── downloader.py
├── extracter.py
├── jscracker
│ ├── jscracker.py
│ └── m2.js
├── multi_th_downloader.py
└── rewriter.py
├── config
├── config.py
└── m3u8_list.py
├── pic
├── porn1.png
├── porn2.png
├── porn3.png
├── porn4.png
└── porn5.png
├── requirements.txt
├── start.py
├── ui
├── dialog1.py
├── dialog1.ui
├── dialog2.py
├── dialog2.ui
├── dialog3.py
├── dialog3.ui
├── dialog4.py
├── dialog4.ui
├── dialog5.py
├── dialog5.ui
├── downloader_ui.py
├── icon.png
├── mainwindow.py
└── mainwindow.ui
└── utils
├── get_config.py
├── log
├── log.py
└── path.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | __pycache__/
3 | /tmp/
4 | /output/
5 | /build/
6 | /dist/
7 | *.spec
8 | /release/
9 | *.pyo
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ###环境要求
2 | - python3.7
3 | - nodejs
4 | - ffmpeg
5 |
6 | #### 使用说明
7 |
8 | 0.导入依赖包
9 |
10 | `pip install -r requirements.txt`
11 |
12 | 1.运行start.py
13 |
14 | 2.打开vpn,打开91,打开几个你想下载的页面
15 | 
16 |
17 | 3.在弹出的窗口中输入你想下载的连接,每个链接占一行
18 | 
19 |
20 | 4.如果你vpn流量很多的话可以点直接下载,然后等待下载完毕就可以了。
21 |
22 | 5.如果想节省流量,点击节省流量下载。这时候会出现一个弹窗,先别急着点确定,先退出vpn,然后再点确定等待下载完毕
23 | 
24 | 
25 |
26 | #### 注意:因为UI是单线程运行的,点击下载后UI界面会卡住失去响应,等几分钟等下载结束后就好了
27 |
--------------------------------------------------------------------------------
/common/cfdownloader.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import cfscrape
4 | from retrying import retry
5 |
6 | from config.config import *
7 |
8 | class CFDownloader(object):
9 | def __init__(self):
10 | super().__init__()
11 | self.scraper = cfscrape.create_scraper()
12 |
13 | @retry(stop_max_attempt_number=5)
14 | def cfdownload(self, url):
15 | return self.scraper.get(url=url, headers=headers).text
--------------------------------------------------------------------------------
/common/downloader.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import re
4 | import os
5 | import copy
6 | import requests
7 | import threading
8 | from retrying import retry
9 | from urllib.parse import urlparse
10 |
11 | from utils.log import logger
12 | from config.config import headers
13 | from utils.path import get_basedir_path
14 | from utils.get_config import get_output_path, get_th_number
15 |
16 | log = logger()
17 |
18 | class Downloader(object):
19 | def __init__(self, m3u8_url, video_name):
20 | self.m3u8_url = m3u8_url
21 | self.video_name = self.get_video_name(video_name)
22 | self.video_id = self.get_video_id()
23 | self.base_dir = get_basedir_path()
24 | self.ts_dir = self.get_ts_dir_path()
25 | self.ts_url = self.get_ts_url()
26 | self.output_name = self.get_output_name()
27 |
28 | def get_ts_url(self):
29 | result = urlparse(self.m3u8_url)
30 | return f'{result.scheme}://{result.hostname}//m3u8/{self.video_id}/'
31 |
32 | def get_video_name(self, video_name):
33 | return video_name.replace(' ','_').replace('&','_').replace(':', '_').replace(':', '_')
34 |
35 | def get_output_name(self):
36 | output_dir = get_output_path()
37 | output_path = output_dir if output_dir.endswith('\\') else output_dir + '\\'
38 | return f'{output_path}{self.video_name}.mp4'
39 |
40 | def get_ts_dir_path(self):
41 | return f'{self.base_dir}\\tmp\{self.video_id}\\'
42 |
43 | def get_video_id(self):
44 | re_res = re.findall('/(\d+)\.m3u8*', self.m3u8_url)
45 | return re_res[0]
46 |
47 | def check_path(self):
48 | """
49 | 检查ts文件夹是否存在
50 | :return:
51 | """
52 | if not os.path.exists(self.ts_dir):
53 | os.mkdir(self.ts_dir)
54 |
55 | def check_output_exit(self):
56 | """
57 | 检查输出结果文件是否存在
58 | :return:
59 | """
60 | if os.path.exists(self.output_name):
61 | log.warning(f'文件 “{self.video_name}” 已存在!')
62 | return False
63 | else:
64 | return True
65 |
66 | def check_m3u8_url(self):
67 | """
68 | 检查是否是m3u8链接
69 | :return:
70 | """
71 | if self.m3u8_url.find('m3u8') != -1:
72 | return True
73 | else:
74 | log.warning(f'文件 “{self.video_name}” {self.m3u8_url} 链接格式不正确!')
75 | return False
76 |
77 | def pre_check(self):
78 | """
79 | True: 没有问题
80 | False: 有问题
81 | :return:
82 | """
83 | return self.check_m3u8_url() & self.check_output_exit()
84 |
85 | def transform(self, order):
86 | os.system(order)
87 |
88 | @classmethod
89 | @retry(stop_max_attempt_number=5)
90 | def get_content(self, url):
91 | return requests.get(url=url, headers=headers, timeout=30).content
92 |
93 | def log_succ(self, video_name):
94 | pass
95 |
96 | def get_ts_file(self, ts_id):
97 | ts_file = self.ts_dir + ts_id + '.ts'
98 | if not os.path.exists(ts_file):
99 | url = self.ts_url + ts_id + '.ts'
100 | content = self.get_content(url)
101 | with open(ts_file, 'wb') as f:
102 | f.write(content)
103 |
104 | def worker(self):
105 | while True:
106 | with downloaderlock:
107 | try:
108 | ts_id = ts_ids.pop()
109 | except IndexError:
110 | break
111 | self.get_ts_file(ts_id)
112 |
113 | def run(self):
114 | log.info(f'Start download {self.video_name} {self.m3u8_url}')
115 | self.check_path()
116 | m3u8_file_str = fr'{self.base_dir}\tmp\{self.video_id}\download.m3u8'
117 | if not os.path.exists(m3u8_file_str):
118 | m3u8_content = requests.get(self.m3u8_url, headers=headers).text
119 | with open(m3u8_file_str, 'w') as m3u8_file:
120 | m3u8_file.write(m3u8_content)
121 | global ts_ids
122 | with open(m3u8_file_str, 'r') as m3u8_file:
123 | ts_ids = []
124 | for line in m3u8_file:
125 | if '#' not in line:
126 | ts_id = line.replace('.ts\n', '')
127 | ts_ids.append(ts_id)
128 | th_list = []
129 | ts_ids_copy = copy.copy(ts_ids)
130 | global downloaderlock
131 | downloaderlock = threading.Lock()
132 | for _ in range(int(get_th_number())):
133 | th = threading.Thread(target=self.worker)
134 | th.start()
135 | th_list.append(th)
136 | for th in th_list:
137 | th.join()
138 | ts_ids_len = len(ts_ids_copy)
139 | files = os.listdir(self.ts_dir)
140 | ts_file_count = 0
141 | ts_files = []
142 | for file in files:
143 | if file[0].isdigit():
144 | ts_file_count += 1
145 | ts_files.append(file)
146 | assert ts_ids_len == ts_file_count
147 | start = 0
148 | end = 50
149 | ts_files = sorted(ts_files, key=lambda x: int(x[:-3]))
150 | merge_count = 0
151 | while start < ts_ids_len:
152 | split = ts_files[start:end]
153 | ts_files_str = ''
154 | for ts in split:
155 | ts_files_str += self.ts_dir + ts + '|'
156 | merge_name = fr'{self.base_dir}\tmp\{self.video_id}\merge{str(merge_count)}.ts'
157 | order_str = f'ffmpeg -i \"concat:{ts_files_str}\" -c copy {merge_name} -y'
158 | # log.info(f'Merge viedeo {self.video_name} {str(merge_count)}')
159 | merge_count += 1
160 | self.transform(order_str)
161 | start += 50
162 | end += 50
163 | merge_files = ''
164 | if merge_count > 0:
165 | for merge_id in range(merge_count):
166 | merge_files += fr'{self.base_dir}\tmp\{self.video_id}\merge{merge_id}.ts|'
167 | else:
168 | merge_files += fr'{self.base_dir}\tmp\{self.video_id}\merge0.ts'
169 | order_str = f'ffmpeg -i \"concat:{merge_files}\" -c copy {self.output_name} -y'
170 | log.info(f'Out put video {self.video_name}')
171 | self.transform(order_str)
--------------------------------------------------------------------------------
/common/extracter.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import re
4 | import threading
5 |
6 | from lxml import etree
7 |
8 | from utils.log import logger
9 | from utils.get_config import get_th_number
10 | from common.cfdownloader import CFDownloader
11 | from common.jscracker.jscracker import Cracker
12 |
13 | log = logger()
14 |
15 | def legal_url(url):
16 | """
17 | 判断url格式是否正确
18 | :param url:
19 | :return:
20 | """
21 | if re.findall('view_video\.php\?viewkey=\w+', url):
22 | return True
23 | else:
24 | log.warning(f'Url: {url} 格式不正确!')
25 | return False
26 |
27 | def worker():
28 | while True:
29 | with lock:
30 | try:
31 | url = url_list.pop()
32 | except IndexError:
33 | break
34 | log.info(f'Get url: {url}')
35 | response = cfdownloader.cfdownload(url)
36 | page = etree.HTML(response)
37 | jscode = re.findall('document\.write\(strencode2\((.*?)\)', response)
38 | jsres = jscracker.crack(jscode)
39 | try:
40 | m3u8_url = re.findall('src=\'(.*?)\'', jsres)[0]
41 | except:
42 | log.info(f'Extract error: {url}')
43 | continue
44 | video_name = ''.join(page.xpath('//div[@id="videodetails"][1]/h4/text()')).strip()
45 | # log.info(f'url:{url} get \n\tm3u8 url: {m3u8_url}\n\tvideo name: {video_name}')
46 | m3u8_list.append((m3u8_url, video_name))
47 |
48 | def extrcat_url(input_text):
49 | """
50 | 提取m3u8url和视频名 放入m3u8_list [("m3u8", "视频名称"),("", "")]
51 | :return:
52 | """
53 | global m3u8_list, url_list
54 | m3u8_list = []
55 | url_list = []
56 | origin_url_list = input_text.split('\n')
57 | for url in origin_url_list:
58 | if not legal_url(url):
59 | continue
60 | url_list.append(url)
61 | log.info(f'Get {len(url_list)} urls : {str(url_list)}')
62 | global cfdownloader, jscracker, lock
63 | lock = threading.Lock()
64 | cfdownloader = CFDownloader()
65 | jscracker = Cracker()
66 | th_list = []
67 | th_number = get_th_number()
68 | for _ in range(int(th_number)):
69 | th = threading.Thread(target=worker)
70 | th.start()
71 | th_list.append(th)
72 | for th in th_list:
73 | th.join()
74 | log.info(f'Get {len(m3u8_list)} m3u8 urls: {str(m3u8_list)}')
75 | return m3u8_list
--------------------------------------------------------------------------------
/common/jscracker/jscracker.py:
--------------------------------------------------------------------------------
1 | import execjs
2 |
3 | class Cracker(object):
4 | def __init__(self):
5 | super().__init__()
6 | self.ctx = self.get_ctx()
7 |
8 | def get_ctx(self):
9 | js = self.get_js()
10 | return execjs.compile(js)
11 |
12 | def get_js(self):
13 | with open('./common/jscracker/m2.js', 'r') as f:
14 | return f.read()
15 |
16 | def crack(self, code):
17 | return self.ctx.call('strencode2',code)
--------------------------------------------------------------------------------
/common/jscracker/m2.js:
--------------------------------------------------------------------------------
1 | ;var encode_version = 'jsjiami.com.v5', eexda = '__0x9ff10', __0x9ff10=['w7FkXcKcwqs=','VMKAw7Fhw6Q=','w5nDlTY7w4A=','wqQ5w4pKwok=','dcKnwrTCtBg=','w45yHsO3woU=','54u75py15Y6177y0PcKk5L665a2j5pyo5b2156i677yg6L+S6K2D5pW65o6D5oqo5Lmn55i/5bSn5L21','RsOzwq5fGQ==','woHDiMK0w7HDiA==','54uS5pyR5Y6r7764wr3DleS+ouWtgeaesOW/sOeooe+/nei/ruitteaWsuaOmeaKiuS4o+eateW2i+S8ng==','bMOKwqA=','V8Knwpo=','csOIwoVsG1rCiUFU','5YmL6ZiV54qm5pyC5Y2i776Lw4LCrOS+muWssOacteW8lOeqtg==','w75fMA==','YsOUwpU=','wqzDtsKcw5fDvQ==','wqNMOGfCn13DmjTClg==','wozDisOlHHI=','GiPConNN','XcKzwrDCvSg=','U8K+wofCmcO6'];(function(_0x1f2e93,_0x60307d){var _0x1f9a0b=function(_0x35f19b){while(--_0x35f19b){_0x1f2e93['push'](_0x1f2e93['shift']());}};_0x1f9a0b(++_0x60307d);}(__0x9ff10,0x152));var _0x43d9=function(_0x13228a,_0x2ce452){_0x13228a=_0x13228a-0x0;var _0x424175=__0x9ff10[_0x13228a];if(_0x43d9['initialized']===undefined){(function(){var _0x270d2c=typeof window!=='undefined'?window:typeof process==='object'&&typeof require==='function'&&typeof global==='object'?global:this;var _0x58680b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';_0x270d2c['atob']||(_0x270d2c['atob']=function(_0x5536e1){var _0x15e9d3=String(_0x5536e1)['replace'](/=+$/,'');for(var _0x4e6299=0x0,_0x3590d2,_0x48c90b,_0x557f6a=0x0,_0x2b086d='';_0x48c90b=_0x15e9d3['charAt'](_0x557f6a++);~_0x48c90b&&(_0x3590d2=_0x4e6299%0x4?_0x3590d2*0x40+_0x48c90b:_0x48c90b,_0x4e6299++%0x4)?_0x2b086d+=String['fromCharCode'](0xff&_0x3590d2>>(-0x2*_0x4e6299&0x6)):0x0){_0x48c90b=_0x58680b['indexOf'](_0x48c90b);}return _0x2b086d;});}());var _0x4a2d38=function(_0x1f120d,_0x1d6e11){var _0x4c36f9=[],_0x1c4b64=0x0,_0x18ce5c,_0x39c9fa='',_0x6d02b2='';_0x1f120d=atob(_0x1f120d);for(var _0x13b203=0x0,_0x24d88b=_0x1f120d['length'];_0x13b203<_0x24d88b;_0x13b203++){_0x6d02b2+='%'+('00'+_0x1f120d['charCodeAt'](_0x13b203)['toString'](0x10))['slice'](-0x2);}_0x1f120d=decodeURIComponent(_0x6d02b2);for(var _0x1f76f3=0x0;_0x1f76f3<0x100;_0x1f76f3++){_0x4c36f9[_0x1f76f3]=_0x1f76f3;}for(_0x1f76f3=0x0;_0x1f76f3<0x100;_0x1f76f3++){_0x1c4b64=(_0x1c4b64+_0x4c36f9[_0x1f76f3]+_0x1d6e11['charCodeAt'](_0x1f76f3%_0x1d6e11['length']))%0x100;_0x18ce5c=_0x4c36f9[_0x1f76f3];_0x4c36f9[_0x1f76f3]=_0x4c36f9[_0x1c4b64];_0x4c36f9[_0x1c4b64]=_0x18ce5c;}_0x1f76f3=0x0;_0x1c4b64=0x0;for(var _0x2b6a92=0x0;_0x2b6a92<_0x1f120d['length'];_0x2b6a92++){_0x1f76f3=(_0x1f76f3+0x1)%0x100;_0x1c4b64=(_0x1c4b64+_0x4c36f9[_0x1f76f3])%0x100;_0x18ce5c=_0x4c36f9[_0x1f76f3];_0x4c36f9[_0x1f76f3]=_0x4c36f9[_0x1c4b64];_0x4c36f9[_0x1c4b64]=_0x18ce5c;_0x39c9fa+=String['fromCharCode'](_0x1f120d['charCodeAt'](_0x2b6a92)^_0x4c36f9[(_0x4c36f9[_0x1f76f3]+_0x4c36f9[_0x1c4b64])%0x100]);}return _0x39c9fa;};_0x43d9['rc4']=_0x4a2d38;_0x43d9['data']={};_0x43d9['initialized']=!![];}var _0x302f80=_0x43d9['data'][_0x13228a];if(_0x302f80===undefined){if(_0x43d9['once']===undefined){_0x43d9['once']=!![];}_0x424175=_0x43d9['rc4'](_0x424175,_0x2ce452);_0x43d9['data'][_0x13228a]=_0x424175;}else{_0x424175=_0x302f80;}return _0x424175;};function strencode2(_0x4f0d7a){var _0x4c6de5={'Anfny':function _0x4f6a21(_0x51d0ce,_0x5a5f36){return _0x51d0ce(_0x5a5f36);}};return _0x4c6de5[_0x43d9('0x0','fo#E')](unescape,_0x4f0d7a);};(function(_0x17883e,_0x4a42d3,_0xe4080c){var _0x301ffc={'lPNHL':function _0x1c947e(_0x4d57b6,_0x51f6a5){return _0x4d57b6!==_0x51f6a5;},'EPdUx':function _0x55f4cc(_0x34b7bc,_0x9f930c){return _0x34b7bc===_0x9f930c;},'kjFfJ':'jsjiami.com.v5','DFsBH':function _0x5f08ac(_0x1e6fa1,_0x4c0aef){return _0x1e6fa1+_0x4c0aef;},'akiuH':_0x43d9('0x1','KYjt'),'VtfeI':function _0x4f3b7b(_0x572344,_0x5f0cde){return _0x572344(_0x5f0cde);},'Deqmq':_0x43d9('0x2','oYRG'),'oKQDc':_0x43d9('0x3','i^vo'),'UMyIE':_0x43d9('0x4','oYRG'),'lRwKx':function _0x5b71b4(_0x163a75,_0x4d3998){return _0x163a75===_0x4d3998;},'TOBCR':function _0x314af8(_0x3e6efe,_0x275766){return _0x3e6efe+_0x275766;},'AUOVd':_0x43d9('0x5','lALy')};_0xe4080c='al';try{if('EqF'!==_0x43d9('0x6','xSW]')){_0xe4080c+=_0x43d9('0x7','oYRG');_0x4a42d3=encode_version;if(!(_0x301ffc[_0x43d9('0x8','fo#E')](typeof _0x4a42d3,_0x43d9('0x9','*oMH'))&&_0x301ffc[_0x43d9('0xa','ov6D')](_0x4a42d3,_0x301ffc[_0x43d9('0xb','3k]D')]))){_0x17883e[_0xe4080c](_0x301ffc[_0x43d9('0xc','@[')]('',_0x301ffc[_0x43d9('0xd','i^vo')]));}}else{return _0x301ffc[_0x43d9('0xe','rvlM')](unescape,input);}}catch(_0x23e6c5){if('svo'!==_0x301ffc[_0x43d9('0xf','TpCD')]){_0x17883e[_0xe4080c]('');}else{_0xe4080c='al';try{_0xe4080c+=_0x301ffc[_0x43d9('0x10','doK*')];_0x4a42d3=encode_version;if(!(_0x301ffc[_0x43d9('0x11','ZRZ4')](typeof _0x4a42d3,_0x301ffc['UMyIE'])&&_0x301ffc[_0x43d9('0x12','@[')](_0x4a42d3,_0x301ffc['kjFfJ']))){_0x17883e[_0xe4080c](_0x301ffc[_0x43d9('0x13','KYjt')]('',_0x43d9('0x14','xSW]')));}}catch(_0x4202f6){_0x17883e[_0xe4080c](_0x301ffc[_0x43d9('0x15','oYRG')]);}}}});;encode_version = 'jsjiami.com.v5';
2 |
--------------------------------------------------------------------------------
/common/multi_th_downloader.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import threading
4 | import multiprocessing
5 |
6 | from utils.log import logger
7 | from common.downloader import Downloader
8 | from utils.get_config import get_th_number
9 | from config.m3u8_list import m3u8_list as m3u8_list_file
10 |
11 | log = logger()
12 |
13 | def worker():
14 | while True:
15 | with lock:
16 | try:
17 | task = download_list.pop()
18 | except IndexError:
19 | break
20 | proc = multiprocessing.Process(target=wrapper,args=(task,))
21 | proc.start()
22 | proc.join()
23 |
24 | def wrapper(task):
25 | downloader = Downloader(task[0], task[1])
26 | if not downloader.pre_check():
27 | return
28 | downloader.run()
29 |
30 | def download_multi_thread(m3u8_list=None):
31 | global download_list, lock
32 | download_list = m3u8_list if m3u8_list is not None else m3u8_list_file
33 | lock = threading.Lock()
34 | th_list = []
35 | th_number = get_th_number()
36 | for _ in range(int(th_number)):
37 | th = threading.Thread(target=worker)
38 | th.start()
39 | th_list.append(th)
40 | for th in th_list:
41 | th.join()
42 |
43 |
44 | if __name__ == '__main__':
45 | download_multi_thread() # 手动粘贴m3u8链接到m3u8_list后启动
--------------------------------------------------------------------------------
/common/rewriter.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import re
4 |
5 | class Rewriter(object):
6 | """
7 | 重写配置文件
8 | """
9 | @classmethod
10 | def rewrite_config(cls, config_file, old_config, new_config):
11 | with open(config_file, 'r+', encoding='utf-8') as f:
12 | old = f.read()
13 | f.seek(0)
14 | new = re.sub(old_config, new_config, old)
15 | f.write(new)
--------------------------------------------------------------------------------
/config/config.py:
--------------------------------------------------------------------------------
1 | headers = {
2 | 'User-Agent': "Mozilla/5.0 (Windows NT 6.1; Win64; x64) "
3 | "AppleWebKit/537.36 (KHTML, like Gecko) "
4 | "Chrome/72.0.3626.109 Safari/537.36",
5 | 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
6 | }
7 |
8 | output_path = 'F:/20211207' # 指定下载目录
9 |
10 | th_number = 20 # 指定线程数定线程数定线程数
--------------------------------------------------------------------------------
/config/m3u8_list.py:
--------------------------------------------------------------------------------
1 | #
2 | m3u8_list = [
3 | ("https://ccn.killcovid2021.com//m3u8/544787/544787.m3u8?st=GhkD-0e0UMukP1-E0QMGOA&e=1635160967","真空出门,奶头激凸,被路人视奸骚逼都湿了!"),
4 | ("https://ccn.killcovid2021.com//m3u8/544738/544738.m3u8?st=k58UL_w7QdItih1TjECIRw&e=1635160972","让公司领导玩我老婆,没办法为了升职"),
5 | ]
6 |
--------------------------------------------------------------------------------
/pic/porn1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielRWong/91porn-downloader/f7b75f0526a5c7c7fed607ba2af075571fec1731/pic/porn1.png
--------------------------------------------------------------------------------
/pic/porn2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielRWong/91porn-downloader/f7b75f0526a5c7c7fed607ba2af075571fec1731/pic/porn2.png
--------------------------------------------------------------------------------
/pic/porn3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielRWong/91porn-downloader/f7b75f0526a5c7c7fed607ba2af075571fec1731/pic/porn3.png
--------------------------------------------------------------------------------
/pic/porn4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielRWong/91porn-downloader/f7b75f0526a5c7c7fed607ba2af075571fec1731/pic/porn4.png
--------------------------------------------------------------------------------
/pic/porn5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DanielRWong/91porn-downloader/f7b75f0526a5c7c7fed607ba2af075571fec1731/pic/porn5.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | lxml==4.2.5
2 | requests==2.19.1
3 | cfscrape==2.1.1
4 | PyExecJS==1.5.1
5 | retrying==1.3.3
6 | PyQt5==5.15.4
7 |
--------------------------------------------------------------------------------
/start.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import sys
4 |
5 | from PyQt5.QtWidgets import QApplication
6 |
7 | from ui.downloader_ui import MainWindow
8 | from utils.path import check_output_path
9 |
10 |
11 | def run():
12 | check_output_path()
13 | app = QApplication(sys.argv)
14 | main = MainWindow()
15 | main.show()
16 | sys.exit(app.exec_())
17 |
18 |
19 | if __name__ == '__main__':
20 | run()
21 |
--------------------------------------------------------------------------------
/ui/dialog1.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file 'dialog1.ui'
4 | #
5 | # Created by: PyQt5 UI code generator 5.15.4
6 | #
7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is
8 | # run again. Do not edit this file unless you know what you are doing.
9 |
10 |
11 | from PyQt5 import QtCore, QtGui, QtWidgets
12 |
13 |
14 | class Ui_Dialog(object):
15 | def setupUi(self, Dialog):
16 | Dialog.setObjectName("Dialog")
17 | Dialog.resize(262, 91)
18 | self.pushButton = QtWidgets.QPushButton(Dialog)
19 | self.pushButton.setGeometry(QtCore.QRect(80, 50, 75, 23))
20 | self.pushButton.setObjectName("pushButton")
21 | self.label = QtWidgets.QLabel(Dialog)
22 | self.label.setGeometry(QtCore.QRect(30, 10, 201, 31))
23 | self.label.setObjectName("label")
24 |
25 | self.retranslateUi(Dialog)
26 | QtCore.QMetaObject.connectSlotsByName(Dialog)
27 |
28 | def retranslateUi(self, Dialog):
29 | _translate = QtCore.QCoreApplication.translate
30 | Dialog.setWindowTitle(_translate("Dialog", "91下载器"))
31 | self.pushButton.setText(_translate("Dialog", "确定"))
32 | self.label.setText(_translate("Dialog", "链接提取完毕,请退出vpn后点击确定"))
33 |
--------------------------------------------------------------------------------
/ui/dialog1.ui:
--------------------------------------------------------------------------------
1 |
2 |
https://www.91porn.com/view_video.php?viewkey=d9f57047721ceceedabe&page=1&viewtype=basic&category=rf
\n" 52 | "https://www.91porn.com/view_video.php?viewkey=1338676198dfde1bf0f7&page=3&viewtype=basic&category=rf
")) 53 | self.label_2.setText(_translate("Dialog", "2.如果不缺vpn流量点直接下载")) 54 | self.label_3.setText(_translate("Dialog", "3.如果想节省vpn流量,点省vpn流量下载,等弹出提示框后你就可以退出vpn了,然后再点确定")) 55 | self.label_4.setText(_translate("Dialog", "0.首先打开vpn,进入91,打开你想下载的视频网页")) 56 | self.label_5.setText(_translate("Dialog", "4.等待视频下载结束")) 57 | self.pushButton.setText(_translate("Dialog", "确定")) 58 | -------------------------------------------------------------------------------- /ui/dialog2.ui: -------------------------------------------------------------------------------- 1 | 2 |