.
675 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: gunicorn -w 1 -k uvicorn.workers.UvicornWorker app.main:app -b 0.0.0.0:8080
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [Streaming-Media-Server-Pro](https://github.com/239144498/Streaming-Media-Server-Pro)
4 | -------------
5 | [](https://github.com/239144498/Streaming-Media-Server-Pro/actions/workflows/docker-image.yml)
6 | [](https://app.netlify.com/sites/nowtv/deploys)
7 | [](https://www.python.org/downloads/release/python-380/)
8 | [](https://hub.docker.com/r/239144498/streaming)
9 | [](https://github.com/239144498/Streaming-Media-Server-Pro/stargazers)
10 | [](https://github.com/239144498/Streaming-Media-Server-Pro/blob/main/LICENSE)
11 |
12 | Documentation: [English version](https://github.com/239144498/Streaming-Media-Server-Pro/blob/main/README_EN.md) | 中文版
13 |
14 |
15 |
16 | [更新日志](https://github.com/239144498/Streaming-Media-Server-Pro/wiki/%E6%9B%B4%E6%96%B0%E6%97%A5%E5%BF%97)
17 | ---
18 |
19 | - 我创建了`IPTV频道`群组,可供交流、测试、反馈, **加入可直接访问 [https://t.me/+QmBC4d4jtgo2M2M9](https://t.me/+QmBC4d4jtgo2M2M9) ,或者扫码加入:**
20 |
21 |
22 |
23 |
24 |
25 | 目录
26 | -------------------
27 | - [项目树形图](#项目树形图)
28 | - [公益视频网站](#公益视频网站)
29 | - [核心功能](#核心功能)
30 | - [程序接口指南](#程序接口指南)
31 | - [播放效果](#播放效果)
32 | - [原理介绍](#原理介绍)
33 | - [文字详解](#文字详解)
34 | - [使用方式](#使用方式)
35 | - [python部署:](#python部署)
36 | - [安装依赖](#安装依赖)
37 | - [运行](#运行)
38 | - [License](#License)
39 |
40 | 项目树形图
41 | ---
42 |
43 | ```
44 | .
45 | ├── app
46 | │ ├── __init__.py
47 | │ ├── main.py
48 | │ ├── log
49 | │ ├── api
50 | │ │ ├── __init__.py
51 | │ │ ├── a4gtv
52 | │ │ │ ├── __init__.py
53 | │ │ │ ├── endecrypt.py
54 | │ │ │ ├── generateEpg.py
55 | │ │ │ ├── tasks.py
56 | │ │ │ ├── tools.py
57 | │ │ │ └── utile.py
58 | │ │ └── v2
59 | │ │ ├── __init__.py
60 | │ │ └── endpoints
61 | │ │ ├── __init__.py
62 | │ │ ├── more.py
63 | │ │ └── sgtv.py
64 | │ ├── assets
65 | │ │ ├── EPG.xml
66 | │ │ ├── diyepg.txt
67 | │ ├── common
68 | │ │ ├── __init__.py
69 | │ │ ├── costum_logging.py
70 | │ │ ├── diyEpg.py
71 | │ │ ├── gitrepo.py
72 | │ │ └── header.py
73 | │ ├── conf
74 | │ │ ├── __init__.py
75 | │ │ ├── config.ini
76 | │ │ └── config.py
77 | │ ├── db
78 | │ │ ├── __init__.py
79 | │ │ ├── DBtools.py
80 | │ │ └── dbMysql.py
81 | │ └── scheams
82 | │ ├── __init__.py
83 | │ └── basic.py
84 | ├── main.py
85 | ├── requirements.txt
86 | ├── Dockerfile
87 | ├── README.md
88 | ├── Procfile
89 | └── LICENSE
90 |
91 | ```
92 |
93 | 核心功能
94 | ---
95 |
96 | - 高效流媒体(具有缓冲区)
97 | - 代理任意视频网站的视频流
98 | - 生成m3u文件
99 | - 生成m3u8文件
100 | - 异步下载流
101 | - 流媒体转发
102 | - 生成[EPG节目单](https://agit.ai/239144498/demo/raw/branch/master/4gtvchannel.xml) 每日实时更新
103 | - 分布式处理ts片
104 | - Redis缓存参数
105 | - MySql缓存数据
106 | - 正向代理请求
107 | - 自定义节目频道
108 | - 自定义电视台标
109 | - 自定义清晰度
110 | - 支持反向代理或使用CDN(负载均衡)
111 |
112 | 程序接口指南
113 | ---
114 | [https://stream.naihe.cf/docs](https://stream.naihe.cf/docs)
115 |
116 |
117 | 播放效果
118 | ---
119 |
120 |
121 |
122 |
123 |
124 | 原理介绍
125 | ---
126 | 如下图所示:
127 |
128 |
129 | 文字详解
130 | ---
131 | 图中多台服务器是一种理想情况下实现,实际python程序、redis和mysql都可以在同一台服务器中实现
132 | - ① 客户端请求m3u8文件
133 | - 1-> 查看内存是否缓存,否则服务器执行图流程2
134 | - 2-> BackgroundTasks任务:执行图流程3,分布式下载数量根据设置的缓冲区大小决定
135 | - 3<- 返回m3u8文件
136 | - ② 客户端请求ts片
137 | - 1-> 查看本地是否缓存,否则服务器执行图流程2
138 | - 2-> BackgroundTasks任务:执行图流程3
139 | - 3-> 查看内存是否已下载完成状态,下载完执行图流程4,否则循环判断等待
140 | - 4<- 返回ts文件
141 | - ③ 还有很多技术细节就不一一展开,只列出以上部分
142 |
143 | 该项目根据分析4gtv网站的接口,通过算法得到生成ts视频的一些关键参数,省去请求网站从而得到m3u8文件的通信时长等开销,针对海外视频网站被墙隔离,支持以下几种观看方式:
144 | - 通过**具有缓冲区的中转服务**观看(调用api接口 /online.m3u8)
145 | - 通过**CDN**或**反向代理**观看(调用api接口 /channel.m3u8?&host=xxx)
146 | - 使用**科学上网软件**观看(调用api接口 /channel2.m3u8)
147 |
148 | 使用方式
149 | ---
150 | > 💡提示:最好将本项目部署至美国地区的服务器,否则可能会出现奇怪的BUG。
151 |
152 | 推荐大家使用[Digitalocean](https://www.digitalocean.com/?refcode=45e25f5e4569&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge)的服务器,主要是因为免费。
153 |
154 |
155 |
156 | 使用我的邀请链接注册,你可以获得$200的credit,当你在上面消费$25时,我也可以获得$25的奖励。
157 |
158 | 我的邀请链接:
159 |
160 | [https://m.do.co/c/45e25f5e4569](https://m.do.co/c/45e25f5e4569)
161 | > 根据以下通用命令部署本项目
162 | ### python部署:
163 | python版本>=3.8+
164 | ``` code
165 | git clone https://github.com/239144498/Streaming-Media-Server-Pro.git
166 | ```
167 | ### 安装依赖
168 | ``` code
169 | pip install -r requirements.txt
170 | ```
171 | ### 运行
172 | ``` code
173 | python3 main.py
174 | ```
175 |
176 | **(docker部署)更多使用教程详情 https://www.cnblogs.com/1314h/p/16651157.html**
177 |
178 | 现已支持频道
179 | ---
180 | - [x] 在diychannel.txt文件添加自定义频道
181 |
182 | License
183 | ---
184 | [GNU-3.0 © naihe](https://github.com/239144498/Streaming-Media-Server-Pro/blob/main/LICENSE)
185 |
186 |
--------------------------------------------------------------------------------
/README_EN.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [Streaming-Media-Server-Pro](https://github.com/239144498/Streaming-Media-Server-Pro)
4 | -------------
5 | [](https://github.com/239144498/Streaming-Media-Server-Pro/actions/workflows/docker-image.yml)
6 | [](https://app.netlify.com/sites/nowtv/deploys)
7 | [](https://www.python.org/downloads/release/python-380/)
8 | [](https://hub.docker.com/r/239144498/streaming)
9 | [](https://github.com/239144498/Streaming-Media-Server-Pro/stargazers)
10 | [](https://github.com/239144498/Streaming-Media-Server-Pro/blob/main/LICENSE)
11 |
12 | Documentation: English version | [中文版](https://github.com/239144498/Streaming-Media-Server-Pro)
13 |
14 |
15 |
16 | >_ In today's fast growing internet, there are thousands of users who have a need to watch TV, and I,
17 | want to create a goal that everyone has their own TV channel, where everyone can filter their favorite shows to their liking,
18 | and have a free and smooth viewing experience._
19 |
20 | ---
21 | ### The latest version of the program structural refactoring, network requests to asynchronous + generator way, performance has been greatly improved; and new log management, all the features of the program has been basically perfect, Please help to light up the star⭐.
22 |
23 | ---
24 |
25 | Contents
26 | -------------------
27 | - [Project tree diagram](#project-tree-diagram)
28 | - [Public service video sites](#public-service-video-sites)
29 | - [Core functions](#core-functions)
30 | - [API reference](#api-reference)
31 | - [Playback effects](#playback-effects)
32 | - [Principle](#principle)
33 | - [Text Detail](#text-detail)
34 | - [Usage](#usage)
35 | - [python deployment:](#python-deployment)
36 | - [Installing dependencies](#installing-dependencies)
37 | - [Run](#run)
38 | - [Channels are now supported](#channels-are-now-supported)
39 | - [📋 Reward List Donation List](#-reward-list-donation-list)
40 | - [❤ Donation](#-donation)
41 |
42 | Project tree diagram
43 | ---
44 |
45 | ```
46 | .
47 | ├── app
48 | │ ├── __init__.py
49 | │ ├── main.py
50 | │ ├── api
51 | │ │ ├── __init__.py
52 | │ │ │ ├── a4gtv
53 | │ │ │ │ ├── __init__.py
54 | │ │ │ │ ├── endecrypt.py
55 | │ │ │ │ ├─ generateEpg.py
56 | │ │ │ │ ├── tasks.py
57 | │ │ │ ├─ tools.py
58 | │ │ │ │ └─ utile.py
59 | │ │ │ └── v2
60 | │ │ │ ├── __init__.py
61 | │ │ │ └── endpoints
62 | │ │ │ ├── __init__.py
63 | │ │ ├── more.py
64 | │ │ └── sgtv.py
65 | │ ├── assets
66 | │ │ ├── EPG.xml
67 | │ │ ├── diyepg.txt
68 | │ │ └── log
69 | │ ├── common
70 | │ │ ├── __init__.py
71 | │ │ ├── costum_logging.py
72 | │ │ ├── diyEpg.py
73 | │ │ ├── gitrepo.py
74 | │ │ ├── header.py
75 | │ ├── conf
76 | │ │ ├── __init__.py
77 | │ │ ├── config.ini
78 | │ │ └── config.py
79 | │ ├── db
80 | │ │ ├── __init__.py
81 | │ │ ├── dbtools.py
82 | │ │ └── dbMysql.py
83 | │ └── scheams
84 | │ ├── __init__.py
85 | │ └── basic.py
86 | ├── main.py
87 | ├── requirements.txt
88 | ├── Dockerfile
89 | ├── README.md
90 | ├─ Procfile
91 | └─ LICENSE
92 |
93 | ```
94 |
95 | Public service video sites
96 | ---
97 |
98 | The back-end interface to the project that allows you to watch all the TVs within the interface online.
99 |
100 | [https://player.naihe.cf](https://player.naihe.cf)
101 |
102 | 
103 |
104 | 
105 |
106 | Core functions
107 | ---
108 |
109 | - Generate m3u files
110 | - Generate m3u8 files
111 | - Video staging (with buffer)
112 | - Asynchronous download of videos
113 | - Streaming and forwarding
114 | - Generate [EPG programme listings](https://agit.ai/239144498/demo/raw/branch/master/4gtvchannel.xml) Live daily updates
115 | - Distributed processing of ts clips
116 | - Redis cache parameters
117 | - MySql or PostgreSql caching of videos
118 | - Forward proxy requests
119 | - Custom addition of program channels
120 | - Custom TV station labels
121 | - Customizable clarity
122 | - Reverse proxy or set of CDN requests (load balancing)
123 |
124 | API reference
125 | ---
126 | [https://stream.naihe.cf/redoc](https://stream.naihe.cf/redoc)
127 |
128 |
129 | Playback effects
130 | ---
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | Principle
139 | ---
140 | As shown in the image below.
141 |
142 |
143 | Text Detail
144 | ---
145 | The diagram of multiple servers is an ideal case implementation, the actual python program, redis and mysql can all be implemented in the same server
146 | - ① Client requests m3u8 files
147 | - 1-> Check if memory is cached, otherwise server executes diagram flow 2
148 | - 2-> BackgroundTasks task: execute flow 3 of the diagram, the number of distributed downloads is determined by the set buffer size
149 | - 3<- Return m3u8 file
150 | - ② Client request ts piece
151 | - 1-> Check if it is cached locally, otherwise the server executes flow 2
152 | - 2-> BackgroundTasks task: execute diagram flow 3
153 | - 3-> check if the memory has been downloaded to complete the state, the download is complete execution diagram flow 4, otherwise loop judgment wait
154 | - 4<- return ts file
155 | - ③ There are many more technical details so I won't start one by one, just list the above parts
156 |
157 | The project is based on the analysis of the interface of the 4gtv website, through the algorithm to get some key parameters for generating ts video, eliminating the overhead of requesting the website and thus getting the communication length of m3u8 files, etc. For overseas video websites isolated by walls, the following viewing methods are supported.
158 | - Watching via **relay service** with buffer (call api interface /online.m3u8)
159 | - Watching via **CDN** or **reverse proxy** (call api interface /channel.m3u8?&host=xxx)
160 | - Watching with **scientific internet software** (call api interface /channel2.m3u8)
161 |
162 | Usage
163 | ---
164 | ### python deployment:
165 | python version recommended 3.9+
166 | ``` code
167 | git clone https://github.com/239144498/Streaming-Media-Server-Pro.git
168 | ```
169 | ### Installing dependencies
170 | ``` code
171 | pip install -r requirements.txt
172 | ```
173 | ### Run
174 | ``` code
175 | python3 main.py
176 | ```
177 |
178 | **(docker deployment) More tutorial details https://www.cnblogs.com/1314h/p/16651157.html**
179 |
180 |
181 | - [x] Add more channels to diychannel.txt file
182 |
183 |
184 | 📋 Reward List Donation List
185 | ---
186 | Many thanks to "[these users](https://github.com/239144498/Streaming-Media-Server-Pro/wiki/Donation-List)" for sponsoring this project!
187 |
188 | ❤ Donation
189 | ---
190 | If you find this project helpful, please consider donating to this project to motivate me to devote more time to maintenance and development. If you find this project helpful, please consider supporting the project going forward.
191 |
192 | [](https://ko-fi.com/naihe)
193 |
194 | PayPal: [https://www.paypal.me/naihes](https://www.paypal.me/naihes)
195 |
196 | > Every time you spend money, you're casting a vote for the kind of world you want. -- Anna Lappe
197 |
198 | 
199 |
200 | ** The `star` or `sponsorship` you give on GitHub is what keeps me going for a long time, thank you from the bottom of my heart to each and every supporter, "every time you spend money you're voting for the world you want". Also, recommending this project to as many people as possible is a way to support it, the more people who use it the more motivation there is to update it.**
201 |
202 | License
203 | ---
204 | [GNU v3.0](https://github.com/239144498/Streaming-Media-Server-Pro/blob/main/LICENSE)
205 |
206 | Disclaimers
207 | ---
208 | - this program is a free open source project for you to manage and watch IPTV channels, to facilitate downloading and to learn Python, please observe the relevant laws and regulations when using it, please do not abuse it.
209 | - this program is implemented by calling the official interface, no damage to the official interface.
210 | - This program only does redirection/traffic forwarding, it does not intercept, store or tamper with any user data.
211 | - **Before using this program, you should understand and bear the corresponding risk, hope to use this program only for learning purposes, any infringement of other people's interests, commercial use, damage to national reputation or other violations of the law and other acts caused by all the consequences of their own responsibility, not related to the author himself;**
212 | - If there is any infringement, please contact me at [email](239144498@qq.com) and it will be dealt with promptly.
213 |
214 |
--------------------------------------------------------------------------------
/app/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2022/10/8
3 | # @Author : Naihe
4 | # @Email : 239144498@qq.com
5 | # @File : __init__.py.py
6 | # @Software: PyCharm
7 |
--------------------------------------------------------------------------------
/app/api/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2022/10/8
3 | # @Author : Naihe
4 | # @Email : 239144498@qq.com
5 | # @File : __init__.py
6 | # @Software: PyCharm
7 | import base64
8 | import sys
9 | import logging
10 |
11 | from loguru import logger
12 | from fastapi import FastAPI
13 | from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor
14 | from apscheduler.schedulers.background import BackgroundScheduler
15 | from apscheduler.triggers.cron import CronTrigger
16 |
17 | from app.conf import config
18 | from app.common.costum_logging import InterceptHandler, format_record
19 | from app.plugins.a4gtv.tasks import gotask, sqltask, filetask
20 | from .v2 import v2
21 | from ..conf.config import DEBUG
22 | from ..scheams.response import Response200
23 |
24 |
25 | def init_app():
26 | app = FastAPI(
27 | title=config.TITLE,
28 | description=config.DESC,
29 | version=config.VERSION,
30 | contact=config.CONTACT,
31 | debug=DEBUG
32 | )
33 | logging.getLogger().handlers = [InterceptHandler()]
34 | logger.configure(handlers=[{"sink": sys.stdout, "level": logging.DEBUG, "format": format_record}])
35 | logger.add(config.LOG_DIR / "日志文件.log", encoding='utf-8', rotation="0:00", enqueue=True, serialize=False,
36 | retention="15 days")
37 | logger.debug('日志系统已加载')
38 | logging.getLogger("uvicorn.access").handlers = [InterceptHandler()]
39 | return app
40 |
41 |
42 | app = init_app()
43 | app.include_router(v2)
44 |
45 |
46 | @app.get('/', summary="首页")
47 | async def index():
48 | return Response200(data=base64.b64decode("6L+Z5piv5LiA5Liq5byA5rqQ55qESVBUVuacjeWKoemhueebru+8jOWKn+iDveW8uuWkp+S4lOmAguWQiOS7u+aEj+W5s+WPsOOAgg==").decode("utf-8"),
49 | msg=base64.b64decode("5LqG6Kej5pu05aSa6K+36K6/6Zeu77yaaHR0cHM6Ly9naXRodWIuY29tLzIzOTE0NDQ5OC9TdHJlYW1pbmctTWVkaWEtU2VydmVyLVBybw==").decode("utf-8"), code=200)
50 |
51 |
52 | @app.on_event("startup")
53 | async def startup():
54 | import pytz
55 | executors = {
56 | 'default': ThreadPoolExecutor(5),
57 | 'processpool': ProcessPoolExecutor(2)
58 | }
59 | job_defaults = {
60 | 'coalesce': True, # 默认为新任务关闭合并模式()
61 | 'max_instances': 3 # 设置新任务的默认最大实例数为3
62 | }
63 | global scheduler
64 | scheduler = BackgroundScheduler()
65 | scheduler.configure(executors=executors, job_defaults=job_defaults,
66 | timezone=pytz.timezone('Asia/Shanghai'))
67 | # cron表达式
68 | # 0 0 * * 0-6 每天凌晨执行一次 更新epg
69 | # 0 * * * * 每1小时执行一次 清理缓存
70 | # */10 * * * * 每10分钟执行一次 清理缓存的视频文件
71 | scheduler.add_job(gotask, CronTrigger.from_crontab("0 0 * * 0-6"), max_instances=2, misfire_grace_time=120)
72 | scheduler.add_job(sqltask, CronTrigger.from_crontab("0 * * * *"), max_instances=3, misfire_grace_time=120)
73 | scheduler.add_job(filetask, CronTrigger.from_crontab("*/10 * * * *"), max_instances=3, misfire_grace_time=120)
74 | logger.info("已开启定时任务")
75 | scheduler.start()
76 |
77 |
78 | @app.on_event("shutdown")
79 | async def shutdown():
80 | global scheduler
81 | scheduler.shutdown()
82 | logger.info("已关闭定时任务")
83 |
--------------------------------------------------------------------------------
/app/api/v2/__init__.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter
2 | from .endpoints import sgtv, more
3 |
4 | v2 = APIRouter()
5 |
6 | v2.include_router(sgtv)
7 | v2.include_router(more)
8 |
--------------------------------------------------------------------------------
/app/api/v2/endpoints/__init__.py:
--------------------------------------------------------------------------------
1 | from .sgtv import sgtv # 4gtv
2 | from .more import more
3 |
--------------------------------------------------------------------------------
/app/api/v2/endpoints/more.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2022/10/9
3 | # @Author : Naihe
4 | # @Email : 239144498@qq.com
5 | # @File : more.py
6 | # @Software: PyCharm
7 | import urllib
8 | from base64 import b64decode
9 |
10 | import aiohttp
11 | from fastapi import APIRouter, Query, Response
12 | from fastapi.responses import FileResponse
13 | from fastapi.requests import Request
14 | from starlette.responses import StreamingResponse
15 | from app.plugins.a4gtv.more_util import parse, processing, splicing
16 | from app.conf import config
17 | from app.conf.config import headers
18 | from app.scheams.response import Response200, Response400
19 |
20 |
21 | more = APIRouter(tags=["更多频道"])
22 |
23 |
24 | @more.get('/diychannel.m3u', summary="自定义IPTV频道")
25 | async def diychannel():
26 | """
27 | 新版接口
28 | 不止于4gtv,还可以添加更多频道到程序中,未来将推出代理自定义频道功能
29 | """
30 | filename = config.ROOT / "assets/diyepg.txt"
31 | return FileResponse(path=filename, status_code=200, headers={
32 | 'Cache-Control': 'no-cache',
33 | 'Pragma': 'no-cache',
34 | 'Content-Type': 'application/vnd.apple.mpegurl'
35 | })
36 |
37 |
38 | @more.get('/proxy', summary="代理任意m3u8")
39 | async def proxy(request: Request, url: str = Query(..., regex=config.url_regex)):
40 | """
41 | 可代理任意m3u8链接,解决网站播放其他域名链接出现的跨域问题,解决封锁地区问题等等
42 | - **url**: m3u8链接
43 | - url example1:/proxy?url=https://example.com/cctv1.m3u8
44 | - url example2编码处理:/proxy?url=https%3A%2F%2Fexample.com%2Fcctv1.m3u8%3Ftoken%3D123456
45 | """
46 | url = dict(request.query_params)
47 | if url.get("url"):
48 | url = parse(url)
49 | try:
50 | headers = {
51 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0"
52 | }
53 | async with aiohttp.ClientSession(headers=headers) as session:
54 | async with session.get(url=url, allow_redirects=True) as res:
55 | data = await res.text()
56 | return StreamingResponse(processing(url, iter(data.split("\n"))))
57 | except (urllib.error.HTTPError, urllib.error.URLError) as e:
58 | return Response400(data=str(e))
59 | return Response400(data="None")
60 |
61 |
62 | @more.get('/pdl', summary="代理下载")
63 | async def pdl(request: Request, url: str = Query(...)):
64 | """
65 | 可代理任意m3u8链接,解决网站播放其他域名链接出现的跨域问题,解决封锁地区问题等等
66 | - **url**: 视频链接
67 | """
68 | url = b64decode(url.encode("utf-8")).decode("utf-8")
69 | header = {
70 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0",
71 | "Accept": "*/*",
72 | "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
73 | "Upgrade-Insecure-Requests": "1",
74 | }
75 | async with aiohttp.ClientSession(headers=header) as session:
76 | async with session.get(url=url) as res:
77 | return Response(content=await res.read(), status_code=200, headers=headers, media_type='video/MP2T')
78 |
79 |
80 | @more.get('/count', summary="统计")
81 | async def count1():
82 | """
83 | 统计使用次数
84 | """
85 | return Response200(data=str(config.count))
86 |
87 |
88 |
--------------------------------------------------------------------------------
/app/api/v2/endpoints/sgtv.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2022/10/8
3 | # @Author : Naihe
4 | # @Email : 239144498@qq.com
5 | # @File : sgtv.py
6 | # @Software: PyCharm
7 | import asyncio
8 |
9 | from fastapi import APIRouter, Query, Response
10 | from fastapi.background import BackgroundTasks
11 | from fastapi.responses import StreamingResponse, RedirectResponse
12 | from app.plugins.a4gtv.tools import generate_m3u, now_time
13 | from app.plugins.a4gtv.utile import get, backtaskonline, backtasklocal
14 | from app.common.request import request
15 | from app.conf.config import default_cfg, localhost, host2, host1, headers, headers2
16 | from app.db.DBtools import DBconnect
17 | from app.scheams.api_model import Clarity, Channels
18 | from app.scheams.response import Response200, Response400
19 |
20 | from app.db.localfile import vfile
21 | from loguru import logger
22 |
23 |
24 | sgtv = APIRouter(tags=["4GTV"])
25 |
26 |
27 | @sgtv.get('/online.m3u8', summary="带缓冲区m3u8")
28 | async def online(
29 | background_tasks: BackgroundTasks,
30 | host=Query(localhost),
31 | fid=Query(...),
32 | hd: Clarity = Query("1080")):
33 | """
34 | 该版本具有redis缓存,视频中转缓存处理等优点,直白说就是播放稳定不卡顿,看超清、4k不是问题
35 | - **host**: 服务器地址
36 | - **fid**: 频道id
37 | - **hd**: 清晰度
38 | """
39 | if fid not in get.idata:
40 | return Response400(data=f"Not found {fid}", code=404)
41 | t = get.idata[fid].get("lt", 0) - now_time()
42 | if t > 0:
43 | return Response400(data=f"{fid} 频道暂不可用,请过 {t} 秒后重试", code=405)
44 | code = get.check(fid)
45 | if code != 200:
46 | return Response400(data=f"{fid} 频道暂不可用,请过 {get.idata[fid].get('lt', 0) - now_time()} 秒后重试", code=406)
47 | return Response("".join((i for i in get.new_generatem3u8(host, fid, hd, background_tasks))), 200, headers=headers2)
48 |
49 |
50 | @sgtv.get('/channel.m3u8', summary="代理|转发m3u8")
51 | async def channel1(
52 | host=Query(localhost),
53 | fid=Query(...),
54 | hd: Clarity = Query("720")):
55 | """
56 | 在redis中设置截止时间,过期重新获取保存到redis,默认通过读取redis参数,构造ts链接
57 | 新增对接口复用,channel2接口重定向到该接口做转发,默认采取代理方式
58 | - **host**: 服务器地址
59 | - **fid**: 频道id
60 | - **hd**: 清晰度
61 | """
62 | if fid not in get.idata:
63 | return Response400(data=f"Not found {fid}", code=404)
64 | t = get.idata[fid].get("lt", 0) - now_time()
65 | if t > 0:
66 | return Response400(data=f"{fid} 频道暂不可用,请过 {t} 秒后重试", code=405)
67 | code = get.check(fid) # 檢查是否出错
68 | if code != 200:
69 | return Response400(data=f"{fid} 频道暂不可用,请过 {get.idata[fid].get('lt', 0) - now_time()} 秒后重试", code=406)
70 | return Response("".join((i for i in get.generatem3u8(host or "239144498@qq.com", fid, hd))), 200, headers=headers2)
71 |
72 |
73 | @sgtv.get('/channel2.m3u8', summary="重定向m3u8")
74 | async def channel2(
75 | host=Query(None),
76 | fid=Query(...),
77 | hd: Clarity = Query("720")):
78 | """
79 | - **host**: 服务器地址
80 | - **fid**: 频道id
81 | - **hd**: 清晰度
82 | """
83 | if fid not in get.idata:
84 | return Response400(data=f"Not found {fid}", code=404)
85 | t = get.idata[fid].get("lt", 0) - now_time()
86 | if t > 0: # 冷卻期
87 | return Response400(data=f"{fid} 频道暂不可用,请过 {t} 秒后重试", code=405)
88 | code = get.check(fid) # 檢查是否出错
89 | if code != 200:
90 | return Response400(data=f"{fid} 频道暂不可用,请过 {get.idata[fid].get('lt', 0) - now_time()} 秒后重试", code=406)
91 | host = host or host2 if "4gtv-live" in fid else host1
92 | return RedirectResponse(f"channel.m3u8?fid={fid}&hd={hd}&host={host}", status_code=302)
93 |
94 |
95 | @sgtv.get('/channel3.m3u8', summary="重定向m3u8")
96 | async def channel3(
97 | fid=Query(...),
98 | hd: Clarity = Query("720")):
99 | """
100 | - **fid**: 频道id
101 | - **hd**: 清晰度
102 | """
103 | if fid not in get.idata:
104 | return Response400(data=f"Not found {fid}", code=404)
105 | t = get.idata[fid].get("lt", 0) - now_time()
106 | if t > 0:
107 | return Response400(data=f"{fid} 频道暂不可用,请过 {t} 秒后重试", code=405)
108 | code = get.check(fid) # 檢查是否出错
109 | if code != 200:
110 | return Response400(data=f"{fid} 频道暂不可用,请过 {get.idata[fid].get('lt', 0) - now_time()} 秒后重试", code=406)
111 | return RedirectResponse(get.geturl(fid, hd), status_code=307)
112 |
113 |
114 | @sgtv.get('/program.m3u', summary="IPTV频道列表")
115 | async def program(
116 | host=Query(localhost),
117 | hd: Clarity = Query("720"),
118 | name: Channels = Query("channel2")):
119 | """
120 | 生成频道表,由程序生成数据
121 | - **host**: 服务器地址
122 | - **hd**: 清晰度
123 | - **name**: 接口指向
124 | """
125 | name += ".m3u8"
126 | return StreamingResponse(generate_m3u(host, hd, name), 200, headers=headers2)
127 |
128 |
129 | @sgtv.get('/epg.xml', summary="IPTV节目预告")
130 | def epg():
131 | """
132 | 获取4gtv未来3天所有节目表
133 | """
134 | return RedirectResponse("https://agit.ai/239144498/demo/raw/branch/master/4gtvchannel.xml", status_code=302)
135 |
136 |
137 | @sgtv.get('/call.ts', summary="缓存式ts视频下载")
138 | async def call(background_tasks: BackgroundTasks, fid: str, seq: str, hd: str):
139 | """
140 | 读取数据库ts片响应给客户端,采用多线程下载ts片,加载视频没有等待时长!
141 | """
142 | # if default_cfg['defaultdb'] == "" or sqlState is False:
143 | # #return Response200(msg="此功能禁用,请连接数据库")
144 | # logger.debug("未连接数据库,将使用硬盘缓存")
145 | logger.info(f"{fid} {seq}")
146 | if fid not in get.idata:
147 | return Response400(data=f"Not found {fid}", code=404)
148 | vname = fid + str(seq) + ".ts"
149 | gap, seq, url, begin = get.generalfun(fid)
150 | if default_cfg.get("downchoose") == "online":
151 | background_tasks.add_task(backtaskonline, url, fid, seq, hd, begin, None)
152 | elif default_cfg.get("downchoose") == "local":
153 | background_tasks.add_task(backtasklocal, url, fid, seq, hd, begin, None)
154 | logger.debug('启动后台任务backtasklocal')
155 | for i in range(1, 10):
156 | logger.debug(f"第{i}次尝试获取{ vname }")
157 | if get.filename.get(vname) and get.filename.get(vname) != 0:
158 | # 从mysql数据库加载
159 | if default_cfg.get("defaultdb") == "mysql":
160 | sql = "SELECT vcontent FROM video where vname='{}'".format(vname)
161 | content = DBconnect.fetchone(sql)
162 | return Response(content=content['vcontent'], status_code=200, headers=headers)
163 | # 从本地文件夹加载
164 | else:
165 | content = vfile.file_get(vname)
166 | return Response(content=content, status_code=200, headers=headers)
167 | else:
168 | await asyncio.sleep(1 + i * 0.095)
169 | return Response400(msg="NOT FOUND " + vname)
170 |
171 |
172 | @sgtv.get("/live/{file_path:path}", summary="代理ts视频下载")
173 | async def downlive(file_path: str, token1: str = None, expires1: int = None):
174 | """
175 | 用于代理请求,客户端无需翻墙即可观看海外电视
176 | """
177 | file_path = "/live/" + file_path
178 | if "live/pool/" not in file_path:
179 | return Response400(msg="wrong parameter")
180 | if token1 and expires1:
181 | file_path += f"?token1={token1}&expires1={expires1}"
182 | url = host2 + file_path if "live/pool/4gtv-live" in file_path else host1 + file_path
183 | header = {
184 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0",
185 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
186 | "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
187 | "Accept-Encoding": "gzip, deflate, br",
188 | "Upgrade-Insecure-Requests": "1",
189 | }
190 | with request.get(url=url, headers=header) as res:
191 | return Response(content=res.content, status_code=200, headers=headers, media_type='video/MP2T')
192 |
--------------------------------------------------------------------------------
/app/assets/config.ini:
--------------------------------------------------------------------------------
1 | # Config配置教程链接
2 | # Configuration tutorial view link: https://www.cnblogs.com/1314h/p/16651157.html
3 | [default]
4 | # mysql:视频缓存到mysql,否则缓存到本地
5 | defaultdb =
6 | # local:本地多线程下载 online:远程分布式下载
7 | downchoose = local
8 | port = 8080
9 | localhost =
10 | vbuffer = 3
11 | downurls = ["%(localhost)s/url3?url="]
12 |
13 | [advanced]
14 | host1 = https://4gtvfreepc-cds.cdn.hinet.net
15 | host2 = https://4gtvfree-cds.cdn.hinet.net
16 | tvglogo = fsHEAD_FRAME
17 | proxies =
18 | debug = False
19 |
20 | [mysql]
21 | host = 127.0.0.1
22 | user = root
23 | password = 123456789
24 | port = 3306
25 | database = media
26 |
27 | [redis]
28 | host = 127.0.0.1
29 | port = 6379
30 | password = 123456789
31 |
32 | [other]
33 | xmlowner =
34 | xmlrepo =
35 | xmlaccess_token =
--------------------------------------------------------------------------------
/app/common/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2022/10/8
3 | # @Author : Naihe
4 | # @Email : 239144498@qq.com
5 | # @File : __init__.py
6 | # @Software: PyCharm
7 |
--------------------------------------------------------------------------------
/app/common/costum_logging.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2022/10/8
3 | # @Author : Naihe
4 | # @Email : 239144498@qq.com
5 | # @File : costum_logging.py
6 | # @Software: PyCharm
7 | import logging
8 | from pprint import pformat
9 | from loguru import logger
10 |
11 |
12 | class InterceptHandler(logging.Handler):
13 | def emit(self, record):
14 | # Get corresponding Loguru level if it exists
15 | try:
16 | level = logger.level(record.levelname).name
17 | except ValueError:
18 | level = record.levelno
19 |
20 | # Find caller from where originated the logged message
21 | frame, depth = logging.currentframe(), 2
22 | while frame.f_code.co_filename == logging.__file__:
23 | frame = frame.f_back
24 | depth += 1
25 |
26 | logger.opt(depth=depth, exception=record.exc_info).log(
27 | level, record.getMessage()
28 | )
29 |
30 |
31 | def format_record(record: dict) -> str:
32 | format_string = '{level: <8} {time:YYYY-MM-DD HH:mm:ss.SSS} - {name}:{function} - {message}'
33 |
34 | if record["extra"].get("payload") is not None:
35 | record["extra"]["payload"] = pformat(
36 | record["extra"]["payload"], indent=4, compact=True, width=88
37 | )
38 | format_string += "\n{extra[payload]}"
39 |
40 | format_string += "{exception}\n"
41 | return format_string
--------------------------------------------------------------------------------
/app/common/diyEpg.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin python3
2 | # -*- coding: utf-8 -*-
3 | from app.conf import config
4 |
5 |
6 | def return_diyepg():
7 | filename = config.ROOT / "assets/diyepg.txt"
8 | if filename.is_file():
9 | with open(filename, "r", encoding="utf-8") as f:
10 | return f.read()
11 | return ""
12 |
13 |
--------------------------------------------------------------------------------
/app/common/header.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2022/10/6
3 | # @Author : Naihe
4 | # @Email : 239144498@qq.com
5 | # @File : header.py
6 | # @Software: PyCharm
7 | from random import randint
8 |
9 |
10 | def random_header():
11 | data = [
12 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A',
13 | 'Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25',
14 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13+ (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2',
15 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10',
16 | 'Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko ) Version/5.1 Mobile/9B176 Safari/7534.48.3',
17 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1',
18 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; da-dk) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1',
19 | 'Mozilla/5.0 (Windows; U; Windows NT 6.1; tr-TR) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
20 | 'Mozilla/5.0 (Windows; U; Windows NT 6.1; ko-KR) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
21 | 'Mozilla/5.0 (Windows; U; Windows NT 6.1; fr-FR) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
22 | 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
23 | 'Mozilla/5.0 (Windows; U; Windows NT 6.1; cs-CZ) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
24 | 'Mozilla/5.0 (Windows; U; Windows NT 6.0; ja-JP) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
25 | 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
26 | 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_5_8; zh-cn) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
27 | 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_5_8; ja-jp) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
28 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; ja-jp) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
29 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; zh-cn) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
30 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; sv-se) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
31 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; ko-kr) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
32 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; ja-jp) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
33 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; it-it) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
34 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; fr-fr) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
35 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; es-es) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
36 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-us) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
37 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-gb) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
38 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; de-de) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
39 | 'Mozilla/5.0 (Windows; U; Windows NT 6.1; sv-SE) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4',
40 | 'Mozilla/5.0 (Windows; U; Windows NT 6.1; ja-JP) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4',
41 | 'Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4',
42 | 'Mozilla/5.0 (Windows; U; Windows NT 6.0; hu-HU) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4',
43 | 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4',
44 | 'Mozilla/5.0 (Windows; U; Windows NT 6.0; de-DE) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4',
45 | 'Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4',
46 | 'Mozilla/5.0 (Windows; U; Windows NT 5.1; ja-JP) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4',
47 | 'Mozilla/5.0 (Windows; U; Windows NT 5.1; it-IT) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4',
48 | 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4',
49 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-us) AppleWebKit/534.16+ (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4',
50 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; fr-ch) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4',
51 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; de-de) AppleWebKit/534.15+ (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4',
52 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; ar) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4',
53 | 'Mozilla/5.0 (Android 2.2; Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4',
54 | 'Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-HK) AppleWebKit/533.18.1 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5',
55 | 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5',
56 | 'Mozilla/5.0 (Windows; U; Windows NT 6.0; tr-TR) AppleWebKit/533.18.1 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5',
57 | 'Mozilla/5.0 (Windows; U; Windows NT 6.0; nb-NO) AppleWebKit/533.18.1 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5',
58 | 'Mozilla/5.0 (Windows; U; Windows NT 6.0; fr-FR) AppleWebKit/533.18.1 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5',
59 | 'Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5',
60 | 'Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU) AppleWebKit/533.18.1 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5',
61 | 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; zh-cn) AppleWebKit/533.18.1 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5',
62 | 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko',
63 | 'Mozilla/5.0 (compatible, MSIE 11, Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko',
64 | 'Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0',
65 | 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 7.0; InfoPath.3; .NET CLR 3.1.40767; Trident/6.0; en-IN)',
66 | 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)',
67 | 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)',
68 | 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
69 | 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/4.0; InfoPath.2; SV1; .NET CLR 2.0.50727; WOW64)',
70 | 'Mozilla/5.0 (compatible; MSIE 10.0; Macintosh; Intel Mac OS X 10_7_3; Trident/6.0)',
71 | 'Mozilla/4.0 (Compatible; MSIE 8.0; Windows NT 5.2; Trident/6.0)',
72 | 'Mozilla/4.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
73 | 'Mozilla/1.22 (compatible; MSIE 10.0; Windows 3.1)',
74 | 'Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US))',
75 | 'Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)',
76 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 7.1; Trident/5.0)',
77 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; Media Center PC 6.0; InfoPath.3; MS-RTC LM 8; Zune 4.7)',
78 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; Media Center PC 6.0; InfoPath.3; MS-RTC LM 8; Zune 4.7',
79 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Zune 4.0; InfoPath.3; MS-RTC LM 8; .NET4.0C; .NET4.0E)',
80 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; chromeframe/12.0.742.112)',
81 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)',
82 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)',
83 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Zune 4.0; Tablet PC 2.0; InfoPath.3; .NET4.0C; .NET4.0E)',
84 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0',
85 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; yie8)',
86 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; .NET CLR 1.1.4322; .NET4.0C; Tablet PC 2.0)',
87 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; FunWebProducts)',
88 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; chromeframe/13.0.782.215)',
89 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; chromeframe/11.0.696.57)',
90 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0) chromeframe/10.0.648.205',
91 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/4.0; GTB7.4; InfoPath.1; SV1; .NET CLR 2.8.52393; WOW64; en-US)',
92 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0; chromeframe/11.0.696.57)',
93 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/4.0; GTB7.4; InfoPath.3; SV1; .NET CLR 3.1.76908; WOW64; en-US)',
94 | 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; InfoPath.2; SV1; .NET CLR 3.3.69573; WOW64; en-US)',
95 | 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)',
96 | 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; InfoPath.1; SV1; .NET CLR 3.8.36217; WOW64; en-US)',
97 | 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; .NET CLR 2.7.58687; SLCC2; Media Center PC 5.0; Zune 3.4; Tablet PC 3.6; InfoPath.3)',
98 | 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; Media Center PC 4.0; SLCC1; .NET CLR 3.0.04320)',
99 | 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 1.1.4322)',
100 | 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727)',
101 | 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
102 | 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; SLCC1; .NET CLR 1.1.4322)',
103 | 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.0; Trident/4.0; InfoPath.1; SV1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 3.0.04506.30)',
104 | 'Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 5.0; Trident/4.0; FBSMTWB; .NET CLR 2.0.34861; .NET CLR 3.0.3746.3218; .NET CLR 3.5.33652; msn OptimizedIE8;ENUS)',
105 | 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.2; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)',
106 | 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; Media Center PC 6.0; InfoPath.2; MS-RTC LM 8)',
107 | 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; Media Center PC 6.0; InfoPath.2; MS-RTC LM 8',
108 | 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; Media Center PC 6.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C)',
109 | 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; InfoPath.3; .NET4.0C; .NET4.0E; .NET CLR 3.5.30729; .NET CLR 3.0.30729; MS-RTC LM 8)',
110 | 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; InfoPath.2)',
111 | 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Zune 3.0)',
112 | 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1',
113 | 'Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.0',
114 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10; rv:33.0) Gecko/20100101 Firefox/33.0',
115 | 'Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/31.0',
116 | 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20130401 Firefox/31.0',
117 | 'Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0',
118 | 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20120101 Firefox/29.0',
119 | 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/29.0',
120 | 'Mozilla/5.0 (X11; OpenBSD amd64; rv:28.0) Gecko/20100101 Firefox/28.0',
121 | 'Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0',
122 | 'Mozilla/5.0 (Windows NT 6.1; rv:27.3) Gecko/20130101 Firefox/27.3',
123 | 'Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:27.0) Gecko/20121011 Firefox/27.0',
124 | 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0',
125 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:25.0) Gecko/20100101 Firefox/25.0',
126 | 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0',
127 | 'Mozilla/5.0 (Windows NT 6.0; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0',
128 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0',
129 | 'Mozilla/5.0 (Windows NT 6.2; rv:22.0) Gecko/20130405 Firefox/23.0',
130 | 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20130406 Firefox/23.0',
131 | 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:23.0) Gecko/20131011 Firefox/23.0',
132 | 'Mozilla/5.0 (Windows NT 6.2; rv:22.0) Gecko/20130405 Firefox/22.0',
133 | 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:22.0) Gecko/20130328 Firefox/22.0',
134 | 'Mozilla/5.0 (Windows NT 6.1; rv:22.0) Gecko/20130405 Firefox/22.0',
135 | 'Mozilla/5.0 (Microsoft Windows NT 6.2.9200.0); rv:22.0) Gecko/20130405 Firefox/22.0',
136 | 'Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/21.0.1',
137 | 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/21.0.1',
138 | 'Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:21.0.0) Gecko/20121011 Firefox/21.0.0',
139 | 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:21.0) Gecko/20130331 Firefox/21.0',
140 | 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:21.0) Gecko/20100101 Firefox/21.0',
141 | 'Mozilla/5.0 (X11; Linux i686; rv:21.0) Gecko/20100101 Firefox/21.0',
142 | 'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:21.0) Gecko/20130514 Firefox/21.0',
143 | 'Mozilla/5.0 (Windows NT 6.2; rv:21.0) Gecko/20130326 Firefox/21.0',
144 | 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130401 Firefox/21.0',
145 | 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130331 Firefox/21.0',
146 | 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130330 Firefox/21.0',
147 | 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0',
148 | 'Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20130401 Firefox/21.0',
149 | 'Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20130328 Firefox/21.0',
150 | 'Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20100101 Firefox/21.0',
151 | 'Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20130401 Firefox/21.0',
152 | 'Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20130331 Firefox/21.0',
153 | 'Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20100101 Firefox/21.0',
154 | 'Mozilla/5.0 (Windows NT 5.0; rv:21.0) Gecko/20100101 Firefox/21.0',
155 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0',
156 | 'Mozilla/5.0 (Windows NT 6.2; Win64; x64;) Gecko/20100101 Firefox/20.0',
157 | 'Mozilla/5.0 (Windows x86; rv:19.0) Gecko/20100101 Firefox/19.0',
158 | 'Mozilla/5.0 (Windows NT 6.1; rv:6.0) Gecko/20100101 Firefox/19.0',
159 | 'Mozilla/5.0 (Windows NT 6.1; rv:14.0) Gecko/20100101 Firefox/18.0.1',
160 | 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:18.0) Gecko/20100101 Firefox/18.0',
161 | 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:17.0) Gecko/20100101 Firefox/17.0.6',
162 | 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',
163 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36',
164 | 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36',
165 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36',
166 | 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.36',
167 | 'Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36',
168 | 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36',
169 | 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2224.3 Safari/537.36',
170 | 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36',
171 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36',
172 | 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36',
173 | 'Mozilla/5.0 (Windows NT 4.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36',
174 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36',
175 | 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36',
176 | 'Mozilla/5.0 (X11; OpenBSD i386) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36',
177 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1944.0 Safari/537.36',
178 | 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.3319.102 Safari/537.36',
179 | 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2309.372 Safari/537.36',
180 | 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2117.157 Safari/537.36',
181 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36',
182 | 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1866.237 Safari/537.36',
183 | 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/4E423F',
184 | 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36 Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10',
185 | 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.517 Safari/537.36',
186 | 'Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36',
187 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1664.3 Safari/537.36',
188 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1664.3 Safari/537.36',
189 | 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36',
190 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1623.0 Safari/537.36',
191 | 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.17 Safari/537.36',
192 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36',
193 | 'Mozilla/5.0 (X11; CrOS i686 4319.74.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.57 Safari/537.36',
194 | 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.2 Safari/537.36',
195 | 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1468.0 Safari/537.36',
196 | 'Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1467.0 Safari/537.36',
197 | 'Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1464.0 Safari/537.36',
198 | 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1500.55 Safari/537.36',
199 | 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36',
200 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36',
201 | 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36',
202 | 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36',
203 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36',
204 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36',
205 | 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.90 Safari/537.36',
206 | 'Mozilla/5.0 (X11; NetBSD) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36',
207 | 'Mozilla/5.0 (X11; CrOS i686 3912.101.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36',
208 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17',
209 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1309.0 Safari/537.17',
210 | 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.15 (KHTML, like Gecko) Chrome/24.0.1295.0 Safari/537.15',
211 | 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.14 (KHTML, like Gecko) Chrome/24.0.1292.0 Safari/537.14',
212 | 'Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16',
213 | 'Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14',
214 | 'Mozilla/5.0 (Windows NT 6.0; rv:2.0) Gecko/20100101 Firefox/4.0 Opera 12.14',
215 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0) Opera 12.14',
216 | 'Opera/12.80 (Windows NT 5.1; U; en) Presto/2.10.289 Version/12.02',
217 | 'Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00',
218 | 'Opera/9.80 (Windows NT 5.1; U; zh-sg) Presto/2.9.181 Version/12.00',
219 | 'Opera/12.0(Windows NT 5.2;U;en)Presto/22.9.168 Version/12.00',
220 | 'Opera/12.0(Windows NT 5.1;U;en)Presto/22.9.168 Version/12.00',
221 | 'Mozilla/5.0 (Windows NT 5.1) Gecko/20100101 Firefox/14.0 Opera/12.0',
222 | 'Opera/9.80 (Windows NT 6.1; WOW64; U; pt) Presto/2.10.229 Version/11.62',
223 | 'Opera/9.80 (Windows NT 6.0; U; pl) Presto/2.10.229 Version/11.62',
224 | 'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52',
225 | 'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; de) Presto/2.9.168 Version/11.52',
226 | 'Opera/9.80 (Windows NT 5.1; U; en) Presto/2.9.168 Version/11.51',
227 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; de) Opera 11.51',
228 | 'Opera/9.80 (X11; Linux x86_64; U; fr) Presto/2.9.168 Version/11.50',
229 | 'Opera/9.80 (X11; Linux i686; U; hu) Presto/2.9.168 Version/11.50',
230 | 'Opera/9.80 (X11; Linux i686; U; ru) Presto/2.8.131 Version/11.11',
231 | 'Opera/9.80 (X11; Linux i686; U; es-ES) Presto/2.8.131 Version/11.11',
232 | 'Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/5.0 Opera 11.11',
233 | 'Opera/9.80 (X11; Linux x86_64; U; bg) Presto/2.8.131 Version/11.10',
234 | 'Opera/9.80 (Windows NT 6.0; U; en) Presto/2.8.99 Version/11.10',
235 | 'Opera/9.80 (Windows NT 5.1; U; zh-tw) Presto/2.8.131 Version/11.10',
236 | 'Opera/9.80 (Windows NT 6.1; Opera Tablet/15165; U; en) Presto/2.8.149 Version/11.1',
237 | 'Opera/9.80 (X11; Linux x86_64; U; Ubuntu/10.10 (maverick); pl) Presto/2.7.62 Version/11.01',
238 | 'Opera/9.80 (X11; Linux i686; U; ja) Presto/2.7.62 Version/11.01',
239 | 'Opera/9.80 (X11; Linux i686; U; fr) Presto/2.7.62 Version/11.01',
240 | 'Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.7.62 Version/11.01',
241 | 'Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.7.62 Version/11.01',
242 | 'Opera/9.80 (Windows NT 6.1; U; sv) Presto/2.7.62 Version/11.01',
243 | 'Opera/9.80 (Windows NT 6.1; U; en-US) Presto/2.7.62 Version/11.01',
244 | 'Opera/9.80 (Windows NT 6.1; U; cs) Presto/2.7.62 Version/11.01',
245 | 'Opera/9.80 (Windows NT 6.0; U; pl) Presto/2.7.62 Version/11.01',
246 | 'Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.7.62 Version/11.01',
247 | 'Opera/9.80 (Windows NT 5.1; U;) Presto/2.7.62 Version/11.01',
248 | 'Opera/9.80 (Windows NT 5.1; U; cs) Presto/2.7.62 Version/11.01',
249 | 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.13) Gecko/20101213 Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.7.62 Version/11.01',
250 | 'Mozilla/5.0 (Windows NT 6.1; U; nl; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.01',
251 | 'Mozilla/5.0 (Windows NT 6.1; U; de; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.01',
252 | 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; de) Opera 11.01',
253 | 'Opera/9.80 (X11; Linux x86_64; U; pl) Presto/2.7.62 Version/11.00',
254 | 'Opera/9.80 (X11; Linux i686; U; it) Presto/2.7.62 Version/11.00',
255 | 'Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.6.37 Version/11.00',
256 | 'Opera/9.80 (Windows NT 6.1; U; pl) Presto/2.7.62 Version/11.00',
257 | 'Opera/9.80 (Windows NT 6.1; U; ko) Presto/2.7.62 Version/11.00',
258 | 'Opera/9.80 (Windows NT 6.1; U; fi) Presto/2.7.62 Version/11.00',
259 | 'Opera/9.80 (Windows NT 6.1; U; en-GB) Presto/2.7.62 Version/11.00',
260 | 'Opera/9.80 (Windows NT 6.1 x64; U; en) Presto/2.7.62 Version/11.00',
261 | 'Opera/9.80 (Windows NT 6.0; U; en) Presto/2.7.39 Version/11.00']
262 | return data[randint(0, 249)]
263 |
--------------------------------------------------------------------------------
/app/common/request.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin python3
2 | # -*- coding: utf-8 -*-
3 | # @Author: Naihe
4 | # @Email: 239144498@qq.com
5 | # @Software: Streaming-Media-Server-Pro
6 | import hashlib
7 | import os
8 | import requests
9 |
10 | from app.conf.config import data3, mdata
11 |
12 | requests.packages.urllib3.disable_warnings()
13 |
14 |
15 | class netreq(object):
16 | """
17 | 对网络请求进行封装,增加了代理功能
18 | """
19 |
20 | def __init__(self, proxies=None):
21 | self.request = self.session()
22 | self.proxies = {
23 | 'http': proxies or os.environ.get("proxies"),
24 | 'https': proxies or os.environ.get("proxies")
25 | }
26 |
27 | def session(self):
28 | return requests.session()
29 |
30 | def get(self, url, headers=None, **kwargs):
31 | return self.request.get(url, headers=headers, proxies=self.proxies, timeout=10, **kwargs)
32 |
33 | def post(self, url, data=None, json=None, headers=None, **kwargs):
34 | if data3['a1'] in url:
35 | headers = {
36 | "v": hashlib.md5(bytes(str(data) + mdata, 'utf8')).hexdigest(),
37 | **headers
38 | }
39 | return self.request.post(url, data=data, json=json, headers=headers, proxies=self.proxies, timeout=10, **kwargs)
40 |
41 | def put(self, url, data=None, json=None, headers=None, **kwargs):
42 | return self.request.put(url, data=data, json=json, headers=headers, proxies=self.proxies, **kwargs)
43 |
44 | def delete(self, url, data=None, json=None, headers=None, **kwargs):
45 | return self.request.delete(url, data=data, json=json, headers=headers, proxies=self.proxies, **kwargs)
46 |
47 |
48 | request = netreq()
49 |
50 |
--------------------------------------------------------------------------------
/app/conf/__init__.py:
--------------------------------------------------------------------------------
1 | import sentry_sdk
2 | from sentry_sdk.integrations.fastapi import FastApiIntegration
3 | from sentry_sdk.integrations.starlette import StarletteIntegration
4 |
5 | from .config import config
6 |
7 | sentry_sdk.init(
8 | dsn="https://12ff5d8b96484788be20f0f17362f084@o4503953349279744.ingest.sentry.io/4504005849579520",
9 | traces_sample_rate=1.0,
10 | send_default_pii=True,
11 | integrations=[
12 | StarletteIntegration(transaction_style="endpoint"),
13 | FastApiIntegration(transaction_style="endpoint"),
14 | ],
15 | )
16 |
--------------------------------------------------------------------------------
/app/conf/config.py:
--------------------------------------------------------------------------------
1 | import hashlib
2 | import time
3 | import uuid
4 | from pathlib import Path
5 | from typing import Optional
6 | from urllib.parse import urljoin
7 |
8 | from pydantic import BaseSettings
9 | import os
10 |
11 | import requests
12 | from loguru import logger
13 | from configparser import ConfigParser
14 | from platform import platform, python_version, machine
15 |
16 |
17 | class Config(BaseSettings):
18 | TITLE: Optional[str] = "Streaming Media Server Pro"
19 |
20 | DESC: Optional[str] = """
21 | ### **程序主要功能:**
22 | - 高效流媒体(具有缓冲区)
23 | - 代理任意视频网站的视频流
24 | - 生成m3u文件
25 | - 生成m3u8文件
26 | - 异步下载流
27 | - 流媒体转发
28 | - 生成[EPG节目单](https://agit.ai/239144498/demo/raw/branch/master/4gtvchannel.xml) 每日实时更新
29 | - 分布式处理ts片
30 | - Redis缓存参数
31 | - MySql缓存数据
32 | - 正向代理请求
33 | - 自定义节目频道
34 | - 自定义电视台标
35 | - 自定义清晰度
36 | - 支持反向代理或CDN(负载均衡)
37 | ### 使用说明
38 | 1. 接口说明: [查看接口使用教程](https://www.cnblogs.com/1314h/p/16651157.html)
39 | 2. 项目代码: [Github开源代码](https://github.com/239144498/Streaming-Media-Server-Pro)
40 | ### 接口列表:
41 | - **向下滑动查看**
42 | """
43 |
44 | VERSION = "2.6"
45 |
46 | CONTACT = {
47 | "name": "Naihe",
48 | "url": "https://github.com/239144498/",
49 | "email": "239144498@qq.com",
50 | }
51 |
52 | ORIGINS = [
53 | "*"
54 | ]
55 |
56 | ROOT = Path(__file__).parent.parent # .app
57 |
58 | LOG_DIR = ROOT / "log"
59 |
60 | datadir = ROOT / 'vtemp'
61 |
62 | count = 0
63 |
64 | url_regex = r"(http|https)://((?:[\w-]+\.)+[a-z0-9]+)((?:\/[^/?#]*)+)?(\?[^#]+)?(#.+)?"
65 |
66 |
67 | logger.info("配置加载中...")
68 | config = Config()
69 |
70 | request = requests.session()
71 |
72 | cfg = ConfigParser()
73 | cfg.read(config.ROOT / "assets/config.ini", encoding="utf-8")
74 | redis_cfg = dict(cfg.items("redis"))
75 | mysql_cfg = dict(cfg.items("mysql"))
76 | default_cfg = dict(cfg.items("default"))
77 | advanced_cfg = dict(cfg.items("advanced"))
78 | other_cfg = dict(cfg.items("other"))
79 | PORT = int(os.getenv("PORT", default=default_cfg.get("port")))
80 | mdata = hashlib.md5(config.VERSION.encode()).hexdigest()
81 | vbuffer = int(default_cfg.get("vbuffer"))
82 | downurls = eval(default_cfg.get("downurls"))
83 | downurls = downurls * (vbuffer // len(downurls) + 1)
84 | localhost = os.environ.get("localhost") or default_cfg.get("localhost")
85 | defaultdb = default_cfg.get("defaultdb")
86 | purl = os.getenv("purl")
87 | host1 = advanced_cfg.get("host1")
88 | host2 = advanced_cfg.get("host2")
89 | tvglogo = advanced_cfg.get("tvglogo")
90 | proxies = advanced_cfg.get("proxies")
91 | DEBUG = eval(os.getenv("DEBUG", default=advanced_cfg.get("debug", "False")))
92 | if proxies:
93 | os.environ["proxies"] = proxies
94 |
95 | xmlowner = other_cfg.get("xmlowner")
96 | xmlrepo = other_cfg.get("xmlrepo")
97 | xmlaccess_token = other_cfg.get("xmlaccess_token")
98 | repoowner = other_cfg.get("repoowner")
99 | repoaccess_token = other_cfg.get("repoaccess_token")
100 | mac = uuid.UUID(int=uuid.getnode()).hex[-12:]
101 | if xmlowner and xmlaccess_token:
102 | xmlState = True
103 | else:
104 | xmlState = False
105 | if repoowner and repoaccess_token:
106 | repoState = True
107 | else:
108 | repoState = False
109 |
110 | headers = {
111 | 'Content-Type': 'video/MP2T',
112 | 'Cache-Control': 'max-age=600',
113 | 'Accept-Ranges': 'bytes'
114 | }
115 | headers2 = {
116 | 'Cache-Control': 'no-cache',
117 | 'Pragma': 'no-cache',
118 | 'Content-Type': 'application/vnd.apple.mpegurl',
119 | 'Expires': '-1',
120 | }
121 | machine = f"Pyhton/{python_version()} ({machine()} {platform()} {mac}) Version/{config.VERSION}"
122 | print(".", end="")
123 | data3 = None
124 | try:
125 | tx = int(time.time() - int(request.get(urljoin(data3["a1"], "sync"), headers={"User-Agent": machine}).json()["data"]))
126 | except Exception as e:
127 | tx = 0
128 | print(".", end="")
129 |
130 | gdata = None
131 | print(".", end="")
132 | version = None
133 | print(".", end="\n")
134 | if config.VERSION != str(version):
135 | logger.warning(f"当前版本为{config.VERSION},最新版本为{version},请及时更新!")
136 | logger.warning("更新地址:https://github.com/239144498/Streaming-Media-Server-Pro")
137 |
138 | if localhost and "http" not in localhost:
139 | logger.warning("localhost配置错误,具体查看教程https://www.cnblogs.com/1314h/p/16651157.html")
140 |
141 | logger.info("配置加载完成")
142 |
--------------------------------------------------------------------------------
/app/db/DBtools.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin python3
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2022/10/7
4 | # @Author : Naihe
5 | # @Email : 239144498@qq.com
6 | # @File : dbMysql.py
7 | # @Software: PyCharm
8 | import pymysql
9 | import redis
10 | from loguru import logger
11 |
12 | from app.conf.config import redis_cfg, defaultdb
13 | from app.db.dbMysql import get_mysql_conn, init_database
14 |
15 | logger.info("正在检测数据库连接状态...")
16 |
17 |
18 | def connect_redis():
19 | try:
20 | cur = redis.StrictRedis(
21 | host=redis_cfg['host'],
22 | port=int(redis_cfg['port']),
23 | password=redis_cfg['password'],
24 | ssl=False,
25 | decode_responses=True,
26 | health_check_interval=30,
27 | )
28 | cur.setex("1", 1, 1)
29 | redisState = True
30 | logger.success("redis已连接")
31 | except:
32 | cur = None
33 | redisState = False
34 | logger.warning("redis连接失败")
35 | return cur, redisState
36 |
37 |
38 | cur, redisState = connect_redis()
39 |
40 | def mysql_connect_test():
41 | try:
42 | DBconnect = get_mysql_conn()
43 | print(DBconnect.ping())
44 | logger.success("mysql已连接")
45 | return DBconnect, True
46 | except pymysql.err.OperationalError as e:
47 | DBconnect = None
48 | logger.error(e)
49 | logger.error("mysql连接失败")
50 | return DBconnect, False
51 |
52 |
53 | if defaultdb == "mysql":
54 | # 尝试初始化,创建video表,然后再连接
55 | try:
56 | init_database()
57 | logger.success("mysql已创建初始化表")
58 | except pymysql.err.OperationalError as e:
59 | logger.error(e)
60 | logger.error("mysql初始化表失败")
61 | DBconnect, sqlState = mysql_connect_test()
62 | else:
63 | DBconnect = None
64 | sqlState = False
65 | logger.warning("defaultdb未配置mysql")
--------------------------------------------------------------------------------
/app/db/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2022/10/7
3 | # @Author : Naihe
4 | # @Email : 239144498@qq.com
5 | # @File : __init__.py
6 | # @Software: PyCharm
7 |
--------------------------------------------------------------------------------
/app/db/dbMysql.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin python3
2 | # -*- coding: utf-8 -*-
3 | import time
4 | import pymysql
5 | import contextlib
6 |
7 | from loguru import logger
8 | from pymysql.cursors import DictCursor
9 |
10 | from app.conf.config import mysql_cfg
11 |
12 |
13 | class MySQLConnect(object):
14 | def __init__(self, cursorclass=DictCursor, config=None):
15 | self.MYSQL_config = config
16 | self.cursorclass = cursorclass
17 | self.connection = pymysql.connect(
18 | host=config['host'],
19 | port=config['port'],
20 | user=config['user'],
21 | password=config['password'],
22 | db=config['database'],
23 | cursorclass=cursorclass,
24 | charset=config['charset'],
25 | connect_timeout=5, # 连接超时秒数
26 | read_timeout=10, # 读取超时秒数
27 | write_timeout=10 # 写入超时秒数
28 | )
29 | self.connection.autocommit(True)
30 |
31 | def ping(self):
32 | self.connection.ping(reconnect=True)
33 |
34 | @contextlib.contextmanager
35 | def cursor(self, cursor=None):
36 | cursor = self.connection.cursor(cursor)
37 | try:
38 | yield cursor
39 | except Exception as err:
40 | self.connection.rollback()
41 | raise err
42 | finally:
43 | cursor.close()
44 |
45 | def close(self):
46 | self.connection.close()
47 |
48 | def fetchone(self, sql=None):
49 | with self.cursor() as cursor:
50 | cursor.execute(sql)
51 | return cursor.fetchone()
52 |
53 | def execute(self, sql, value):
54 | with self.cursor() as cursor:
55 | return cursor.execute(sql, value)
56 |
57 |
58 | def get_mysql_conn(cursorclass=DictCursor):
59 | mysql_config = {
60 | 'host': mysql_cfg['host'],
61 | 'user': mysql_cfg['user'],
62 | 'password': mysql_cfg['password'],
63 | 'port': int(mysql_cfg['port']),
64 | 'database': mysql_cfg['database'],
65 | 'charset': 'utf8'
66 | }
67 | return MySQLConnect(cursorclass, mysql_config)
68 |
69 |
70 | # 初始化数据库,创建database和表
71 | def init_database(cursorclass=DictCursor):
72 | mysql_config = {
73 | 'host': mysql_cfg['host'],
74 | 'user': mysql_cfg['user'],
75 | 'password': mysql_cfg['password'],
76 | 'port': int(mysql_cfg['port']),
77 | 'database': 'mysql',
78 | 'charset': 'utf8'
79 | }
80 | mysql = MySQLConnect(cursorclass, mysql_config)
81 | sql = "select count(1) cnt from information_schema.TABLES where TABLE_SCHEMA='media' and TABLE_NAME='video'"
82 | result = mysql.fetchone(sql)
83 |
84 | if result['cnt']:
85 | logger.info("video表已存在")
86 | else:
87 | with mysql.connection.cursor() as cursor:
88 | cursor.execute('CREATE DATABASE media')
89 | cursor.execute(
90 | 'create table media.video(vname varchar(30) not null,CONSTRAINT video_pk PRIMARY KEY (vname),vcontent MEDIUMBLOB NOT NULL,vsize varchar(20) NULL,ctime timestamp(0) default now())')
91 | cursor.execute('SET GLOBAL event_scheduler = ON')
92 | cursor.execute('DROP event IF EXISTS media.auto_delete')
93 | cursor.execute('CREATE EVENT media.auto_delete ON SCHEDULE EVERY 30 minute DO TRUNCATE video')
94 |
95 | return '初始化数据库表完成'
96 |
--------------------------------------------------------------------------------
/app/db/localfile.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import time
5 | from loguru import logger
6 | from app.conf import config
7 |
8 |
9 | class Vfile():
10 | def __init__(self):
11 | self.datadir = config.datadir
12 | if not self.datadir.is_dir():
13 | self.datadir.mkdir()
14 | logger.warning(f'已创建视频缓存路径:{self.datadir}')
15 |
16 | def file_get(self, subpath):
17 | self.filepath = self.datadir / subpath
18 | try:
19 | with open(self.filepath, 'rb') as f:
20 | content = f.read()
21 | except Exception as e:
22 | logger.error(f'读取文件失败:{self.filepath} {str(e)}')
23 | return None
24 |
25 | if len(content) > 1024:
26 | logger.debug(f'读取文件成功:{self.filepath}')
27 | else:
28 | logger.warning(f'读取文件异常:{self.filepath} 文件大小 {len(content)}')
29 | return content
30 |
31 | def file_store(self, subpath, content):
32 | self.filepath = self.datadir / subpath
33 | if len(content) == 0:
34 | logger.error('写入文件为空')
35 | else:
36 | try:
37 | with open(self.filepath, 'wb') as f:
38 | f.write(content)
39 | logger.debug(f'写入文件成功:{self.filepath}')
40 | return True
41 | except Exception as e:
42 | logger.error(f'写入文件失败:{self.filepath} {str(e)}')
43 | return False
44 |
45 | def clean_file(self, timeout=180):
46 | # 删除存在时间超过180秒的文件
47 | cnt = 0
48 | for f in self.datadir.glob("*.ts"):
49 | path = self.datadir / f
50 | st_mtime = path.stat().st_mtime
51 | if time.time() > st_mtime + timeout:
52 | cnt += 1
53 | path.unlink()
54 | return cnt
55 |
56 |
57 | # 创建文件接口对象
58 | vfile = Vfile()
59 |
60 | if __name__ == '__main__':
61 | vfile.clean_file()
62 |
--------------------------------------------------------------------------------
/app/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin python3
2 | # -*- coding: utf-8 -*-
3 | # @Author: Naihe
4 | # @Email: 239144498@qq.com
5 | # @Software: Streaming-Media-Server-Pro
6 | import uvicorn
7 | from app.api import app
8 | from app.conf.config import PORT
9 |
10 |
11 | if __name__ == '__main__':
12 | uvicorn.run(app=app, host="0.0.0.0", port=PORT, log_level="info")
13 |
--------------------------------------------------------------------------------
/app/plugins/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2022/11/13
3 | # @Author : Naihe
4 | # @Email : 239144498@qq.com
5 | # @File : __init__.py.py
6 | # @Software: PyCharm
7 |
--------------------------------------------------------------------------------
/app/plugins/a4gtv/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2022/10/8
3 | # @Author : Naihe
4 | # @Email : 239144498@qq.com
5 | # @File : __init__.py.py
6 | # @Software: PyCharm
7 |
--------------------------------------------------------------------------------
/app/plugins/a4gtv/endecrypt.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin python3
2 | # -*- coding: utf-8 -*-
3 | import base64
4 | import hashlib
5 | from urllib.parse import urljoin
6 |
7 | from loguru import logger
8 |
9 | from app.plugins.a4gtv.tools import now_time
10 | from app.common.request import request
11 | from app.conf.config import data3, machine, config, mdata, tx
12 |
13 |
14 | def get4gtvurl(fsid):
15 | _a = now_time()
16 | url = urljoin(data3['a1'], "?type=v5".format(fsid))
17 | data = {"t": _a - tx, "fid": fsid, "v": config.VERSION}
18 | header = {
19 | "Accept": "*/*",
20 | "User-Agent": machine,
21 | "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
22 | "v": hashlib.md5(bytes(str(data) + mdata, 'utf8')).hexdigest(),
23 | }
24 | with request.post(url=url, headers=header, data=data, allow_redirects=True) as res:
25 | logger.success(f"{fsid} {res.status_code}")
26 | try:
27 | data = res.json()["data"]
28 | msg = res.json()["msg"]
29 | return res.status_code, data["url"], data['data'], _a, msg
30 | except:
31 | return res.status_code, None, res.text, _a, ""
32 |
33 |
34 | if __name__ == '__main__':
35 | a = get4gtvurl("4gtv-4gtv018")
36 | print(a)
37 |
--------------------------------------------------------------------------------
/app/plugins/a4gtv/more_util.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2022/10/30
3 | # @Author : Naihe
4 | # @Email : 239144498@qq.com
5 | # @File : more_util.py
6 | # @Software: PyCharm
7 | import re
8 | from base64 import b64encode
9 | from urllib.parse import urlencode, unquote, quote_plus, urlparse, parse_qsl, urlunparse, urljoin
10 | from app.conf import config
11 |
12 |
13 | def is_url(url):
14 | regex = re.compile(config.url_regex)
15 | if regex.match(url):
16 | return True
17 | else:
18 | return False
19 |
20 |
21 | def parse(url):
22 | url = urlencode(url).replace("url=", "")
23 | url = unquote(url)
24 | return url
25 |
26 |
27 | def processing(url, data):
28 | for _temp in data:
29 | if ".ts" in _temp:
30 | if is_url(_temp):
31 | yield "/pdl?url=" + b64encode(_temp.encode("utf-8")).decode("utf-8")
32 | else:
33 | yield "/pdl/?url=" + b64encode(urljoin(url, _temp).encode("utf-8")).decode("utf-8")
34 | else:
35 | yield _temp
36 | yield "\n"
37 |
38 |
39 | def splicing(url, query_params):
40 | url_parsed = list(urlparse(url))
41 | temp_para = parse_qsl(url_parsed[4])
42 | temp_para.extend(parse_qsl(query_params.__str__()))
43 | url_parsed[4] = urlencode(temp_para)
44 | url_new = urlunparse(url_parsed)
45 | return url_new
46 |
47 |
--------------------------------------------------------------------------------
/app/plugins/a4gtv/tasks.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2022/10/12
3 | # @Author : Naihe
4 | # @Email : 239144498@qq.com
5 | # @File : tasks.py
6 | # @Software: PyCharm
7 | import requests
8 | from loguru import logger
9 |
10 | from app.conf import config
11 | from app.conf.config import repoState
12 | from app.plugins.a4gtv.utile import get
13 | from app.db.localfile import vfile # 新增本地文件处理模块
14 |
15 |
16 | def gotask():
17 | get.filename.clear()
18 | if repoState:
19 | with requests.get("https://agit.ai/239144498/demo/raw/branch/master/4gtvchannel.xml") as res:
20 | with open(config.ROOT / "assets/EPG.xml", "wb") as f:
21 | f.write(res.content)
22 | logger.success("今日任务完成")
23 |
24 |
25 | def sqltask():
26 | # 保留最新100条缓存,避免长时间运行内存溢出
27 | keys = list(get.filename)
28 | keys.reverse()
29 | _ = {}
30 | if len(keys) > 100:
31 | for index, element in enumerate(keys):
32 | if index < 100:
33 | _.update({element: get.filename.get(element)})
34 | get.filename = _
35 | logger.success("get.filename 删除完成")
36 |
37 |
38 | def filetask():
39 | # 保留最近3分钟的视频文件,避免占用过多磁盘空间
40 | cnt = vfile.clean_file()
41 | logger.success('成功删除视频文件'+str(cnt)+'个')
42 |
43 |
44 | if __name__ == '__main__':
45 | gotask()
46 |
--------------------------------------------------------------------------------
/app/plugins/a4gtv/tools.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin python3
2 | # -*- coding: utf-8 -*-
3 | import time
4 |
5 | from loguru import logger
6 | from urllib.parse import urljoin
7 |
8 | from app.common.request import request
9 | from app.conf.config import gdata, host1, host2, tvglogo
10 |
11 |
12 | def safe_int(s):
13 | try:
14 | return int(s)
15 | except ValueError:
16 | return s
17 |
18 |
19 | def generate_m3u(host, hd, name):
20 | yield '#EXTM3U x-tvg-url=""\n'
21 | for i in gdata:
22 | # tvg-ID="" 频道id匹配epg fsLOGO_MOBILE 台标 | fsHEAD_FRAME 播放预览
23 | yield '#EXTINF:{} tvg-chno="{}" tvg-id="{}" tvg-name="{}" tvg-logo="{}" group-title="{}",{}\n'.format(
24 | -1, i['fnCHANNEL_NO'], i['fs4GTV_ID'], i['fsNAME'], i[tvglogo], i['fsTYPE_NAME'], i['fsNAME'])
25 | yield urljoin(host, f"{name}?fid={i['fs4GTV_ID']}&hd={hd}") + "\n"
26 | logger.success(name + " m3u generated successfully")
27 |
28 |
29 | def writefile(filename, content):
30 | with open(filename, "wb") as f:
31 | f.write(content)
32 |
33 |
34 | def get_4gtv(url):
35 | header = {
36 | "Accept": "*/*",
37 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0",
38 | "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
39 | }
40 | with request.get(url=url, headers=header) as res:
41 | return res.text
42 |
43 |
44 | def generate_url(fid, host, begin, seq, url):
45 | # host 可自定义,为空时使用默认参数
46 | if "4gtv-4gtv" in fid or "-ftv10" in fid or "-longturn17" in fid or "-longturn18" in fid:
47 | return urljoin(host or host1, url.format(begin, seq))
48 | elif "4gtv-live" in fid:
49 | return urljoin(host or host2, url.format(fid, f"720{seq}"))
50 | else:
51 | return urljoin(host or host1, url.format(seq))
52 |
53 |
54 | def now_time(_=None):
55 | if _:
56 | return time.time()
57 | else:
58 | return int(time.time())
59 |
--------------------------------------------------------------------------------
/app/plugins/a4gtv/utile.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin python3
2 | # -*- coding: utf-8 -*-
3 | import base64
4 | import time
5 | import datetime
6 | import requests
7 |
8 | from loguru import logger
9 | from threading import Thread
10 | from urllib.parse import urlparse, urljoin
11 |
12 | from app.conf.config import config, request, gdata
13 | from app.plugins.a4gtv.endecrypt import get4gtvurl
14 | from app.common.header import random_header
15 | from app.conf.config import repoowner, default_cfg, localhost, vbuffer, \
16 | mysql_cfg, downurls
17 | from app.db.DBtools import redisState, cur, DBconnect
18 | from app.db.localfile import vfile
19 | from app.plugins.a4gtv.tools import now_time, safe_int, generate_url
20 |
21 |
22 | class container:
23 | def __init__(self):
24 | self.para = {}
25 | self.filename = {}
26 | self.owner = repoowner
27 | self.idata = {}
28 | self.init()
29 |
30 | def init(self):
31 | if redisState:
32 | keys = cur.keys()
33 | _ = []
34 | for k in keys: # 排除非电视节目
35 | if "4gtv" in str(k) or "litv" in str(k):
36 | _.append(k)
37 | for key, value in zip(_, cur.mget(_)):
38 | _ = eval(value)
39 | if type(_) is not list or len(_) < 3:
40 | continue
41 | self.updatelocal(key, _)
42 | logger.success("init final")
43 |
44 | def updateonline(self, fid):
45 | status_code, a6, a12, a1, msg = get4gtvurl(fid)
46 | if (status_code == 200 or abs(status_code - 300) < 10) and "成功" in msg:
47 | a10, a11, a3, a2, a7, a8, a9 = list(map(safe_int, ''.join([chr(ord(i) + 2) for i in base64.b64decode(a12).decode("utf-8")[::-1]]).split(':')))
48 | self.updatelocal(fid, [a1, a2, a3, a11 + a10, a10 / -a1, a6, a7, a8, a9])
49 | config.count += 1
50 | if redisState:
51 | cur.setex(fid, a2 - a1, str([a1, a2, a3, a11 + a10, a10 / -a1, a6, a7, a8, a9]))
52 | return 200
53 | elif abs(status_code - 503) < 10: # 服务器维护
54 | self.idata[fid]["lt"] = a1 + 30
55 | elif status_code == 403: # 链接失效
56 | self.idata[fid]["lt"] = a1 + 60
57 | elif status_code == 229: # 频率过快
58 | self.idata[fid]["lt"] = a1 + 3
59 | elif status_code == 230: # 接口每日上限
60 | self.idata[fid]["lt"] = a1 + 3600
61 | elif status_code == 216:
62 | logger.error("该ip已被程序拉黑,无法访问")
63 | logger.error("了解封禁规则https://github.com/239144498/Streaming-Media-Server-Pro/issues/14")
64 | exit(-1)
65 | elif status_code == 410: # 过期
66 | self.idata[fid]["lt"] = a1 + 3
67 | elif status_code == 411: # 验证失败 升级最新版解决
68 | logger.warning("请升级到最新版")
69 | self.idata[fid]["lt"] = a1 + 999
70 | else: # 其他情况
71 | self.idata[fid]["lt"] = a1 + 120
72 | logger.warning("未获得数据")
73 | logger.warning(f"{status_code}, {a12}")
74 | return 404
75 |
76 | def updatelocal(self, fid, _):
77 | self.para[fid] = {
78 | "a1": _[0],
79 | "a2": _[1],
80 | "a3": _[2],
81 | "a4": _[3],
82 | "a5": _[4],
83 | "a6": _[5],
84 | "a7": _[6],
85 | "a8": _[7],
86 | "a9": _[8],
87 | }
88 | return 200
89 |
90 | def check(self, fid):
91 | """
92 | 处理参数
93 | :param fid:
94 | :return:
95 | """
96 | code = 200
97 | if not self.para.get(fid) or self.para.get(fid)['a2'] - now_time() < 0:
98 | if redisState:
99 | _temp = cur.get(fid)
100 | if not _temp or eval(_temp)[1] - now_time() < 0:
101 | code = self.updateonline(fid)
102 | else:
103 | _ = eval(_temp)
104 | code = self.updatelocal(fid, _)
105 | else:
106 | code = self.updateonline(fid)
107 | return code
108 |
109 | def generalfun(self, fid):
110 | """
111 | 通用生成参数
112 | :param fid:
113 | :param hd:
114 | :return:
115 | """
116 | data = self.para.get(fid)
117 | if "4gtv-4gtv" in fid or "litv-ftv10" in fid or "litv-longturn17" == fid or "litv-longturn18" == fid:
118 | url = self.para[fid]["a8"] + "?" + data["a9"]
119 | now = now_time()
120 | seq = round(data["a4"] + now * data["a5"]) + data["a3"]
121 | begin = data["a7"] * round(data["a4"] + now * data["a5"]) + data["a3"]
122 | return data["a7"], seq, url, begin
123 | if "4gtv-live" in fid:
124 | url = self.para[fid]["a8"] + "?" + data["a9"]
125 | now = now_time()
126 | seq = round(data["a4"] + now * data["a5"]) + data["a3"]
127 | return data["a7"], seq, url, 0
128 | if "litv-ftv" in fid or "litv-longturn" in fid:
129 | url = self.para[fid]["a8"] + "?" + data["a9"]
130 | now = now_time()
131 | seq = round(data["a4"] + now * data["a5"]) + data["a3"]
132 | return data["a7"], seq, url, 0
133 |
134 | def generatem3u8(self, host, fid, hd):
135 | gap, seq, url, begin = self.generalfun(fid)
136 | yield f"""#EXTM3U
137 | #EXT-X-VERSION:3
138 | #EXT-X-TARGETDURATION:{gap}
139 | #EXT-X-ALLOW-CACHE:YES
140 | #EXT-X-MEDIA-SEQUENCE:{seq}
141 | #EXT-X-INDEPENDENT-SEGMENTS"""
142 | for num1 in range(5):
143 | yield f"\n#EXTINF:{self.para[fid]['a7']}," \
144 | + "\n" + generate_url(fid, host, begin + (num1 * self.para[fid]['a7']), seq + num1, url)
145 | logger.success(fid + " m3u8 generated successfully")
146 |
147 | def new_generatem3u8(self, host, fid, hd, background_tasks):
148 | gap, seq, url, begin = self.generalfun(fid)
149 | if default_cfg.get("downchoose") == "online":
150 | background_tasks.add_task(backtaskonline, url, fid, seq, hd, begin, host)
151 | elif default_cfg.get("downchoose") == "local":
152 | background_tasks.add_task(backtasklocal, url, fid, seq, hd, begin, host)
153 | yield f"""#EXTM3U
154 | #EXT-X-VERSION:3
155 | #EXT-X-TARGETDURATION:{gap}
156 | #EXT-X-MEDIA-SEQUENCE:{seq}
157 | #EXT-X-INDEPENDENT-SEGMENTS"""
158 | tsname = fid + str(seq) + ".ts"
159 | if tsname in self.filename and self.filename.get(tsname) == 1:
160 | for num1 in range(vbuffer):
161 | url = "\n" + urljoin(localhost, f"call.ts?fid={fid}&seq={seq + num1}&hd={hd}")
162 | yield f"\n#EXTINF:{self.para[fid]['a7']}," + url
163 | else:
164 | for num1 in range(1):
165 | url = "\n" + urljoin(localhost, f"call.ts?fid={fid}&seq={seq + num1}&hd={hd}")
166 | yield f"\n#EXTINF:{self.para[fid]['a7']}," + url
167 | logger.success(fid + " m3u8 generated successfully")
168 |
169 | def geturl(self, fid, hd):
170 | return f"{self.para[fid]['a6']}?fid={fid}&hd={hd}&type=rd"
171 |
172 |
173 | get = container()
174 |
175 |
176 | def call_get(url, data):
177 | with request.post(url, json=data, timeout=10) as res:
178 | get.filename.update({data['filepath']: 1})
179 | logger.info((data['filepath'], url[:20], res.text))
180 |
181 |
182 | def backtaskonline(url, fid, seq, hd, begin, host):
183 | threads = []
184 | # 分布式下载,改成你的链接,看不懂就去看我发布的教程
185 | # urlset = ["https://www.example1.com/url3?url=", "https://www.example2.com/url3?url=",
186 | # "https://www.example3.com/url3?url=", "https://www.example4.com/url3?url=",
187 | # "https://www.example5.com/url3?url="]
188 | urlset = downurls
189 | for i in range(0, vbuffer):
190 | tsname = fid + str(seq + i) + ".ts"
191 | # .ts已下载或正在下载
192 | if tsname in get.filename:
193 | continue
194 | get.filename.update({tsname: 0})
195 | herf = generate_url(fid, host, begin + (i * get.para[fid]['a7']), seq + i, url)
196 | x = urlset.pop()
197 | data = {
198 | "f": herf,
199 | "g": tsname,
200 | 'a': mysql_cfg["host"],
201 | 'b': mysql_cfg["user"],
202 | 'c': mysql_cfg["password"],
203 | 'd': int(mysql_cfg["port"]),
204 | 'e': mysql_cfg["database"],
205 | }
206 | t = Thread(target=call_get, args=(x, data))
207 | threads.append(t)
208 | for index, element in enumerate(threads):
209 | element.start()
210 | time.sleep(1 + index * 0.1)
211 |
212 |
213 | def backtasklocal(url, fid, seq, hd, begin, host):
214 | threads = []
215 | # 本地多线程下载
216 | for i in range(0, vbuffer):
217 | tsname = fid + str(seq + i) + ".ts"
218 | # .ts已下载或正在下载
219 | if tsname in get.filename:
220 | continue
221 | get.filename.update({tsname: 0})
222 | herf = generate_url(fid, host, begin + (i * get.para[fid]['a7']), seq + i, url)
223 | t = Thread(target=downvideo, args=(herf, tsname))
224 | threads.append(t)
225 | logger.info('启动downvideo完成')
226 | for index, element in enumerate(threads):
227 | element.start()
228 | time.sleep(1 + index * 0.1)
229 |
230 |
231 | def downvideo(url: str, filepath: str):
232 | """
233 | 本地下载存放到数据库
234 | :param url:
235 | :param filepath:
236 | :return:
237 | """
238 | header = {
239 | "User-Agent": random_header(),
240 | "Accept-Encoding": "gzip, deflate, br",
241 | }
242 | a = time.time()
243 | repo = str(datetime.date.today())
244 | logger.debug('开始下载视频')
245 | with requests.get(url=url, headers=header, timeout=10) as res:
246 | status = res.status_code
247 | content = res.content
248 | logger.debug('完成下载视频')
249 | b = time.time()
250 | # 保存到mysql数据库
251 | if default_cfg.get("defaultdb") == "mysql":
252 | sql = "insert into video(vname, vcontent, vsize) values(%s, %s, %s)"
253 | a1 = DBconnect.execute(sql, (filepath, content, len(content))) # 执行sql语句
254 | # 保存到本地硬盘
255 | else:
256 | a1 = vfile.file_store(filepath, content)
257 | logger.debug('保存视频完成')
258 |
259 | get.filename.update({filepath: 1})
260 | c = time.time()
261 | return {
262 | "总时长": round(c - a, 2),
263 | "请求": round(b - a, 2),
264 | "请求状态": status,
265 | "上传": round(c - b, 2),
266 | "上传状态": a1,
267 | "字节": len(content),
268 | "repo": repo
269 | }
270 |
--------------------------------------------------------------------------------
/app/scheams/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2022/10/9
3 | # @Author : Naihe
4 | # @Email : 239144498@qq.com
5 | # @File : __init__.py.py
6 | # @Software: PyCharm
7 |
--------------------------------------------------------------------------------
/app/scheams/api_model.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2022/10/22
3 | # @Author : Naihe
4 | # @Email : 239144498@qq.com
5 | # @File : api_model.py
6 | # @Software: PyCharm
7 | from enum import Enum
8 |
9 |
10 | class Clarity(str, Enum):
11 | f = "360"
12 | s = "480"
13 | c = "720"
14 | h = "1080"
15 |
16 |
17 | class Channels(str, Enum):
18 | online = "online"
19 | channel = "channel"
20 | channel2 = "channel2"
21 | channel3 = "channel3"
22 |
23 |
24 | class Witch(str, Enum):
25 | yes = "y"
26 | no = "n"
27 |
--------------------------------------------------------------------------------
/app/scheams/response.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2022/10/9
3 | # @Author : Naihe
4 | # @Email : 239144498@qq.com
5 | # @File : response.py
6 | # @Software: PyCharm
7 | from typing import Union
8 |
9 | from fastapi.responses import JSONResponse, Response
10 |
11 |
12 | def Response200(*, data: Union[list, dict, str] = None, msg="请求成功", code=200) -> Response:
13 | return JSONResponse(
14 | status_code=code,
15 | content={
16 | "code": code,
17 | "msg": msg,
18 | "data": data,
19 | }
20 | )
21 |
22 |
23 | def Response400(*, data: Union[list, dict, str] = None, msg="请求成功", code=400) -> Response:
24 | return JSONResponse(
25 | status_code=code,
26 | content={
27 | "code": code,
28 | "msg": msg,
29 | "data": data,
30 | }
31 | )
32 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin python3
2 | # -*- coding: utf-8 -*-
3 | # @Author: Naihe
4 | # @Email: 239144498@qq.com
5 | # @Software: Streaming-Media-Server-Pro
6 | import uvicorn
7 | from app.api import app
8 | from app.conf.config import PORT
9 |
10 |
11 | if __name__ == '__main__':
12 | uvicorn.run(app=app, host="0.0.0.0", port=PORT, log_level="info")
13 |
14 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | aiohttp>=3.8.1
2 | APScheduler>=3.9.1
3 | fastapi>=0.86.0
4 | loguru>=0.5.3
5 | pydantic>=1.8.2
6 | PyMySQL>=1.0.2
7 | pytz>=2021.3
8 | redis>=4.3.4
9 | requests>=2.28.1
10 | sentry_sdk>=1.9.10
11 | starlette>=0.20.4
12 | uvicorn>=0.17.6
13 | gunicorn
14 |
--------------------------------------------------------------------------------