├── .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 | ![avatar](pic/porn1.png) 16 | 17 | 3.在弹出的窗口中输入你想下载的连接,每个链接占一行 18 | ![avatar](pic/porn3.png) 19 | 20 | 4.如果你vpn流量很多的话可以点直接下载,然后等待下载完毕就可以了。 21 | 22 | 5.如果想节省流量,点击节省流量下载。这时候会出现一个弹窗,先别急着点确定,先退出vpn,然后再点确定等待下载完毕 23 | ![avatar](pic/porn4.png) 24 | ![avatar](pic/porn5.png) 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 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 262 10 | 91 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 90 20 | 50 21 | 75 22 | 23 23 | 24 | 25 | 26 | 确定 27 | 28 | 29 | 30 | 31 | 32 | 30 33 | 10 34 | 201 35 | 31 36 | 37 | 38 | 39 | 链接提取完毕,请退出vpn后点击确定 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ui/dialog2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'dialog2.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(633, 199) 18 | self.label = QtWidgets.QLabel(Dialog) 19 | self.label.setGeometry(QtCore.QRect(9, 27, 348, 16)) 20 | self.label.setObjectName("label") 21 | self.textBrowser = QtWidgets.QTextBrowser(Dialog) 22 | self.textBrowser.setGeometry(QtCore.QRect(9, 45, 615, 51)) 23 | self.textBrowser.setObjectName("textBrowser") 24 | self.label_2 = QtWidgets.QLabel(Dialog) 25 | self.label_2.setGeometry(QtCore.QRect(10, 100, 162, 16)) 26 | self.label_2.setObjectName("label_2") 27 | self.label_3 = QtWidgets.QLabel(Dialog) 28 | self.label_3.setGeometry(QtCore.QRect(10, 120, 541, 16)) 29 | self.label_3.setObjectName("label_3") 30 | self.label_4 = QtWidgets.QLabel(Dialog) 31 | self.label_4.setGeometry(QtCore.QRect(9, 9, 270, 16)) 32 | self.label_4.setObjectName("label_4") 33 | self.label_5 = QtWidgets.QLabel(Dialog) 34 | self.label_5.setGeometry(QtCore.QRect(10, 140, 108, 16)) 35 | self.label_5.setObjectName("label_5") 36 | self.pushButton = QtWidgets.QPushButton(Dialog) 37 | self.pushButton.setGeometry(QtCore.QRect(270, 160, 75, 23)) 38 | self.pushButton.setObjectName("pushButton") 39 | 40 | self.retranslateUi(Dialog) 41 | QtCore.QMetaObject.connectSlotsByName(Dialog) 42 | 43 | def retranslateUi(self, Dialog): 44 | _translate = QtCore.QCoreApplication.translate 45 | Dialog.setWindowTitle(_translate("Dialog", "91下载器")) 46 | self.label.setText(_translate("Dialog", "1.将网页链接复制到输入框,多个链接的话每个链接占一行,如:")) 47 | self.textBrowser.setHtml(_translate("Dialog", "\n" 48 | "\n" 51 | "

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 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 633 10 | 199 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 9 20 | 27 21 | 348 22 | 16 23 | 24 | 25 | 26 | 1.将网页链接复制到输入框,多个链接的话每个链接占一行,如: 27 | 28 | 29 | 30 | 31 | 32 | 9 33 | 45 34 | 615 35 | 51 36 | 37 | 38 | 39 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 40 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 41 | p, li { white-space: pre-wrap; } 42 | </style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;"> 43 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">https://www.91porn.com/view_video.php?viewkey=d9f57047721ceceedabe&amp;page=1&amp;viewtype=basic&amp;category=rf</p> 44 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">https://www.91porn.com/view_video.php?viewkey=1338676198dfde1bf0f7&amp;page=3&amp;viewtype=basic&amp;category=rf</p></body></html> 45 | 46 | 47 | 48 | 49 | 50 | 10 51 | 100 52 | 162 53 | 16 54 | 55 | 56 | 57 | 2.如果不缺vpn流量点直接下载 58 | 59 | 60 | 61 | 62 | 63 | 10 64 | 120 65 | 541 66 | 16 67 | 68 | 69 | 70 | 3.如果想节省vpn流量,点省vpn流量下载,等弹出提示框后你就可以退出vpn了,然后再点确定 71 | 72 | 73 | 74 | 75 | 76 | 9 77 | 9 78 | 270 79 | 16 80 | 81 | 82 | 83 | 0.首先打开vpn,进入91,打开你想下载的视频网页 84 | 85 | 86 | 87 | 88 | 89 | 10 90 | 140 91 | 108 92 | 16 93 | 94 | 95 | 96 | 4.等待视频下载结束 97 | 98 | 99 | 100 | 101 | 102 | 270 103 | 160 104 | 75 105 | 23 106 | 107 | 108 | 109 | 确定 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /ui/dialog3.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'dialog3.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(191, 88) 18 | self.label = QtWidgets.QLabel(Dialog) 19 | self.label.setGeometry(QtCore.QRect(60, 20, 101, 21)) 20 | self.label.setObjectName("label") 21 | self.pushButton = QtWidgets.QPushButton(Dialog) 22 | self.pushButton.setGeometry(QtCore.QRect(60, 50, 75, 23)) 23 | self.pushButton.setObjectName("pushButton") 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.label.setText(_translate("Dialog", "视频下载完成")) 32 | self.pushButton.setText(_translate("Dialog", "确定")) 33 | -------------------------------------------------------------------------------- /ui/dialog3.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 191 10 | 88 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 60 20 | 20 21 | 101 22 | 21 23 | 24 | 25 | 26 | 视频下载完成 27 | 28 | 29 | 30 | 31 | 32 | 60 33 | 50 34 | 75 35 | 23 36 | 37 | 38 | 39 | 确定 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ui/dialog4.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'dialog4.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(434, 85) 18 | self.label = QtWidgets.QLabel(Dialog) 19 | self.label.setGeometry(QtCore.QRect(20, 10, 54, 12)) 20 | self.label.setObjectName("label") 21 | self.label_2 = QtWidgets.QLabel(Dialog) 22 | self.label_2.setGeometry(QtCore.QRect(20, 30, 54, 12)) 23 | self.label_2.setObjectName("label_2") 24 | self.lineEdit = QtWidgets.QLineEdit(Dialog) 25 | self.lineEdit.setGeometry(QtCore.QRect(80, 10, 341, 16)) 26 | self.lineEdit.setObjectName("lineEdit") 27 | self.lineEdit_2 = QtWidgets.QLineEdit(Dialog) 28 | self.lineEdit_2.setGeometry(QtCore.QRect(80, 30, 341, 16)) 29 | self.lineEdit_2.setObjectName("lineEdit_2") 30 | self.pushButton = QtWidgets.QPushButton(Dialog) 31 | self.pushButton.setGeometry(QtCore.QRect(120, 50, 75, 23)) 32 | self.pushButton.setObjectName("pushButton") 33 | self.pushButton_2 = QtWidgets.QPushButton(Dialog) 34 | self.pushButton_2.setGeometry(QtCore.QRect(240, 50, 75, 23)) 35 | self.pushButton_2.setObjectName("pushButton_2") 36 | 37 | self.retranslateUi(Dialog) 38 | QtCore.QMetaObject.connectSlotsByName(Dialog) 39 | 40 | def retranslateUi(self, Dialog): 41 | _translate = QtCore.QCoreApplication.translate 42 | Dialog.setWindowTitle(_translate("Dialog", "91下载器")) 43 | self.label.setText(_translate("Dialog", "线程数:")) 44 | self.label_2.setText(_translate("Dialog", "目标路径:")) 45 | self.pushButton.setText(_translate("Dialog", "修改")) 46 | self.pushButton_2.setText(_translate("Dialog", "退出")) 47 | -------------------------------------------------------------------------------- /ui/dialog4.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 434 10 | 85 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 20 20 | 10 21 | 54 22 | 12 23 | 24 | 25 | 26 | 线程数: 27 | 28 | 29 | 30 | 31 | 32 | 20 33 | 30 34 | 54 35 | 12 36 | 37 | 38 | 39 | 目标路径: 40 | 41 | 42 | 43 | 44 | 45 | 80 46 | 10 47 | 341 48 | 16 49 | 50 | 51 | 52 | 53 | 54 | 55 | 80 56 | 30 57 | 341 58 | 16 59 | 60 | 61 | 62 | 63 | 64 | 65 | 120 66 | 50 67 | 75 68 | 23 69 | 70 | 71 | 72 | 修改 73 | 74 | 75 | 76 | 77 | 78 | 240 79 | 50 80 | 75 81 | 23 82 | 83 | 84 | 85 | 退出 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ui/dialog5.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'dialog5.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(192, 81) 18 | self.label = QtWidgets.QLabel(Dialog) 19 | self.label.setGeometry(QtCore.QRect(60, 16, 81, 20)) 20 | self.label.setObjectName("label") 21 | self.pushButton = QtWidgets.QPushButton(Dialog) 22 | self.pushButton.setGeometry(QtCore.QRect(60, 40, 75, 23)) 23 | self.pushButton.setObjectName("pushButton") 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.label.setText(_translate("Dialog", "缓存清理成功")) 32 | self.pushButton.setText(_translate("Dialog", "确定")) 33 | -------------------------------------------------------------------------------- /ui/dialog5.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 192 10 | 81 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 60 20 | 16 21 | 81 22 | 20 23 | 24 | 25 | 26 | 缓存清理成功 27 | 28 | 29 | 30 | 31 | 32 | 60 33 | 40 34 | 75 35 | 23 36 | 37 | 38 | 39 | 确定 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ui/downloader_ui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import shutil 5 | from functools import partial 6 | 7 | from PyQt5 import QtGui 8 | from PyQt5.QtWidgets import QMainWindow, QDialog 9 | 10 | from common.rewriter import Rewriter 11 | from utils.path import make_path 12 | from common.multi_th_downloader import download_multi_thread 13 | from common.extracter import extrcat_url 14 | from ui.mainwindow import Ui_MainWindow 15 | from ui.dialog1 import Ui_Dialog as Ui_Dialog1 16 | from ui.dialog2 import Ui_Dialog as Ui_Dialog2 17 | from ui.dialog3 import Ui_Dialog as Ui_Dialog3 18 | from ui.dialog4 import Ui_Dialog as Ui_Dialog4 19 | from ui.dialog5 import Ui_Dialog as Ui_Dialog5 20 | # from ui.dialog6 import Ui_Dialog as Ui_Dialog6 21 | from config.config import th_number,output_path 22 | 23 | 24 | class MainWindow(QMainWindow, Ui_MainWindow): 25 | def __init__(self): 26 | super(MainWindow, self).__init__() 27 | self.setupUi(self) 28 | self.setWindowIcon(QtGui.QIcon("./ui/icon.png")) 29 | self.pushButton.clicked.connect(self.onclick_direct_download) 30 | self.pushButton_2.clicked.connect(self.onclick_setting) 31 | self.pushButton_3.clicked.connect(self.onclick_part_download) 32 | self.pushButton_4.clicked.connect(self.onclick_clean_cache) 33 | self.pushButton_5.clicked.connect(self.onclick_help) 34 | self.pushButton_6.clicked.connect(self.onclick_exit) 35 | 36 | def onclick_setting(self): 37 | dialog = Dialog4(self) 38 | dialog.lineEdit.setText(str(th_number)) 39 | dialog.lineEdit_2.setText(output_path) 40 | dialog.show() 41 | 42 | def onclick_clean_cache(self): 43 | tmp_dirs = os.listdir("./tmp/") 44 | base_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 45 | for dir in tmp_dirs: 46 | dir_path = os.path.join(base_path+"/tmp/", dir) 47 | shutil.rmtree(dir_path) 48 | dialog = Dialog5(self) 49 | dialog.show() 50 | 51 | def onclick_direct_download(self): 52 | # prompt = Dialog6(self) 53 | # prompt.show() 54 | input = self.textEdit.toPlainText() 55 | m3u8_list = extrcat_url(input) 56 | download_multi_thread(m3u8_list) 57 | # prompt.close() 58 | dialog = Dialog3(self) 59 | dialog.show() 60 | 61 | def onclick_help(self): 62 | dialog = Dialog2(self) 63 | dialog.show() 64 | 65 | def onclick_exit(self): 66 | self.close() 67 | 68 | def onclick_part_download(self): 69 | input = self.textEdit.toPlainText() 70 | m3u8_list = extrcat_url(input) 71 | dialog = Dialog1(parent=self, m3u8_list=m3u8_list) 72 | dialog.show() 73 | 74 | 75 | class Dialog1(QDialog, Ui_Dialog1): 76 | def __init__(self, m3u8_list, parent=None): 77 | super(Dialog1, self).__init__(parent) 78 | self.setupUi(self) 79 | self.setWindowIcon(QtGui.QIcon("./ui/icon.png")) 80 | self.pushButton.clicked.connect(partial(self.start_download, m3u8_list)) 81 | # self.setWindowFlags(Qt.FramelessWindowHint) 82 | 83 | def start_download(self, m3u8_list): 84 | self.close() 85 | # prompt = Dialog6(self) 86 | # prompt.show() 87 | download_multi_thread(m3u8_list) 88 | # prompt.close() 89 | dialog = Dialog3(self) 90 | dialog.show() 91 | 92 | 93 | class Dialog2(QDialog, Ui_Dialog2): 94 | def __init__(self, parent=None): 95 | super(Dialog2, self).__init__(parent) 96 | self.setupUi(self) 97 | self.setWindowIcon(QtGui.QIcon("./ui/icon.png")) 98 | self.pushButton.clicked.connect(self.close) 99 | 100 | 101 | class Dialog3(QDialog, Ui_Dialog3): 102 | def __init__(self, parent=None): 103 | super(Dialog3, self).__init__(parent) 104 | self.setupUi(self) 105 | self.setWindowIcon(QtGui.QIcon("./ui/icon.png")) 106 | self.pushButton.clicked.connect(self.close) 107 | 108 | 109 | class Dialog4(QDialog, Ui_Dialog4): 110 | def __init__(self, parent=None): 111 | super(Dialog4, self).__init__(parent) 112 | self.setupUi(self) 113 | self.setWindowIcon(QtGui.QIcon("./ui/icon.png")) 114 | self.pushButton.clicked.connect(self.change_settings) 115 | self.pushButton_2.clicked.connect(self.close) 116 | 117 | def change_settings(self): 118 | new_th_number = self.lineEdit.text() 119 | new_output_path = self.lineEdit_2.text() 120 | global th_number,output_path 121 | if new_th_number != th_number: 122 | th_number_re1 = "(th_number =) \d+" 123 | th_number_re2 = "\\1 " + new_th_number 124 | Rewriter.rewrite_config("./config/config.py",th_number_re1, th_number_re2) 125 | th_number = new_th_number 126 | if new_output_path != output_path: 127 | output_path_re1 = "(output_path =) '\w:.*'" 128 | output_path_re2 = "\\1 '" + new_output_path.replace('\\','/') + "'" 129 | Rewriter.rewrite_config("./config/config.py", output_path_re1, output_path_re2) 130 | output_path = new_output_path 131 | make_path(new_output_path) 132 | self.close() 133 | 134 | 135 | class Dialog5(QDialog, Ui_Dialog5): 136 | def __init__(self, parent=None): 137 | super(Dialog5, self).__init__(parent) 138 | self.setupUi(self) 139 | self.setWindowIcon(QtGui.QIcon("./ui/icon.png")) 140 | self.pushButton.clicked.connect(self.close) 141 | 142 | 143 | # class Dialog6(QDialog, Ui_Dialog6): 144 | # def __init__(self, parent=None): 145 | # super(Dialog6, self).__init__(parent) 146 | # self.setupUi(self) 147 | # self.setWindowIcon(QtGui.QIcon("./ui/icon.png")) 148 | # self.setWindowFlags(Qt.FramelessWindowHint) 149 | -------------------------------------------------------------------------------- /ui/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRWong/91porn-downloader/f7b75f0526a5c7c7fed607ba2af075571fec1731/ui/icon.png -------------------------------------------------------------------------------- /ui/mainwindow.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'mainwindow.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_MainWindow(object): 15 | def setupUi(self, MainWindow): 16 | MainWindow.setObjectName("MainWindow") 17 | MainWindow.resize(805, 604) 18 | self.centralwidget = QtWidgets.QWidget(MainWindow) 19 | self.centralwidget.setObjectName("centralwidget") 20 | self.gridLayout = QtWidgets.QGridLayout(self.centralwidget) 21 | self.gridLayout.setObjectName("gridLayout") 22 | self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget) 23 | self.pushButton_3.setObjectName("pushButton_3") 24 | self.gridLayout.addWidget(self.pushButton_3, 3, 1, 1, 1) 25 | self.pushButton_6 = QtWidgets.QPushButton(self.centralwidget) 26 | self.pushButton_6.setObjectName("pushButton_6") 27 | self.gridLayout.addWidget(self.pushButton_6, 3, 2, 1, 1) 28 | self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget) 29 | self.pushButton_2.setObjectName("pushButton_2") 30 | self.gridLayout.addWidget(self.pushButton_2, 2, 0, 1, 1) 31 | self.textEdit = QtWidgets.QTextEdit(self.centralwidget) 32 | self.textEdit.setObjectName("textEdit") 33 | self.gridLayout.addWidget(self.textEdit, 0, 0, 1, 3) 34 | self.pushButton_4 = QtWidgets.QPushButton(self.centralwidget) 35 | self.pushButton_4.setObjectName("pushButton_4") 36 | self.gridLayout.addWidget(self.pushButton_4, 2, 1, 1, 1) 37 | self.pushButton_5 = QtWidgets.QPushButton(self.centralwidget) 38 | self.pushButton_5.setObjectName("pushButton_5") 39 | self.gridLayout.addWidget(self.pushButton_5, 2, 2, 1, 1) 40 | self.pushButton = QtWidgets.QPushButton(self.centralwidget) 41 | self.pushButton.setObjectName("pushButton") 42 | self.gridLayout.addWidget(self.pushButton, 3, 0, 1, 1) 43 | MainWindow.setCentralWidget(self.centralwidget) 44 | self.menubar = QtWidgets.QMenuBar(MainWindow) 45 | self.menubar.setGeometry(QtCore.QRect(0, 0, 805, 23)) 46 | self.menubar.setObjectName("menubar") 47 | MainWindow.setMenuBar(self.menubar) 48 | self.statusbar = QtWidgets.QStatusBar(MainWindow) 49 | self.statusbar.setObjectName("statusbar") 50 | MainWindow.setStatusBar(self.statusbar) 51 | 52 | self.retranslateUi(MainWindow) 53 | QtCore.QMetaObject.connectSlotsByName(MainWindow) 54 | 55 | def retranslateUi(self, MainWindow): 56 | _translate = QtCore.QCoreApplication.translate 57 | MainWindow.setWindowTitle(_translate("MainWindow", "91下载器")) 58 | self.pushButton_3.setText(_translate("MainWindow", "省vpn流量下载")) 59 | self.pushButton_6.setText(_translate("MainWindow", "退出")) 60 | self.pushButton_2.setText(_translate("MainWindow", "设置")) 61 | self.pushButton_4.setText(_translate("MainWindow", "清除缓存")) 62 | self.pushButton_5.setText(_translate("MainWindow", "帮助")) 63 | self.pushButton.setText(_translate("MainWindow", "直接下载")) 64 | -------------------------------------------------------------------------------- /ui/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 805 10 | 604 11 | 12 | 13 | 14 | 91下载器 15 | 16 | 17 | 18 | 19 | 20 | 21 | 省vpn流量下载 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 | 0 69 | 0 70 | 805 71 | 23 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /utils/get_config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import re 4 | 5 | from utils.path import get_basedir_path 6 | 7 | def get_th_number(): 8 | with open(f'{get_basedir_path()}\config\config.py', 'r', encoding='utf-8') as f: 9 | config = f.read() 10 | return re.findall('th_number = (\d+)', config)[0] 11 | 12 | def get_output_path(): 13 | with open(f'{get_basedir_path()}\config\config.py', 'r', encoding='utf-8') as f: 14 | config = f.read() 15 | return re.findall('output_path = \'(.+)\'', config)[0] -------------------------------------------------------------------------------- /utils/log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRWong/91porn-downloader/f7b75f0526a5c7c7fed607ba2af075571fec1731/utils/log -------------------------------------------------------------------------------- /utils/log.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import logging.config 3 | import os 4 | 5 | LOGGING = { 6 | 'version': 1, 7 | 'disable_existing_loggers': False, 8 | 'formatters': { 9 | 'standard': { 10 | 'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' 11 | '[%(levelname)s][%(message)s]' 12 | }, 13 | 'simple': { 14 | 'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s' 15 | }, 16 | 'collect': { 17 | 'format': '%(asctime)s %(levelname)s %(message)s' 18 | } 19 | }, 20 | 21 | 'handlers': { 22 | # 打印到终端的日志 23 | 'console': { 24 | 'level': 'INFO', 25 | # 'filters': ['require_debug_true'], 26 | 'class': 'logging.StreamHandler', 27 | 'formatter': 'collect' 28 | }, 29 | # 打印到文件的日志:收集错误及以上的日志 30 | 'info': { 31 | 'level': 'info'.upper(), 32 | 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件 33 | 'filename': os.path.dirname(os.path.realpath(__file__)) + '/log', 34 | 'maxBytes': 1024 * 1024 * 50, # 日志大小 5M 35 | 'backupCount': 5, 36 | 'formatter': 'collect', 37 | 'encoding': 'utf-8', 38 | }, 39 | }, 40 | 41 | 'loggers': { 42 | '': { 43 | 'handlers': ['console','info'], 44 | 'level': 'DEBUG', 45 | 'propagate': True, 46 | }, 47 | }, 48 | } 49 | 50 | 51 | def logger(): 52 | logging.config.dictConfig(LOGGING) 53 | logger = logging.getLogger(__name__) 54 | return logger 55 | 56 | -------------------------------------------------------------------------------- /utils/path.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | 5 | from utils.log import logger 6 | from config.config import output_path 7 | 8 | log = logger() 9 | 10 | def get_basedir_path(): 11 | return os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 12 | 13 | def check_output_path(): 14 | if not os.path.exists(output_path): 15 | log.info(f'Make output dir {output_path}') 16 | make_path(output_path) 17 | 18 | def make_path(path): 19 | path_tuple = os.path.split(path) 20 | if not os.path.exists(path_tuple[0]): 21 | make_path(path_tuple[0]) 22 | try: 23 | os.mkdir(path) 24 | except FileExistsError: 25 | pass --------------------------------------------------------------------------------