Merge branch 'Guovin:master' into main

This commit is contained in:
alantang 2024-11-29 21:37:47 +08:00 committed by GitHub
commit 0a8c1270ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 2006 additions and 979 deletions

View file

@ -10,7 +10,6 @@ on:
- main
- dev
- gd
- gd-test
jobs:
push:
runs-on: ${{ matrix.operating-system }}
@ -25,10 +24,10 @@ jobs:
- uses: actions/checkout@v3
with:
ref: ${{ env.BRANCH_NAME }}
- name: Run with setup-python 3.8
- name: Run with setup-python 3.13
uses: actions/setup-python@v4
with:
python-version: '3.8'
python-version: '3.13'
update-environment: true
cache: 'pipenv'
- name: Check open_driver config
@ -41,33 +40,36 @@ jobs:
except:
open_driver = False
print(open_driver)')" >> $GITHUB_ENV
- name: Check open_ffmpeg config
id: check_ffmpeg
run: |
echo "OPEN_FFMPEG=$(python -c '
try:
from utils.config import config
open_ffmpeg = config.open_ffmpeg
except:
open_ffmpeg = False
print(open_ffmpeg)')" >> $GITHUB_ENV
# - name: Check open_ffmpeg config
# id: check_ffmpeg
# run: |
# echo "OPEN_FFMPEG=$(python -c '
# try:
# from utils.config import config
# open_ffmpeg = config.open_ffmpeg
# except:
# open_ffmpeg = False
# print(open_ffmpeg)')" >> $GITHUB_ENV
- name: Set up Chrome
if: env.OPEN_DRIVER == 'True' || env.OPEN_DRIVER == 'true'
if: env.OPEN_DRIVER == 'True'
uses: browser-actions/setup-chrome@latest
with:
chrome-version: stable
- name: Download chrome driver
if: env.OPEN_DRIVER == 'True' || env.OPEN_DRIVER == 'true'
if: env.OPEN_DRIVER == 'True'
uses: nanasess/setup-chromedriver@master
- name: Install FFmpeg
if: env.OPEN_FFMPEG == 'True' || env.OPEN_FFMPEG == 'true'
run: sudo apt-get update && sudo apt-get install -y ffmpeg
# - name: Install FFmpeg
# if: env.OPEN_FFMPEG == 'True'
# run: sudo apt-get update && sudo apt-get install -y ffmpeg
- name: Install pipenv
run: pip3 install --user pipenv
- name: Install dependecies
run: pipenv --python 3.8 && pipenv install
- name: Build
run: pipenv run build
run: pipenv --python 3.13 && pipenv install --deploy
- name: Install selenium
if: env.OPEN_DRIVER == 'True'
run: pipenv install selenium
- name: Update
run: pipenv run dev
- name: Commit and push if changed
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
@ -95,13 +97,11 @@ jobs:
if [[ -f "$final_m3u_file" ]]; then
git add -f "$final_m3u_file"
fi
if [[ -f "output/result_cache.pkl" ]]; then
git add -f "output/result_cache.pkl"
if [[ -f "output/cache.pkl" ]]; then
git add -f "output/cache.pkl"
fi
if [[ -f "output/user_result.log" ]]; then
git add -f "output/user_result.log"
elif [[ -f "output/result.log" ]]; then
git add -f "output/result.log"
if [[ -f "output/sort.log" ]]; then
git add -f "output/sort.log"
fi
if [[ -f "updates/fofa/fofa_hotel_region_result.pkl" ]]; then
git add -f "updates/fofa/fofa_hotel_region_result.pkl"

View file

@ -14,7 +14,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.8'
python-version: '3.13'
update-environment: true
cache: 'pipenv'
@ -22,10 +22,7 @@ jobs:
run: pip3 install --user pipenv
- name: Install dependencies with pipenv
run: pipenv --python 3.8 && pipenv install
- name: Install PyInstaller
run: pipenv install pyinstaller
run: pipenv --python 3.13 && pipenv install --dev
- name: Build the application
run: pipenv run pyinstaller tkinter_ui/tkinter_ui.spec
@ -36,7 +33,7 @@ jobs:
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: iptv-update-tool
name: IPTV
path: dist
- name: Get version from version.json
@ -79,6 +76,6 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: dist/iptv-update-tool.exe
asset_name: iptv-update-tool.exe
asset_path: dist/IPTV.exe
asset_name: IPTV.exe
asset_content_type: application/octet-stream

View file

@ -1,5 +1,36 @@
# 更新日志Changelog
## v1.5.4
### 2024/11/29
- ⚠️ Python 升级至 3.13,该版本已不支持 Win7若有需要请使用 v1.5.3
- ⚠️ Github 仓库改名iptv-api使用旧接口地址请及时更换新地址
- ⚠️ Docker 新镜像仓库启用guovern/iptv-api旧版的 tv-driver 改为guovern/iptv-api:latesttv-requests 改为 guovern/iptv-api:liteiptv-api:latest 为完整版、iptv-api:lite 为精简版,请使用新的名称命令进行拉取,旧仓库将不再维护
- ❤️ 新增微信公众号关注途径公众号搜索Govin推荐关注公众号可订阅更新通知与使用技巧等文章推送还可进行交流讨论
- ✨ 更换测速方法yt-dlp重构测速逻辑提升准确性、稳定性与效率减小接口切换延迟#563
- ✨ 新增支持 ARM v7#562
- ✨ 新增双结果 API 访问ip/m3u, ip/txt#581
- ✨ 新增启动 API 服务命令pipenv run service
- 🪄 优化 Docker 镜像大小(完整版:-25%,精简版:-66%
- 🐛 修复部分播放器不支持的信息间隔符(#581
<details>
<summary>English</summary>
- ⚠️ Python has been upgraded to version 3.13, which no longer supports Win7. If needed, please use version v1.5.3.
- ⚠️ The GitHub repository has been renamed to iptv-api. If you are using the old API address, please update it to the new one promptly.
- ⚠️ New Docker image repository is now active: guovern/iptv-api (the old tv-driver is now guovern/iptv-api:latest, and tv-requests is now guovern/iptv-api:lite). iptv-api:latest is the full version, and iptv-api:lite is the lightweight version. Please use the new names to pull the images, as the old repository will no longer be maintained.
- ❤️ A new way to follow the WeChat official account (search for: Govin) has been added. It is recommended to follow the official account to subscribe to update notifications, usage tips, and engage in discussions.
- ✨ The speed measurement method has been changed to yt-dlp, and the speed measurement logic has been refactored to improve accuracy, stability, and efficiency, reducing interface switching delay (#563).
- ✨ Support for ARM v7 has been added (#562).
- ✨ Dual result API access (ip/m3u, ip/txt) has been added (#581).
- ✨ A command to start the API service (pipenv run service) has been added.
- 🪄 The size of the Docker image has been optimized (Full version: -25%, Lite version: -66%).
- 🐛 Fixed the information delimiter issue for some players that do not support it (#581).
</details>
## v1.5.3
### 2024/11/19
@ -59,7 +90,7 @@
- ✨ 新增频道接口白名单:不参与测速,永远保留在结果最前面(#470
使用方法:
1. 模板频道接口地址后添加$!即可实现(如:广东珠江,http://xxx.m3u$!
2. 额外信息补充(如:广东珠江,http://xxx.m3u$!额外信息 更多接口白名单请至https://github.com/Guovin/TV/issues/514 讨论
2. 额外信息补充(如:广东珠江,http://xxx.m3u$!额外信息 更多接口白名单请至https://github.com/Guovin/iptv-api/issues/514 讨论
- ✨ 新增 🈳 无结果频道分类:无结果频道默认归类至该底部分类下(#473
- ✨ 接口地址增加来源类型说明
- ✨ 默认模板增加广东民生(#481、广州综合#504
@ -78,7 +109,7 @@
- ✨ Added channel interface whitelist: Not participating in speed testing, always kept at the very front of the results. (#470)
Usage:
1. Add $! after the template channel interface address (e.g., Guangdong Pearl River, http://xxx.m3u$!).
2. Additional information can be appended (e.g., Guangdong Pearl River, http://xxx.m3u$! Additional Information) (#470). For more interface whitelists, please discuss at https://github.com/Guovin/TV/issues/514.
2. Additional information can be appended (e.g., Guangdong Pearl River, http://xxx.m3u$! Additional Information) (#470). For more interface whitelists, please discuss at https://github.com/Guovin/iptv-api/issues/514.
- ✨ Added 🈳 No Results Channel Category: Channels without results are categorized under this bottom category by default (#473).
- ✨ Interface addresses now include source type descriptions.
- ✨ Default templates now include Guangdong People's Livelihood (#481) and Guangzhou Comprehensive (#504).

View file

@ -1,51 +1,58 @@
FROM python:3.8-slim
FROM python:3.13 AS builder
ARG APP_WORKDIR=/tv
ARG LITE=False
WORKDIR /app
COPY Pipfile* ./
RUN pip install -i https://mirrors.aliyun.com/pypi/simple pipenv
RUN PIPENV_VENV_IN_PROJECT=1 pipenv install --deploy\
&& if [ "$LITE" = False ]; then pipenv install selenium; fi
FROM python:3.13-slim
ARG APP_WORKDIR=/iptv-api
ARG LITE=False
ENV APP_WORKDIR=$APP_WORKDIR
COPY . $APP_WORKDIR
ENV LITE=$LITE
ENV PATH="/.venv/bin:$PATH"
WORKDIR $APP_WORKDIR
RUN pip install -i https://mirrors.aliyun.com/pypi/simple pipenv \
&& pipenv install
COPY . $APP_WORKDIR
RUN echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm main contrib non-free non-free-firmware\n \
deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm main contrib non-free non-free-firmware\n \
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm-updates main contrib non-free non-free-firmware\n \
deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm-updates main contrib non-free non-free-firmware\n \
deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm-updates main contrib non-free non-free-firmware\n \
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm-backports main contrib non-free non-free-firmware\n \
deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm-backports main contrib non-free non-free-firmware\n \
deb https://mirrors.tuna.tsinghua.edu.cn/debian-security/ bookworm-security main contrib non-free non-free-firmware\n \
deb-src https://mirrors.tuna.tsinghua.edu.cn/debian-security/ bookworm-security main contrib non-free non-free-firmware\n" \
COPY --from=builder /app/.venv /.venv
RUN echo "deb https://mirrors.aliyun.com/debian/ bookworm main contrib non-free non-free-firmware\n \
deb-src https://mirrors.aliyun.com/debian/ bookworm main contrib non-free non-free-firmware\n \
deb https://mirrors.aliyun.com/debian/ bookworm-updates main contrib non-free non-free-firmware\n \
deb-src https://mirrors.aliyun.com/debian/ bookworm-updates main contrib non-free non-free-firmware\n \
deb-src https://mirrors.aliyun.com/debian/ bookworm-updates main contrib non-free non-free-firmware\n \
deb https://mirrors.aliyun.com/debian/ bookworm-backports main contrib non-free non-free-firmware\n \
deb-src https://mirrors.aliyun.com/debian/ bookworm-backports main contrib non-free non-free-firmware\n \
deb https://mirrors.aliyun.com/debian-security/ bookworm-security main contrib non-free non-free-firmware\n \
deb-src https://mirrors.aliyun.com/debian-security/ bookworm-security main contrib non-free non-free-firmware\n" \
> /etc/apt/sources.list
RUN apt-get update && apt-get install -y --no-install-recommends \
cron \
ffmpeg
RUN apt-get update && apt-get install -y --no-install-recommends cron
ARG INSTALL_CHROMIUM=false
RUN if [ "$INSTALL_CHROMIUM" = "true" ]; then \
apt-get install -y --no-install-recommends \
chromium \
chromium-driver; \
fi
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
RUN if [ "$LITE" = False ]; then apt-get install -y --no-install-recommends chromium chromium-driver; fi \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
RUN (crontab -l ; \
echo "0 22 * * * cd $APP_WORKDIR && /usr/local/bin/pipenv run python main.py scheduled_task"; \
echo "0 10 * * * cd $APP_WORKDIR && /usr/local/bin/pipenv run python main.py scheduled_task") | crontab -
echo "0 22 * * * cd $APP_WORKDIR && /.venv/bin/python main.py"; \
echo "0 10 * * * cd $APP_WORKDIR && /.venv/bin/python main.py") | crontab -
EXPOSE 8000
COPY entrypoint.sh /tv_entrypoint.sh
COPY entrypoint.sh /iptv-api-entrypoint.sh
COPY config /tv_config
COPY config /iptv-api-config
RUN chmod +x /tv_entrypoint.sh
RUN chmod +x /iptv-api-entrypoint.sh
ENTRYPOINT /tv_entrypoint.sh
ENTRYPOINT /iptv-api-entrypoint.sh

26
Pipfile
View file

@ -4,13 +4,16 @@ url = "https://mirrors.aliyun.com/pypi/simple"
verify_ssl = true
[scripts]
build = "python main.py"
dev = "python main.py"
service = "python service/app.py"
ui = "python tkinter_ui/tkinter_ui.py"
multicast_tmp = "python updates/multicast/update_tmp.py"
docker_run = "docker run -v ./config:/iptv-api/config -v ./output:/iptv-api/output -d -p 8000:8000 guovern/iptv-api"
docker_run_lite = "docker run -v ./config:/iptv-api-lite/config -v ./output:/iptv-api-lite/output -d -p 8000:8000 guovern/iptv-api:lite"
tkinter_build = "pyinstaller tkinter_ui/tkinter_ui.spec"
docker_build = "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 --build-arg APP_WORKDIR=/iptv-api -t guovern/iptv-api ."
docker_build_lite = "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 --build-arg APP_WORKDIR=/iptv-api-lite --build-arg LITE=True -t guovern/iptv-api:lite ."
[dev-packages]
[packages]
requests = "*"
selenium = "*"
bs4 = "*"
@ -21,8 +24,21 @@ aiohttp = "*"
flask = "*"
opencc-python-reimplemented = "*"
fake-useragent = "*"
pillow = "*"
yt-dlp = "*"
[packages]
requests = "*"
bs4 = "*"
tqdm = "*"
async-timeout = "*"
aiohttp = "*"
flask = "*"
opencc-python-reimplemented = "*"
fake-useragent = "*"
gunicorn = "*"
pillow = "*"
yt-dlp = "*"
[requires]
python_version = "3.8"
python_version = "3.13"

1743
Pipfile.lock generated

File diff suppressed because it is too large Load diff

108
README.md
View file

@ -1,9 +1,9 @@
<div align="center">
<img src="./static/images/logo.png" alt="logo"/>
<h1 align="center">IPTV电视直播源更新工具</h1>
<h1 align="center">IPTV-API</h1>
</div>
<div align="center">自定义频道菜单,根据模板频道,自动获取并更新最新的直播源接口,测速校验后生成可用的接口文件</div>
<div align="center">自定义频道,自动获取直播源接口,测速验效后生成可用的结果</div>
<div align="center">默认结果包含:📺央视频道、💰央视付费频道、📡卫视频道、🏠广东频道、🌊港·澳·台频道、🎬电影频道、🎥咪咕直播、🏀体育频道、🪁动画频道、🎮游戏频道、🎵音乐频道、🏛经典剧场</div>
<details>
@ -62,14 +62,17 @@
</details>
<br>
<p align="center">
<a href="https://github.com/Guovin/TV/releases/latest">
<img src="https://img.shields.io/github/v/release/guovin/tv" />
<a href="https://github.com/Guovin/iptv-api/releases/latest">
<img src="https://img.shields.io/github/v/release/guovin/iptv-api" />
</a>
<a href="https://www.python.org/">
<img src="https://img.shields.io/badge/python-%20%3E%3D%203.8-47c219" />
<img src="https://img.shields.io/badge/python-%20%3D%203.13-47c219" />
</a>
<a href="https://github.com/Guovin/TV/releases/latest">
<img src="https://img.shields.io/github/downloads/guovin/tv/total" />
<a href="https://github.com/Guovin/iptv-api/releases/latest">
<img src="https://img.shields.io/github/downloads/guovin/iptv-api/total" />
</a>
<a href="https://hub.docker.com/repository/docker/guovern/iptv-api">
<img src="https://img.shields.io/docker/pulls/guovern/iptv-api?label=docker:iptv-api" />
</a>
<a href="https://hub.docker.com/repository/docker/guovern/tv-requests">
<img src="https://img.shields.io/docker/pulls/guovern/tv-requests?label=docker:requests" />
@ -77,46 +80,57 @@
<a href="https://hub.docker.com/repository/docker/guovern/tv-driver">
<img src="https://img.shields.io/docker/pulls/guovern/tv-driver?label=docker:driver" />
</a>
<a href="https://github.com/Guovin/TV/fork">
<img src="https://img.shields.io/github/forks/guovin/tv" />
<a href="https://github.com/Guovin/iptv-api/fork">
<img src="https://img.shields.io/github/forks/guovin/iptv-api" />
</a>
</p>
[English](./README_en.md) | 中文
## ✅ 特点
- [✅ 特点](#特点)
- [🔗 最新结果](#最新结果)
- [⚙️ 配置参数](./docs/config.md)
- [🚀 快速上手](#快速上手)
- [📖 详细教程](./docs/tutorial.md)
- [🗓️ 更新日志](./CHANGELOG.md)
- [💰️ 赞赏](#赞赏)
- [👀 关注](#关注)
- [📣 免责声明](#免责声明)
- [⚖️ 许可证](#许可证)
## 特点
- ✅ 自定义模板,生成您想要的频道
- ✅ 支持多种获取源方式:组播源、酒店源、订阅源、关键字搜索
- ✅ 接口测速验效,响应时间、分辨率优先级,过滤无效接口
- ✅ 偏好设置IPv6、接口来源排序优先级与数量配置、接口白名单
- ✅ 定时执行,北京时间每日 6:00 与 18:00 执行更新
- ✅ 支持多种运行方式工作流、命令行、GUI 软件、Docker(amd64/arm64)
- ✅ 支持多种运行方式工作流、命令行、GUI 软件、Docker(amd64/arm64/arm v7)
- ✨ 更多功能请见[配置参数](./docs/config.md)
## 🔗 最新结果
## 最新结果
- 接口源:
```bash
https://ghproxy.net/raw.githubusercontent.com/Guovin/TV/gd/output/result.m3u
https://ghproxy.net/raw.githubusercontent.com/Guovin/iptv-api/gd/output/result.m3u
```
```bash
https://ghproxy.net/raw.githubusercontent.com/Guovin/TV/gd/output/result.txt
https://ghproxy.net/raw.githubusercontent.com/Guovin/iptv-api/gd/output/result.txt
```
- 数据源:
```bash
https://ghproxy.net/raw.githubusercontent.com/Guovin/TV/gd/source.json
https://ghproxy.net/raw.githubusercontent.com/Guovin/iptv-api/gd/source.json
```
## ⚙️ 配置
## 配置
[配置参数](./docs/config.md)
## 🚀 快速上手
## 快速上手
### 方式一:工作流
@ -129,16 +143,24 @@ pip install pipenv
```
```python
pipenv install
pipenv install --dev
```
启动更新:
```python
pipenv run build
pipenv run dev
```
启动服务:
```python
pipenv run service
```
### 方式三GUI 软件
1. 下载[更新工具软件](https://github.com/Guovin/TV/releases),打开软件,点击更新,即可完成更新
1. 下载[IPTV-API 更新软件](https://github.com/Guovin/iptv-api/releases),打开软件,点击更新,即可完成更新
2. 或者在项目目录下运行以下命令,即可打开 GUI 软件:
@ -146,41 +168,39 @@ pipenv run build
pipenv run ui
```
<img src="./docs/images/ui.png" alt="更新工具软件" title="更新工具软件" style="height:600px" />
<img src="./docs/images/ui.png" alt="IPTV-API更新软件" title="IPTV-API更新软件" style="height:600px" />
### 方式四Docker
- driver性能要求较高更新速度较慢稳定性、成功率高修改配置 open_driver = False 可切换到 request 版本(推荐酒店源、组播源、关键字搜索使用此版本)
- requests轻量级性能要求低更新速度快稳定性不确定推荐订阅源使用此版本
建议都试用一次,选择自己合适的版本
- iptv-api完整版本性能要求较高更新速度较慢稳定性、成功率高修改配置 open_driver = False 可切换到 Lite 版本运行模式(推荐酒店源、组播源、关键字搜索使用此版本)
- iptv-api:lite精简版本轻量级性能要求低更新速度快稳定性不确定推荐订阅源使用此版本
1. 拉取镜像:
- driver
- iptv-api
```bash
docker pull guovern/tv-driver:latest
docker pull guovern/iptv-api:latest
```
- requests
- iptv-api:lite
```bash
docker pull guovern/tv-requests:latest
docker pull guovern/iptv-api:lite
```
2. 运行容器:
- driver
- iptv-api
```bash
docker run -d -p 8000:8000 guovern/tv-driver
docker run -d -p 8000:8000 guovern/iptv-api
```
- requests
- iptv-api:lite
```bash
docker run -d -p 8000:8000 guovern/tv-requests
docker run -d -p 8000:8000 guovern/iptv-api:lite
```
卷挂载参数(可选):
@ -188,29 +208,31 @@ docker run -d -p 8000:8000 guovern/tv-requests
以宿主机路径/etc/docker 为例:
- driver
- iptv-api
```bash
docker run -v /etc/docker/config:/tv-driver/config -v /etc/docker/output:/tv-driver/output -d -p 8000:8000 guovern/tv-driver
docker run -v /etc/docker/config:/iptv-api/config -v /etc/docker/output:/iptv-api/output -d -p 8000:8000 guovern/iptv-api
```
- requests
- iptv-api:lite
```bash
docker run -v /etc/docker/config:/tv-requests/config -v /etc/docker/output:/tv-requests/output -d -p 8000:8000 guovern/tv-requests
docker run -v /etc/docker/config:/iptv-api-lite/config -v /etc/docker/output:/iptv-api-lite/output -d -p 8000:8000 guovern/iptv-api:lite
```
3. 更新结果:
- 接口地址ip:8000
- 接口详情ip:8000/result
- M3u 接口ip:8000/m3u
- Txt 接口ip:8000/txt
- 接口内容ip:8000/content
- 测速日志ip:8000/log
## 🗓️ 更新日志
## 更新日志
[更新日志](./CHANGELOG.md)
## 💰️ 赞赏
## 赞赏
<div>开发维护不易,请我喝杯咖啡☕️吧~</div>
@ -218,16 +240,16 @@ docker run -v /etc/docker/config:/tv-requests/config -v /etc/docker/output:/tv-r
| ----------------------------------------- | ------------------------------------------- |
| ![支付宝扫码](./static/images/alipay.jpg) | ![微信扫码](./static/images/appreciate.jpg) |
## 👀 关注
## 关注
微信公众号搜索 Govin或扫码接收更新推送、学习更多使用技巧
![微信公众号](./static/images/qrcode.jpg)
## 📣 免责声明
## 免责声明
本项目仅供学习交流用途,接口数据均来源于网络,如有侵权,请联系删除
## ⚖️ 许可证
## 许可证
[MIT](./LICENSE) License &copy; 2024-PRESENT [Govin](https://github.com/guovin)

View file

@ -1,9 +1,9 @@
<div align="center">
<img src="./static/images/logo.png" alt="logo"/>
<h1 align="center">IPTV live TV source update tool</h1>
<h1 align="center">IPTV-API</h1>
</div>
<div align="justify">Customize the channel menu, automatically obtain and update the latest live source interfaces based on the template channels, and generate available interface files after speed test verification.</div>
<div align="center">Customize channels, automatically obtain live source interface, and generate usable results after speed test</div>
<div align="justify">Default results include: 📺CCTV Channel, 💰CCTV Pay Channel, 📡Satellite TV Channel, 🏠Guangdong Channel, 🌊Hong Kong · Macao · Taiwan Channel, 🎬Movie Channel, 🎥Migu Live Streaming, 🏀Sports Channel, 🪁Animation channel, 🎮Game channel, 🎵Music channel, 🏛Classic Theater.</div>
<details>
@ -62,14 +62,17 @@
</details>
<br>
<p align="center">
<a href="https://github.com/Guovin/TV/releases/latest">
<img src="https://img.shields.io/github/v/release/guovin/tv" />
<a href="https://github.com/Guovin/iptv-api/releases/latest">
<img src="https://img.shields.io/github/v/release/guovin/iptv-api" />
</a>
<a href="https://www.python.org/">
<img src="https://img.shields.io/badge/python-%20%3E%3D%203.8-47c219" />
<img src="https://img.shields.io/badge/python-%20%3D%203.13-47c219" />
</a>
<a href="https://github.com/Guovin/TV/releases/latest">
<img src="https://img.shields.io/github/downloads/guovin/tv/total" />
<a href="https://github.com/Guovin/iptv-api/releases/latest">
<img src="https://img.shields.io/github/downloads/guovin/iptv-api/total" />
</a>
<a href="https://hub.docker.com/repository/docker/guovern/iptv-api">
<img src="https://img.shields.io/docker/pulls/guovern/iptv-api?label=docker:iptv-api" />
</a>
<a href="https://hub.docker.com/repository/docker/guovern/tv-requests">
<img src="https://img.shields.io/docker/pulls/guovern/tv-requests?label=docker:requests" />
@ -77,46 +80,57 @@
<a href="https://hub.docker.com/repository/docker/guovern/tv-driver">
<img src="https://img.shields.io/docker/pulls/guovern/tv-driver?label=docker:driver" />
</a>
<a href="https://github.com/Guovin/TV/fork">
<img src="https://img.shields.io/github/forks/guovin/tv" />
<a href="https://github.com/Guovin/iptv-api/fork">
<img src="https://img.shields.io/github/forks/guovin/iptv-api" />
</a>
</p>
[中文](./README.md) | English
## ✅ Features
- [✅ Features](#features)
- [🔗 Latest results](#latest-results)
- [⚙️ Config parameter](./docs/config_en.md)
- [🚀 Quick Start](#quick-start)
- [📖 Detailed Tutorial](./docs/tutorial_en.md)
- [🗓️ Changelog](./CHANGELOG.md)
- [💰️ Appreciate](#appreciate)
- [👀 Follow](#follow)
- [📣 Disclaimer](#disclaimer)
- [⚖️ License](#license)
## Features
- ✅ Customize the template to generate the channel you want
- ✅ Supports multiple source acquisition methods: multicast source, hotel source, subscription source, keyword search
- ✅ Interface speed testing and verification, with priority on response time and resolution, filtering out ineffective interfaces
- ✅ Preferences: IPv6, priority and quantity of interface source sorting, and interface whitelist
- ✅ Scheduled execution at 6:00 AM and 18:00 PM Beijing time daily
- ✅ Supports various execution methods: workflows, command line, GUI software, Docker(amd64/arm64)
- ✅ Supports various execution methods: workflows, command line, GUI software, Docker(amd64/arm64/arm v7)
- ✨ For more features, see [Config parameter](./docs/config_en.md)
## 🔗 Latest results
## Latest results
- Interface source:
```bash
https://ghproxy.net/raw.githubusercontent.com/Guovin/TV/gd/output/result.m3u
https://ghproxy.net/raw.githubusercontent.com/Guovin/iptv-api/gd/output/result.m3u
```
```bash
https://ghproxy.net/raw.githubusercontent.com/Guovin/TV/gd/output/result.txt
https://ghproxy.net/raw.githubusercontent.com/Guovin/iptv-api/gd/output/result.txt
```
- Data source:
```bash
https://ghproxy.net/raw.githubusercontent.com/Guovin/TV/gd/source.json
https://ghproxy.net/raw.githubusercontent.com/Guovin/iptv-api/gd/source.json
```
## ⚙️ Config
## Config
[Config parameter](./docs/config_en.md)
## 🚀 Quick Start
## Quick Start
### Method 1: Workflow
@ -129,16 +143,24 @@ pip install pipenv
```
```python
pipenv install
pipenv install --dev
```
Start update:
```python
pipenv run build
pipenv run dev
```
Start service:
```python
pipenv run service
```
### Method 3: GUI Software
1. Download [Update tool software](https://github.com/Guovin/TV/releases), open the software, click update to complete the update
1. Download [IPTV-API update software](https://github.com/Guovin/iptv-api/releases), open the software, click update to complete the update
2. Or run the following command in the project directory to open the GUI software:
@ -146,41 +168,41 @@ pipenv run build
pipenv run ui
```
<img src="./docs/images/ui.png" alt="Update tool software" title="Update tool software" style="height:600px" />
<img src="./docs/images/ui.png" alt="IPTV-API update software" title="IPTV-API update software" style="height:600px" />
### Method 4: Docker
- driver: Higher performance requirements, slower update speed, high stability and success rate. Set open_driver = False to switch to the request version (recommended for hotel sources, multicast sources, and online searches)
- requests: Lightweight, low performance requirements, fast update speed, stability uncertain (recommend using this version for the subscription source)
- iptv-api (Full version): Higher performance requirements, slower update speed, high stability and success rate. Set open_driver = False to switch to the lite running mode (recommended for hotel sources, multicast sources, and online searches)
- iptv-api:lite (Condensed version): Lightweight, low performance requirements, fast update speed, stability uncertain (recommend using this version for the subscription source)
It's recommended to try each one and choose the version that suits you
1. Pull the image:
- driver
- iptv-api
```bash
docker pull guovern/tv-driver:latest
docker pull guovern/iptv-api:latest
```
- requests
- iptv-api:lite
```bash
docker pull guovern/tv-requests:latest
docker pull guovern/iptv-api:lite
```
2. Run the container:
- driver
- iptv-api
```bash
docker run -d -p 8000:8000 guovern/tv-driver
docker run -d -p 8000:8000 guovern/iptv-api
```
- requests
- iptv-api:lite
```bash
docker run -d -p 8000:8000 guovern/tv-requests
docker run -d -p 8000:8000 guovern/iptv-api:lite
```
Volume Mount Parameter (Optional):
@ -188,29 +210,31 @@ This allows synchronization of files between the host machine and the container.
Taking the host path /etc/docker as an example:
- driver
- iptv-api
```bash
docker run -v /etc/docker/config:/tv-driver/config -v /etc/docker/output:/tv-driver/output -d -p 8000:8000 guovern/tv-driver
docker run -v /etc/docker/config:/iptv-api/config -v /etc/docker/output:/iptv-api/output -d -p 8000:8000 guovern/iptv-api
```
- requests
- iptv-api:lite
```bash
docker run -v /etc/docker/config:/tv-requests/config -v /etc/docker/output:/tv-requests/output -d -p 8000:8000 guovern/tv-requests
docker run -v /etc/docker/config:/iptv-api-lite/config -v /etc/docker/output:/iptv-api-lite/output -d -p 8000:8000 guovern/iptv-api:lite
```
3. Update results:
- API address: ip:8000
- API details: ip:8000/result
- M3u apiip:8000/m3u
- Txt apiip:8000/txt
- API content: ip:8000/content
- Speed test log: ip:8000/log
## 🗓️ Changelog
## Changelog
[Changelog](./CHANGELOG.md)
## 💰️ Appreciate
## Appreciate
<div>Development and maintenance are not easy, please buy me a coffee ~</div>
@ -218,16 +242,16 @@ docker run -v /etc/docker/config:/tv-requests/config -v /etc/docker/output:/tv-r
| ------------------------------------- | ----------------------------------------- |
| ![Alipay](./static/images/alipay.jpg) | ![Wechat](./static/images/appreciate.jpg) |
## 👀 Follow
## Follow
Wechat public account search Govin, or scan code:
Wechat public account search for Govin, or scan the code to receive updates and learn more tips:
![Wechat public account](./static/images/qrcode.jpg)
## 📣 Disclaimer
## Disclaimer
This project is for learning and communication purposes only. All interface data comes from the internet. If there is any infringement, please contact us for removal.
## ⚖️ License
## License
[MIT](./LICENSE) License &copy; 2024-PRESENT [Govin](https://github.com/guovin)

View file

@ -9,7 +9,7 @@ online_search_page_num = 1
urls_limit = 10
open_keep_all = False
open_sort = True
sort_timeout = 10
sort_timeout = 5
open_ffmpeg = True
open_filter_resolution = True
min_resolution = 1920x1080

View file

@ -12,7 +12,7 @@
| urls_limit | 10 | 单个频道接口数量 |
| open_keep_all | False | 保留所有检索结果,会保留非模板频道名称的结果,推荐手动维护时开启 |
| open_sort | True | 开启排序功能(响应速度、日期、分辨率) |
| sort_timeout | 10 | 单个接口测速超时时长,单位秒(s);数值越大测速所属时间越长,能提高获取接口数量,但质量会有所下降;数值越小测速所需时间越短,能获取低延时的接口,质量较好;调整此值能优化更新时间 |
| sort_timeout | 5 | 单个接口测速超时时长,单位秒(s);数值越大测速所属时间越长,能提高获取接口数量,但质量会有所下降;数值越小测速所需时间越短,能获取低延时的接口,质量较好;调整此值能优化更新时间 |
| open_ffmpeg | True | 开启使用 FFmpeg 进行测速,获取更准确的速度与分辨率信息,需要提前手动安装 |
| open_m3u_result | True | 开启转换生成 m3u 文件类型结果链接,支持显示频道图标 |
| open_filter_resolution | True | 开启分辨率过滤低于最小分辨率min_resolution的接口将会被过滤 |

View file

@ -12,7 +12,7 @@
| urls_limit | 10 | Number of interfaces per channel |
| open_keep_all | False | Retain all search results, retain results with non-template channel names, recommended to be turned on when manually maintaining |
| open_sort | True | Enable the sorting function (response speed, date, resolution) |
| sort_timeout | 10 | The timeout duration for speed testing of a single interface, in seconds (s). A larger value means a longer testing period, which can increase the number of interfaces obtained but may decrease their quality. A smaller value means a shorter testing time, which can obtain low-latency interfaces with better quality. Adjusting this value can optimize the update time. |
| sort_timeout | 5 | The timeout duration for speed testing of a single interface, in seconds (s). A larger value means a longer testing period, which can increase the number of interfaces obtained but may decrease their quality. A smaller value means a shorter testing time, which can obtain low-latency interfaces with better quality. Adjusting this value can optimize the update time. |
| open_ffmpeg | True | Enable speed testing using FFmpeg to obtain more accurate speed and resolution information. Manual installation is required in advance. |
| open_m3u_result | True | Enable the conversion to generate m3u file type result links, supporting the display of channel icons |
| open_filter_resolution | True | Enable resolution filtering, interfaces with resolution lower than the minimum resolution (min_resolution) will be filtered |

View file

@ -23,7 +23,7 @@
### 1. Star
打开 https://github.com/Guovin/TV ,点击 Star 收藏该项目(您的 Star 是我持续更新的动力)
打开 https://github.com/Guovin/iptv-api ,点击 Star 收藏该项目(您的 Star 是我持续更新的动力)
![Star](./images/star.png 'Star')
### 2. Watch
@ -149,11 +149,11 @@ https://mirror.ghproxy.com/raw.githubusercontent.com/您的github用户名/仓
如果访问该链接能正常返回更新后的接口内容,说明您的直播源接口链接已经大功告成了!将该链接复制粘贴到 TVBox 等软件配置栏中即可使用~
- 注意:除了首次执行工作流需要您手动触发,后续执行(默认北京时间每日 6:00 18:00将自动触发。如果您修改了模板或配置文件想立刻执行更新可手动触发2中的 Run workflow 即可。
- 注意:除了首次执行工作流需要您手动触发,后续执行(默认北京时间每日 6:00 18:00将自动触发。如果您修改了模板或配置文件想立刻执行更新可手动触发2中的 Run workflow 即可。
### 4.修改工作流更新频率(可选)
如果您想修改更新频率(默认北京时间每日 6:00 18:00可修改 on:schedule:- cron 字段:
如果您想修改更新频率(默认北京时间每日 6:00 18:00可修改 on:schedule:- cron 字段:
![.github/workflows/main.yml](./images/schedule-cron.png '.github/workflows/main.yml')
如果您想 每 2 天执行更新可以这样修改:
@ -162,26 +162,41 @@ https://mirror.ghproxy.com/raw.githubusercontent.com/您的github用户名/仓
- cron: '0 10 */2 * *'
```
#### 1. 强烈不建议修改,因为短时间内的接口内容并无差异,过高的更新频率与高耗时运行的工作流都有可能被判定为资源滥用,导致仓库与账户被封禁的风险。
#### 1. 强烈不建议修改更新频率过高,因为短时间内的接口内容并无差异,过高的更新频率与高耗时运行的工作流都有可能被判定为资源滥用,导致仓库与账户被封禁的风险。
#### 2. 请留意您的工作流运行时长,若发现执行时间过长,需要适当删减模板中频道数量、修改配置中的分页数量和接口数量,以达到合规的运行要求。
### 方式二:命令行
```python
1. 安装 Python
请至官方下载并安装 Python安装时请选择将 Python 添加到系统环境变量 Path 中
请至官方下载并安装 Python安装时请选择将 Python 添加到系统环境变量 Path 中
2. 运行更新
项目目录下打开终端 CMD 依次运行以下命令:
项目目录下打开终端 CMD 依次运行以下命令:
```python
pip install pipenv
pipenv install
pipenv run build
```
```python
pipenv install --dev
```
启动更新:
```python
pipenv run dev
```
启动服务:
```python
pipenv run service
```
### 方式三GUI 软件
1. 下载[更新工具软件](https://github.com/Guovin/TV/releases),打开软件,点击更新,即可完成更新
1. 下载[IPTV-API 更新软件](https://github.com/Guovin/iptv-api/releases),打开软件,点击更新,即可完成更新
2. 或者在项目目录下运行以下命令,即可打开 GUI 软件:
@ -189,37 +204,65 @@ pipenv run build
pipenv run ui
```
![更新工具软件](./images/ui.png '更新工具软件')
![IPTV-API 更新软件](./images/ui.png 'IPTV-API 更新软件')
### 方式四Docker
- requests轻量级性能要求低更新速度快稳定性不确定推荐订阅源使用此版本
- driver性能要求较高更新速度较慢稳定性、成功率高修改配置 open_driver = False 可切换到 request 版本(推荐酒店源、组播源、关键字搜索使用此版本)
- iptv-api完整版本性能要求较高更新速度较慢稳定性、成功率高修改配置 open_driver = False 可切换到 Lite 版本运行模式(推荐酒店源、组播源、关键字搜索使用此版本)
- iptv-api:lite精简版本轻量级性能要求低更新速度快稳定性不确定推荐订阅源使用此版本
1. 拉取镜像:
- iptv-api
```bash
1. 拉取镜像:
requests
docker pull guovern/tv-requests:latest
docker pull guovern/iptv-api:latest
```
driver
docker pull guovern/tv-driver:latest
- iptv-api:lite
```bash
docker pull guovern/iptv-api:lite
```
2. 运行容器:
docker run -d -p 8000:8000 guovern/tv-requests 或 tv-driver
- iptv-api
```bash
docker run -d -p 8000:8000 guovern/iptv-api
```
- iptv-api:lite
```bash
docker run -d -p 8000:8000 guovern/iptv-api:lite
```
卷挂载参数(可选):
实现宿主机文件与容器文件同步,修改模板、配置、获取更新结果文件可直接在宿主机文件夹下操作
配置文件:
-v 宿主机路径/config:/tv-requests/config 或 tv-driver/config
以宿主机路径/etc/docker 为例:
结果文件:
-v 宿主机路径/output:/tv-requests/output 或 tv-driver/output
- iptv-api
3. 查看更新结果:访问(域名:8000
```bash
docker run -v /etc/docker/config:/iptv-api/config -v /etc/docker/output:/iptv-api/output -d -p 8000:8000 guovern/iptv-api
```
#### 注方式一至三更新完成后的结果文件链接http://本地 ip:8000 或 http://localhost:8000
- iptv-api:lite
```bash
docker run -v /etc/docker/config:/iptv-api-lite/config -v /etc/docker/output:/iptv-api-lite/output -d -p 8000:8000 guovern/iptv-api:lite
```
3. 更新结果:
- 接口地址ip:8000
- M3u 接口ip:8000/m3u
- Txt 接口ip:8000/txt
- 接口内容ip:8000/content
- 测速日志ip:8000/log
### 上传更新文件至仓库(可选)

View file

@ -23,7 +23,7 @@ Since this project will continue to iterate and improve, if you want to get the
### 1. Star
Go to https://github.com/Guovin/TV, click on Star to bookmark this project (Your Star is my motivation to keep updating).
Go to https://github.com/Guovin/iptv-api, click on Star to bookmark this project (Your Star is my motivation to keep updating).
![Star](./images/star.png 'Star')
### 2. Watch
@ -146,11 +146,11 @@ https://mirror.ghproxy.com/raw.githubusercontent.com/your github username/reposi
If you can access this link and it returns the updated interface content, then your live source interface link has been successfully created! Simply copy and paste this link into software like TVBox in the configuration field to use~
- Note: Except for the first execution of the workflow, which requires you to manually trigger it, subsequent executions (default: daily at 6:00 am and 18:00 pm Beijing time) will be automatically triggered. If you have modified the template or configuration files and want to execute the update immediately, you can manually trigger (2) Run workflow.
- Note: Except for the first execution of the workflow, which requires you to manually trigger it, subsequent executions (default: 6:00 AM and 18:00 PM Beijing time daily) will be automatically triggered. If you have modified the template or configuration files and want to execute the update immediately, you can manually trigger (2) Run workflow.
### 4.Modify Workflow Update Frequency(optional)
If you want to modify the update frequency (default: daily at 6:00 am and 18:00 pm Beijing time), you can modify the on:schedule:- cron field.
If you want to modify the update frequency (default: 6:00 AM and 18:00 PM Beijing time daily), you can modify the on:schedule:- cron field.
![.github/workflows/main.yml](./images/schedule-cron.png '.github/workflows/main.yml')
If you want to perform updates every 2 days, you can modify it like this:
@ -159,26 +159,41 @@ If you want to perform updates every 2 days, you can modify it like this:
- cron: '0 10 */2 * *'
```
#### 1. It is strongly discouraged to make modifications, as there is no difference in the content of the interface in a short period of time. Both too frequent updates and high-consumption running workflows may be judged as resource abuse, leading to the risk of the repository and account being banned.
#### 1. It is strongly not recommended to modify and update too frequently, because the interface content does not differ within a short period of time, and too high update frequency and time-consuming workflow may be judged as resource abuse, resulting in the risk of warehouse and account being blocked.
#### 2. Please pay attention to the runtime of your workflow. If you find that the execution time is too long, you need to appropriately reduce the number of channels in the template, modify the number of pages and interfaces in the configuration, in order to meet the compliant operation requirements.
### Method 2: Command Line
```python
1. Install Python
Please download and install Python from the official site. During installation, choose to add Python to the system's environment variables Path.
Please download and install Python from the official site. During installation, choose to add Python to the system's environment variables Path.
2. Run Update
Open a CMD terminal in the project directory and run the following commands in sequence:
Open a CMD terminal in the project directory and run the following commands in sequence:
```python
pip install pipenv
pipenv install
pipenv run build
```
```python
pipenv install --dev
```
Start update:
```python
pipenv run dev
```
Start service:
```python
pipenv run service
```
### Method 3: GUI Software
1. Download the update tool software, open the software, click update to complete the update.
1. Download [IPTV-API software](https://github.com/Guovin/iptv-api/releases), open the software, click update to complete the update.
2. Alternatively, run the following command in the project directory to open the GUI software:
@ -186,37 +201,67 @@ pipenv run build
pipenv run ui
```
![Update tool software](./images/ui.png 'Update tool software')
![IPTV-API software](./images/ui.png 'IPTV-API software')
### Method 4: Docker
- requests: Lightweight, low performance requirements, fast update speed, stability uncertain (recommend this version for subscription sources)
- driver: Higher performance requirements, slower update speed, high stability and success rate. Set open_driver = False to switch to the request version (recommended for hotel sources, multicast sources, and keyword search)
- iptv-api (Full version): Higher performance requirements, slower update speed, high stability and success rate. Set open_driver = False to switch to the lite running mode (recommended for hotel sources, multicast sources, and online searches)
- iptv-api:lite (Condensed version): Lightweight, low performance requirements, fast update speed, stability uncertain (recommend using this version for the subscription source)
It's recommended to try each one and choose the version that suits you
1. Pull the image:
- iptv-api
```bash
1. Pull the image:
For requests version:
docker pull guovern/tv-requests:latest
docker pull guovern/iptv-api:latest
```
For driver version:
docker pull guovern/tv-driver:latest
- iptv-api:lite
```bash
docker pull guovern/iptv-api:lite
```
2. Run the container:
docker run -d -p 8000:8000 guovern/tv-requests or driver
- iptv-api
```bash
docker run -d -p 8000:8000 guovern/iptv-api
```
- iptv-api:lite
```bash
docker run -d -p 8000:8000 guovern/iptv-api:lite
```
Volume Mount Parameter (Optional):
This allows synchronization of files between the host machine and the container. Modifying templates, configurations, and retrieving updated result files can be directly operated in the host machine's folder.
config:
-v <path>/config:/tv-requests/config or tv-driver/config
Taking the host path /etc/docker as an example:
result:
-v <path>/output:/tv-requests/output or tv-driver/output
- iptv-api
3. Check the update results: Visit (domain:8000)
```bash
docker run -v /etc/docker/config:/iptv-api/config -v /etc/docker/output:/iptv-api/output -d -p 8000:8000 guovern/iptv-api
```
#### Note: Link to the result file after updates of methods one to three: http://local ip:8000 or http://localhost:8000
- iptv-api:lite
```bash
docker run -v /etc/docker/config:/iptv-api-lite/config -v /etc/docker/output:/iptv-api-lite/output -d -p 8000:8000 guovern/iptv-api:lite
```
3. Update results:
- API address: ip:8000
- M3u apiip:8000/m3u
- Txt apiip:8000/txt
- API content: ip:8000/content
- Speed test log: ip:8000/log
### Update the File to the Repository(optional)

View file

@ -1,4 +1,10 @@
from selenium import webdriver
from utils.config import config
if config.open_driver:
try:
from selenium import webdriver
except:
pass
def setup_driver(proxy=None):

View file

@ -1,4 +1,3 @@
from driver.setup import setup_driver
from utils.retry import (
retry_func,
locate_element_with_retry,
@ -7,13 +6,21 @@ from utils.retry import (
from time import sleep
import re
from bs4 import BeautifulSoup
from selenium.webdriver.common.by import By
from utils.config import config
if config.open_driver:
try:
from selenium.webdriver.common.by import By
except:
pass
def get_soup_driver(url):
"""
Get the soup by driver
"""
from driver.setup import setup_driver
driver = setup_driver()
retry_func(lambda: driver.get(url), name=url)
sleep(1)

View file

@ -1,6 +1,6 @@
#!/bin/bash
for file in /tv_config/*; do
for file in /iptv-api-config/*; do
filename=$(basename "$file")
target_file="$APP_WORKDIR/config/$filename"
if [ ! -e "$target_file" ]; then
@ -8,8 +8,10 @@ for file in /tv_config/*; do
fi
done
service cron start
. /.venv/bin/activate
pipenv run python $APP_WORKDIR/main.py
service cron start &
gunicorn -w 4 -b 0.0.0.0:8000 main:app
python $APP_WORKDIR/main.py &
python -m gunicorn service.app:app -b 0.0.0.0:8000 --timeout=1000

82
main.py
View file

@ -1,13 +1,12 @@
import asyncio
from utils.config import config
import utils.constants as constants
from service.app import run_service
from utils.channel import (
get_channel_items,
append_total_data,
process_sort_channel_list,
write_channel_to_file,
setup_logging,
cleanup_logging,
get_channel_data_cache_with_compare,
format_channel_url_info,
)
@ -16,7 +15,6 @@ from utils.tools import (
get_pbar_remaining,
get_ip_address,
convert_to_m3u,
get_result_file_content,
process_nested_dict,
format_interval,
check_ipv6_support,
@ -27,46 +25,11 @@ from updates.multicast import get_channels_by_multicast
from updates.hotel import get_channels_by_hotel
from updates.fofa import get_channels_by_fofa
from updates.online_search import get_channels_by_online_search
import os
from tqdm import tqdm
from tqdm.asyncio import tqdm_asyncio
from time import time
from flask import Flask, render_template_string
import sys
import atexit
import pickle
import copy
app = Flask(__name__)
atexit.register(cleanup_logging)
@app.route("/")
def show_index():
return get_result_file_content()
@app.route("/result")
def show_result():
return get_result_file_content(show_result=True)
@app.route("/log")
def show_log():
user_log_file = "output/" + (
"user_result.log" if os.path.exists("config/user_config.ini") else "result.log"
)
if os.path.exists(user_log_file):
with open(user_log_file, "r", encoding="utf-8") as file:
content = file.read()
else:
content = constants.waiting_tip
return render_template_string(
"<head><link rel='icon' href='{{ url_for('static', filename='images/favicon.ico') }}' type='image/x-icon'></head><pre>{{ content }}</pre>",
content=content,
)
class UpdateSource:
@ -140,7 +103,6 @@ class UpdateSource:
async def main(self):
try:
if config.open_update:
setup_logging()
main_start_time = time()
self.channel_items = get_channel_items()
channel_names = [
@ -173,7 +135,7 @@ class UpdateSource:
0,
)
self.start_time = time()
self.pbar = tqdm_asyncio(total=self.total, desc="Sorting")
self.pbar = tqdm(total=self.total, desc="Sorting")
self.channel_data = await process_sort_channel_list(
self.channel_data,
ipv6=ipv6_support,
@ -191,24 +153,17 @@ class UpdateSource:
)
self.pbar.close()
user_final_file = config.final_file
update_file(user_final_file, "output/result_new.txt")
update_file(user_final_file, constants.result_path)
if config.open_use_old_result:
if open_sort:
get_channel_data_cache_with_compare(
channel_data_cache, self.channel_data
)
with open(
resource_path("output/result_cache.pkl", persistent=True), "wb"
resource_path(constants.cache_path, persistent=True),
"wb",
) as file:
pickle.dump(channel_data_cache, file)
if open_sort:
user_log_file = "output/" + (
"user_result.log"
if os.path.exists("config/user_config.ini")
else "result.log"
)
update_file(user_log_file, "output/result_new.log", copy=True)
cleanup_logging()
convert_to_m3u()
total_time = format_interval(time() - main_start_time)
print(
@ -228,6 +183,8 @@ class UpdateSource:
True,
url=f"{get_ip_address()}" if open_service else None,
)
if open_service:
run_service()
except asyncio.exceptions.CancelledError:
print("Update cancelled!")
@ -247,31 +204,8 @@ class UpdateSource:
self.pbar.close()
def scheduled_task():
if __name__ == "__main__":
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
update_source = UpdateSource()
loop.run_until_complete(update_source.start())
def run_service():
try:
if not os.environ.get("GITHUB_ACTIONS"):
ip_address = get_ip_address()
print(f"📄 Result detail: {ip_address}/result")
print(f"📄 Log detail: {ip_address}/log")
print(f"✅ You can use this url to watch IPTV 📺: {ip_address}")
app.run(host="0.0.0.0", port=8000)
except Exception as e:
print(f"❌ Service start failed: {e}")
if __name__ == "__main__":
if len(sys.argv) == 1 and config.open_service:
loop = asyncio.new_event_loop()
async def run_service_async():
loop.run_in_executor(None, run_service)
asyncio.run(run_service_async())
scheduled_task()

61
service/app.py Normal file
View file

@ -0,0 +1,61 @@
import os
import sys
sys.path.append(os.path.dirname(sys.path[0]))
from flask import Flask, render_template_string
from utils.tools import get_result_file_content, get_ip_address, resource_path
import utils.constants as constants
app = Flask(__name__)
@app.route("/")
def show_index():
return get_result_file_content()
@app.route("/txt")
def show_txt():
return get_result_file_content(file_type="txt")
@app.route("/m3u")
def show_m3u():
return get_result_file_content(file_type="m3u")
@app.route("/content")
def show_content():
return get_result_file_content(show_content=True)
@app.route("/log")
def show_log():
log_path = resource_path(constants.sort_log_path)
if os.path.exists(log_path):
with open(log_path, "r", encoding="utf-8") as file:
content = file.read()
else:
content = constants.waiting_tip
return render_template_string(
"<head><link rel='icon' href='{{ url_for('static', filename='images/favicon.ico') }}' type='image/x-icon'></head><pre>{{ content }}</pre>",
content=content,
)
def run_service():
try:
if not os.environ.get("GITHUB_ACTIONS"):
ip_address = get_ip_address()
print(f"📄 Result content: {ip_address}/content")
print(f"📄 Log content: {ip_address}/log")
print(f"🚀 M3u api: {ip_address}/m3u")
print(f"🚀 Txt api: {ip_address}/txt")
print(f"✅ You can use this url to watch IPTV 📺: {ip_address}")
app.run(host="0.0.0.0", port=8000)
except Exception as e:
print(f"❌ Service start failed: {e}")
if __name__ == "__main__":
run_service()

View file

@ -44,14 +44,14 @@ class AboutUI:
project_label.pack()
project_link = tk.Label(
project_row_column2,
text="https://github.com/Guovin/TV",
text="https://github.com/Guovin/iptv-api",
fg="blue",
cursor="hand2",
)
project_link.pack()
project_link.bind(
"<Button-1>",
lambda e: webbrowser.open_new_tab("https://github.com/Guovin/TV"),
lambda e: webbrowser.open_new_tab("https://github.com/Guovin/iptv-api"),
)
disclaimer_label = tk.Label(

View file

@ -7,7 +7,7 @@ from tkinter import messagebox
from PIL import Image, ImageTk
from utils.config import config
from utils.tools import resource_path
from main import UpdateSource, run_service
from main import UpdateSource
import asyncio
import threading
import webbrowser
@ -114,15 +114,10 @@ class TkinterUI:
def on_run_update(self):
loop = asyncio.new_event_loop()
async def run_service_async():
loop.run_in_executor(None, run_service)
def run_loop():
asyncio.set_event_loop(loop)
loop.run_until_complete(self.run_update())
if config.open_service:
asyncio.run(run_service_async())
self.thread = threading.Thread(target=run_loop, daemon=True)
self.thread.start()

View file

@ -45,7 +45,7 @@ exe = EXE(
a.binaries,
a.datas,
[],
name='iptv-update-tool',
name='IPTV-API',
debug=True,
bootloader_ignore_signals=False,
strip=False,

View file

@ -3,7 +3,6 @@ from time import time
from requests import get
from concurrent.futures import ThreadPoolExecutor, as_completed
import updates.fofa.fofa_map as fofa_map
from driver.setup import setup_driver
import re
from utils.config import config
import utils.constants as constants
@ -87,6 +86,8 @@ async def get_channels_by_fofa(urls=None, multicast=False, callback=None):
proxy = None
open_proxy = config.open_proxy
open_driver = config.open_driver
if open_driver:
from driver.setup import setup_driver
open_sort = config.open_sort
if open_proxy:
test_url = fofa_urls[0][0]
@ -214,7 +215,7 @@ def process_fofa_json_url(url, region, open_sort, hotel_name="酒店源"):
total_url = (
add_url_info(
f"{url}{item_url}",
f"{region}{hotel_name}|cache:{url}",
f"{region}{hotel_name}-cache:{url}",
)
if open_sort
else add_url_info(

View file

@ -13,7 +13,6 @@ from utils.retry import (
retry_func,
find_clickable_element_with_retry,
)
from selenium.webdriver.common.by import By
from tqdm.asyncio import tqdm_asyncio
from concurrent.futures import ThreadPoolExecutor, as_completed
from requests_custom.utils import get_soup_requests, close_session
@ -23,6 +22,12 @@ from updates.subscribe import get_channels_by_subscribe_urls
from collections import defaultdict
import updates.fofa.fofa_map as fofa_map
if config.open_driver:
try:
from selenium.webdriver.common.by import By
except:
pass
async def get_channels_by_hotel(callback=None):
"""

View file

@ -18,7 +18,6 @@ from utils.retry import (
retry_func,
find_clickable_element_with_retry,
)
from selenium.webdriver.common.by import By
from tqdm.asyncio import tqdm_asyncio
from concurrent.futures import ThreadPoolExecutor, as_completed
from requests_custom.utils import get_soup_requests, close_session
@ -27,6 +26,12 @@ from urllib.parse import parse_qs
from collections import defaultdict
from .update_tmp import get_multicast_region_result_by_rtp_txt
if config.open_driver:
try:
from selenium.webdriver.common.by import By
except:
pass
async def get_channels_by_multicast(names, callback=None):
"""

View file

@ -1,7 +1,5 @@
from asyncio import create_task, gather
from utils.config import config
import utils.constants as constants
from utils.speed import get_speed
from utils.channel import (
format_channel_name,
get_results_from_soup,
@ -22,11 +20,16 @@ from utils.retry import (
retry_func,
find_clickable_element_with_retry,
)
from selenium.webdriver.common.by import By
from tqdm.asyncio import tqdm_asyncio
from concurrent.futures import ThreadPoolExecutor
from requests_custom.utils import get_soup_requests, close_session
if config.open_driver:
try:
from selenium.webdriver.common.by import By
except:
pass
async def get_channels_by_online_search(names, callback=None):
"""

View file

@ -2,7 +2,7 @@ from asyncio import Semaphore
from tqdm import tqdm
from tqdm.asyncio import tqdm_asyncio
from utils.config import config
from utils.speed import get_speed
from utils.speed import get_speed_requests
from concurrent.futures import ThreadPoolExecutor
from driver.utils import get_soup_driver
from requests_custom.utils import get_soup_requests, close_session
@ -71,7 +71,7 @@ async def get_proxy_list_with_test(base_url, proxy_list):
async def get_speed_task(url, timeout, proxy):
async with semaphore:
return await get_speed(url, timeout=timeout, proxy=proxy)
return await get_speed_requests(url, timeout=timeout, proxy=proxy)
response_times = await tqdm_asyncio.gather(
*(get_speed_task(base_url, timeout=30, proxy=url) for url in proxy_list),

View file

@ -8,54 +8,23 @@ from utils.tools import (
remove_cache_info,
resource_path,
write_content_into_txt,
get_logger,
)
from utils.speed import (
get_speed,
sort_urls_by_speed_and_resolution,
is_ffmpeg_installed,
speed_cache,
)
import os
from collections import defaultdict
import re
from bs4 import NavigableString
import logging
from logging.handlers import RotatingFileHandler
from opencc import OpenCC
import asyncio
import base64
import pickle
import copy
import datetime
handler = None
def setup_logging():
"""
Setup logging
"""
global handler
if not os.path.exists(constants.output_dir):
os.makedirs(constants.output_dir)
handler = RotatingFileHandler(constants.log_path, encoding="utf-8")
logging.basicConfig(
handlers=[handler],
format="%(message)s",
level=logging.INFO,
)
def cleanup_logging():
"""
Cleanup logging
"""
global handler
if handler:
for handler in logging.root.handlers[:]:
handler.close()
logging.root.removeHandler(handler)
if os.path.exists(constants.log_path):
os.remove(constants.log_path)
import asyncio
from logging import INFO
def get_name_url(content, pattern, multiline=False, check_url=True):
@ -117,7 +86,7 @@ def get_channel_items():
)
if config.open_use_old_result:
result_cache_path = resource_path("output/result_cache.pkl")
result_cache_path = resource_path(constants.cache_path)
if os.path.exists(result_cache_path):
with open(result_cache_path, "rb") as file:
old_result = pickle.load(file)
@ -258,7 +227,7 @@ def get_channel_multicast_result(result, search_result):
(
add_url_info(
f"http://{url}/rtp/{ip}",
f"{result_region}{result_type}{multicast_name}|cache:{url}",
f"{result_region}{result_type}{multicast_name}-cache:{url}",
)
if config.open_sort
else add_url_info(
@ -463,9 +432,7 @@ def init_info_data(data, cate, name):
data[cate][name] = []
def append_data_to_info_data(
info_data, cate, name, data, origin=None, check=True, insert=False
):
def append_data_to_info_data(info_data, cate, name, data, origin=None, check=True):
"""
Append channel data to total info data
"""
@ -486,14 +453,7 @@ def append_data_to_info_data(
or (not check)
or (check and check_url_by_patterns(pure_url))
):
if insert:
info_data[cate][name].insert(
0, (url, date, resolution, url_origin)
)
else:
info_data[cate][name].append(
(url, date, resolution, url_origin)
)
info_data[cate][name].append((url, date, resolution, url_origin))
urls.append(pure_url)
except:
continue
@ -585,115 +545,45 @@ def append_total_data(
)
async def sort_channel_list(
cate,
name,
info_list,
semaphore,
ffmpeg=False,
ipv6_proxy=None,
callback=None,
):
"""
Sort the channel list
"""
async with semaphore:
data = []
try:
if info_list:
sorted_data = await sort_urls_by_speed_and_resolution(
info_list, ffmpeg=ffmpeg, ipv6_proxy=ipv6_proxy, callback=callback
)
if sorted_data:
for (url, date, resolution, origin), response_time in sorted_data:
logging.info(
f"Name: {name}, URL: {url}, Date: {date}, Resolution: {resolution}, Response Time: {response_time} ms"
)
data.append((url, date, resolution, origin))
except Exception as e:
logging.error(f"Error: {e}")
finally:
return {"cate": cate, "name": name, "data": data}
async def process_sort_channel_list(data, ipv6=False, callback=None):
"""
Processs the sort channel list
"""
ipv6_proxy = None if (not config.open_ipv6 or ipv6) else constants.ipv6_proxy
ffmpeg_installed = is_ffmpeg_installed()
if config.open_ffmpeg and not ffmpeg_installed:
print("FFmpeg is not installed, using requests for sorting.")
is_ffmpeg = config.open_ffmpeg and ffmpeg_installed
semaphore = asyncio.Semaphore(5)
need_sort_data = copy.deepcopy(data)
process_nested_dict(need_sort_data, seen=set(), flag=r"cache:(.*)", force_str="!")
result = {}
semaphore = asyncio.Semaphore(10)
async def limited_get_speed(info, ipv6_proxy, callback):
async with semaphore:
return await get_speed(info[0], ipv6_proxy=ipv6_proxy, callback=callback)
tasks = [
asyncio.create_task(
sort_channel_list(
cate,
name,
info_list,
semaphore,
ffmpeg=is_ffmpeg,
limited_get_speed(
info,
ipv6_proxy=ipv6_proxy,
callback=callback,
)
)
for cate, channel_obj in need_sort_data.items()
for name, info_list in channel_obj.items()
for channel_obj in need_sort_data.values()
for info_list in channel_obj.values()
for info in info_list
]
sort_results = await asyncio.gather(*tasks)
sort_data = {}
for result in sort_results:
if result:
cate, name, result_data = result["cate"], result["name"], result["data"]
append_data_to_info_data(sort_data, cate, name, result_data, check=False)
await asyncio.gather(*tasks)
logger = get_logger(constants.sort_log_path, level=INFO, init=True)
for cate, obj in data.items():
for name, info_list in obj.items():
sort_info_list = sort_data.get(cate, {}).get(name, [])
sort_urls = {
remove_cache_info(sort_url[0])
for sort_url in sort_info_list
if sort_url and sort_url[0]
}
for url, date, resolution, origin in info_list:
if "$" in url:
info = url.partition("$")[2]
if info and info.startswith("!"):
info_list = sort_urls_by_speed_and_resolution(name, info_list, logger)
append_data_to_info_data(
sort_data,
result,
cate,
name,
[(url, date, resolution, origin)],
check=False,
insert=True,
)
continue
matcher = re.search(r"cache:(.*)", info)
if matcher:
cache_key = matcher.group(1)
if not cache_key:
continue
url = remove_cache_info(url)
if url in sort_urls or cache_key not in speed_cache:
continue
cache = speed_cache[cache_key]
if not cache:
continue
response_time, resolution = cache
if response_time and response_time != float("inf"):
append_data_to_info_data(
sort_data,
cate,
name,
[(url, date, resolution, origin)],
info_list,
check=False,
)
logging.info(
f"Name: {name}, URL: {url}, Date: {date}, Resolution: {resolution}, Response Time: {response_time} ms"
)
return sort_data
return result
def write_channel_to_file(data, ipv6=False, callback=None):

View file

@ -270,7 +270,9 @@ class ConfigManager:
@property
def open_driver(self):
return self.config.getboolean("Settings", "open_driver", fallback=True)
return not os.environ.get("LITE") and self.config.getboolean(
"Settings", "open_driver", fallback=True
)
@property
def hotel_page_num(self):

View file

@ -2,9 +2,13 @@ import os
output_dir = "output"
log_file = "result_new.log"
result_path = os.path.join(output_dir, "result_new.txt")
log_path = os.path.join(output_dir, log_file)
cache_path = os.path.join(output_dir, "cache.pkl")
sort_log_path = os.path.join(output_dir, "sort.log")
log_path = os.path.join(output_dir, "log.log")
url_pattern = r"((https?):\/\/)?(\[[0-9a-fA-F:]+\]|([\w-]+\.)+[\w-]+)(:[0-9]{1,5})?(\/[^\s]*)?(\$[^\s]+)?"
@ -95,4 +99,4 @@ foodie_url = "http://www.foodieguide.com/iptvsearch/"
foodie_hotel_url = "http://www.foodieguide.com/iptvsearch/hoteliptv.php"
waiting_tip = "🔍️正在更新,请耐心等待更新完成..."
waiting_tip = "🔍️未找到结果文件,若已启动更新,请耐心等待更新完成..."

View file

@ -1,9 +1,14 @@
from time import sleep
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
from utils.config import config
if config.open_driver:
try:
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
except:
pass
max_retries = 2

View file

@ -3,13 +3,62 @@ from time import time
import asyncio
import re
from utils.config import config
from utils.tools import is_ipv6, add_url_info, remove_cache_info, get_resolution_value
import utils.constants as constants
from utils.tools import is_ipv6, remove_cache_info, get_resolution_value, get_logger
import subprocess
import yt_dlp
from concurrent.futures import ProcessPoolExecutor
import functools
logger = get_logger(constants.log_path)
async def get_speed(url, timeout=config.sort_timeout, proxy=None):
def get_info_yt_dlp(url, timeout=config.sort_timeout):
"""
Get the speed of the url
Get the url info by yt_dlp
"""
ydl_opts = {
"socket_timeout": timeout,
"skip_download": True,
"quiet": True,
"no_warnings": True,
"format": "best",
"logger": logger,
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
return ydl.sanitize_info(ydl.extract_info(url, download=False))
async def get_speed_yt_dlp(url, timeout=config.sort_timeout):
"""
Get the speed of the url by yt_dlp
"""
try:
async with asyncio.timeout(timeout + 2):
start_time = time()
loop = asyncio.get_running_loop()
with ProcessPoolExecutor() as exc:
info = await loop.run_in_executor(
exc, functools.partial(get_info_yt_dlp, url, timeout)
)
fps = (
int(round((time() - start_time) * 1000))
if len(info)
else float("inf")
)
resolution = (
f"{info['width']}x{info['height']}"
if "width" in info and "height" in info
else None
)
return (fps, resolution)
except:
return (float("inf"), None)
async def get_speed_requests(url, timeout=config.sort_timeout, proxy=None):
"""
Get the speed of the url by requests
"""
async with ClientSession(
connector=TCPConnector(verify_ssl=False), trust_env=True
@ -113,15 +162,11 @@ async def check_stream_speed(url_info):
speed_cache = {}
async def get_speed_by_info(
url_info, ffmpeg, semaphore, ipv6_proxy=None, callback=None
):
async def get_speed(url, ipv6_proxy=None, callback=None):
"""
Get the info with speed
Get the speed of the url
"""
async with semaphore:
url, _, resolution, _ = url_info
url_info = list(url_info)
try:
cache_key = None
url_is_ipv6 = is_ipv6(url)
if "$" in url:
@ -129,37 +174,14 @@ async def get_speed_by_info(
matcher = re.search(r"cache:(.*)", cache_info)
if matcher:
cache_key = matcher.group(1)
url_show_info = remove_cache_info(cache_info)
url_info[0] = url
if cache_key in speed_cache:
speed = speed_cache[cache_key][0]
url_info[2] = speed_cache[cache_key][1]
if speed != float("inf"):
if url_show_info:
url_info[0] = add_url_info(url, url_show_info)
return (tuple(url_info), speed)
else:
return float("inf")
try:
return speed_cache[cache_key][0]
if ipv6_proxy and url_is_ipv6:
url_speed = 0
speed = (url_info, url_speed)
elif ffmpeg:
speed = await check_stream_speed(url_info)
url_speed = speed[1] if speed != float("inf") else float("inf")
if url_speed == float("inf"):
url_speed = await get_speed(url)
resolution = speed[0][2] if speed != float("inf") else None
speed = 0
else:
url_speed = await get_speed(url)
speed = (
(url_info, url_speed) if url_speed != float("inf") else float("inf")
)
speed = await get_speed_yt_dlp(url)
if cache_key and cache_key not in speed_cache:
speed_cache[cache_key] = (url_speed, resolution)
if url_show_info:
speed[0][0] = add_url_info(speed[0][0], url_show_info)
speed = (tuple(speed[0]), speed[1])
speed_cache[cache_key] = speed
return speed
except:
return float("inf")
@ -168,30 +190,43 @@ async def get_speed_by_info(
callback()
async def sort_urls_by_speed_and_resolution(
data, ffmpeg=False, ipv6_proxy=None, callback=None
):
def sort_urls_by_speed_and_resolution(name, data, logger=None):
"""
Sort by speed and resolution
"""
semaphore = asyncio.Semaphore(20)
response = await asyncio.gather(
*(
get_speed_by_info(
url_info, ffmpeg, semaphore, ipv6_proxy=ipv6_proxy, callback=callback
filter_data = []
for url, date, resolution, origin in data:
if origin == "important":
filter_data.append((url, date, resolution, origin))
continue
cache_key_match = re.search(r"cache:(.*)", url.partition("$")[2])
cache_key = cache_key_match.group(1) if cache_key_match else None
if cache_key and cache_key in speed_cache:
cache = speed_cache[cache_key]
if cache:
response_time, cache_resolution = cache
resolution = cache_resolution or resolution
if response_time != float("inf"):
url = remove_cache_info(url)
try:
if logger:
logger.info(
f"Name: {name}, URL: {url}, Date: {date}, Resolution: {resolution}, Response Time: {response_time} ms"
)
for url_info in data
)
)
valid_response = [res for res in response if res != float("inf")]
except Exception as e:
print(e)
filter_data.append((url, date, resolution, origin))
def combined_key(item):
(_, _, resolution, _), response_time = item
_, _, resolution, origin = item
if origin == "important":
return -float("inf")
else:
resolution_value = get_resolution_value(resolution) if resolution else 0
return (
-(config.response_time_weight * response_time)
+ config.resolution_weight * resolution_value
config.response_time_weight * response_time
- config.resolution_weight * resolution_value
)
sorted_res = sorted(valid_response, key=combined_key, reverse=True)
return sorted_res
filter_data.sort(key=combined_key)
return filter_data

View file

@ -3,7 +3,6 @@ import datetime
import os
import urllib.parse
import ipaddress
from urllib.parse import urlparse
import socket
from utils.config import config
import utils.constants as constants
@ -13,6 +12,23 @@ from flask import render_template_string, send_file
import shutil
import requests
import sys
import logging
from logging.handlers import RotatingFileHandler
def get_logger(path, level=logging.ERROR, init=False):
"""
get the logger
"""
if not os.path.exists(constants.output_dir):
os.makedirs(constants.output_dir)
if init and os.path.exists(path):
os.remove(path)
handler = RotatingFileHandler(path, encoding="utf-8")
logger = logging.getLogger(path)
logger.addHandler(handler)
logger.setLevel(level)
return logger
def format_interval(t):
@ -376,20 +392,26 @@ def convert_to_m3u():
m3u_file_path = os.path.splitext(user_final_file)[0] + ".m3u"
with open(m3u_file_path, "w", encoding="utf-8") as m3u_file:
m3u_file.write(m3u_output)
print(f"Result m3u file generated at: {m3u_file_path}")
print(f"M3U result file generated at: {m3u_file_path}")
def get_result_file_content(show_result=False):
def get_result_file_content(show_content=False, file_type=None):
"""
Get the content of the result file
"""
user_final_file = resource_path(config.final_file)
if os.path.exists(user_final_file):
result_file = (
os.path.splitext(user_final_file)[0] + f".{file_type}"
if file_type
else user_final_file
)
if os.path.exists(result_file):
if config.open_m3u_result:
user_final_file = os.path.splitext(user_final_file)[0] + ".m3u"
if show_result == False:
return send_file(user_final_file, as_attachment=True)
with open(user_final_file, "r", encoding="utf-8") as file:
if file_type == "m3u" or not file_type:
result_file = os.path.splitext(user_final_file)[0] + ".m3u"
if file_type != "txt" and show_content == False:
return send_file(result_file, as_attachment=True)
with open(result_file, "r", encoding="utf-8") as file:
content = file.read()
else:
content = constants.waiting_tip
@ -452,7 +474,7 @@ def add_url_info(url, info):
Add url info to the URL
"""
if info:
separator = "|" if "$" in url else "$"
separator = "-" if "$" in url else "$"
url += f"{separator}{info}"
return url
@ -469,7 +491,7 @@ def remove_cache_info(str):
"""
Remove the cache info from the string
"""
return re.sub(r"cache:.*|\|cache:.*", "", str)
return re.sub(r"[^a-zA-Z\u4e00-\u9fa5\$]?cache:.*", "", str)
def resource_path(relative_path, persistent=False):

View file

@ -1,4 +1,4 @@
{
"version": "1.5.2",
"name": "IPTV电视直播源更新工具"
"version": "1.5.4",
"name": "IPTV-API"
}