AI 生成: 途播播放器专用直播源,每天自动更新央视、卫视、地方台等频道,生成可访问的 M3U 格式文件。

This commit is contained in:
admin
2026-09-28 22:21:33 +08:00
commit d93877b89a
9 changed files with 295 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
__pycache__/
*.pyc
output/*.m3u
.venv/
venv/
+22
View File
@@ -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
+65
View File
@@ -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
+49
View File
@@ -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)
+2
View File
@@ -0,0 +1,2 @@
# 每天凌晨 3 点自动更新直播源
0 3 * * * cd /app && /usr/local/bin/python /app/updater.py >> /var/log/updater.log 2>&1
View File
+2
View File
@@ -0,0 +1,2 @@
Flask==3.0.3
requests==2.32.3
+71
View File
@@ -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
+79
View File
@@ -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()