乐于分享
好东西不私藏

(源码更新)频道识别台标

(源码更新)频道识别台标

    用百度免费API识别,除了组播识别外,增加酒店源等数字顺序的频道识别。

https://nbhg.537224.xyz/udpxy/

<?php/** * */$web_root = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\');define('FFMPEG_PATH', '/www/server/ffmpeg/ffmpeg-6.1/ffmpeg'); define('SCREENSHOT_DIR', __DIR__ . '/screenshots');if (!is_dir(SCREENSHOT_DIR)) mkdir(SCREENSHOT_DIR, 0777, true);$action = $_GET['action'] ?? '';// --- 1. 物理清空 ---if ($action === 'clear') {    $files = glob(SCREENSHOT_DIR . '/*.jpg');     foreach ($files as $file) if (is_file($file)) unlink($file);    echo json_encode(['status' => 'ok']);    exit;}// --- 2. 增强版 OCR (高精度+区域切片) ---function get_ocr_result($imagePath, $apiKey, $secretKey) {    if (empty($apiKey) || empty($secretKey)) return "未命名频道";    $auth_url = "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={$apiKey}&client_secret={$secretKey}";    $auth_data = json_decode(@file_get_contents($auth_url), true);    $token = $auth_data['access_token'] ?? '';    if (!$token) return "鉴权失败";    $src = imagecreatefromjpeg($imagePath);    if($src) {        $crop_w = imagesx($src) * 0.25;        $crop_h = imagesy($src) * 0.20;        $dest = imagecreatetruecolor($crop_w, $crop_h);        imagecopy($dest, $src, 0, 0, 10, 10, $crop_w, $crop_h);        ob_start();        imagejpeg($dest);        $img_64 = base64_encode(ob_get_clean());        imagedestroy($src);        imagedestroy($dest);    } else {        $img_64 = base64_encode(file_get_contents($imagePath));    }    $url = 'https://aip.baidubce.com/rest/2.0/ocr/v1/accurate_basic?access_token=' . $token;    $options = ['http' => ['method' => 'POST', 'header' => "Content-Type: application/x-www-form-urlencoded\r\n", 'content' => http_build_query(['image' => $img_64])]];    $json = json_decode(@file_get_contents($url, false, stream_context_create($options)), true);    return !empty($json['words_result']) ? $json['words_result'][0]['words'] : "识别重试";}// --- 3. 核心扫描 ---if ($action === 'scan') {    header('Content-Type: application/json');    $input = json_decode(file_get_contents("php://input"), true);    $targetUrl = $input['url'];     $filename = md5($targetUrl) . '.jpg';    $savePath = SCREENSHOT_DIR . '/' . $filename;    $webPath = $web_root . '/screenshots/' . $filename . '?t=' . time();    $cmd = sprintf('%s -y -timeout 5000000 -i %s -ss 00:00:05 -frames:v 1 -q:v 2 %s 2>&1',            escapeshellcmd(FFMPEG_PATH), escapeshellarg($targetUrl), escapeshellarg($savePath));    exec($cmd);    if (file_exists($savePath)) {        $ocrName = get_ocr_result($savePath, $input['apiKey'], $input['secretKey']);        echo json_encode(['status' => 'ok', 'img' => $webPath, 'url' => $targetUrl, 'name' => $ocrName]);    } else {        echo json_encode(['status' => 'error']);    }    exit;}?><!DOCTYPE html><htmllang="zh-CN"><head>    <metacharset="UTF-8">    <metaname="viewport"content="width=device-width, initial-scale=1.0">    <title>火锅版IPTV频道识别器</title>    <style>        :root { --bg: #050505; --card: #111; --cyan: #00f2ff; --gray: #222; --green: #28a745; --orange: #fd7e14; }        body { background: var(--bg); color: #ccc; font-family: system-ui, -apple-system, sans-serif; margin: 0; padding-bottom: 90px; }        .container { max-width: 1200px; margin: 0 auto; padding: 15px; }        .panel { background: var(--card); border: 1px solid var(--gray); border-radius: 12px; padding: 20px; margin-bottom: 20px; }        .row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 12px; }        input { background: #1a1a1a; border: 1px solid #333; color: #fff; padding: 12px; border-radius: 6px; width: 100%; box-sizing: border-box; }        /* 顶部等长等宽按钮组 */        .top-btns { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }        button { padding: 14px; border: none; border-radius: 6px; font-weight: bold; cursor: pointer; transition: 0.2s; font-size: 14px; }        .btn-run { background: var(--cyan); color: #000; }        .btn-clear { background: #333; color: #ff4d4d; border: 1px solid #444; }        #results { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 15px; }        .card { background: var(--card); border: 1px solid var(--gray); border-radius: 8px; overflow: hidden; }        .card img { width: 100%; height: 150px; object-fit: cover; background: #000; display: block; }        .card-edit { padding: 10px; border-top: 1px solid #222; }        .name-edit { background: transparent; border: none; color: var(--cyan); font-size: 16px; font-weight: bold; width: 100%; text-align: center; outline: none; }        /* 底部工具栏 - 取消状态文字,保持纯净 */        .footer-tools { position: fixed; bottom: 0; left: 0; right: 0; background: rgba(5,5,5,0.9); padding: 15px 20px; border-top: 1px solid #333; backdrop-filter: blur(10px); z-index: 1000; }        .export-group { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; max-width: 800px; margin: 0 auto; }        .btn-m3u { background: var(--green); color: #fff; }        .btn-txt { background: var(--orange); color: #fff; }        button:active { opacity: 0.8; transform: scale(0.98); }    </style></head><body><divclass="container">    <divclass="panel">        <divclass="row">            <inputtype="text"id="ak"placeholder="百度 API Key">            <inputtype="password"id="sk"placeholder="百度 Secret Key">        </div>        <inputtype="text"id="urlTpl"placeholder="URL模板: http://host/live/{ID}.m3u8"style="margin-bottom:12px;">        <divclass="row">            <inputtype="number"id="sid"value="1"placeholder="Start">            <inputtype="number"id="eid"value="10"placeholder="End">        </div>        <divclass="top-btns">            <buttonclass="btn-run"id="runBtn"onclick="startScan()">▶ 开始识别</button>            <buttonclass="btn-clear"onclick="clearServer()">🗑 清空缓存</button>        </div>    </div>    <divid="results"></div></div><divclass="footer-tools">    <divclass="export-group">        <buttonclass="btn-m3u"onclick="exportFile('m3u')">一键下载 M3U</button>        <buttonclass="btn-txt"onclick="exportFile('txt')">一键下载 TXT</button>    </div></div><script>let scanning = false;async function startScan() {    if(scanning) return;    const tpl = document.getElementById('urlTpl').value;    const sid = parseInt(document.getElementById('sid').value);    const eid = parseInt(document.getElementById('eid').value);    if(!tpl.includes('{ID}')) return alert("模板需含{ID}");    scanning = true;    const runBtn = document.getElementById('runBtn');    runBtn.innerText = "识别中...";    for(let i = sid; i <= eid; i++) {        if(!scanning) break;        const target = tpl.replace('{ID}', i);        try {            const res = await fetch('?action=scan', {                method: 'POST',                body: JSON.stringify({                     url: target,                     apiKey: document.getElementById('ak').value,                     secretKey: document.getElementById('sk').value                 })            });            const data = await res.json();            if(data.status === 'ok') {                const div = document.createElement('div');                div.className = 'card';                div.dataset.url = data.url;                div.innerHTML = `<img src="${data.img}"><div class="card-edit"><input type="text" class="name-edit" value="${data.name}"></div>`;                document.getElementById('results').prepend(div);            }        } catch(e) {}    }    scanning = false;    runBtn.innerText = "▶ 开始识别";}function exportFile(type) {    const cards = document.querySelectorAll('.card');    if(cards.length === 0) return alert("无数据可导出");    let content = "";    if(type === 'm3u') {        content = "#EXTM3U\n";        cards.forEach(card => {            const name = card.querySelector('.name-edit').value;            content += `#EXTINF:-1,${name}\n${card.dataset.url}\n`;        });    } else {        cards.forEach(card => {            const name = card.querySelector('.name-edit').value;            content += `${name},${card.dataset.url}\n`;        });    }    const blob = new Blob([content], { type: 'text/plain' });    const a = document.createElement('a');    a.href = URL.createObjectURL(blob);    a.download = `playlist.${type}`;    a.click();}async function clearServer() {    if(!confirm("确定物理删除所有缓存图片吗?")) return;    await fetch('?action=clear');    document.getElementById('results').innerHTML = '';}</script></body></html>