乐于分享
好东西不私藏

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

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

    用百度免费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 { backgroundvar(--bg); color#cccfont-family: system-ui, -apple-system, sans-serif; margin0padding-bottom90px; }        .container { max-width1200pxmargin0 auto; padding15px; }        .panel { backgroundvar(--card); border1px solid var(--gray); border-radius12pxpadding20pxmargin-bottom20px; }        .row { display: grid; grid-template-columns1fr 1fr; gap12pxmargin-bottom12px; }        input { background#1a1a1aborder1px solid #333color#fffpadding12pxborder-radius6pxwidth100%box-sizing: border-box; }        /* 顶部等长等宽按钮组 */        .top-btns { display: grid; grid-template-columns1fr 1fr; gap12px; }        button { padding14pxborder: none; border-radius6pxfont-weight: bold; cursor: pointer; transition0.2sfont-size14px; }        .btn-run { backgroundvar(--cyan); color#000; }        .btn-clear { background#333color#ff4d4dborder1px solid #444; }        #results { display: grid; grid-template-columnsrepeat(auto-fill, minmax(260px1fr)); gap15px; }        .card { backgroundvar(--card); border1px solid var(--gray); border-radius8pxoverflow: hidden; }        .card img { width100%height150pxobject-fit: cover; background#000display: block; }        .card-edit { padding10pxborder-top1px solid #222; }        .name-edit { background: transparent; border: none; colorvar(--cyan); font-size16pxfont-weight: bold; width100%text-align: center; outline: none; }        /* 底部工具栏 - 取消状态文字,保持纯净 */        .footer-tools { position: fixed; bottom0left0right0backgroundrgba(5,5,5,0.9); padding15px 20pxborder-top1px solid #333backdrop-filterblur(10px); z-index1000; }        .export-group { display: grid; grid-template-columns1fr 1fr; gap12pxmax-width800pxmargin0 auto; }        .btn-m3u { backgroundvar(--green); color#fff; }        .btn-txt { backgroundvar(--orange); color#fff; }        button:active { opacity0.8transformscale(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',                bodyJSON.stringify({                     url: target,                     apiKeydocument.getElementById('ak').value                    secretKeydocument.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 === 0return 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>