【20241023更新】飞友们怎么过滤视频广告?

ltxlong · 2024-10-8 19:33:55 · 244 次点击

就是那种m3u8的ts片段广告,就是那种解析视频几乎都有的,澳门xxx…


找没有广告的源?太难了,之前有,现在几乎都有广告了


想要的效果是 js 油猴脚本过滤


看了一圈,有人通过ts的命名规律过滤,现在几乎不适用了,但了解到重点关注的是 #EXT-X-DISCONTINUITY 标识


问题就在这里,有 #EXT-X-DISCONTINUITY 不一定后面的就是广告


如果 ts 片段是 1 2 3 4 5 6 7 8 9,在 3 和 4 之间插入广告,那么会产生两个 #EXT-X-DISCONTINUITY,因为 3 广告 4,意味着:3 和 广告 不连续,广告和4不连续


直接用console.log打印m3u8分割后的数组,然后下载一些 #EXT-X-DISCONTINUITY 后的第一个ts,发现有的是广告,有的不是广告,甚至#EXTINF描述的时长也不准确

const lines = m3u8_content.split(‘\n’);

console.log(lines)


无法准确的识别广告片段,就会导致误删正常的片段


折腾了几天,最终选择两个脚本


一个是油猴的



(好用,但也会误删,若误删会删很多,宁删错不放过)


一个是自己用GPT辅助写的简单脚本

(好用,不会误删!)(推荐)


【2024-10-23 12:45更新】


// ==UserScript==
// @name M3U8 Filter Ad Script
// @namespace http://tampermonkey.net/
// @version 1.0.3
// @description 自用,拦截和过滤 m3u8(解析/采集资源) 的广告切片,同时在console打印过滤的行信息,不会误删。
// @author ltxlong
// @match *://*/*
// @grant unsafeWindow
// @run-at document-start
// @license MIT
// @downloadURL https://update.greasyfork.org/scripts/512300/M3U8%20Filter%20Ad%20Script.user.js
// @updateURL https://update.greasyfork.org/scripts/512300/M3U8%20Filter%20Ad%20Script.meta.js
// ==/UserScript==

(function() {
'use strict';

let ts_name_len = 0; // ts前缀长度

let ts_name_len_extend = 1; // 容错

let first_extinf_row = '';

let the_extinf_judge_row_n = 0;

let the_same_extinf_name_n = 0;

let the_extinf_benchmark_n = 5; // 基准

let prev_ts_name_index = -1; // ts序列号

let ts_type = 0; // 0:xxxx000数字递增.ts模式0 ;1:xxxxxxxxxx.ts模式1

let the_ext_x_mode = 0; // 0:ext_x_discontinuity判断模式0 ;1:ext_x_discontinuity判断模式1

function isM3U8File(url) {
return /\.m3u8($|\?)/.test(url);
}

function extractNumberBeforeTS(str) {
// 匹配 .ts 前面的数字
const match = str.match(/(\d+)\.ts/);

if (match) {
// 使用 parseInt 去掉前导 0
return parseInt(match[1], 10);
}

return null; // 如果不匹配,返回 null
}

function filterLines(lines) {
let result = [];

// 先根据第一、二个ts名称来初始化参数
for (let i = 0; i < lines.length; i++) {

const line = lines[i];

if (the_extinf_judge_row_n === 0 && line.startsWith('#EXTINF')) {
first_extinf_row = line;

the_extinf_judge_row_n++;
} else if (the_extinf_judge_row_n === 1 && line.startsWith('#EXTINF')) {
if (line !== first_extinf_row) {
first_extinf_row = '';
}

the_extinf_judge_row_n++;
}

let the_ts_name_len = line.indexOf('.ts'); // ts前缀长度

if (the_ts_name_len > 0) {

ts_name_len = the_ts_name_len;

let ts_name_index = extractNumberBeforeTS(line);
if (ts_name_index === null) {
ts_type = 1; // ts命名模式

console.log('----------------------------识别ts模式1---------------------------');

break;
} else {

if (the_extinf_judge_row_n === 1) {

prev_ts_name_index = ts_name_index; // ts序列号

} else if (the_extinf_judge_row_n === 2) {

if (ts_name_index !== prev_ts_name_index + 1) {
ts_type = 1; // ts命名模式

console.log('----------------------------识别ts模式1---------------------------');

break;
} else {
prev_ts_name_index--;
}
}
}

if (the_extinf_judge_row_n === 2) {
console.log('----------------------------识别ts模式0---------------------------')

break;
}
}

}

// 开始遍历过滤
for (let i = 0; i < lines.length; i++) {

let ts_index_check = false;

const line = lines[i];

if (ts_type === 0) {

if (line.startsWith('#EXT-X-DISCONTINUITY') && lines[i + 1] && lines[i + 2]) {

// 检查当前行是否跟 #EXT-X-PLAYLIST-TYPE相关
if (i > 0 && lines[i - 1].startsWith('#EXT-X-PLAYLIST-TYPE')) {
result.push(line);

continue;
} else {
let the_ts_name_len = lines[i + 2].indexOf('.ts'); // ts前缀长度

if (the_ts_name_len > 0) {

// 根据ts名字长度过滤
if (the_ts_name_len - ts_name_len > ts_name_len_extend) {
// 广告过滤
if (lines[i + 3] && lines[i + 3].startsWith('#EXT-X-DISCONTINUITY')) {
// 打印即将过滤的行
console.log('------------------------------------------------------------------');
console.log('过滤规则: #EXT-X-DISCONTINUITY-ts文件名长度-');
console.log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2], "\n", lines[i + 3]);
console.log('------------------------------------------------------------------');

i += 3;
} else {
// 打印即将过滤的行
console.log('------------------------------------------------------------------');
console.log('过滤规则: #EXT-X-DISCONTINUITY-ts文件名长度');
console.log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2]);
console.log('------------------------------------------------------------------');

i += 2;
}

continue;
} else {
ts_name_len = the_ts_name_len;
}

// 根据ts序列号过滤
let the_ts_name_index = extractNumberBeforeTS(lines[i + 2]);

if (the_ts_name_index !== prev_ts_name_index + 1) {

// 广告过滤
if (lines[i + 3] && lines[i + 3].startsWith('#EXT-X-DISCONTINUITY')) {
// 打印即将过滤的行
console.log('------------------------------------------------------------------');
console.log('过滤规则: #EXT-X-DISCONTINUITY-ts序列号-');
console.log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2], "\n", lines[i + 3]);
console.log('------------------------------------------------------------------');

i += 3;
} else {
// 打印即将过滤的行
console.log('------------------------------------------------------------------');
console.log('过滤规则: #EXT-X-DISCONTINUITY-ts序列号');
console.log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2]);
console.log('------------------------------------------------------------------');

i += 2;
}

continue;
}
}
}
}

if (line.startsWith('#EXTINF') && lines[i + 1]) {

let the_ts_name_len = lines[i + 1].indexOf('.ts'); // ts前缀长度

if (the_ts_name_len > 0) {

// 根据ts名字长度过滤
if (the_ts_name_len - ts_name_len > ts_name_len_extend) {
// 广告过滤
if (lines[i + 2] && lines[i + 2].startsWith('#EXT-X-DISCONTINUITY')) {
// 打印即将过滤的行
console.log('------------------------------------------------------------------');
console.log('过滤规则: #EXTINF-ts文件名长度-');
console.log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2]);
console.log('------------------------------------------------------------------');

i += 2;
} else {
// 打印即将过滤的行
console.log('------------------------------------------------------------------');
console.log('过滤规则: #EXTINF-ts文件名长度');
console.log('过滤的行:', "\n", line, "\n", lines[i + 1]);
console.log('------------------------------------------------------------------');

i += 1;
}

continue;
} else {
ts_name_len = the_ts_name_len;
}

// 根据ts序列号过滤
let the_ts_name_index = extractNumberBeforeTS(lines[i + 1]);

if (the_ts_name_index === prev_ts_name_index + 1) {

prev_ts_name_index++;

} else {
// 广告过滤
if (lines[i + 2].startsWith('#EXT-X-DISCONTINUITY')) {
// 打印即将过滤的行
console.log('------------------------------------------------------------------');
console.log('过滤规则: #EXTINF-ts序列号-');
console.log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2]);
console.log('------------------------------------------------------------------');

i += 2;
} else {
// 打印即将过滤的行
console.log('------------------------------------------------------------------');
console.log('过滤规则: #EXTINF-ts序列号');
console.log('过滤的行:', "\n", line, "\n", lines[i + 1]);
console.log('------------------------------------------------------------------');

i += 1;
}

continue;
}
}
}
} else {

if (line.startsWith('#EXTINF')) {
if (line === first_extinf_row && the_same_extinf_name_n <= the_extinf_benchmark_n && the_ext_x_mode === 0) {
the_same_extinf_name_n++;
} else {
the_ext_x_mode = 1;
}

if (the_same_extinf_name_n > the_extinf_benchmark_n) {
the_ext_x_mode = 1;
}
}

if (line.startsWith('#EXT-X-DISCONTINUITY')) {
// 检查当前行是否跟 #EXT-X-PLAYLIST-TYPE相关
if (i > 0 && lines[i - 1].startsWith('#EXT-X-PLAYLIST-TYPE')) {
result.push(line);

continue;
} else {

// 如果第 i+2 行是 .ts 文件,跳过当前行和接下来的两行
if (lines[i + 1] && lines[i + 1].startsWith('#EXTINF') && lines[i + 2] && lines[i + 2].indexOf('.ts') > 0) {

let the_ext_x_discontinuity_condition_flag = false;

if (the_ext_x_mode === 1) {
the_ext_x_discontinuity_condition_flag = lines[i + 1] !== first_extinf_row && the_same_extinf_name_n > the_extinf_benchmark_n;
}

// 进一步检测第 i+3 行是否也是 #EXT-X-DISCONTINUITY
if (lines[i + 3] && lines[i + 3].startsWith('#EXT-X-DISCONTINUITY') && the_ext_x_discontinuity_condition_flag) {
// 打印即将过滤的行
console.log('------------------------------------------------------------------');
console.log('过滤规则: #EXT-X-DISCONTINUITY-广告-#EXT-X-DISCONTINUITY过滤');
console.log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2], "\n", lines[i + 3]);
console.log('------------------------------------------------------------------');

i += 3; // 跳过当前行和接下来的三行
} else {
// 打印即将过滤的行
console.log('------------------------------------------------------------------');
console.log('过滤规则: #EXT-X-DISCONTINUITY-单个标识过滤');
console.log('过滤的行:', "\n", line);
console.log('------------------------------------------------------------------');
}

continue;
}
}
}
}

// 保留不需要过滤的行
result.push(line);
}

return result;
}

async function safelyProcessM3U8(url, content) {
try {
const lines = content.split('\n');
const newLines = filterLines(lines);

return newLines.join('\n');
} catch (e) {
console.error(`处理 m3u8 文件时出错: ${url}`, e);

return content;
}
}

function hookXHR() {
const OriginalXHR = unsafeWindow.XMLHttpRequest;
unsafeWindow.XMLHttpRequest = class extends OriginalXHR {
constructor() {
super();

this.addEventListener('readystatechange', async function () {
if (this.readyState === 4 && this.status === 200 && isM3U8File(this.responseURL)) {
const modifiedResponse = await safelyProcessM3U8(this.responseURL, this.responseText);
Object.defineProperty(this, 'responseText', { value: modifiedResponse });
Object.defineProperty(this, 'response', { value: modifiedResponse });
}
}, false);
}
};
}

function initHook() {
hookXHR();
}

initHook();

})();




问题和解决:

如果进入播放页,播放的时候发现进度条只有几秒,直接刷新页面就可以了

如果播放的时候卡住了,直接刷新页面 或者 快进一下(如:按->右方向键)就好;有时候是网速问题和这脚本无关

如果播放的时候还有进度条,但突然结束了,快进一下再刷新页面即可

这脚本和其他过滤脚本相比最大的优点是:不会误删!


期间有改进过自己简单的脚本2.0,思路:

广告一般是几秒的,比如5秒,这样大小就基本固定了

在检查 #EXT-X-DISCONTINUITY 的基础上,检查 ts 的size,

就是请求head,如果小于2M的,该ts就算广告


2.0比1.0准确了很多,但是由于引入了size的请求,就有问题了:

需要同步请求才可以成功过滤,并且播放开始会卡很久时间


于是又想了一个思路3.0,即所有的都异步请求,修改完m3u8再开始加载视频,

改了很久,还是视频加载失败…


还有一个思路4.0,不是边播边下载吗?直接修改完后替换缓存,没改成功…


最终就只有简单暴力的1.0版本好用~

至于为什么不hook fetch而是hook xhr,因为用hook fetch过滤失败了…


测试:




凡人修仙传,121集,tab: S2-H,时间:1:24开始有一个广告


(不是针对飞友的站点,因为速度快就拿来测了)


期待飞友的代码优化 :tieba_095:

举报· 244 次点击
登录 注册 站外分享
4 条回复  
75905pfkc 初学 2024-10-8 19:33:55

https://kimivod.com/

不知以上网址是否也是贴片广告,中间有广告.

如果脚本能过滤就好了.

diff 初学 2024-10-8 19:33:55

现在用的油猴脚本,经常误删,等大佬出手

snk 初学 2024-10-8 19:33:55

我好久没见过 那种广告了


也许你可以换一个没有广告的网站


比如 凡人修仙传 在线播放-注视影视

rardos 初学 2024-10-8 19:33:55

这个油猴脚本的js,放到本身cms的html里面好像没用,我想多端使用,用扩展只有电脑能用用了

返回顶部