/**
* MoonTVPlus 超级M3U8广告过滤脚本
*
* 使用说明:
* - 此脚本用于在管理配置中自定义M3U8播放列表的去广告逻辑
* - 保存到数据库中的 SiteConfig.CustomAdFilterCode 字段
* - 客户端会自动缓存代码,只在版本更新时重新获取
* - 函数签名必须为: filterAdsFromM3U8(type, m3u8Content)
* - type 参数为视频源类型(可选),m3u8Content 为播放列表内容
* - 函数需要返回处理后的M3U8内容
*
* 工作流程:
* 1. 解析M3U8结构(提取片段信息和元数据)
* 2. 多维度分析(时长异常、位置异常、不连续标记)
* 3. 科学统计(Z-score、标准差、百分位数)
* 4. 智能过滤(动态阈值、综合评分)
* 5. 完善重建(保留关键标签、更新头部信息)
*/
function filterAdsFromM3U8(type, m3u8Content) {
if (!m3u8Content) return '';
try {
// 使用超级算法
return SuperFilterAdsFromM3U8(m3u8Content, null);
} catch (error) {
console.error('[MoonTVPlus AdFilter] 超级过滤异常,降级使用基础过滤:', error);
// 降级方案
return filterAdsFromM3U8Basic(m3u8Content);
}
}
/**
* 基础M3U8广告过滤(降级方案)
*/
function filterAdsFromM3U8Basic(m3u8Content) {
if (!m3u8Content) return '';
const adKeywords = [
'sponsor',
'/ad/',
'/ads/',
'advert',
'advertisement',
'/adjump',
'redtraffic'
];
const lines = m3u8Content.split('\n');
const filteredLines = [];
let i = 0;
while (i < lines.length) {
const line = lines[i];
if (line.includes('#EXT-X-DISCONTINUITY')) {
i++;
continue;
}
if (line.includes('#EXTINF:')) {
if (i + 1 < lines.length) {
const nextLine = lines[i + 1];
const containsAdKeyword = adKeywords.some(keyword =>
nextLine.toLowerCase().includes(keyword.toLowerCase())
);
if (containsAdKeyword) {
i += 2;
continue;
}
}
}
filteredLines.push(line);
i++;
}
return filteredLines.join('\n');
}
/**
* 超级M3U8广告过滤
*/
function SuperFilterAdsFromM3U8(m3u8Content, regexFilter) {
if (!m3u8Content) return '';
// 第一阶段:预处理
let processedContent = regexFilter
? applyRegexFilter(m3u8Content, regexFilter)
: m3u8Content;
// 解析M3U8结构
const parsed = parseM3U8Structure(processedContent);
if (!parsed.segments || parsed.segments.length === 0) {
return processedContent;
}
// 第二阶段:科学分析
const stats = calculateSegmentStats(parsed.segments);
const analyzedSegments = analyzeSegments(parsed.segments, stats);
const filteredSegments = applyFilterDecision(analyzedSegments, stats);
// 第三阶段:重建M3U8
return rebuildM3U8(parsed.headers, filteredSegments, processedContent);
}
/**
* 应用正则过滤
*/
function applyRegexFilter(content, regexFilter) {
try {
const regex = new RegExp(regexFilter, 'gi');
return content.replace(regex, '');
} catch (e) {
console.warn('[MoonTVPlus AdFilter] 正则过滤失败:', e);
return content;
}
}
/**
* 深度解析M3U8结构
*/
function parseM3U8Structure(content) {
const lines = content.split('\n');
const segments = [];
const headers = { main: [], other: [] };
let currentDiscontinuity = false;
let currentMap = null;
let segmentIndex = 0;
for (let i = 0; i < lines.length; i++) {
const line = (lines[i] || '').trim();
// 收集头部信息
if (i < 10 && line.startsWith('#EXT')) {
headers.main.push(line);
continue;
}
// 处理关键标签
if (line.startsWith('#EXT-X-MAP:')) {
currentMap = line;
continue;
}
if (line.includes('#EXT-X-DISCONTINUITY')) {
currentDiscontinuity = true;
continue;
}
// 解析片段
if (line.startsWith('#EXTINF:')) {
const durationMatch = line.match(/#EXTINF:([\d.]+)/);
if (durationMatch && lines[i + 1] && !(lines[i + 1] || '').startsWith('#')) {
const duration = parseFloat(durationMatch[1]);
const url = (lines[i + 1] || '').trim();
segments.push({
index: segmentIndex++,
startLine: i,
endLine: i + 1,
duration: duration,
url: url,
hasDiscontinuity: currentDiscontinuity,
hasMap: currentMap !== null,
content: currentMap
? [currentMap, line, lines[i + 1]].join('\n')
: [line, lines[i + 1]].join('\n'),
isAd: false,
adScore: 0
});
currentDiscontinuity = false;
currentMap = null;
i++;
}
} else if (line.startsWith('#')) {
headers.other.push(line);
}
}
return { segments, headers };
}
/**
* 计算高级统计量
*/
function calculateSegmentStats(segments) {
const durations = segments.map(s => s.duration);
const totalDuration = durations.reduce((sum, d) => sum + d, 0);
const avgDuration = totalDuration / durations.length;
// 计算标准差
const squaredDiffs = durations.map(d => Math.pow(d - avgDuration, 2));
const stdDev = Math.sqrt(squaredDiffs.reduce((sum, sd) => sum + sd, 0) / durations.length);
// 排序后的时长数组用于百分位计算
const sortedDurations = durations.slice().sort((a, b) => a - b);
const p10 = sortedDurations[Math.floor(durations.length * 0.1)];
const p90 = sortedDurations[Math.floor(durations.length * 0.9)];
return {
avgDuration: avgDuration,
stdDev: stdDev,
p10: p10,
p90: p90,
totalDuration: totalDuration,
segmentCount: segments.length,
durationRange: [sortedDurations[0], sortedDurations[sortedDurations.length - 1]]
};
}
/**
* 多维度片段分析
*/
function analyzeSegments(segments, stats) {
const avgDuration = stats.avgDuration;
const stdDev = stats.stdDev;
const p10 = stats.p10;
return segments.map(segment => {
const deviation = Math.abs(segment.duration - avgDuration);
const zScore = stdDev === 0 ? 0 : deviation / stdDev;
// 1. 时长异常检测
const durationAbnormality = Math.min(1, zScore / 3);
// 2. 位置异常检测
let positionFactor = 0;
if (segment.index < 3 && segment.duration < p10) {
positionFactor = 0.8;
} else if (segment.index > segments.length - 3 && segment.duration < p10) {
positionFactor = 0.5;
}
// 3. 不连续标记检测
const discontinuityFactor = segment.hasDiscontinuity ? 0.3 : 0;
// 综合广告概率
const adScore = Math.min(1,
(durationAbnormality * 0.6) +
(positionFactor * 0.3) +
(discontinuityFactor * 0.1)
);
return Object.assign({}, segment, {
adScore: adScore,
isAd: adScore > 0.65,
stats: { deviation: deviation, zScore: zScore }
});
});
}
/**
* 智能过滤决策
*/
function applyFilterDecision(segments, stats) {
const avgDuration = stats.avgDuration;
const stdDev = stats.stdDev;
// 动态调整阈值
const baseThreshold = 0.65;
const dynamicThreshold = Math.min(0.8, Math.max(0.5,
baseThreshold - (stdDev / avgDuration) * 0.2
));
return segments.filter(segment => {
// 明确广告标记
if (segment.isAd && segment.adScore > dynamicThreshold) {
return false;
}
// 极短片段过滤
if (segment.duration < 1.0 && segment.index > 3) {
return false;
}
// 保留关键片段
if (segment.hasMap) {
return true;
}
return true;
});
}
/**
* 完美重建M3U8
*/
function rebuildM3U8(headers, segments, originalContent) {
const keepLines = new Set();
// 保留头部
headers.main.forEach((_, i) => keepLines.add(i));
// 保留片段
segments.forEach(segment => {
for (let i = segment.startLine; i <= segment.endLine; i++) {
keepLines.add(i);
}
});
// 处理关键标签
const lines = originalContent.split('\n');
const criticalTags = [
'#EXT-X-VERSION',
'#EXT-X-TARGETDURATION',
'#EXT-X-MEDIA-SEQUENCE',
'#EXT-X-PLAYLIST-TYPE',
'#EXT-X-ENDLIST'
];
for (let i = 0; i < lines.length; i++) {
const line = (lines[i] || '').trim();
if (criticalTags.some(tag => line.startsWith(tag))) {
keepLines.add(i);
}
}
// 重建
const filteredLines = lines.filter((_, i) => keepLines.has(i));
updateM3U8Headers(filteredLines, segments);
return filteredLines.join('\n');
}
/**
* 更新M3U8头部信息
*/
function updateM3U8Headers(lines, segments) {
if (segments.length === 0) return;
// 更新TARGETDURATION
const maxDuration = Math.max.apply(null, segments.map(s => s.duration));
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-TARGETDURATION')) {
lines[i] = '#EXT-X-TARGETDURATION:' + Math.ceil(maxDuration);
break;
}
}
// 更新MEDIA-SEQUENCE
if (segments[0].index > 0) {
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE')) {
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + segments[0].index;
break;
}
}
}
}
功能说明
去广告功能增强。
使用场景
自定义去广告脚本。
AI 整合适配代码如下:
其他内容
其他可供参考的内容,如: