序
- 基于知乎一位大佬python版的思路,现在用
C
和node
实现 - 解密原理:刪除mp4文件开头的
FFFFFF
- 改进:可批量、深遍历、可指定文件夾/文件
node 实现
- 基本实现
思路:先读取整个文件,再写入
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[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 => run(
pathNm ||
process.argv[2] ||
__dirname
);
module.exports = _defaulit;
- 流形式实现
以文件流形式来重写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
}
- 对比
readFile
:一次性把整个文件读到内存中- 整个文件:内存中加载整个文件
- 一次性:一段连续读取时间
- 影响:
- 单个文件:前期等待时间长,总体时间短;
- 批量:前期等待时间长,总体时间短;
- 总体内存消耗大;
- 书写简单
- 流形式:
- 部分文件内容:每次仅加载一小部分内容
- 多次读写:多次把加载到的数据追加到新文件
- 影响:
- 单个文件:前期等待时间短,总体时间长;
- 批量:前期等待时间短,总体时间长;
- 总体内存消耗小;
- 书写复杂
C 语言实现
- 基本实现
#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[1024];
if (outFile == 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); // 清空缓存
}
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()
{
char fileName[] = "D:\\bili\\BilibiliDownload\\625588762\\4";
forEachFile(fileName, toResetMp4);
}
int main()
{
run();
return 0;
}
C
- 遍历文件采用的是动态读取文件夹
- 会读取到正在复制过程的副本文件
- 改进:静态读取
- 一次性读取所有文件夹的内容
- 用链表缓存文件夹内容
- 遍历文件采用的是动态读取文件夹