本文共1081字,预计阅读需要4分钟
今日阅读:
- Widevine DRM 视频解密方法 小记:之前在Udemy买的课程总想下载下来,感觉不下载下来就对不起这个钱,但是课程跟网飞一样用了DRM下载没用。根据视频中的讲述应该是L3级别加密,之前流行的浏览器只能破解纯本地的L1加密。想要L3可能需要一台root的安卓机然后搭配hook框架去提取。
今日硬件:
因为买错了内存,所以来公司拆之前放在这里的旧暗影精灵内存。
顺利拆完之后发现后盖怎么安装都不平,还以为是板子变形了,仔细一看好家伙,电池怀孕了。
我记得21年左右刚换过电池现在又鼓包了,可能是长时间没用的原因。
一边拆机一边给U盘刷入官方的镜像做启动盘,突然发现买的UNC小主机里面自带一块安装U盘,连启动引导文件都做好了,插上去就能用。
愉快装上系统成功点亮。不要小看这个手机支架,没有这个手机支架打光我都不知道要怎么拆UNC小主机里面的层叠式架构。螺丝位藏得太深了,说明书上也没有拆机部分的图解。
笔记本虽然拆掉了一个8G但是还是能用的因为原本这个笔记本的8G内存就是我后面装上去搞得16G双通道。现在手头还有2G+8G的内存条,不过没有其他用武之地了。
因为我买的16G正飞速赶来。留着以后再攒个机器吧。
今日软件:
今天不是想推荐而是想吐槽,正如上文我搞了一个NUC小主机,但是桌面就一套键鼠,所以还得自己得想一下局域网内键鼠共享的方案。
看到这个好评比较多,就下载了第一代的PRO使用(因为只有这个版本有开源版和破解版能用),实际体验一言难尽。
即使选择IP直连,还是会匹配设备名——他一开始默认设置的电脑用户名,后面导致无法连接需要手动改名才行。
其次一开始连接的时候要在主控机上点一个接受申请,而被控机那边看到的不是等待却是报错,这个折腾了好久才发现是正常现象,提示太不友好了。
最后使用方面也是断断续续,重启电脑后发现先干脆连接就一直闪断了。
今日代码:
因为DRM的破解太过麻烦,我随便在网上找有没有其他人下载好的,结果发现还真有一个做的非常简洁的在线站courseflix.
播放速度很快,还有原版字幕。琢磨了下怎么提取出来视频下载,一开始以为播放的时候会有一个XHR请求到播放地址,后面发现我想多了。
播放地址是直接写在原网页的HTML里的,我直接获取就可以了。之后用Python快速撸了一个多线程下载,走香港线路可以跑到30M/s的平均速度。
可能因为并发太高导致有的时候下载失败,又加了一个视频长度对比功能,发现文件夹里有下载了一半未完成的文件直接删掉重新去拉取。
import requests
from bs4 import BeautifulSoup
import threading
import os
import json
import warnings
from tqdm import tqdm
import urllib3
warnings.filterwarnings("ignore", category=urllib3.exceptions.InsecureRequestWarning)
max_concurrent_downloads = 20
semaphore = threading.Semaphore(max_concurrent_downloads)
def download_file(url, folder, max_retries=5):
with semaphore:
filename = url.split('/')[-1]
filepath = os.path.join(folder, filename)
# 如果文件已存在,先验证其完整性
if os.path.exists(filepath):
with requests.get(url, stream=True, verify=False) as r:
total_size = int(r.headers.get('content-length', 0))
if os.path.getsize(filepath) == total_size:
print(f"文件 {filename} 已存在且完整,跳过下载。")
return
else:
print(f"文件 {filename} 不完整,将被重新下载。")
os.remove(filepath)
retries = 0
while retries < max_retries:
try:
with requests.get(url, stream=True, verify=False) as r:
total_size = int(r.headers.get('content-length', 0))
with open(filepath, 'wb') as f, tqdm(
desc=filename,
total=total_size,
unit='iB',
unit_scale=True,
unit_divisor=1024,
) as bar:
for chunk in r.iter_content(chunk_size=8192):
size = f.write(chunk)
bar.update(size)
# 再次检查文件完整性
if os.path.getsize(filepath) != total_size:
print(f"文件 {filename} 下载不完整,将被删除。")
os.remove(filepath)
raise Exception("文件不完整")
break
except Exception as e:
print(f"下载错误: {e}")
retries += 1
if os.path.exists(filepath):
os.remove(filepath)
if retries == max_retries:
print(f"下载失败 {url} 在 {max_retries} 次重试之后。")
def extract_videos_and_subtitles(html):
soup = BeautifulSoup(html, 'html.parser')
# 找到含有视频信息的 astro-island 标签
astro_island = soup.find('astro-island')
if not astro_island:
return [], []
json_data = astro_island['props']
json_data = json_data.replace('"', '"')
data = json.loads(json_data)
video_urls = []
subtitle_urls = []
videos = data.get('videos', [])
if len(videos) > 1 and isinstance(videos[1], list):
for video_group in videos[1]:
if len(video_group) > 1 and isinstance(video_group[1], dict):
video_data = video_group[1]
video_url = video_data.get('url', [0, None])[1]
subtitle_url = video_data.get('subtitle', [0, None])[1]
if video_url:
video_urls.append(video_url)
if subtitle_url:
subtitle_urls.append(subtitle_url)
return video_urls, subtitle_urls
def download_videos_and_subtitles(video_urls, subtitle_urls, folder):
threads = []
for url in video_urls + subtitle_urls:
thread = threading.Thread(target=download_file, args=(url, folder))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
def main(url, folder):
response = requests.get(url)
html = response.text
video_urls, subtitle_urls = extract_videos_and_subtitles(html)
threads = []
for url in video_urls + subtitle_urls:
thread = threading.Thread(target=download_file, args=(url, folder))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
url = input("请输入 URL: ")
folder = os.getcwd() # 当前工作目录
main(url, folder)
今日见闻:
忘记看什么了,值班却一宿没睡。
今日废话:
我的水平仍旧十分有限。