就是那种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开始有一个广告
(不是针对飞友的站点,因为速度快就拿来测了)
期待飞友的代码优化 |