├── LICENSE ├── README.md └── main.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Alan_Wanco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple_Eplus_DRM_DL 2 | 一个配合N_m3u8DL-RE简单下载eplusDRM视频的工具 3 | ## 快速开始 4 | ``` 5 | eplus_drm_archive_download.exe --url-mpd --cookie-mpd --auth 6 | ``` 7 | ## Release内bat脚本用法: 8 | 打开`运行我.bat`依次输入`mpd地址`、`mpd对应的cookie`和`auth验证的url` 9 | ## 注意!!: 10 | * 需要提前安装好微软的vc库才能正常使用mp4decrypt,N_m3u8DL-RE也有一些环境要求 11 | * 解压路径里不能有非英文字符否则解码mp4decrypt可能会报错 12 | 13 | ## `mpd地址`、`mpd对应的cookie`和`auth验证token`的获取方法: 14 | 1. 用记事本打开`find_mpd.js`,全部复制内容 15 | 2. 用edge打开武士道系live的eplus网页,按F12进入开发者工具,在上面一行菜单栏里寻找`控制台`栏,点进去 16 | 3. 随便找个地方右键`清除控制台`,然后输入脚本内容的时候可能会要你输入一行文字才能让你粘贴脚本内容,输入就行 17 | 4. 粘贴find_mpd.js的内容后回车,会出现一行`https://vod.live.eplus.jp/out/v1/`开头的链接,右键复制,这就是`mpd地址` 18 | 19 | 5. 把开发者工具从`控制台`调到`网络`一栏,在网络一栏下面有一栏,(里面可能有保留日志禁用缓存之类的那一行),找到前面icon里的`启用筛选器`和后面有文字说明的`禁用缓存`,这两个全部点上 20 | 6. 在筛选器里输入mpd,然后切换回eplus网页本身,把复制过来的`https://vod.live.eplus.jp/out/v1/`开头的链接粘贴进`eplus网页`的`地址栏` 21 | 7. 这时候你的浏览器应该会提示你下载好了mpd文件,不管它,点开`开发者工具`-`网络`这时候下面可能有一个mpd文件的行,名称和`https://vod.live.eplus.jp/out/v1/`开头链接的结尾是一致的,点击那一行,出现资源详情 22 | 8. 点击`标头`一栏,找到`响应标头`下的Cookie一行,把Cookie一栏下那么长的内容( 23 | `CloudFront-Key-Pair-Id`开头的那些,不包括"Cookie")全部复制,这样我们就得到了`mpd对应的cookie` 24 | 25 | 9、接下来继续在开发者工具这里,把筛选框内的「mpd」删除,换成「drm」,如果这里出不来东西的话,就保持这个筛选框不变,切回网页后点击刷新,同时点击播放网页回放,这时候才会出现几条内容,我们点击「get_auth_token_drm?...」开头的那段,复制这段的url地址,这样我们就有了`auth验证的url`。 26 | 27 | 10. 回到最开头,打开运行我.bat,分别输入`mpd地址`、`mpd对应的cookie`和`auth验证的url` 28 | # Tips 29 | * cookie很长,三个字符串都可以找个记事本记一下,复制的时候注意一下前后不要带空格,cookie结尾不要带分号 30 | * Cookie的过期时间是一小时,token的过期时间最短只有几分钟,尽量快速下载 31 | * eplus不用挂代理,有可能出问题的地方大概也就auth验证那里,出问题的话Cookie和Auth多刷新几次试试。 32 | * **以及cookie一小时刷新一次,其他两个字符串都不需要重新获取,记得开个记事本记录一下就行。** 33 | * 程序跑完就下完了,记得文件夹结构最好不要动,eplus_drm_archive_download.exe、N_m3u8DL-RE.exe、mp4decrypt.exe、ffmpeg.exe、google_aosp_on_ia_emulator_14.0.0_9389cec2_4464_l3和运行我.bat这六个一定要在同一个文件夹内。 34 | * 最后合并音视频文件时如果出现大量WARN报错(如下图),检查mp4decrypt.exe的路径是否存在非英文字符或者mp4decrypt.exe的依赖是否正常安装 35 | ![f325c1fe9adc267bd18b29490d421680](https://github.com/AlanWanco/Simple_Eplus_DRM_DL/assets/45628961/2d161d6c-d187-41c6-ad7e-606642dfa242) 36 | 37 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import base64, re, requests, os, argparse, subprocess 2 | from datetime import datetime 3 | from pywidevine.cdm import Cdm 4 | from pywidevine.device import Device 5 | from pywidevine.pssh import PSSH 6 | 7 | parser = argparse.ArgumentParser(description='eplusDRM下载') 8 | parser.add_argument('--url-mpd', help='推流的mpd地址', required=True, type=str)# args.url_mpd 9 | parser.add_argument('--cookie-mpd', help='输入推流MPD的cookie', required=True, type=str)# args.cookie_mpd 10 | parser.add_argument('--auth', '-a', help='输入推流开始后可以获取的token', required=True, type=str) 11 | args = parser.parse_args() 12 | 13 | url_mpd = args.url_mpd 14 | cookies_mpd = args.cookie_mpd 15 | auth_token = args.auth 16 | 17 | cookies_dict_mpd = {key: value for key, value in (pair.split('=') for pair in cookies_mpd.split(';'))} 18 | 19 | api_url = "https://cdrm-project.com/api" 20 | license_url = "https://lic.drmtoday.com/license-proxy-widevine/cenc/?specConform=true" 21 | headers = {'accept': '"*/*"','content-length': '"316"','Connection': 'keep-alive','X-Dt-Auth-Token': auth_token} 22 | 23 | MATCH_IV = (r'IV=(?P.*?)(?=\n|$)') 24 | MATCH_STREAM = 'https://vod.live.eplus.jp/out/v1/(?P.*?)/' 25 | MATCH_UUID = r"""cenc:default_KID=\"(?P.*?)\"""" 26 | 27 | formatted_datetime = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") 28 | path = os.getcwd() 29 | file = f'eplus_drm_{formatted_datetime}' 30 | device = Device.load(r".\google_aosp_on_ia_emulator_14.0.0_9389cec2_4464_l3.wvd") 31 | 32 | def get_key(): 33 | pssh_cdm = PSSH(pssh) 34 | cdm = Cdm.from_device(device) 35 | session_id = cdm.open() 36 | challenge = cdm.get_license_challenge(session_id, pssh_cdm) 37 | licence = requests.post(license_url, headers = headers, data=challenge) 38 | if licence.status_code == 200: 39 | cdm.parse_license(session_id, licence.content) 40 | keys = cdm.get_keys(session_id) 41 | if len(keys) > 1: 42 | second_key = keys[1] 43 | key_fin = second_key.kid.hex +':'+ second_key.key.hex() 44 | print("获得key值:", key_fin) 45 | else: 46 | print('token已过期!') 47 | cdm.close(session_id) 48 | if key_fin: 49 | return key_fin 50 | 51 | def get_pssh(keyId): 52 | array_of_bytes = bytearray(b'\x00\x00\x008pssh\x00\x00\x00\x00') 53 | array_of_bytes.extend(bytes.fromhex('edef8ba979d64acea3c827dcd51d21ed')) #是谷歌drm的 system id,不用改 54 | array_of_bytes.extend(b'\x00\x00\x00\x18\x12\x10') 55 | array_of_bytes.extend(bytes.fromhex(keyId.replace('-', ''))) 56 | array_of_bytes.extend(b'H\xe3\xdc\x95\x9b\x06') 57 | return base64.b64encode(bytes.fromhex(array_of_bytes.hex())) 58 | 59 | def createpsshfromkid(kid): 60 | kid = kid.replace('-', '') 61 | if len(kid) == 32 and isinstance(kid, bytes): 62 | raise AssertionError('Wrong KID length') 63 | return get_pssh(kid).decode('utf-8') 64 | 65 | def find_base(match, text): 66 | result = re.search(match, text) 67 | print('获得base值:', result.group("base")) 68 | return result.group("base") 69 | 70 | def mpd_download(file, path, cookies_mpd, url_mpd): 71 | print('=======调用N_m3u8DL-RE下载回放=======') 72 | command = fr'.\N_m3u8DL-RE --save-name "{file}" --save-dir "{path}" --download-retry-count 5 --auto-select --thread-count 16 --mux-after-done format=mp4 --check-segments-count --ffmpeg-binary-path .\ffmpeg.exe -H "Cookie: {cookies_mpd}" -mt --del-after-done --key "{mpd_key}" --decryption-binary-path .\mp4decrypt.exe "{url_mpd}"' 73 | # print(command) 74 | subprocess.call(command) 75 | 76 | if __name__ == "__main__": 77 | try: 78 | res = requests.get(url_mpd, cookies=cookies_dict_mpd) 79 | if res.status_code==200: 80 | mpd_base = find_base(MATCH_STREAM, url_mpd) 81 | m = re.search(MATCH_UUID, res.text) 82 | if m: 83 | uuid = m.group("mpd_url") 84 | print("获得UUID", uuid) 85 | pssh = createpsshfromkid(uuid) 86 | print('获得PSSH值', pssh) 87 | mpd_key = get_key() 88 | else: 89 | print('未能访问MPD地址') 90 | 91 | if mpd_key: 92 | mpd_download(file, path, cookies_mpd, url_mpd) 93 | else: 94 | print('没有返回正确的key!检查auth token是否有问题') 95 | 96 | except Exception as e: 97 | print(e) 98 | --------------------------------------------------------------------------------