├── .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 |