简单的m3u8-Downloader

Posted by

Linux使用的m3u8下载器,为了下载剧集方便写了批量命名。
.\run.py

  1. import downloader
  2. import requests
  3. import json
  4.  
  5. cfg = {
  6.     "maxPoolsize": 32,
  7.     "workDir": '/home/huxiaohong/webdl/',
  8.     "tempDir": '/home/huxiaohong/webdl/temp/',
  9. }
  10.  
  11. if __name__ == "__main__":
  12.     mod = input("请输入工作模式(剧集E/单集S/腾讯超前点播C):")
  13.     if mod == "E":
  14.         title = input("请输入剧集通名,用*代替集数(无需后缀):")
  15.         while True:
  16.             url = input("请输入m3u8地址:\n")
  17.             epi = input("请输入集数:\n")
  18.             name = title.replace("*", epi)
  19.             dl = downloader.Downloader(cfg["maxPoolsize"])
  20.             dl.run(url, cfg["tempDir"], cfg["workDir"], name)
  21.     elif mod == "S":
  22.         while True:
  23.             url = input("请输入m3u8地址:\n")
  24.             title = input("请输入要保存的文件名(无需后缀):\n")
  25.             dl = downloader.Downloader(cfg["maxPoolsize"])
  26.             dl.run(url, cfg["tempDir"], cfg["workDir"], title)
  27.     elif mod == "C":
  28.         title = input("请输入剧集通名,用*代替集数(无需后缀):")
  29.         while True:
  30.             url = input("请输入视频播放页地址:\n")
  31.             epi = input("请输入集数:\n")
  32.             vid = url.split("/")[-1].split(".")[0]
  33.             body = main(vid)
  34.             name = title.replace("*", epi)
  35.             dl = downloader.Downloader(cfg["maxPoolsize"])
  36.             dl.crun(body, cfg["tempDir"], cfg["workDir"], name)
  37.     else:
  38.         print("输入有误,退出脚本。")

downloader.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. from urllib.parse import urlparse, urljoin
  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.wdir = ''
  18.         self.tdir = ''
  19.         self.succed = {}
  20.         self.failed = []
  21.         self.ts_total = 0
  22.  
  23.     def _get_http_session(self, pool_connections, pool_maxsize, max_retries):
  24.             session = requests.Session()
  25.             adapter = requests.adapters.HTTPAdapter(pool_connections=pool_connections, pool_maxsize=pool_maxsize, max_retries=max_retries)
  26.             session.mount('http://', adapter)
  27.             session.mount('https://', adapter)
  28.             return session
  29.  
  30.     def run(self, m3u8_url, tempdir='', workdir='', fname='test'):
  31.         self.wdir = workdir
  32.         self.tdir = tempdir
  33.         self.fname = fname+ '.mp4'
  34.         if self.wdir and not os.path.isdir(self.wdir):
  35.             os.makedirs(self.wdir)
  36.         if self.tdir and not os.path.isdir(self.tdir):
  37.             os.makedirs(self.tdir)
  38.         r = self.session.get(m3u8_url, timeout=10)
  39.         if r.ok:
  40.             body = r.text
  41.             if body:
  42.                 ts_list = [urljoin(m3u8_url, n.strip()) for n in body.split('\n') if n and not n.startswith("#")]
  43.                 total_sum = len(ts_list)
  44.                 ts_list = zip(ts_list, [n for n in range(len(ts_list))])
  45.                 if ts_list:
  46.                     self.ts_total = total_sum
  47.                     self._download(ts_list)
  48.         else:
  49.             print(r.status_code)
  50.         self._muxing()
  51.  
  52.     def crun(self, m3u8, tempdir='', workdir='', fname='test'):
  53.         self.wdir = workdir
  54.         self.tdir = tempdir
  55.         self.fname = fname+ '.mp4'
  56.         if self.wdir and not os.path.isdir(self.wdir):
  57.             os.makedirs(self.wdir)
  58.         if self.tdir and not os.path.isdir(self.tdir):
  59.             os.makedirs(self.tdir)
  60.         body = m3u8
  61.         if body:
  62.             ts_list = [n.strip() for n in body.split('\n') if n and not n.startswith("#")]
  63.             total_sum = len(ts_list)
  64.             ts_list = zip(ts_list, [n for n in range(len(ts_list))])
  65.             if ts_list:
  66.                 self.ts_total = total_sum
  67.                 self._download(ts_list)
  68.         else:
  69.             print("Crun Error!")
  70.         self._muxing()
  71.  
  72.     def _muxing(self):
  73.         os.chdir(self.tdir)
  74.         concat_li = ""
  75.         for i in range(self.ts_total):
  76.             concat_li += str(i) + ".ts|"
  77.         cmd= "ffmpeg -i concat:\""+ concat_li.rstrip("|")+ "\" -c copy \""+ os.path.join(self.wdir, self.fname)+ "\""
  78.         os.system(cmd)
  79.         os.system("rm ./*")
  80.         os.system("ffmpeg -i \""+ os.path.join(self.wdir, self.fname)+ "\"")
  81.  
  82.     def _download(self, ts_list):
  83.         self.pool.map(self._worker, ts_list)
  84.         if self.failed:
  85.             ts_list = self.failed
  86.             self.failed = []
  87.             self._download(ts_list)
  88.  
  89.     def _worker(self, ts_tuple):
  90.         url = ts_tuple[0]
  91.         index = ts_tuple[1]
  92.         retry = self.retry
  93.         while retry:
  94.             try:
  95.                 r = self.session.get(url, timeout=20)
  96.                 if r.ok:
  97.                     file_name = str(index)+ ".ts"
  98.                     print("Downloading:%d/%d " % (index, self.ts_total)+ file_name)
  99.                     with open(os.path.join(self.tdir, file_name), 'wb') as f:
  100.                         f.write(r.content)
  101.                     self.succed[index] = file_name
  102.                     return
  103.             except:
  104.                 retry -= 1
  105.         print('[FAIL]%s' % url)
  106.         self.failed.append((url, index))
  107.  
  108.  
  109. if __name__ == '__main__':
  110.     downloader = Downloader(32)
  111.     downloader.run('http://cache.m.iqiyi.com/mus/text/239741901/3f9cd3606562f0f28e8a7897cc4f9a0d/afbe8fd3d73448c9/1402186353/20200320/af/ec/5defd99d5813b899b49754f0d46114e3.m3u8?qd_originate=tmts_py&tvid=14152064500&bossStatus=0&qd_vip=0&px=d63lRWMeJM9dTe7ncU0507bsGFwTXqJhTbN5qHjZctYDqIh4bhJpWBVuwDLQiLY33ua9&src=02022001010000000000&prv=&previewType=&previewTime=&from=&qd_time=1585195697126&qd_p=3cffa0e6&qd_asc=1371879cfc0d1f4a80b552a59d792ade&qypid=14152064500_04000000001000000000_96&qd_k=0bab9b8397d2fcb91a2d694d34ae478f&isdol=0&code=2&ff=f4v&iswb=0&vf=79c02fa5c7b17bfaf8e5935aa015f294&np_tag=nginx_part_tag', 'E:/N_m3u8DL-CLI/temp/', 'E:/N_m3u8DL-CLI/', "123456")

运行

python3 run.py

目前使用时,爱奇艺合并完成后有时会出现帧率不对,所以最后用ffmpeg输出了一下视频信息,如果不一致的话需要重新下载,腾讯视频不存在这个问题。优酷aes-cbc加密的有空再继续写。

Leave a Reply

电子邮件地址不会被公开。 必填项已用*标注