B站uwp应用的视频解密

阿弥陀佛

  1. 基于知乎一位大佬python版的思路,现在用 Cnode 实现
  2. 解密原理:刪除mp4文件开头的FFFFFF
  3. 改进:可批量、深遍历、可指定文件夾/文件

node 实现

  1. 基本实现
    思路:先读取整个文件,再写入



const fs = require('fs')
const path = require('path')
const fsPro = fs.promises

/** promise扁平化 */
const promise = () => {
    const _o = {};
    _o.pro = new Promise((resolve, reject) => {
        _o.reject = reject
        _o.resolve = resolve
    });
    return _o;
}
/** 是否需要解密 */
const needReset = buf => buf && buf[0] == 255 && buf[1] == 255 && buf[2] == 255;
/** 是否为MP4文件 */
const isMp4 = pathNm => path.extname(pathNm) == '.mp4';
/** 是否为文件夹 */
const isDir = pathNm => {
    return fsPro.stat(pathNm)
        .then((curStat) => {
            return curStat.isDirectory();
        }).catch(err => {
            console.log(`文件${pathNm} 解析失敗\n${err}`);
            return null;
        })
};
/** 文件遍历 */
const dirEach = (dirPath, cb) => {
    const pro = promise()

    fs.readdir(
        dirPath,
        function (err, datas) {
            if (err) {
                console.log(`文件夹${dirPath} 读取失败\n`, err)
            } else {
                Promise.all(
                    datas.map(pathNm => cb(path.join(dirPath, pathNm), dirPath, pathNm))
                ).then(() => pro.resolve())
            }
        }
    )
    return pro.pro
}
/** mp4解密 */
const resetMp4 = filePath => {
    const txt = `文件${filePath}`
    const pro = promise()
    fsPro.readFile(filePath)
        .then(buf => {
            if (needReset(buf)) {
                console.log(txt + ` 解密中`);
                fs.writeFile(
                    filePath,
                    buf.slice(3),
                    err => {
                        if (err) return Promise.reject(err)
                        console.log(txt + ` 解密成功`);
                        pro.resolve()
                    }
                )
            } else {
                pro.resolve()
                console.log(txt + ` 不需要解密`)
            }
        }).catch(err => {
            pro.resolve()
            console.log(txt + ` 解密失敗\n${err}`);
        })

    return pro.pro
}
/** 文件操作 */
const resetFile = async pathNm => isMp4(pathNm) && resetMp4(pathNm);

/** 执行解密 */
const run = async pathNm => {

    return (await isDir(pathNm)) ?
        dirEach(pathNm, run) :
        resetFile(pathNm);

}

const _defaulit = pathNm => {

    let _pathNm = pathNm ||
        process.argv[2] ||
        __dirname;

    fs.access(
        _pathNm,
        0 | 2,
        err => err ?
            console.log('打开异常,请检查后重新输入\n', err) :
            run(_pathNm)
    );
}


module.exports = _defaulit;

  1. 流形式实现
    以文件流形式来重写mp4解密函数resetMp4

/** mp4解密 */
const resetMp4 = async filePath => {

    const { toReset, cs } = await readFile(filePath)

    const txt = `文件${filePath}`

    if (toReset) {
        console.log(txt + ` 解密中`)
        await writeFile(cs, filePath)
        console.log(txt + ` 解密成功`)
    } else {
        console.log(txt + ` 不需要解密`);
    }
}

/** 读取文件
 *  1. 判断是否需要解密
 *  2. 若要,则需返回可读流 */
const readFile = filePath => {
    const _pro = promise()

    fs.createReadStream(filePath).once(
        'readable',
        function () {

            let toReset = needReset(this.read(3));

            let resolveVal = { toReset, cs: 0 };

            if (toReset) {
                resolveVal.cs = this
            } else {
                this.push(null)
                this.destroy()
            }
            _pro.resolve(resolveVal)
        })

    return _pro.pro
}

/** 文件解密 */
const writeFile = (csFile, csFilePath) => {
    const _pro = promise()

    let wFilePath = csFilePath + "_v"

    const wFile = fs.createWriteStream(wFilePath)

    csFile.pipe(wFile)

    wFile.on('close', () => {
        fs.unlinkSync(csFilePath)
        fs.renameSync(wFilePath, csFilePath)
        _pro.resolve()
    })

    return _pro.pro
}
  1. 对比
    1. readFile:一次性把整个文件读到内存中
      • 整个文件:内存中加载整个文件
      • 一次性:一段连续读取时间
      • 影响:
        • 单个文件:前期等待时间长,总体时间短;
        • 批量:前期等待时间长,总体时间短;
        • 总体内存消耗大;
        • 书写简单
    2. 流形式:
      • 部分文件内容:每次仅加载一小部分内容
      • 多次读写:多次把加载到的数据追加到新文件
      • 影响:
        • 单个文件:前期等待时间短,总体时间长;
        • 批量:前期等待时间短,总体时间长;
        • 总体内存消耗小;
        • 书写复杂

C 语言实现

  1. 基本实现

#define _CRT_SECURE_NO_WARNINGS 1
#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <io.h>
// 判断是否为文件夹
#define isDir(fileInfo) (_A_SUBDIR & fileInfo.attrib)
// 文件深遍历
int forEachFile(char* _fileName, void callback(struct _finddata_t fileInfo, char* curFileNm));
// 判断MP4文件是否需要解密
int needReset(FILE* pFile)
{
	int idx = ftell(pFile);
	rewind(pFile);
	int res = fgetc(pFile) == 255 && fgetc(pFile) == 255 && fgetc(pFile) == 255;
	fseek(pFile, idx, SEEK_SET);
	return res;
}
// 判断为mp4文件
int isMP4(char* curFileNm)
{
	return strstr(curFileNm, ".mp4") != NULL;
}
// 文件深遍历
int forEachFile(char* _fileName, void callback(struct _finddata_t fileInfo, char* curFileNm))
{
	//文件存储信息结构体
	struct _finddata_t fileInfo;

	char fileName[256] = "";
	strcat(fileName, _fileName);
	strcat(fileName, "\\*.*");

	long fHandle = _findfirst(fileName, &fileInfo);

	if (fHandle != -1L)
	{
		_findnext(fHandle, &fileInfo);
		_findnext(fHandle, &fileInfo);

		do
		{
			char fileNm[255] = "";
			strcat(fileNm, _fileName);
			strcat(fileNm, "\\");
			strcat(fileNm, fileInfo.name);

			isDir(fileInfo) ? forEachFile(fileNm, callback)
				: callback(fileInfo, fileNm);
		} while (_findnext(fHandle, &fileInfo) == 0);
	}

	_findclose(fHandle);

	return 0;
}
// mp4文件解密逻辑
int resetFile(char* filePath)
{

	FILE* inFile = fopen(filePath, "rb");
	if (inFile == NULL)
	{
		printf("文件:%s 解析異常\n", filePath);
		return 1;
	}
	if (!needReset(inFile))
	{
		return 0;
	}

	char catchFilePath[255] = "";
	strcat(catchFilePath, filePath);
	strcat(catchFilePath, "_c");

	FILE* outFile = fopen(catchFilePath, "wb");

	int len;

	char* buffer = (char*)malloc(1024);

	if (outFile == NULL || buffer == NULL)
	{
		remove(catchFilePath);
		return 1;
	}
	printf("文件解码中:%s \n", filePath);

	fseek(inFile, 3, SEEK_SET);

	while ((len = fread(buffer, 1, 1024, inFile)) > 0) //从源文件中读取数据并放到缓冲区中
	{
		fwrite(buffer, 1, len, outFile); //  将缓冲区的数据写到目标文件中
		fflush(outFile);				 // 刷新缓冲区
		memset(buffer, 0, 1024);		 // 清空缓存
	}

	free(buffer);
	fclose(outFile);
	fclose(inFile);

	remove(filePath);
	rename(catchFilePath, filePath);
	printf("文件解码完成:%s \n", filePath);
	return 0;
}
// 解密MP4文件
void toResetMp4(struct _finddata_t fileInfo, char* curFileNm)
{
	isMP4(curFileNm) && resetFile(curFileNm);
}

void run(const char* fileName)
{
	_access(fileName, 0) ?
		printf("路径错误,请确实后再执行") :
		forEachFile(fileName, toResetMp4);

}


  1. C
    1. 遍历文件采用的是动态读取文件夹
      • 会读取到正在复制过程的副本文件
      • 改进:静态读取
        • 一次性读取所有文件夹的内容
        • 用链表缓存文件夹内容

阿弥陀佛

posted @ 2022-09-01 17:03  妙9999  阅读(518)  评论(0编辑  收藏  举报