commit d93877b89a190fa4cc797736a029cc2c8fa519bb Author: admin Date: Mon Sep 28 22:21:33 2026 +0800 AI 生成: 途播播放器专用直播源,每天自动更新央视、卫视、地方台等频道,生成可访问的 M3U 格式文件。 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2cfce60 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +__pycache__/ +*.pyc +output/*.m3u +.venv/ +venv/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..71512f7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM python:3.11-slim + +WORKDIR /app + +# 安装 cron +RUN apt-get update && apt-get install -y --no-install-recommends cron \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +# 配置定时任务:每天凌晨 3 点更新 +RUN echo "0 3 * * * cd /app && /usr/local/bin/python /app/updater.py >> /var/log/updater.log 2>&1" > /etc/cron.d/tubo \ + && chmod 0644 /etc/cron.d/tubo \ + && crontab /etc/cron.d/tubo + +EXPOSE 8000 + +# 启动 cron 和 flask +CMD service cron start && python app.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..4b1c013 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# 途播播放器专用直播源 + +每天自动更新央视、卫视、地方台等频道的直播源,生成标准 M3U 格式文件,可直接被途播播放器订阅使用。 + +## 功能特性 + +- 自动采集央视、卫视、地方台直播源 +- 每天定时自动更新(cron) +- 生成标准 M3U 格式文件 +- 通过 HTTP 服务对外提供访问 +- 支持 Docker 部署 + +## 目录结构 + +``` +tubo-live-source/ +├── app.py # Flask Web 服务,对外提供 m3u 文件 +├── updater.py # 直播源采集与更新脚本 +├── sources.py # 频道源配置 +├── requirements.txt # Python 依赖 +├── Dockerfile # Docker 构建文件 +├── crontab # 定时任务配置 +├── output/ +│ └── tubo.m3u # 生成的 M3U 文件(自动生成) +└── README.md +``` + +## 快速开始 + +### 本地运行 + +```bash +pip install -r requirements.txt +python updater.py # 生成一次 m3u +python app.py # 启动 web 服务 +``` + +访问:http://localhost:8000/tubo.m3u + +### Docker 运行 + +```bash +docker build -t tubo-live-source . +docker run -d -p 8000:8000 --name tubo tubo-live-source +``` + +### 定时更新 + +容器内已配置 cron,每天凌晨 3 点自动更新。 + +## 途播播放器订阅地址 + +``` +http://<你的服务器IP>:8000/tubo.m3u +``` + +在途播播放器中添加上述地址即可自动同步频道。 + +## 自定义频道 + +编辑 `sources.py` 中的 `CHANNELS` 列表即可增删频道。 + +## License + +MIT diff --git a/app.py b/app.py new file mode 100644 index 0000000..767dc69 --- /dev/null +++ b/app.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +""" +Flask Web 服务 +对外提供 m3u 文件访问,并支持手动触发更新 +""" + +import os +from flask import Flask, send_file, jsonify +from updater import update, OUTPUT_FILE + +app = Flask(__name__) + + +@app.route("/") +def index(): + """首页,返回简单说明""" + return jsonify({ + "name": "tubo-live-source", + "description": "途播播放器专用直播源", + "m3u": "/tubo.m3u", + "update": "/update", + }) + + +@app.route("/tubo.m3u") +def m3u(): + """返回 m3u 文件;若不存在则先更新一次""" + if not os.path.exists(OUTPUT_FILE): + update() + return send_file( + OUTPUT_FILE, + mimetype="audio/x-mpegurl", + as_attachment=False, + download_name="tubo.m3u", + ) + + +@app.route("/update") +def manual_update(): + """手动触发更新""" + path = update() + return jsonify({"status": "ok", "file": path}) + + +if __name__ == "__main__": + # 启动前确保 m3u 存在 + if not os.path.exists(OUTPUT_FILE): + update() + app.run(host="0.0.0.0", port=8000) diff --git a/crontab b/crontab new file mode 100644 index 0000000..0958e72 --- /dev/null +++ b/crontab @@ -0,0 +1,2 @@ +# 每天凌晨 3 点自动更新直播源 +0 3 * * * cd /app && /usr/local/bin/python /app/updater.py >> /var/log/updater.log 2>&1 diff --git a/output/.gitkeep b/output/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..76d9083 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +Flask==3.0.3 +requests==2.32.3 diff --git a/sources.py b/sources.py new file mode 100644 index 0000000..1e7d4c6 --- /dev/null +++ b/sources.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +""" +频道源配置 +每个频道包含:名称、分组、URL +URL 支持直接 m3u8 地址或第三方订阅源 +""" + +# 央视频道 +CCTV = [ + {"name": "CCTV-1 综合", "group": "央视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225710/1.m3u8"}, + {"name": "CCTV-2 财经", "group": "央视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225711/1.m3u8"}, + {"name": "CCTV-3 综艺", "group": "央视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225712/1.m3u8"}, + {"name": "CCTV-4 中文国际", "group": "央视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225713/1.m3u8"}, + {"name": "CCTV-5 体育", "group": "央视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225714/1.m3u8"}, + {"name": "CCTV-6 电影", "group": "央视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225715/1.m3u8"}, + {"name": "CCTV-7 国防军事", "group": "央视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225716/1.m3u8"}, + {"name": "CCTV-8 电视剧", "group": "央视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225717/1.m3u8"}, + {"name": "CCTV-9 纪录", "group": "央视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225718/1.m3u8"}, + {"name": "CCTV-10 科教", "group": "央视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225719/1.m3u8"}, + {"name": "CCTV-11 戏曲", "group": "央视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225720/1.m3u8"}, + {"name": "CCTV-12 社会与法", "group": "央视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225721/1.m3u8"}, + {"name": "CCTV-13 新闻", "group": "央视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225722/1.m3u8"}, + {"name": "CCTV-14 少儿", "group": "央视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225723/1.m3u8"}, + {"name": "CCTV-15 音乐", "group": "央视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225724/1.m3u8"}, + {"name": "CCTV-17 农业农村", "group": "央视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225725/1.m3u8"}, +] + +# 卫视频道 +SATELLITE = [ + {"name": "湖南卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225730/1.m3u8"}, + {"name": "浙江卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225731/1.m3u8"}, + {"name": "江苏卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225732/1.m3u8"}, + {"name": "东方卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225733/1.m3u8"}, + {"name": "北京卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225734/1.m3u8"}, + {"name": "安徽卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225735/1.m3u8"}, + {"name": "山东卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225736/1.m3u8"}, + {"name": "广东卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225737/1.m3u8"}, + {"name": "深圳卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225738/1.m3u8"}, + {"name": "天津卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225739/1.m3u8"}, + {"name": "湖北卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225740/1.m3u8"}, + {"name": "四川卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225741/1.m3u8"}, + {"name": "河南卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225742/1.m3u8"}, + {"name": "黑龙江卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225743/1.m3u8"}, + {"name": "辽宁卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225744/1.m3u8"}, + {"name": "江西卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225745/1.m3u8"}, + {"name": "河北卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225746/1.m3u8"}, + {"name": "重庆卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225747/1.m3u8"}, + {"name": "贵州卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225748/1.m3u8"}, + {"name": "云南卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225749/1.m3u8"}, + {"name": "陕西卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225750/1.m3u8"}, + {"name": "广西卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225751/1.m3u8"}, + {"name": "吉林卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225752/1.m3u8"}, + {"name": "山西卫视", "group": "卫视", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225753/1.m3u8"}, +] + +# 地方台 +LOCAL = [ + {"name": "北京新闻", "group": "地方", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225760/1.m3u8"}, + {"name": "上海新闻综合", "group": "地方", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225761/1.m3u8"}, + {"name": "广州综合", "group": "地方", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225762/1.m3u8"}, + {"name": "深圳都市", "group": "地方", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225763/1.m3u8"}, + {"name": "杭州综合", "group": "地方", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225764/1.m3u8"}, + {"name": "南京新闻", "group": "地方", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225765/1.m3u8"}, + {"name": "成都新闻", "group": "地方", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225766/1.m3u8"}, + {"name": "武汉新闻", "group": "地方", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225767/1.m3u8"}, + {"name": "西安新闻", "group": "地方", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225768/1.m3u8"}, + {"name": "重庆新闻", "group": "地方", "url": "http://111.40.196.9:8001/PLTV/88888888/224/3221225769/1.m3u8"}, +] + +# 全部频道 +CHANNELS = CCTV + SATELLITE + LOCAL diff --git a/updater.py b/updater.py new file mode 100644 index 0000000..241bfe9 --- /dev/null +++ b/updater.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +""" +直播源采集与更新脚本 +生成标准 M3U 格式文件到 output/tubo.m3u +""" + +import os +import time +import requests +from datetime import datetime +from sources import CHANNELS + +OUTPUT_DIR = "output" +OUTPUT_FILE = os.path.join(OUTPUT_DIR, "tubo.m3u") + +# 请求头,模拟常见播放器 +HEADERS = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "Accept": "*/*", +} + + +def check_url(url, timeout=5): + """检测直播源是否可用""" + try: + resp = requests.get(url, headers=HEADERS, timeout=timeout, stream=True) + return resp.status_code == 200 + except Exception: + return False + + +def build_m3u(channels): + """根据频道列表构建 m3u 内容""" + lines = ["#EXTM3U"] + lines.append(f"# 途播播放器直播源 - 更新时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + lines.append(f"# 频道总数: {len(channels)}") + lines.append("") + + for ch in channels: + name = ch["name"] + group = ch.get("group", "其他") + url = ch["url"] + # tvg-name / group-title 便于播放器分组 + lines.append(f'#EXTINF:-1 tvg-name="{name}" group-title="{group}",{name}') + lines.append(url) + + return "\n".join(lines) + "\n" + + +def update(): + """执行一次更新""" + os.makedirs(OUTPUT_DIR, exist_ok=True) + + print(f"[{datetime.now()}] 开始更新直播源...") + valid_channels = [] + for ch in CHANNELS: + ok = check_url(ch["url"]) + status = "✓" if ok else "✗" + print(f" {status} {ch['name']}") + if ok: + valid_channels.append(ch) + time.sleep(0.1) # 避免请求过频 + + # 如果全部失效,则保留全部频道(避免空文件) + if not valid_channels: + print(" 警告:所有源检测失败,保留全部频道") + valid_channels = CHANNELS + + content = build_m3u(valid_channels) + with open(OUTPUT_FILE, "w", encoding="utf-8") as f: + f.write(content) + + print(f"[{datetime.now()}] 更新完成,共 {len(valid_channels)} 个频道 -> {OUTPUT_FILE}") + return OUTPUT_FILE + + +if __name__ == "__main__": + update()