├── README.md └── m3u8.py /README.md: -------------------------------------------------------------------------------- 1 | # m3u8-Downloader 2 | Download m3u8 to ts with multithreading 3 | 4 | # How to use 5 | ``` 6 | downloader = Downloader(50) 7 | downloader.run('http://m3u8.test.com/test.m3u8', '/home/video/') 8 | ``` 9 | -------------------------------------------------------------------------------- /m3u8.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | 3 | from gevent import monkey 4 | monkey.patch_all() 5 | from gevent.pool import Pool 6 | import gevent 7 | import requests 8 | import urlparse 9 | import os 10 | import time 11 | 12 | class Downloader: 13 | def __init__(self, pool_size, retry=3): 14 | self.pool = Pool(pool_size) 15 | self.session = self._get_http_session(pool_size, pool_size, retry) 16 | self.retry = retry 17 | self.dir = '' 18 | self.succed = {} 19 | self.failed = [] 20 | self.ts_total = 0 21 | 22 | def _get_http_session(self, pool_connections, pool_maxsize, max_retries): 23 | session = requests.Session() 24 | adapter = requests.adapters.HTTPAdapter(pool_connections=pool_connections, pool_maxsize=pool_maxsize, max_retries=max_retries) 25 | session.mount('http://', adapter) 26 | session.mount('https://', adapter) 27 | return session 28 | 29 | def run(self, m3u8_url, dir=''): 30 | self.dir = dir 31 | if self.dir and not os.path.isdir(self.dir): 32 | os.makedirs(self.dir) 33 | 34 | r = self.session.get(m3u8_url, timeout=10) 35 | if r.ok: 36 | body = r.content 37 | if body: 38 | ts_list = [urlparse.urljoin(m3u8_url, n.strip()) for n in body.split('\n') if n and not n.startswith("#")] 39 | ts_list = zip(ts_list, [n for n in xrange(len(ts_list))]) 40 | if ts_list: 41 | self.ts_total = len(ts_list) 42 | print self.ts_total 43 | g1 = gevent.spawn(self._join_file) 44 | self._download(ts_list) 45 | g1.join() 46 | else: 47 | print r.status_code 48 | 49 | def _download(self, ts_list): 50 | self.pool.map(self._worker, ts_list) 51 | if self.failed: 52 | ts_list = self.failed 53 | self.failed = [] 54 | self._download(ts_list) 55 | 56 | def _worker(self, ts_tuple): 57 | url = ts_tuple[0] 58 | index = ts_tuple[1] 59 | retry = self.retry 60 | while retry: 61 | try: 62 | r = self.session.get(url, timeout=20) 63 | if r.ok: 64 | file_name = url.split('/')[-1].split('?')[0] 65 | print file_name 66 | with open(os.path.join(self.dir, file_name), 'wb') as f: 67 | f.write(r.content) 68 | self.succed[index] = file_name 69 | return 70 | except: 71 | retry -= 1 72 | print '[FAIL]%s' % url 73 | self.failed.append((url, index)) 74 | 75 | def _join_file(self): 76 | index = 0 77 | outfile = '' 78 | while index < self.ts_total: 79 | file_name = self.succed.get(index, '') 80 | if file_name: 81 | infile = open(os.path.join(self.dir, file_name), 'rb') 82 | if not outfile: 83 | outfile = open(os.path.join(self.dir, file_name.split('.')[0]+'_all.'+file_name.split('.')[-1]), 'wb') 84 | outfile.write(infile.read()) 85 | infile.close() 86 | os.remove(os.path.join(self.dir, file_name)) 87 | index += 1 88 | else: 89 | time.sleep(1) 90 | if outfile: 91 | outfile.close() 92 | 93 | if __name__ == '__main__': 94 | downloader = Downloader(50) 95 | downloader.run('http://m3u8.test.com/test.m3u8', '/home/video/') 96 | --------------------------------------------------------------------------------