首页
影视
壁纸
留言
关于
今日热榜
推荐
音乐解锁
听歌
阅读器
听歌2
小乞丐
摸鱼
Search
1
Navicat Premium for Mac 中文破解版 (强大的数据库管理工具)
9,629 阅读
2
植物大战僵尸中文版 for Mac (兼容 M1系统) 中文版
5,767 阅读
3
CleanMyMac X 4.15.2 中文破解版 (Mac优化清理工具)
3,296 阅读
4
PDF Expert 中文破解版 (好用的PDF编辑器)
3,044 阅读
5
Parallels Desktop 17 中文版 (PD虚拟机无限试用版本)
2,818 阅读
生活杂记
macOS
编程技术
奇技淫巧
音乐
视频
δ
Search
标签搜索
python
漫画
php
mac
redis
mysql
mac 软件
macOS
音乐
极客爱情
数据库
游戏
吊打面试官
2.0
面试
罗小黑
linux
纯音乐
使用教程
B站
Kain
累计撰写
210
篇文章
累计收到
26
条评论
首页
栏目
生活杂记
macOS
编程技术
奇技淫巧
音乐
视频
δ
页面
影视
壁纸
留言
关于
今日热榜
推荐
音乐解锁
听歌
阅读器
听歌2
小乞丐
摸鱼
搜索到
40
篇与
的结果
保利威视 加密视频 分析下载
2022年08月07日
205 阅读
0 评论
0 点赞
2022-08-07
各位看官看的懂就看,看不懂别问我,不知道会不会被河蟹
计算Charles激活码(GO版本)
2021年07月06日
227 阅读
0 评论
0 点赞
2021-07-06
Charles 是一款HTTP代理服务器/HTTP监视器/反向代理服务器,当程序通过Charles的代理访问互联网时,Charles可以监控这个程序发送和接收的所有数据。下面奉上注册码生成方法。Let`s go.
2021-02-06
支付宝五福53张自动领取程序
支付宝联合其他52个产品推出送五福的活动,用pyhton简单写了一个自动连续领取的程序,只需要每隔60s把手机的验证码输入即可。
2021年02月06日
66 阅读
0 评论
0 点赞
2020-12-22
按日落时间执行小米刷步
按日落时间执行小米刷步
2020年12月22日
146 阅读
0 评论
0 点赞
2020-11-26
Jetbrains系列产品重置试用方法
该文复制于 https://zhile.io/ 的站长大佬。[原文链接],留存以作个备份。之前的使用的破解 jar 包也是魔改于大佬弃坑的作品 jetbrains-agent 项目。jetbrains-agent 于11-26日失效,因为 zhile.io 站长大佬被 JB 公司盯上了所以不再继续开发相应破解工具,所以你懂的。文章内容正式开始本站惯例:本文假定你知道Jetbrains家的产品。不知道可以问问搜索引擎。没错,jetbrains-agent这个项目停止了。市面上漫天飞的各种最新都是其他大神的魔改版本。[/斜眼]我不是要专门写个博文来说明jetbrains-agent项目已经停止,然后缅怀感叹一番。这篇文章是想和大家聊聊另一种思路。0x0. 项目背景Jetbrains家的产品有一个很良心的地方,他会允许你试用30天(这个数字写死在代码里了)以评估是否你真的需要为它而付费。但很多时候会出现一种情况:IDE并不能按照我们实际的试用时间来计算。我举个例子:如果我们开始了试用,然后媳妇生孩子要你回去陪产!陪产时我们并无空闲对IDE试用评估,它依旧算试用时间。(只是举个例子,或许你并没有女朋友)发现了吗?你未能真的有30天来对它进行全面的试用评估,你甚至无法作出是否付费的决定。此时你会想要延长试用时间,然而Jetbrains并未提供相关功能,该怎么办?事实上有一款插件可以实现这个功能,你或许可以用它来重置一下试用时间。但切记不要无休止的一直试用,这并不是这个插件的本意!0x1. 如何安装1). 插件市场安装:在Settings/Preferences... -> Plugins 内手动添加第三方插件仓库地址:https://plugins.zhile.io搜索:IDE Eval Reset插件进行安装。如果搜索不到请注意是否做好了上一步?网络是否通畅?插件会提示安装成功。2). 下载安装:点击这个链接(v2.1.13)下载插件的zip包(macOS可能会自动解压,然后把zip包丢进回收站)通常可以直接把zip包拖进IDE的窗口来进行插件的安装。如果无法拖动安装,你可以在Settings/Preferences... -> Plugins 里手动安装插件(Install Plugin From Disk...)插件会提示安装成功。0x2. 如何使用一般来说,在IDE窗口切出去或切回来时(窗口失去/得到焦点)会触发事件,检测是否长时间(25天)没有重置,给通知让你选择。(初次安装因为无法获取上次重置时间,会直接给予提示)也可以手动唤出插件的主界面:如果IDE没有打开项目,在Welcome界面点击菜单:Get Help -> Eval Reset如果IDE打开了项目,点击菜单:Help -> Eval Reset唤出的插件主界面中包含了一些显示信息,2个按钮,1个勾选项:按钮:Reload 用来刷新界面上的显示信息。按钮:Reset 点击会询问是否重置试用信息并重启IDE。选择Yes则执行重置操作并重启IDE生效,选择No则什么也不做。(此为手动重置方式)勾选项:Auto reset before per restart 如果勾选了,则自勾选后每次重启/退出IDE时会自动重置试用信息,你无需做额外的事情。(此为自动重置方式)0x3. 如何更新1). 插件更新机制(推荐):IDE会自行检测其自身和所安装插件的更新并给予提示。如果本插件有更新,你会收到提示看到更新日志,自行选择是否更新。点击IDE的Check for Updates... 菜单手动检测IDE和所安装插件的更新。如果本插件有更新,你会收到提示看到更新日志,自行选择是否更新。插件更新可能会需要重启IDE。2). 手动更新:从本页面下载最新的插件zip包安装更新。参考本文:下载安装小节。插件更新需要重启IDE。0x4. 一些说明本插件默认不会显示其主界面,如果你需要,参考本文:如何使用小节。市场付费插件的试用信息也会一并重置。对于某些付费插件(如:Iedis 2,`MinBatis)来说,你可能需要去取掉javaagent`配置(如果有)后重启IDE:如果IDE没有打开项目,在Welcome界面点击菜单:Configure -> Edit Custom VM Options... -> 移除 -javaagent: 开头的行。如果IDE打开了项目,点击菜单:Help -> Edit Custom VM Options... -> 移除 -javaagent: 开头的行。重置需要重启IDE生效!重置后并不弹出Licenses对话框让你选择输入License或试用,这和之前的重置脚本/插件不同(省去这烦人的一步)。如果长达25天不曾有任何重置动作,IDE会有通知询问你是否进行重置。如果勾选:Auto reset before per restart ,重置是静默无感知的。简单来说:勾选了Auto reset before per restart则无需再管,一劳永逸。0x5. 开源信息插件是学习研究项目,源代码是开放的。源码仓库地址:Gitee。如果你有更好的想法,欢迎给我提Pull Request来共同研究完善。插件源码使用:GPL-2.0开源协议发布。插件使用PHP编写,毕竟PHP是世界上最好的编程语言!0x6. 支持的产品IntelliJ IDEAAppCodeCLionDataGripGoLandPhpStormPyCharmRiderRubyMineWebStorm
2020年11月26日
210 阅读
0 评论
0 点赞
2020-10-10
【Python]】基于Python3实现的m3u8批量下载器 解密&合并&多线程 (开车新姿势~)
一、前言闲来无聊写的m3u8批量下载器,实现了多线程下载、AES常规解密、合并、批量下载四大功能。二、m3u8概述M3U8 是 Unicode 版本的 M3U,用 UTF-8 编码。"M3U" 和 "M3U8" 文件都是苹果公司使用的 HTTP Live Streaming(HLS) 协议格式的基础,HLS 的工作原理是把整个流分成一个个小的基于 HTTP 的文件来下载,每次只下载一些。当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的网络速率,所以广泛用于在线视频的播放、传输。一个m3u8文件主要由信息头(记录版本、是否加密、key的位置)、ts流列表两个部分构成。下面是常见的两类m3u8文件。m3u8表现形式1(假设m3u8文件的url=https://www.xxx.com/yyy/zzz/index.m3u8)这种m3u8链接对应的视频可能有多种分辨率,比如下面这个例子只有720x480这个分辨率,对应的相对url为1000kb/hls/index.m3u8,是一个相对路径,720x480分辨率的视频绝对路径就是https://www.xxx.com/yyy/zzz/1000kb/hls/index.m3u8,需要再次访问这个链接下载这个分辨率对应的m3u8文件。#EXTM3U #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1000000,RESOLUTION=20x480 000kb/hls/index.m3u8m3u8表现形式2(假设m3u8文件的url=https://www.xxx.com/yyy/zzz/index.m3u8)这种m3u8已经把ts列表直接放进来了,所以下载所有的ts流组合即可得到视频的全部内容。注意第5行,注明了该视频进行了加密,加密方式为AES-128(默认是CBC模式),加密key的路径为key.key,是一个相对路径,套上基路径就是https://www.xxx.com/yyy/zzz/key.key,(注:key也可能这个key的url是一个http开头的绝对路径)。有的m3u8可能还在这一行加一个IV,也就是AES-CBC加密、解密中的IV。下面的例子中ts也是相对路径,同样需要加上基路径,比如第1个ts的绝对路径就是https://www.xxx.com/yyy/zzz/QxiMvI3688000.ts#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:1 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-KEY:METHOD=AES-128,URI="key.key" #EXTINF:0.834167, QxiMvI3688000.ts #EXTINF:0.834167, QxiMvI3688001.ts #EXTINF:0.834167, QxiMvI3688002.ts #EXTINF:0.834167, QxiMvI3688003.ts #EXTINF:0.834167, QxiMvI3688004.ts #EXTINF:0.834167, QxiMvI3688005.ts ...三、基于Python3实现的m3u8批量下载器(解密&多线程&合并)1、下载思路经过简单的分析m3u8协议及其文件格式,现在只要把他们串起来就好了。①、下载m3u8文件,如果其内容的表示形式是第1种,则还需要再次访问对应的分辨率的url,重新下载m3u8 ②、解析m3u8,判断是否加密了(需要提取加密方式、加密key、IV),提取ts列表 ③、多线程下载所有ts(注意别打乱顺序,在m3u8文件中的顺序就是在完整视频中的顺序,所以需要记录原来的顺序,或者按照顺序进行ts重命名) ④、合并(如果加密了,则对每个ts解密) ⑤、调用FFmpeg,将合并好的视频信息放入一个mp4容器中(直接放在mp4文件也行) ⑥、回到①,开始下载下一个m3u82、Python源码实现受博主编码能力影响,加上博主又很懒,怎么简单怎么来,代码冗余度比较高。。。下面的代码已经过了4-5千个m3u8的下载测试,但是不能保证没有bug,如有问题欢迎斧正哈~# UTF-8 # author hestyle # desc: 必须在终端直接执行,不能在pycharm等IDE中直接执行,否则看不到动态进度条效果 import os import sys import m3u8 import requests import traceback import threadpool import shutil from Crypto.Cipher import AES headers = { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Connection": "Keep-Alive", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "zh-CN,zh;q=0.9", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36" } ######################配置信息########################## # m3u8链接批量输入文件 m3u8InputFilePath = "/Users/kain/Code/GIT/m3u8downloader/input/m3u8s_input.txt" # 视频保存路径 saveRootDirPath = "/Users/kain/Code/GIT/m3u8downloader/output" # 下载出错的m3u8保存文件 errorM3u8InfoDirPath = "/Users/kain/Code/GIT/m3u8downloader/output/error.txt" # m3u8文件、key文件下载尝试次数,ts流默认无限次尝试下载,直到成功 m3u8TryCountConf = 10 # 线程数(同时下载的分片数) processCountConf = 50 ####################################################### # 全局变量 # 全局线程池 taskThreadPool = None # 当前下载的m3u8 url m3u8Url = None # url前缀 rootUrlPath = None # title title = None # ts count sumCount = None # 已处理的ts doneCount = None # cache path cachePath = saveRootDirPath + "/cache" # log path logPath = cachePath + "/log.log" # log file logFile = None # 1、下载m3u8文件 def getM3u8Info(): global m3u8Url global logFile global rootUrlPath tryCount = m3u8TryCountConf while True: if tryCount < 0: print("\t{0}下载失败!".format(m3u8Url)) logFile.write("\t{0}下载失败!".format(m3u8Url)) return None tryCount = tryCount - 1 try: response = requests.get(m3u8Url, headers=headers, timeout=20, allow_redirects=True) if response.status_code == 301: nowM3u8Url = response.headers["location"] print("\t{0}重定向至{1}!".format(m3u8Url, nowM3u8Url)) logFile.write("\t{0}重定向至{1}!\n".format(m3u8Url, nowM3u8Url)) m3u8Url = nowM3u8Url continue expected_length = int(response.headers.get('Content-Length')) actual_length = len(response.content) if expected_length > actual_length: raise Exception("m3u8下载不完整") print("\t{0}下载成功!".format(m3u8Url)) logFile.write("\t{0}下载成功!".format(m3u8Url)) rootUrlPath = m3u8Url[0:m3u8Url.rindex('/')] break except TimeoutError: print("\t{0}下载失败!正在重试".format(m3u8Url)) logFile.write("\t{0}下载失败!正在重试".format(m3u8Url)) traceback.print_exc() # 解析m3u8中的内容 m3u8Info = m3u8.loads(response.text) # 有可能m3u8Url是一个多级码流 if m3u8Info.is_variant: print("\t{0}为多级码流!".format(m3u8Url)) logFile.write("\t{0}为多级码流!".format(m3u8Url)) for rowData in response.text.split('\n'): # 寻找响应内容的中的m3u8 if rowData.endswith(".m3u8"): m3u8Url = m3u8Url.replace("index.m3u8", rowData) rootUrlPath = m3u8Url[0:m3u8Url.rindex('/')] return getM3u8Info() # 遍历未找到就返回None print("\t{0}响应未寻找到m3u8!".format(response.text)) logFile.write("\t{0}响应未寻找到m3u8!".format(response.text)) return None else: return m3u8Info # 2、下载key文件 def getKey(keyUrl): global logFile tryCount = m3u8TryCountConf while True: if tryCount < 0: print("\t{0}下载失败!".format(keyUrl)) logFile.write("\t{0}下载失败!".format(keyUrl)) return None tryCount = tryCount - 1 try: response = requests.get(keyUrl, headers=headers, timeout=20, allow_redirects=True) if response.status_code == 301: nowKeyUrl = response.headers["location"] print("\t{0}重定向至{1}!".format(keyUrl, nowKeyUrl)) logFile.write("\t{0}重定向至{1}!\n".format(keyUrl, nowKeyUrl)) keyUrl = nowKeyUrl continue expected_length = int(response.headers.get('Content-Length')) actual_length = len(response.content) if expected_length > actual_length: raise Exception("key下载不完整") print("\t{0}下载成功!key = {1}".format(keyUrl, response.content.decode("utf-8"))) logFile.write("\t{0}下载成功! key = {1}".format(keyUrl, response.content.decode("utf-8"))) break except : print("\t{0}下载失败!".format(keyUrl)) logFile.write("\t{0}下载失败!".format(keyUrl)) return response.text # 3、多线程下载ts流 def mutliDownloadTs(playlist): global logFile global sumCount global doneCount global taskThreadPool taskList = [] # 每个ts单独作为一个task for index in range(len(playlist)): dict = {"playlist": playlist, "index": index} taskList.append((None, dict)) # 重新设置ts数量,已下载的ts数量 doneCount = 0 sumCount = len(taskList) printProcessBar(sumCount, doneCount, 50) # 构造thread pool requests = threadpool.makeRequests(downloadTs, taskList) [taskThreadPool.putRequest(req) for req in requests] # 等待所有任务处理完成 taskThreadPool.wait() print("") return True # 4、下载单个ts playlists[index] def downloadTs(playlist, index): global logFile global sumCount global doneCount global cachePath global rootUrlPath succeed = False while not succeed: # 文件名格式为 "00000001.ts",index不足8位补充0 outputPath = cachePath + "/" + "{0:0>8}.ts".format(index) outputFp = open(outputPath, "wb+") if playlist[index].startswith("http"): tsUrl = playlist[index] else: tsUrl = rootUrlPath + "/" + playlist[index] try: response = requests.get(tsUrl, timeout=10, headers=headers, stream=True) if response.status_code == 200: expected_length = int(response.headers.get('Content-Length')) actual_length = len(response.content) if expected_length > actual_length: raise Exception("分片下载不完整") outputFp.write(response.content) doneCount += 1 printProcessBar(sumCount, doneCount, 50) logFile.write("\t分片{0:0>8} url = {1} 下载成功!".format(index, tsUrl)) succeed = True except Exception as exception: logFile.write("\t分片{0:0>8} url = {1} 下载失败!正在重试...msg = {2}".format(index, tsUrl, exception)) outputFp.close() # 5、合并ts def mergeTs(tsFileDir, outputFilePath, cryptor, count): global logFile outputFp = open(outputFilePath, "wb+") for index in range(count): printProcessBar(count, index + 1, 50) logFile.write("\t{0}\n".format(index)) inputFilePath = tsFileDir + "/" + "{0:0>8}.ts".format(index) if not os.path.exists(outputFilePath): print("\n分片{0:0>8}.ts, 不存在,已跳过!".format(index)) logFile.write("分片{0:0>8}.ts, 不存在,已跳过!\n".format(index)) continue inputFp = open(inputFilePath, "rb") fileData = inputFp.read() try: if cryptor is None: outputFp.write(fileData) else: outputFp.write(cryptor.decrypt(fileData)) except Exception as exception: inputFp.close() outputFp.close() print(exception) return False inputFp.close() print("") outputFp.close() return True # 6、删除ts文件 def removeTsDir(tsFileDir): # 先清空文件夹 for root, dirs, files in os.walk(tsFileDir, topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) os.rmdir(tsFileDir) return True # 7、convert to mp4(调用了FFmpeg,将合并好的视频内容放置到一个mp4容器中) def ffmpegConvertToMp4(inputFilePath, ouputFilePath): global logFile if not os.path.exists(inputFilePath): print(inputFilePath + " 路径不存在!") logFile.write(inputFilePath + " 路径不存在!\n") return False cmd = r'./ffmpeg -i "{0}" -vcodec copy -acodec copy "{1}"'.format(inputFilePath, ouputFilePath) if os.system(cmd) == 0: print(inputFilePath + "转换成功!") logFile.write(inputFilePath + "转换成功!\n") return True else: print(inputFilePath + "转换失败!") logFile.write(inputFilePath + "转换失败!\n") return False # 8、模拟输出进度条 def printProcessBar(sumCount, doneCount, width): precent = doneCount / sumCount useCount = int(precent * width) spaceCount = int(width - useCount) precent = precent*100 print('\t{0}/{1} {2}{3} {4:.2f}%'.format(sumCount, doneCount, useCount*'■', spaceCount*'□', precent), file=sys.stdout, flush=True, end='\r') # m3u8下载器 def m3uVideo8Downloader(): global title global logFile global m3u8Url global cachePath # 1、下载m3u8 print("\t1、开始下载m3u8...") logFile.write("\t1、开始下载m3u8...\n") m3u8Info = getM3u8Info() if m3u8Info is None: return False tsList = [] for playlist in m3u8Info.segments: tsList.append(playlist.uri) # 2、获取key keyText = "" cryptor = None # 判断是否加密 if (len(m3u8Info.keys) != 0) and (m3u8Info.keys[0] is not None): # 默认选择第一个key,且AES-128算法 key = m3u8Info.keys[0] if key.method != "AES-128": print("\t{0}不支持的解密方式!".format(key.method)) logFile.write("\t{0}不支持的解密方式!\n".format(key.method)) return False # 如果key的url是相对路径,加上m3u8Url的路径 keyUrl = key.uri if not keyUrl.startswith("http"): keyUrl = m3u8Url.replace("index.m3u8", keyUrl) print("\t2、开始下载key...") logFile.write("\t2、开始下载key...\n") keyText = getKey(keyUrl) if keyText is None: return False # 判断是否有偏移量 if key.iv is not None: cryptor = AES.new(bytes(keyText, encoding='utf8'), AES.MODE_CBC, bytes(key.iv, encoding='utf8')) else: cryptor = AES.new(bytes(keyText, encoding='utf8'), AES.MODE_CBC, bytes(keyText, encoding='utf8')) # 3、下载ts print("\t3、开始下载ts...") logFile.write("\t3、开始下载ts...\n") if mutliDownloadTs(tsList): print("\tts下载完成---------------------") logFile.write("\tts下载完成---------------------\n") # 4、合并ts print("\t4、开始合并ts...") logFile.write("\t4、开始合并ts...\n") if mergeTs(cachePath, cachePath + "/cache.flv", cryptor, len(tsList)): print("\tts合并完成---------------------") logFile.write("\tts合并完成---------------------\n") else: print(keyText) print("\tts合并失败!") logFile.write("\tts合并失败!\n") return False # 5、开始转换成mp4 print("\t5、开始mp4转换...") logFile.write("\t5、开始mp4转换...\n") if not ffmpegConvertToMp4(cachePath + "/cache.flv", saveRootDirPath + "/" + title + ".mp4"): return False return True if __name__ == '__main__': # 判断m3u8文件是否存在 if not (os.path.exists(m3u8InputFilePath)): print("{0}文件不存在!".format(m3u8InputFilePath)) exit(0) m3u8InputFp = open(m3u8InputFilePath, "r", encoding="utf-8") # 设置error的m3u8 url输出 errorM3u8InfoFp = open(errorM3u8InfoDirPath, "a+", encoding="utf-8") # 设置log file if not os.path.exists(cachePath): os.makedirs(cachePath) logFile = open(logPath, "w+", encoding="utf-8") # 初始化线程池 taskThreadPool = threadpool.ThreadPool(processCountConf) while True: rowData = m3u8InputFp.readline() rowData = rowData.strip('\n') if rowData == "": break m3u8Info = rowData.split(',') title = m3u8Info[0] m3u8Url = m3u8Info[1] try: print("{0} 开始下载:".format(m3u8Info[0])) logFile.write("{0} 开始下载:\n".format(m3u8Info[0])) if m3uVideo8Downloader(): # 成功下载完一个m3u8则清空logFile logFile.truncate() print("{0} 下载成功!".format(m3u8Info[0])) else: errorM3u8InfoFp.write(title + "," + m3u8Url + '\n') errorM3u8InfoFp.flush() print("{0} 下载失败!".format(m3u8Info[0])) logFile.write("{0} 下载失败!\n".format(m3u8Info[0])) except Exception as exception: print(exception) traceback.print_exc() # 关闭文件 logFile.close() m3u8InputFp.close() errorM3u8InfoFp.close() shutil.rmtree(cachePath) print("----------------下载结束------------------") 四、开车姿势与车速展示1、开车姿势①、导入源码用到库m3u8、traceback、threadpool、pycryptodome、beautifulsoup4(用pip3安装就行)②、将 ffmpeg 放到源码文件所在目录(源码调用了cmd命令进而调用了 ffmpeg),创建 input 、output 文件夹Windows / macOS 使用 FFMPEG : https://www.lanzoux.com/b015tjr7eLinux 请到以下网址根据系统自行下载:https://ffmpeg.org/download.html#build-linux修改代码 237 行,win 系统修改为:.\ffmpeg.exe程序目录③、将准备好的 m3u8s_input.txt 文件(必须是utf-8编码,格式如下)保存在 input 文件夹m3u8s_input.txt 示例title_1,m3u8_url_1 title_2,m3u8_url_2 title_3,m3u8_url_3 ...这是省略号,小白别瞎搞... title_n,m3u8_url_n④、配置好源码中的 m3u8 url 文件路径、视频保存的地址、线程数⑤、控制台/终端使用 Python 执行脚本下面将 python 程序保存名为 run.pypython3 run.py注:为了尽量减少频繁的删除、创建 ts 流文件,源码运行时在 output 目录中创建一个cache 缓存目录,里面暂存下载好的 ts 流。下载过程中不可删除!!!全部下载完成会自动删除 cache 缓存目录2、车速展示(系好安全带)五、后记以上源码仅作为Python技术学习、交流之用,切勿用于任何可能造成违法犯罪的场景,否则后果自负!
2020年10月10日
165 阅读
0 评论
0 点赞
2020-09-03
[Python] 喜马拉雅加密算法分析
喜马拉雅加密算法分析 转载于吾爱论坛 wshuo 大大帖子 https://www.52pojie.cn/thread-1255513-1-1.html知识共享蓝奏云:【下载】download.py为主入口文件,其中有两个参数你可能需要调节self.run_t = 7 self.save_dir = r"/Users/kain/Music/喜马拉雅"一个是同时下载个数,一个是保存路径,剩下的需要调节最下面的整本书的albumId了,另外其中我加入了转换mp3格式的函数,不过我默认注释掉了,如果想使用可以配置ffmpeg到环境变量中去前言这几天一直听听评书,发现喜马拉雅上的资源很多,不过很可惜都是付费的,所以我冲了一个月会员,简单写个爬虫,爬下来几10部,够我一年听的了开始分析打开chrome控制台,点击播放,最先拿到的一个接口就是https://mpay.ximalaya.com/mobile/track/pay/244130607/?device=pc当然这个是付费的一部书,所以如果你浏览器不带 会员的cookie是访问不到的,其中的数字 244130607,这个在他们的接口中叫做 trackId, 每个音频文件对应唯一的一个 trackId也就是对应这个界面的后面的数字,通过这个唯一的trackId可以获取到音频文件,那么看一下这个接口返回的内容{ "ret": 0, "msg": "0", "trackId": 244130607, "uid": 170217760, "albumId": 30816438, "title": "《三体》第一季 第十集 聚会与大撕裂", "domain": "http://audiopay.cos.xmcdn.com", "totalLength": 12780565, "sampleDuration": 0, "sampleLength": 0, "isAuthorized": true, "apiVersion": "1.0.0", "seed": 9583, "fileId": "27*31*44*62*1*8*6*48*52*4*6*17*16*6*35*35*6*43*25*27*48*63*58*4*50*47*60*64*15*39*59*49*2*36*48*48*16*58*18*44*2*32*12*7*52*64*51*26*29*4*22*", "buyKey": "617574686f72697a6564", "duration": 1578, "ep": "20NvOoh6T39X3qwKO4cY5g5bVhg+1nfPHIQafFTmCXihnrqF2PjczO8O0auK1KJhDrJ30XMYfKJo2uz+xgwd3rwRPi5f", "highestQualityLevel": 1, "downloadQualityLevel": 1, "authorizedType": 1 }这里,我充会员了,所以可以直接用浏览器中打开这个url,其中有用的字段有了只有几个 seed和 fileId两个通过js加密算法计算出 m4a的路径,并拼接主域名,然后 ep 经过另一个加密算法得到url的访问参数buy_key sign token timestamp,最后将它们拼接到一起才是一个完整的 音频的url两个js加密算法经过我调试我分别找到了这两个加密的 js算法计算 m4a的路径js算法:function vt(t) { this._randomSeed = t, this.cg_hun() } vt.prototype = { cg_hun: function() { this._cgStr = ""; var t = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/\\:._-1234567890" , e = t.length , n = 0; for (n = 0; n < e; n++) { var r = this.ran() * t.length , o = parseInt(r); this._cgStr += t.charAt(o), t = t.split(t.charAt(o)).join("") } }, cg_fun: function(t) { t = t.split("*"); var e = "" , n = 0; for (n = 0; n < t.length - 1; n++) e += this._cgStr.charAt(t[n]); return e }, ran: function() { this._randomSeed = (211 * this._randomSeed + 30031) % 65536; return this._randomSeed / 65536 }, }; c = function(t, e) { var n = new vt(t).cg_fun(e); return "/" === n[0] ? n : "/".concat(n) } console.log(c(9583,"27*31*44*62*1*8*6*48*52*4*6*17*16*6*35*35*6*43*25*27*48*63*58*4*50*47*60*64*15*39*59*49*2*36*48*48*16*58*18*44*2*32*12*7*52*64*51*26*29*4*22*"))用node跑一下可以得到 m4a的路径输出:/group3/M04/9E/88/wKgMbF4ejn2TfGPRAMMEFYoRHXs027.m4a通过ep来计算url参数的js算法:Z = function() { throw new TypeError("Invalid attempt to destructure non-iterable instance") } J = function(t, e) { var n = [] , r = !0 , o = !1 , i = void 0; try { for (var a, u = t[Symbol.iterator](); !(r = (a = u.next()).done) && (n.push(a.value), !e || n.length !== e); r = !0) ; } catch (t) { o = !0, i = t } finally { try { r || null == u.return || u.return() } finally { if (o) throw i } } return n } Q = function(t) { if (Array.isArray(t)) return t } tt = function(t, e) { return Q(t) || J(t, e) || Z() } function yt(t, e) { for (var n, r = [], o = 0, i = "", a = 0; 256 > a; a++) r[a] = a; for (a = 0; 256 > a; a++) o = (o + r[a] + t.charCodeAt(a % t.length)) % 256, n = r[a], r[a] = r[o], r[o] = n; for (var u = o = a = 0; u < e.length; u++) o = (o + r[a = (a + 1) % 256]) % 256, n = r[a], r[a] = r[o], r[o] = n, i += String.fromCharCode(e.charCodeAt(u) ^ r[(r[a] + r[o]) % 256]); return i } var mt = yt("xm", "Ä[üJ=†Û3áf÷N") gt = [19, 1, 4, 7, 30, 14, 28, 8, 24, 17, 6, 35, 34, 16, 9, 10, 13, 22, 32, 29, 31, 21, 18, 3, 2, 23, 25, 27, 11, 20, 5, 15, 12, 0, 33, 26] bt = function(t) { var e1 = yt( function(t, e) { for (var n = [], r = 0; r < t.length; r++) { for (var o = "a" <= t[r] && "z" >= t[r] ? t[r].charCodeAt() - 97 : t[r].charCodeAt() - "0".charCodeAt() + 26, i = 0; 36 > i; i++) if (e[i] == o) { o = i; break } n[r] = 25 < o ? String.fromCharCode(o - 26 + "0".charCodeAt()) : String.fromCharCode(o + 97) } return n.join("") }("d" + mt + "9",gt) , e2 = function(t) { if (!t) return ""; var e, n, r, o, i, a = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1]; for (o = (t = t.toString()).length, r = 0, i = ""; r < o; ) { do { e = a[255 & t.charCodeAt(r++)] } while (r < o && -1 == e);if (-1 == e) break; do { n = a[255 & t.charCodeAt(r++)] } while (r < o && -1 == n);if (-1 == n) break; i += String.fromCharCode(e << 2 | (48 & n) >> 4); do { if (61 == (e = 255 & t.charCodeAt(r++))) return i; e = a[e] } while (r < o && -1 == e);if (-1 == e) break; i += String.fromCharCode((15 & n) << 4 | (60 & e) >> 2); do { if (61 == (n = 255 & t.charCodeAt(r++))) return i; n = a[n] } while (r < o && -1 == n);if (-1 == n) break; i += String.fromCharCode((3 & e) << 6 | n) } return i }(t) ).split("-") console.log(e1) } var c = bt("20NvOoh6T39X3qwKO4cY5g5bVhg+1nfPHIQafFTmCXihnrqF2PjczO8O0auK1KJhDrJ30XMYfKJo2uz+xgwd3rwRPi5f")这段js比较复杂,调试的时候坑死我了,不在同一个地方,导致我来回复制,最终于才把这个算法整理到这一个js文件中,依然用 node跑一下,输出:[ '617574686f72697a6564', 'ef9a0678d77870843ef203d6333ce021', '5790', '1598533668' ]这几个参数分别对应的是:buy_key sign token timestamp有了这了两个js算法就可以完全的解析 这个接口返回的参数了。python 代码仿写加密算法计算 m4a路径加密算法class vt(): def __init__(self,t): self._randomSeed = t self.cg_hun() def ran(self): self._randomSeed = (211 * self._randomSeed + 30031) % 65536 return self._randomSeed / 65536 def cg_hun(self): self._cgStr = "" t = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/\\:._-1234567890" e = len(t) n = 0 for i in range(e): r = self.ran() * len(t) o = int(r) self._cgStr += t[o] t = "".join(t.split(t[o])) def cg_fun(self,t): t = [int(i) if i else 0 for i in t.split("*")] e = "" n = 0; for n in range(n,len(t)-1): e += self._cgStr[t[n]] return e def path_decode(seed,fileId): c = vt(seed) p = c.cg_fun(fileId) return p if __name__ == '__main__': result = path_decode(9583,"27*31*44*62*1*8*6*48*52*4*6*17*16*6*35*35*6*43*25*27*48*63*58*4*50*47*60*64*15*39*59*49*2*36*48*48*16*58*18*44*2*32*12*7*52*64*51*26*29*4*22*") print(result)通过ep来计算url参数的算法:def yt(t, e): r = [0 for i in range(256)] o = 0 i = "" for a in range(0,256): r[a] = a; for a in range(0,256): o = (o + r[a] + ord(t[a % len(t)])) % 256 n = r[a] r[a] = r[o] r[o] = n u = 0 o = 0 a = 0 for u in range(0,len(e)): a = (a + 1) % 256 o = (o + r[a]) % 256 n = r[a] r[a] = r[o] r[o] = n i += chr(ord(e[u]) ^ r[(r[a] + r[o]) % 256]) return i def bt(t): def arg1(t,e): n = [' ' for i in range(256)] for r in range(0,len(t)): if "a" <= t[r] and "z" >= t[r]: o = ord(t[r]) - 97 else: o = ord(t[r]) - ord("0") + 26 for i in range(0,36): if (e[i] == o): o = i break if 25< o: n[r] = chr(o - 26 + ord("0")) else: n[r] = chr(o + 97) return "".join(n).strip() a1 = arg1("d" + mt + "9", gt) def arg2(t): if not t: return "" e = n = r = o = i = a = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1]; o = len(t) i = "" r = 0 while r < o: while True: e = a[255 & ord(t[r])] r += 1 if not (r < o and -1 == e): break if (-1 == e): break while True: n = a[255 & ord(t[r])] r += 1 if not (r < o and -1 == n): break if (-1 == n): break i += chr(e << 2 | (48 & n) >> 4) while True: e = (255 & ord(t[r])) if 61 == e: return i r += 1 e = a[e] if not (r < o and -1 == e): break if (-1 == e): break i += chr((15 & n) << 4 | (60 & e) >> 2); while True: n = (255 & ord(t[r])) if (61 == n): return i r += 1 n = a[n] if not (r < o and -1 == n): break if (-1 == n): break i += chr((3 & e) << 6 | n) return i a2 = arg2(t) buy_key,sign,token,timestamp = yt(a1,a2).split('-') data = dict( buy_key=buy_key, sign=sign, token=token, timestamp=timestamp, ) return data mt = yt("xm", "Ä[üJ=†Û3áf÷N") gt = [19, 1, 4, 7, 30, 14, 28, 8, 24, 17, 6, 35, 34, 16, 9, 10, 13, 22, 32, 29, 31, 21, 18, 3, 2, 23, 25, 27, 11, 20, 5, 15, 12, 0, 33, 26] def ep_decode(ep): data = bt(ep) return data if __name__ == '__main__': print(ep_decode('20NvOoh6T39X3qwKO4cY5g5bVhg+1nfPHIQafFTmCXihnrqF2PjczO8O0auK1KJhDrJ30XMYfKJo2uz+xgwd3rwRPi5f'))这个接口到此为止才算是完全可以解析。免费接口分析如果你没有充会员,免费的音频还是可以听的,我找到一个免费音频的接口https://www.ximalaya.com/revision/play/v1/audio?id=324681559&ptype=1返回值:{ "ret": 200, "data": { "trackId": 324681559, "canPlay": true, "isPaid": false, "hasBuy": true, "src": "https://aod.cos.tx.xmcdn.com/group84/M03/4A/A6/wKg5Hl8s0cTwcp6xABQ0EbeuW5Q193.m4a", "albumIsSample": false, "sampleDuration": 48, "isBaiduMusic": false, "firstPlayStatus": true, "isVipFree": false } }这个接口还是比较简单的,返回值里面直接包含 m4a音频地址,没有加密措施,另外 url中的数字依然是 trackId,值得一提的是免费音频的trackId不能用在付费接口,我猜测是版本迭代的问题,或者是客户端不同的问题,因为当时我不只是分析网页的接口,还抓包了电脑客户端的接口,具体对应的是网页还是客户端我也忘了。解析整本书的接口喜马拉雅接口主要关键的有两个参数,一个是前面我说的 trackId 另一个就是albumId,trackId 对应唯一的一个音频,而 albumId 对应的是唯一的一本书。https://www.ximalaya.com/revision/album/v1/getTracksList?albumId=30816438&pageNum=1&pageSize=1000返回值中就有每一集的trackId,其实喜马拉雅还有很多其他接口,搜索接口等等,一般的其他的接口需要在请求头中加入xm-sign,我也写了xm-sign的计算方法:import requests import time import hashlib import random import json from requests.packages.urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) # 获取sign签名 def get_sign(headers): serverTimeUrl = "https://www.ximalaya.com/revision/time" response = requests.get(serverTimeUrl,headers=headers,verify=False) serverTime = response.text nowTime = str(round(time.time()*1000)) sign = str(hashlib.md5("himalaya-{}".format(serverTime).encode()).hexdigest()) + "({})".format(str(round(random.random()*100))) + serverTime + "({})".format(str(round(random.random()*100))) + nowTime headers["xm-sign"] = sign return headers def get_header(): headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.90 Safari/537.36" } headers = get_sign(headers) return headers if __name__ == '__main__': # 这是一个搜索接口 url = "https://www.ximalaya.com/revision/search/main?core=all&spellchecker=true&device=iPhone&kw=%E9%9B%AA%E4%B8%AD%E6%82%8D%E5%88%80%E8%A1%8C&page=1&rows=20&condition=relation&fq=&paidFilter=false" s = requests.get(url,headers=get_header(),verify=False) print(s.json())还有很多其他接口,我就懒得说了,因为我不想写了,有了这些就可以满足我下载整本书的需求了最终整合我写了 喜马拉雅 扫码登陆的脚本,因为我不能每次都去复制浏览器中的 cookie,这种重复劳动太傻了import requests import re from threading import Thread import time import requests from io import BytesIO import http.cookiejar as cookielib from PIL import Image import sys import psutil from base64 import b64decode import os requests.packages.urllib3.disable_warnings() class show_code(Thread): def __init__(self,data): Thread.__init__(self) self.data = data def run(self): img = Image.open(BytesIO(self.data)) # 打开图片,返回PIL image对象 img.show() def is_login(session): headers = {'User-Agent':"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36"} url = "https://www.ximalaya.com/revision/main/getCurrentUser" try: session.cookies.load(ignore_discard=True) except Exception: pass response = session.get(url,verify=False,headers=headers) if response.json()['ret'] == 200: print(response.json()) return session,True else: return session,False def login(): if not os.path.exists(".cookie"): os.makedirs('.cookie') if not os.path.exists('.cookie/xmly.txt'): print("hello") with open(".cookie/xmly.txt",'w') as f: f.write("") session = requests.session() session.cookies = cookielib.LWPCookieJar(filename='.cookie/xmly.txt') session,status = is_login(session) if not status: url = "https://passport.ximalaya.com/web/qrCode/gen?level=L" response = session.get(url,verify=False) data = response.json() # with open('qrcode.jpg','wb') as f: # f.write(b64decode(data['img'])) t= show_code(b64decode(data['img'])) t.start() qrId = data['qrId'] url = 'https://passport.ximalaya.com/web/qrCode/check/%s/%s' % (qrId,int(time.time()*1000)) while 1: response = session.get(url,verify=False) data = response.json() # code = re.findall("window.wx_code='(.*?)'",response.text) # sys.exit() if data['ret'] == 0: # for proc in psutil.process_iter(): # 遍历当前process # try: # if proc.name() == "Microsoft.Photos.exe": # proc.kill() # 关闭该process # except Exception as e: # print(e) break time.sleep(1) session.cookies.save() return session if __name__ == '__main__': login()简单的一个扫码登陆脚本,如果cookie自动保存成文件,下次使用的时候直接调用:session = login()就能在保持登陆状态下,访问各种接口
2020年09月03日
262 阅读
0 评论
0 点赞
2020-09-02
M3U8多线程下载器
M3U8多线程下载器 ,支持Windows、MacOS。转载于吾爱破解论坛的 super-star 大大,帖子原址:https://www.52pojie.cn/thread-1248586-1-1.html功能多线程Proxy支持Windows、MacOS预览配置基本同时下载 多线程同时下载多个分片(建议根据网速配置,并不是越大越好)自动合并 下载完自动合并分片删除分片 合并完成后自动删除分片重试次数 下载失败重试的次数ProxyHTTPSOCKS请求头自定义请求头下载地址(Windows & MacOS)1.0.2 【点我下载】Proxy本地M3U8下载解密支持向量自定义并发数重试次数分片下载百分比未来更新直播流下载常见问题为什么有些 M3U8 无法下载?M3U8文件、Key、ts文件经过特殊加密没有权限访问URL尝试配置请求头MacOS 为什么无法打开给权限:sudo chmod -R 777 ./终端启动:./start-mac.sh 或 bash ./start-mac.sh
2020年09月02日
925 阅读
0 评论
0 点赞
2020-09-02
[win/linux/mac][开源][Go+Qt5] 喜马拉雅FM小说专辑下载器
喜马拉雅FM小说专辑下载器, 支持VIP与付费专辑(需自备VIP Token).Github: https://github.com/jing332/xmly-downloader-qt5感谢 @jing332 大大的开源P.S.喜马拉雅已开始封禁账号!请酌情使用本工具!否则后果自负!【建议暂停使用】注意: 因喜马拉雅PC付费接口限制,现在每个账号单日只能下载250条VIP、试听、付费音频!注意: 若要下载VIP或付费音频,请在设置Cookie前确认Token所关联的账号可播放完整音频,否则仅可下载试听前3分钟!增加扫二维码登录自动获取 Cookies功能下载VIP与付费专辑 ( 需设置Cookie(Token) )更改下载目录文件名前添加序号并行下载选择文件拓展名(mp3/m4a) 注: 仅免费音频支持下载mp3格式检查Cookie(Token)是否有效跟踪下载进度, 失败可重试主题(默认, 淡蓝, PS黑, 扁平白)获取Cookietoken是令牌的意思,是登陆账号后喜马拉雅服务器返回的字符串。快捷键F12打开 开发人员工具.截图下载Lanzous: https://www.lanzoux.com/b015qm5za
2020年09月02日
1,609 阅读
0 评论
0 点赞
2020-09-01
Unlock Music 音乐解锁
Unlock Music 音乐解锁在浏览器中解锁加密的音乐文件。 Unlock encrypted music file in the browser.特性支持的格式[x] QQ音乐 (.qmc0/.qmc2/.qmc3/.qmcflac/.qmcogg/.tkm)[x] 写入封面图片[x] Moo音乐格式 (.bkcmp3/.bkcflac)[x] QQ音乐Tm格式 (.tm0/.tm2/.tm3/.tm6)[x] QQ音乐新格式 (实验性支持)[x] .mflac[x] .mgg[x] 网易云音乐格式 (.ncm)[x] 补全ncm的ID3/FlacMeta信息[x] 虾米音乐格式 (.xm) (测试阶段)[x] 酷我音乐格式 (.kwm) (测试阶段)[x] 酷狗音乐格式 (.kgm) (CLI版本)其他特性[x] 在浏览器中解锁[x] 拖放文件[x] 在线播放[x] 批量解锁[x] 渐进式Web应用[x] 多线程使用方法使用已构建版本从GitHub Release下载已构建的版本本地使用请下载legacy版本(modern版本只能通过http/https协议访问)解压缩后即可部署或本地使用(请勿直接运行源代码)自行构建环境要求nodejsnpm获取项目源代码后执行 npm install 安装相关依赖执行 npm run build 即可进行构建,构建输出为 dist 目录npm run serve 可用于开发Release版本发布时间操作legacy版本2020-08-13下载modern版本2020-08-13下载
2020年09月01日
158 阅读
0 评论
0 点赞
1
2
...
4