本文共1081字,预计阅读需要4分钟

今日阅读:

  • Widevine DRM 视频解密方法 小记:之前在Udemy买的课程总想下载下来,感觉不下载下来就对不起这个钱,但是课程跟网飞一样用了DRM下载没用。根据视频中的讲述应该是L3级别加密,之前流行的浏览器只能破解纯本地的L1加密。想要L3可能需要一台root的安卓机然后搭配hook框架去提取。

今日硬件:

因为买错了内存,所以来公司拆之前放在这里的旧暗影精灵内存。

顺利拆完之后发现后盖怎么安装都不平,还以为是板子变形了,仔细一看好家伙,电池怀孕了。

image1

我记得21年左右刚换过电池现在又鼓包了,可能是长时间没用的原因。

一边拆机一边给U盘刷入官方的镜像做启动盘,突然发现买的UNC小主机里面自带一块安装U盘,连启动引导文件都做好了,插上去就能用。

image2 Image3

愉快装上系统成功点亮。不要小看这个手机支架,没有这个手机支架打光我都不知道要怎么拆UNC小主机里面的层叠式架构。螺丝位藏得太深了,说明书上也没有拆机部分的图解。

笔记本虽然拆掉了一个8G但是还是能用的因为原本这个笔记本的8G内存就是我后面装上去搞得16G双通道。现在手头还有2G+8G的内存条,不过没有其他用武之地了。

因为我买的16G正飞速赶来。留着以后再攒个机器吧。

今日软件:

Synergy

今天不是想推荐而是想吐槽,正如上文我搞了一个NUC小主机,但是桌面就一套键鼠,所以还得自己得想一下局域网内键鼠共享的方案。

看到这个好评比较多,就下载了第一代的PRO使用(因为只有这个版本有开源版和破解版能用),实际体验一言难尽。

即使选择IP直连,还是会匹配设备名——他一开始默认设置的电脑用户名,后面导致无法连接需要手动改名才行。

其次一开始连接的时候要在主控机上点一个接受申请,而被控机那边看到的不是等待却是报错,这个折腾了好久才发现是正常现象,提示太不友好了。

最后使用方面也是断断续续,重启电脑后发现先干脆连接就一直闪断了。

今日代码:

image4

因为DRM的破解太过麻烦,我随便在网上找有没有其他人下载好的,结果发现还真有一个做的非常简洁的在线站courseflix.

播放速度很快,还有原版字幕。琢磨了下怎么提取出来视频下载,一开始以为播放的时候会有一个XHR请求到播放地址,后面发现我想多了。

播放地址是直接写在原网页的HTML里的,我直接获取就可以了。之后用Python快速撸了一个多线程下载,走香港线路可以跑到30M/s的平均速度。

可能因为并发太高导致有的时候下载失败,又加了一个视频长度对比功能,发现文件夹里有下载了一半未完成的文件直接删掉重新去拉取。

courseflix_downloader.py
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)

今日见闻:

忘记看什么了,值班却一宿没睡。

今日废话:

我的水平仍旧十分有限。