Greasy Fork

Greasy Fork is available in English.

CCTV客户端视频解析

将CCTV视频解析成HLS地址.

当前为 2025-08-30 提交的版本,查看 最新版本

// ==UserScript==
// @name:en-US         CCTV-HLS-Client
// @name               CCTV客户端视频解析
// @description:en-US  parse cctv video to hls url.
// @description        将CCTV视频解析成HLS地址.
// @namespace          http://greasyfork.icu/users/135090
// @version            1.4.9
// @author             [ZWB](http://greasyfork.icu/zh-CN/users/863179)
// @license            CC
// @grant              none
// @run-at             document-end
// @match              https://*.cctv.com/2*/VID*.shtml*
// @match              https://*.cctv.cn/2*/VID*.shtml*
// @match              https://vdn.apps.cntv.cn/api/getHttpVideoInfo*
// @icon               https://tv.cctv.cn/favicon.ico
// ==/UserScript==

(function () {
    if (location.hostname.indexOf(".cctv.com") > 0 || location.hostname.indexOf(".cctv.cn") > 0) {
        var base = "https://vdn.apps.cntv.cn";
        var apiUrl = base + `/api/getHttpVideoInfo.do?client=flash&im=0&pid=${guid}`;
        location.href = (apiUrl);
    }

    if (location.hostname.indexOf("vdn.apps.cntv.cn") > -1) {
        var data = JSON.parse(document.body.textContent);
        var title = data?.title;
        var hlsUrl = data?.manifest?.hls_enc2_url.replaceAll("main", "2000");
        if (data?.play_channel.indexOf("4K") > 0) {
            hlsUrl = data?.hls_url.replaceAll("main", "4000");
        }
        var tsnlen = hlsUrl.split("/").length - 2;
        var tsn = hlsUrl.split("/")[tsnlen];
        document.body.textContent = hlsUrl.replaceAll('&','%26');
        document.title = tsn || title;
        var txtctt = document.createElement("h2");
        txtctt.textContent = title;
        document.body.appendChild(txtctt);
        downloadM3U8Video(hlsUrl, document.title + '.ts', {
            onProgress: (current, total) => {
                var cotp = `${Math.round((current / total) * 100)}`;
                txtctt.textContent = title + "---下载进程" + cotp + "%";
                console.info(`进度: ${current}/${total} (${cotp}%)`);
            }
        });
    }

    async function downloadM3U8Video(m3u8Url, outputFilename = 'video.m2ts', options = {}) {
        try {
            // 1. 获取并解析M3U8文件
            const response = await fetch(m3u8Url);
            if (!response.ok) throw new Error(`无法访问 M3U8: ${response.status}`);
            const m3u8Content = await response.text();
            const lines = m3u8Content.split('\n');
            const baseUrl = m3u8Url.substring(0, m3u8Url.lastIndexOf("/") + 1);
            if (!confirm('开始')) {
                document.title = title+'.ts';
                // 猫抓调用cbox的时候需要将上一行改成document.title = tsn+'.ts';
                return; 
            }
            // 1.1 解析TS分片URL
            const segments = [];
            for (const line of lines) {
                if (line && !line.startsWith('#') && (line.endsWith('.ts') || line.match(/\.ts\?/))) {
                    const segmentUrl = line.startsWith('http') ? line : new URL(line, baseUrl).href;
                    segments.push(segmentUrl);
                }
            }

            if (segments.length === 0) throw new Error('在 M3U8 文件中未找到分片.');
            console.log(`找到 ${segments.length} 个ts分片.`);

            // 2. 下载所有分片,采用流式合并
            console.log('正在下载分片...');
            const blobs = [];
            const { onProgress } = options;

            for (let i = 0; i < segments.length; i++) {
                try {
                    const segmentResponse = await fetch(segments[i]);
                    if (!segmentResponse.ok) throw new Error(`无法访问分片: ${segmentResponse.status}`);

                    const blob = await segmentResponse.blob();
                    blobs.push(blob);

                    // 调用进度回调
                    if (typeof onProgress === 'function') {
                        onProgress(i + 1, segments.length);
                    }
                } catch (error) {
                    console.error(`下载分片时出现错误 ${segments[i]}:`, error);
                    throw error; // 可以选择继续或抛出错误
                }
            }

            // 3. 下载合并完成后的视频
            console.log('正在创建完整视频的下载链接...');
            const mergedBlob = new Blob(blobs, { type: 'video/mp2t' });
            const url = URL.createObjectURL(mergedBlob);
            const a = document.createElement('a');
            a.href = url;
            a.download = outputFilename;
            a.style.display = "none";
            document.body.appendChild(a);
            a.click();

            // 4. 清理临时链接
            setTimeout(() => {
                document.body.removeChild(a);
                URL.revokeObjectURL(url);
            }, 100);

            console.log('下载完成!');
            return true;
        } catch (error) {
            console.error('下载完整视频时发生错误:', error);
            throw error;
        }
    }
})();