diff --git a/README.md b/README.md index 50478b4..207b79c 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,13 @@ 详细信息和用法请见 [https://mooc.xoy.io/](https://mooc.xoy.io/)。 +### m3u8下载脚本使用 +依赖系统ffmpeg, 需要先安装ffmpeg, 在Ubuntu下已测试。 + +```sh +./download_m3u8.py -l 学术规范与论文写作/Videos/Videos.txt +``` + ### 声明 仅限个人学习和研究使用,切勿用于其他用途。强烈建议到 MOOC 网站进行学习,本程序只是提供一个备选方案。 diff --git a/download_m3u8.py b/download_m3u8.py new file mode 100644 index 0000000..e8ef15c --- /dev/null +++ b/download_m3u8.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +import os +import sys +import argparse + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Download m3u8 to mp4') + parser.add_argument('-l', default=r'', help='视频列表') + parser.add_argument('-d', default=r'', help='下载目录') + args = parser.parse_args() + video_list = os.path.abspath(args.l) + + if args.d == '': + video_dir = os.path.dirname(video_list) + else: + video_dir = os.path.abspath(args.d) + + with open(video_list) as f: + lines = f.readlines() + lines = [_.strip() for _ in lines if _ != '\n'] + for line in lines: + name,url = line.split('\t') + print(name,url) + os.system('ffmpeg -i "{}" -c copy -bsf:a aac_adtstoasc "{}"'\ + .format(url,os.path.join(video_dir,name))) \ No newline at end of file diff --git a/mooc/icourse163.py b/mooc/icourse163.py index 27c8514..2b1a084 100644 --- a/mooc/icourse163.py +++ b/mooc/icourse163.py @@ -3,6 +3,7 @@ import time from .utils import * +import json CANDY = Crawler() CONFIG = {} @@ -11,21 +12,21 @@ def get_summary(url): """从课程主页面获取信息""" + res = CANDY.get(url) + cookie_csrf = res.cookies.get('NTESSTUDYSI') + res_t = res.text - res = CANDY.get(url).text - - term_id = re.search(r'termId : "(\d+)"', res).group(1) - names = re.findall(r'name:"(.+)"', res) + term_id = re.search(r'termId : "(\d+)"', res_t).group(1) + names = re.findall(r'name:"(.+)"', res_t) dir_name = course_dir(*names[:2]) print(dir_name) - return term_id, dir_name + return term_id, dir_name, cookie_csrf def parse_resource(resource): """解析资源地址和下载资源""" - post_data = {'callCount': '1', 'scriptSessionId': '${scriptSessionId}190', 'httpSessionId': '5531d06316b34b9486a6891710115ebc', 'c0-scriptName': 'CourseBean', 'c0-methodName': 'getLessonUnitLearnVo', 'c0-id': '0', 'c0-param0': 'number:' + resource.meta[0], @@ -34,21 +35,28 @@ def parse_resource(resource): res = CANDY.post('https://www.icourse163.org/dwr/call/plaincall/CourseBean.getLessonUnitLearnVo.dwr', data=post_data).text + file_name = resource.file_name if resource.type == 'Video': - resolutions = ['Shd', 'Hd', 'Sd'] - for sp in resolutions[CONFIG['resolution']:]: - # TODO: 增加视频格式选择 - # video_info = re.search(r'%sUrl="(?P.*?(?P\.((m3u8)|(mp4)|(flv))).*?)"' % sp, res) - video_info = re.search(r'(?Pmp4)%sUrl="(?P.*?\.(?P=ext).*?)"' % sp, res) - if video_info: - url, ext = video_info.group('url', 'ext') - ext = '.' + ext - break - res_print(file_name + ext) - FILES['renamer'].write(re.search(r'(\w+\.((m3u8)|(mp4)|(flv)))', url).group(1), file_name, ext) - FILES['video'].write_string(url) - resource.ext = ext + post_data = {'bizId': resource.meta[2], + 'bizType': '1', + 'contentType': '1'} + url = 'https://www.icourse163.org/web/j/resourceRpcBean.getResourceToken.rpc?csrfKey={}'.format(CONFIG['_cookie_csrf']) + res = CANDY.post(url,data=post_data).text + res_json = json.loads(res) + if res_json.get('code', -1) != 0: + print('get signature error.') + return + sig = res_json['result']['videoSignDto']['signature'] + + url = 'https://vod.study.163.com/eds/api/v1/vod/video?videoId={}&signature={}&clientType=1'.format(resource.meta[0], sig) + res = CANDY.get(url).text + res_json = json.loads(res) + url = res_json['result']['videos'][-1]['videoUrl'] + name = res_json['result']['name'] + FILES['video'].write_string('{}\t{}'.format(name,url)) + res_print('{}\t{}'.format(name, url)) + if not CONFIG['sub']: return @@ -165,7 +173,8 @@ def start(url, config): global WORK_DIR CONFIG.update(config) - term_id, dir_name = get_summary(url) + term_id, dir_name, cookie_csrf = get_summary(url) + CONFIG['_cookie_csrf'] = cookie_csrf WORK_DIR = WorkingDir(CONFIG['dir'], dir_name) WORK_DIR.change('Videos')